ONLamp.com
oreilly.comSafari Books Online.Conferences.

advertisement


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.



Sponsored by: