Black Box with a View, Part 2
Pages: 1, 2, 3, 4, 5, 6
Appendix B: Registers and Bitwise Operations
If you have little experience with embedded software development, you may be confused by registers, I/O pins, bit masks, and similar terms in the body of the article. This appendix is a brief introduction to these concepts.
In an embedded program, you must deal directly with the hardware. There is no thick, comfortable blanket of a general-purpose operating system to insulate you from the cold, hard metal of the machine. Nevertheless, direct control of a device is often not very difficult, even though it may feel strange at first.
You usually program hardware by using registers. Here is a typical example, from the Hello World program in the first article:
P2DIR=0xFF; /* Configure all pins on port 2 as outputs. */
P2OUT=0x02; /* Set pin 1 to the high state; all others to
the low state. The mask "0x01" would set pin 0
to the high state, "0x04" would set pin 2
to the high state, etc. */
In this example, P2DIR and P2OUT are registers. Note that the code looks just like assignment to a variable. In fact, the code does write data to a specific memory location--conveniently defined as P2DIR and P2OUT by the MSPGCC header files.
These "memory locations," however, are special. They are windows onto the real world. Reading them brings information from the outside into the program. Writing them causes something to happen (a valve to close, for example). Of course, the appropriate components (valves, temperature sensors, motors, and the like) must be interfaced to the microcontroller in order for these software operations to have any effect!
P2OUT is an example of a very common type of 8-bit register, where each of the 8 bits controls a single digital I/O line. Not all registers perform I/O--some control the internal state of the microcontroller, or serve to configure the operation of other registers. P2DIR is an example of the latter--it controls the operation of P2OUT and P2IN. (This article does not discuss the use of P2IN.)
In any case, you must often deal with data on an individual bit level when working with registers. If you have programmed in C, you must be familiar with the || ("or") and && ("and") operators. Their bitwise counterparts are | and &. These apply the same logic operation, but on individual bits--so the | of unsigned binary numbers 110 and 101 yields 111.
For example, to drive only pin 1 of port 2 to a high state, leaving all of the other pins unchanged, write:
P2OUT |= 0x02;
This is a just a quick way of saying P2OUT = P2OUT | 0x02. The quantity 0x02 is a bit mask. Use Table 2 from Appendix A to compute bit masks. Just regard each power of 2 in the table as a bit number, so 0x02 (which is 2 to the first power) is a mask with bit 1 high and the other bits low. Likewise, 0x03 is a mask with both bit 1 and bit 0 high, while the other bits are low. In other words, just add up all the powers of 2 (0 and 1st power in this case) to turn on the corresponding bits.
Want to force pin 1 of port 2 low, while leaving the other pins unchanged?
P2OUT &= ~0x02;
The ~ operator inverts the state of every single bit, so ~0x02 is a bit mask with every bit high except bit 1 (remember, 0x02 by itself has every bit low except bit 1). Because bit 1 is low in the bit mask, an &= operation with it will force the left side's bit 1 low as well (the "and" of 0 with either 0 or 1 is 0). All of the other bits in the mask, however, are 1, so they cannot affect the state of their corresponding bits on the left side--an "and" of 1 with 1 is 1, while an "and" of 1 with 0 is still 0.
What would you write if you wanted to turn on the LED in the Hello World examples: P2OUT |= 0x02 or P2OUT &= ~0x02? If you are using the easyWeb2 development board from the first article, it is--surprisingly--the latter. On this development board, the LED is wired active low. This means that the LED is on ("active") when the corresponding bit in the register is 0, rather than 1. As you may have guessed, the more intuitive configuration (LED on when the corresponding bit is 1) is called active high. Both variations are common--in fact, you will often encounter hardware where some components are active high, while others are active low.
To conclude this brief introduction to bitwise operations, notice the effort spent on changing individual bits while leaving the other bits untouched. This was not a concern at all in the first article, where the Hello World example used P2OUT=~P2OUT to invert every single bit of port 2. If you write more complex embedded software, however, you have to be more careful. Depending on the application, all kinds of different devices may be attached to the I/O lines of a particular port. If you want to change the state of one device, you cannot allow all of the other attached devices to change as a side effect.
Example 1 therefore uses the ^= operator to toggle the LED state. The ^ is the bitwise "exclusive or" ("xor"). It is very similar to the | (covered earlier) except in one single case: the "xor" of 1 with 1 is 0. Thus, P2OUT ^= 0x02 toggles the state of pin 1 of port 2 (1 "xor" 1 is 0, while 1 "xor" 0 is 1, so the state always changes) while leaving the other pins untouched (0 "xor" with anything behaves just like "or").
If a program uses interrupts, however, the correct bitwise operation is not always enough to avoid unintentionally changing the state of the other bits. The next article, which covers critical regions, will discuss this problem.
Appendix C: Hello World, Optimized
This appendix shows a highly optimized variation on the code from the section Hello World, Interrupted, in the main body of the article. The optimized code still allows you to modify the timer driver and the LED driver independently of each other. Of course, the code shown here loses a lot of flexibility. For example, it can support only one LED. The timer ISR, however, shrinks from eight machine language instructions to two on the author's system.
If you develop embedded software, you often will encounter the need for this kind of optimization. Keep in mind, however, that not every part of the program will need to be accelerated. Make sure to identify the right part through actual testing, not by guesswork. A connected embedded system can really help here, because it can gather timing information and transmit it to an attached PC for analysis.
/* File "main.h".
This file defines two macros that the highly optimized version
of "LED_driver.h" needs. */
#define LED_PORT P2OUT
#define LED_MASK 0x02
Example 9. The main.h file
/* File "LED_driver.h" -- highly optimized version. */
#ifndef LED_DRIVER_H
#define LED_DRIVER_H
#define LED_Toggle() ((LED_PORT) ^= (LED_MASK))
#endif
Example 10. The LED_driver.h, highly optimized version
/* File "LED_timer_driver.h" -- highly optimized version. */
#ifndef LED_TIMER_DRIVER_H
#define LED_TIMER_DRIVER_H
void LED_Timer_Init(void);
void LED_Timer_Start(void);
void LED_Timer_Stop(void);
#endif
Example 11. The LED_timer_driver.h, highly optimized version
/* File "LED_timer_driver.c" -- highly optimized version. */
#include <io.h>
#include <signal.h>
#include "main.h"
#include "LED_driver.h"
#define TIMER_PERIOD 100
void LED_Timer_Init(void) {
TACCR0 = 0;
TACTL = TASSEL_1 | MC_1;
TACCTL0 = CCIE;
}
void LED_Timer_Start(void) {
TACCR0 = TIMER_PERIOD;
}
void LED_Timer_Stop(void) {
TACCR0 = 0;
}
interrupt(TIMERA0_VECTOR) timer (void) {
LED_Toggle();
}
Example 12. The LED_timer_driver.c, highly optimized version
/* File "hello-world-optimized.c". */
#include <io.h>
#include <signal.h>
#include "LED_timer_driver.h"
int main(void) {
P2DIR = 0xFF;
LED_Timer_Init();
eint();
LED_Timer_Start();
_BIS_SR(LPM0_bits + GIE);
for(;;);
}
Example 13. The hello-world-optimized.c file
Read the code in the section Hello World, Interrupted. There definitely are similarities with the optimized files shown here. To test the example, copy all of these files into a separate subdirectory, change to that subdirectory, and compile with the command:
msp430-gcc -std=gnu89 -pedantic -W -Wall -Os -mmcu=msp430x149 \
-o optimized.elf hello-world-optimized.c LED_timer_driver.c
Then load the resulting object file onto the microcontroller development board in the usual manner:
$ msp430-jtag -epv optimized.elf
George Belotsky is a software architect who has done extensive work on high-performance internet servers, as well as hard real-time and embedded systems.
Return to ONLamp.com.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 13 of 13.
-
No 32kHz crystal on the easyWeb2
2006-03-01 09:39:05 georgebelotsky [Reply | View]
Olimex confirms the error in the easyWeb2 description -- there is no 32kHz crystal on the board. See the discussion below if you are having trouble with the interrupt examples.
-
olimex easyweb_test in mspgcc format
2006-03-01 07:29:33 bartvan deenen [Reply | View]
Hi all
I have the olimex program that runs when you first connect the easywebII running with the mspgcc. I've stored the archive on my site <http://www.vandeenensupport.com/easyweb_test.tar.gz>
Just open the archive, fix the ip address in tcpip.h, and type "make install", which will copy the elf file into your easyweb.
Happy hacking.
Bart van Deenen
-
timing
2006-03-01 02:54:00 bartvan deenen [Reply | View]
Hi George
I've tried both clock sources, and still can't really figure out the interrupt frequency I'm getting. I'm now at ACLK, being LFXT1CLK/8. A Readback on BCSCTL1 reads C4, which fits. I should be getting 1MHz on ACTL, so with a TIMER_PERIOD of 10000 I should be getting interrupts a 100 Hz. I am getting interrupts at 1236 Hz, so I'm still missing something somewhere.
Anyway this is all part of the fun :-) I hope you're doing some tcp stuff in the next installment.
-
timing
2006-03-01 03:55:38 bartvan deenen [Reply | View]
Found my timing problem. There is a bug in timera.h, where ID_DIV8 is (3<<6) and it should be (3<<4). So i set the wrong bits. My time measurement wasn't very accurate, the 1236Hz should have been 800 Hz.
I'm also using the bit of code from page 4-12 of the user guide. My code is now:
<code>
void LED_Timer_Init(int* ignore, struct _LED* const ledp) {
if (_ledp) {
return;
}
_ledp = ledp;
_BIC_SR(OSCOFF); // turn on oscillator
BCSCTL1 |= XTS; // high frequency
do {
IFG1 &= ~OFIFG;
volatile unsigned char i;
for(i=0;i<255;i++);
} while ( IFG1 & OFIFG );
BCSCTL2 |= SELM1 | SELM0 | BIT3;
BCSCTL1 &= ~(BIT4 | BIT5);
BCSCTL1 |= BIT5 | BIT4;
TACCR0 = 0;
TACTL = TASSEL_ACLK | MC_UPTO_CCR0;
TACCTL0 = CCIE;
}
</code> -
timing
2006-03-01 10:39:07 georgebelotsky [Reply | View]
Actually,
timera.hlooks correct.
3 << 6 = 0xC0
= 0x80 + 0x40
... which is bits 7 and 6 set
</pre>
From page 11-20 of the
- MSP430x1xx Family User's Guide
IDxbits (which control input clock divider) are 7 and 6 -- if both are set, the clock is divided by 8.
Also, several of TI's code examples -- such as
fet140_ta_09.c-- illustrates the use of the ACLK with an HF crystal.
Best Wishes,
George. -
timing
2006-03-01 11:58:52 bartvan deenen [Reply | View]
Huh, the User Guide shows on page 4-15 the DIVAx bits of BCSCTL1 to be bits 4 and 5 -
timing
2006-03-01 12:00:52 bartvan deenen [Reply | View]
Oh I see
I was dividing the ACLK, and there is another divider for Timer A. Complicated all these timers.
-
timing
2006-03-01 18:11:06 georgebelotsky [Reply | View]
Yes, configuring the hardware can be error prone.
Particularly, watch out for the convenience macros that define symbolic names for various bits. The macros are helpful, but there is nothing to prevent you applying them to the wrong register.
Try TI's examples as a starting point when dealing with MSP430 features that are new to you. You can be reasonably sure that they are setting up the microcontroller correctly.
Get the example to run first, then use it as a guide to build your own device driver for the hardware in question.
Best Wishes,
George.
-
Oops
2006-02-28 06:37:18 bartvan deenen [Reply | View]
Me again :-)
Now I understand ACLK always exists, even without the watch crystal, The problem was the standard period value, which was much too high to see any blinking. I couldn't even hear it when I flipped the buzzer.
We keep chugging along. Later I'll try the uCos-II port, nice to have are RTOS -
Oops
2006-02-28 22:44:15 georgebelotsky [Reply | View]
The easyWeb2 specs do claim a 32kHz crystal.
Upon closer scrutiny, however, there is no such crystal on the schematic. In any case, the diagram shows an 8MHz crystal connected to XIN/XOUT instead.
I also asked a really smart person (Chris Paynter, who designs circuits, PCB layouts, etc. professionally) for a second opinion. He confirmed the above.
This probably explains your experiences (strangely, the interrupt code always worked well during the numerous tests that I carried out).
To be safe, try usingSMCLKas the source. By default, it runs from theDCOCLKat about 800 kHz. Although theDCOis not very accurate, it does not matter for the examples here. The approach is quite simple to set up; just modify "LED_timer_driver.c" as follows.
- Replace the line
#define TIMER_PERIOD 100with#define TIMER_PERIOD (51200-1) - Replace the line
TACTL = TASSEL_1 | MC_1;withTACTL = TASSEL_2 | MC_1 | ID_1 | ID_2;
TheTASSEL_2bit by itself selectsSMCLKas the input clock. The twoID_Xbits cause the input clock to be divided by 8. So, you get ~800kHz / 8 / 51200 = ~2 (i.e. the LED will be toggled around twice per second). Thus, the LED will flash approximately once per second.
- Replace the line
-
easyweb II has no aux clock
2006-02-28 06:06:33 bartvan deenen [Reply | View]
Hi
the interrupt handler didn't work, because of the clock select. TASSEL_1 selects the auxilliary clock. When I switch to TASSEL_2 for the hi-freq clock, and increase the period a little, the interrupt driven led works fine
Does your easyweb II have a watch crystal on it?
Bart
-
Great articles
2006-02-28 04:40:34 bartvan deenen [Reply | View]
I'm an experience embedded soft- and hardware engineer, and thanks to your articles, I'm finally doing some hobby work again. I really like the fact that you're using gcc, and can't wait for the next article.
Bart
-
Using Eclipse for embedded development
2006-02-16 04:58:39 bvankuik [Reply | View]
Excellent article! As an addition, I'd like to point people to the following article. It discusses embedded development using Eclipse:
http://www.olimex.com/dev/pdf/ARM%20Cross%20Development%20with%20Eclipse%20version%203.pdf






