ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


Black Box with a View, Part 2

by George Belotsky
02/02/2006

This article discusses several key development techniques for microcontroller-based embedded systems. The information covered here builds on the first article, Black Box with a View. You should review that article at least briefly, because it describes the tools (hardware and software) needed to follow the examples.

If you lack experience in working with microcontrollers, you should also read Appendix A (which discusses arithmetic operations) and Appendix B (which describes bitwise operations). These appendixes will help you avoid many common mistakes in microcontroller programming.

The design patterns presented here will help you handle the complexity of the network-enabled embedded environment. Connected embedded systems must support a variety of different functions (such as the network hardware and protocol) that simpler devices without connectivity never require. Layered, object-oriented architectures are highly effective in such situations. Yet how can a microcontroller, with its severely limited resources, support these server-grade techniques?

Despite the fundamental differences between embedded systems and conventional PCs, the best development methods for both turn out to be variations on a common theme. The constraints of the embedded environment, however, reduce the design patterns to their most austere, minimalist form. In consequence, you must have an absolutely clear understanding of the patterns in order to apply them successfully in the embedded context. That is the purpose of this article.

A Layered (Hello) World

Layering is an important development technique for managing complexity in software. For example, operating systems use device drivers to create a consistent interface to low-level hardware functions.

Example 1 is a device driver for the LED from the original Hello World program in the first article. Electronic designs often specify several such LEDs to indicate the status of the system (power on, error, data transmission, etc.). These LEDs are simple devices, so one short header (or include) file is sufficient to implement a complete driver.

/* File "LED_driver.h".

   This file is an LED device driver for MSP430 family of
   microcontrollers.  It uses some Object-Oriented Programming (OOP)
   features.

   The first two lines of code as well as the last line prevent this
   file from being processed more than once, which would lead to
   errors.  This "header guard" is a standard technique in C
   programming. */

#ifndef LED_DRIVER_H 
#define LED_DRIVER_H


/* A structure that stores the state of the LED device driver.  
   You must create an instance of this structure for each LED. */
struct _LED {
  volatile unsigned char* reg;
  unsigned char mask;
};


/* Use the following "typedef" to create LED objects.  

   The single-element array works just like an ordinary pointer in
   most cases, except that it cannot be made to point to another
   location.  This effectively creates a "safe pointer", which acts
   more like a C++ reference. */
typedef struct _LED LED_Ref[1];

/* Macro to initialize an LED object (i.e. the constructor). 
   
   This macro is an example of a comma expression, because commas
   separate the subexpressions, which the system evaluates in
   left-to-right order.  The result of the last subexpression becomes
   the result of the whole comma expression.  In this case, the result
   is not used -- only the side effects of the comma expression are
   important. */
#define LED_Init(ledp,r,m) ((ledp)->reg=(r), (ledp)->mask=(m))

/* Macro to toggle the state of an LED. */
#define LED_Toggle(ledp) (*((ledp)->reg) ^= (ledp)->mask)


#endif

Example 1. The LED device driver

You can use Example 1 as a class definition, to create LED objects. Example 2 illustrates this process, by modifying the Hello World program from the first article to use the device driver of Example 1.

/* File "hello-world-layered.c".

   This program illustrates the use of a device driver in an embedded
   system.

   Just like the example in the first article, the code here flashes an
   LED connected to pin 1 of port 2 on the MSP430F149.  The easyWeb2
   development board has an LED connected in this way. */

#include <io.h>
#include "LED_driver.h"  /* The LED device driver. */

int main(void) {

  LED_Ref led; /* Create the LED object. 
               BEWARE THAT THE OBJECT IS NOT YET INITIALIZED.*/
  
  P2DIR=0xFF;  /* Configure port 2; all pins are outputs in this case. */

  LED_Init(led,&P2OUT,2);  /* Initialize the LED object. */

  
  for(;;) {
    /* The following two lines implement a very crude delay loop.
       The actual length of the delay can vary significantly.
       This approach may not work with all compilers. */
    volatile int i;        /* Declare "i" as volatile ... */
    for(i=0;i<20000;i++);  /* ... so that this loop will not be optimized. */

    LED_Toggle(led);       /* Toggle the state of the LED */

  }
  
}

Example 2. The layered Hello World

To test the layered Hello World, place the files from Example 1 and Example 2 in the same directory, change to that directory, and compile the program with the following command. Note that in a larger project, you should use a build tool such as make instead of running the compiler directly.

$ msp430-gcc -std=gnu89 -pedantic -W -Wall -Os -mmcu=msp430x149 \
    -o layered.elf hello-world-layered.c

Now, load the result onto the microcontroller, just like you did in the first article:

$ msp430-jtag -epv layered.elf

The new program is not much larger than the original version, but it has several important features:

  1. The LED device driver provides an object-oriented interface to the underlying hardware. One hardware initialization step, however (P2DIR=0xFF), takes place outside the driver. That allows for future sharing of the hardware by multiple devices.
  2. There is no dynamic memory allocation from the heap (for example, with malloc). That is why it takes two steps to create the LED object.
  3. The implementation of the LED driver uses macros instead of function calls.

These features, some of which may seem counterintuitive, reflect important characteristics of embedded software development. The next three sections of this article consider each of the three features in detail.

Linux Device Drivers

Related Reading

Linux Device Drivers
By Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman

Object-Oriented Design for Microcontrollers

The layered Hello World program from the previous section has some object-oriented characteristics. It begins by creating and initializing an LED object. Then, you use the object's methods to control the LED. To keep the discussion clear, Example 1 defines just one such method--LED_Toggle. Note that the constructor (LED_Init, also defined in Example 1) is really a method of the class, not the object. You call the constructor to bring an object into being; it makes no sense to use it on an object that already exists.

Of course, the C compiler cannot enforce such distinctions--it lacks the object-oriented programming support of languages such as C++ and Java. The programmer must therefore add the required features herself, as a mixture of code and a set of coding rules that she then must follow manually. During this process, it is important to keep in mind that the programs will run in the tightly constrained microcontroller environment.

In Example 1, a simple C struct (called _LED) stores the LED object's state. You must pass a pointer to a struct _LED as the first argument to every method, including the constructor. You therefore create the object in two steps--see the section Avoiding the Heap for the reasons. Refrain from calling the constructor again on the created object.

The constructor takes the port and bit mask (see Appendix B for more information on bitwise operations) of the LED as arguments. After you create the LED object, you should never handle the LED hardware directly, but only through the object's methods. The LED object now "owns" the hardware resource.

Note, however, that port 2 is still configured (P2DIR=0xFF) in the main program, not in the LED object. In fact, the LED object does not own the entire port. In a larger application, other devices could be attached to the remaining pins of the I/O port. You would then transfer the ownership of these I/O lines to the appropriate device drivers. If you are new to microcontroller programming, you may want to review Appendix B for more information on ports, pins, and I/O lines.

In general, the concept of resource ownership is a very important tool in software design. For example, to effectively manage the memory in a C++ program, you must be consistent about which part of the program owns each piece of allocated memory.

One important benefit of having the LED object own the LED hardware is encapsulation. The object hides the details of hardware access from the rest of the program, and provides a consistent interface to control the device. You can change the _LED structure, port the program to a different microcontroller, alter the implementation of LED_Toggle to keep statistics on the system state, and so on, without breaking any client code that uses LED objects.

Unfortunately, the C language does not enforce these safeguards. You must voluntarily refrain from accessing the _LED structure directly. It is therefore very important that you formulate consistent rules during the design phase of your project, in order to avoid programming errors that could break the encapsulation.

For example, choose one way of creating any kind of object, even if this method is not always the best. Do not optimize your program prematurely--exceptions to the rules should be very rare. Strive for a uniform way of coding early, and you will avoid many days (or even weeks) of frustrating debugging later.

If you are familiar with object-oriented programming, you have probably noticed that something is missing--namely inheritance. Many common definitions of an object-oriented language still emphasize inheritance, while the approach taken in this article (which avoids inheritance entirely) is sometimes referred to as object based.

Inheritance, however, has become an overused--even harmful--feature in object-oriented languages, as described in the classic book Design Patterns. The best design emphasizes very shallow hierarchies, as shown in Figure 1.

Thumbnail, click for full-size image.
Figure 1. Inheritance strategies--click for full-size image

The goal is to create a set of objects with well-defined interfaces. Then, it is possible to build the a program from these objects--just like connecting together LEGO blocks. This is composition; in practice, it leads to much greater code reuse than inheritance. The Hello World, Interrupted section of the article illustrates composition.

Of course, some object-oriented languages (such as C++) use inheritance to define interfaces. In such cases, the base class at the top of the hierarchy is abstract. It cannot be instantiated directly, and servers only as an interface specification to its child classes, which can be instantiated.

The right side of Figure 1 illustrates precisely this kind of inheritance--the abstract base class is red. With hierarchies rarely exceeding one level in depth, however, the techniques presented in this article can be used to define interfaces without any formal inheritance mechanism.

The next article will extend these techniques to create a whole set of classes, all of which will support the same interface. This is polymorphism--the most important concept in object-oriented programming. It will also be central to the implementation of a connected embedded system.

Avoiding the Heap

As Example 2 demonstrated, it takes two steps to create an LED object. First, define a variable to hold the object state. Next, initialize the object with a call to LED_Init. The code in Example 1 includes a typedef to use for the first step. The typedef actually creates a single-element array of struct _LED. Just use the array name as the first argument to every method of the LED object, as well as the constructor. The C compiler will convert the array name to a pointer automatically.

This two-step object creation technique is a means to avoid dynamic memory allocation from the heap. On an ordinary PC, the LED constructor can be a function that allocates memory for the struct _LED, initializes the structure, and returns a pointer to it. The article Object-oriented programming in C contains an example of such a constructor.

In microcontroller development, however, you should generally avoid dynamic allocation from the heap, for the following reasons:

The last point is particularly important. Embedded programs are simpler, more efficient, and safer when they do not rely on unpredictable heap allocation.

Of course, you can also avoid the two-step object creation process by defining LED_Init as follows:

#define LED_Init(r,m) {{(r), (m)}}

Then, create the LED object like this:

LED_Ref led = LED_Init(&P2OUT,2);

This approach, however, is not as widely applicable. Unlike with the two-step solution, you cannot write LED_Init as a function if a macro-based implementation proves impossible. As mentioned in the previous section, it is far better to apply one technique consistently throughout the program; having to remember special cases greatly increases the chance of making a mistake.

Another solution is to emulate heap allocation in some simplified way. Example 3 shows one alternative.

#define MAX_LEDS 5  /* Maximum number of LED objects. */

struct _LED* LED_Init(volatile unsigned char* reg,
                      unsigned char mask) {
  static struct _LED mem[MAX_LEDS];  /* The "heap". */
  static unsigned index = 0;         /* Index of the first 
                                        free slot in the "heap". */

  if (index >= MAX_LEDS) {           /* No more room left; */
    return 0;                        /* return the null pointer. */ 
  } else {                    
    mem[index].reg = reg;            /* Initialize the newly */
    mem[index].mask = mask;          /* allocated object. */ 

    ++index;                         /* Prepare for the next allocation. */
    return &mem[index-1];            /* Return the allocated object. */
  
  }

}

Example 3. Simulating heap allocation

Changes to the static array mem persist from one call to LED_Init to the next. Note that this example does not allow you to free the allocated object.

The code in Example 3 shares some of the same dangers as heap-based allocation--namely running out of memory. You need to check the return value of LED_Init. If it is zero, then the constructor did not have enough storage to create the object.

The approach in Example 3 is also potentially wasteful, because the mem array has room for five LED objects. All of this space must still be reserved, even if the client code does not use all five objects.

Nevertheless, many embedded programs can benefit significantly from the careful and limited use of special-purpose dynamic memory allocation techniques. The next article will show such an example.

As a final note, remember that local, nonstatic variables also require a form of dynamic memory allocation! For example, if you declare a variable inside a function, the compiler will generate code to dynamically allocate the required space from the stack when the function starts. The compiler will also include code to free the space when the function exits.

The automatic way in which the system manages memory for local variables makes them far safer than heap-allocated variables. Nevertheless, it is still possible to run out of memory--with disastrous results. For this reason, you should avoid allocating large constructs as local variables.

Automatic array variables with many elements are usually what causes the system to run out of stack memory. If you are using such arrays in your functions, you can often redesign the program to declare the arrays static. This is usually the safest approach, and it also avoids time-consuming array initialization on every function call.

Eliminating the Function Call

Example 1 used macros rather than functions to implement both LED_Init and LED_Toggle. Eliminating function calls can significantly improve the performance of embedded software.

After the compiler processes a program, each function call in the source code will require the execution of at least two machine language instructions in the object code. One is the subroutine call, and the other the subroutine return. On some microcontrollers, these instructions can be particularly expensive, taking multiple clock cycles to complete.

The body of a simple function, however, may be just one or two instructions long. Moreover, these instructions likely require fewer clock cycles than the subroutine call and return. In such cases, eliminating the function call can more than double the performance, with little or no penalty in terms of increased program size.

C compilers have good support for using macros to eliminate function calls. The macro, however, is a potentially dangerous construct. This is clear immediately from the output of the program in Example 4.

#include <stdio.h>

/* Macro to multiply a number by two.  Implemented by adding the
   number to itself. NOTE THAT THIS IMPLEMENTATION IS DELIBERATELY
   POOR, TO ILLUSTRATE THE DANGERS OF MACROS. */
#define mac_times_two(a) a+a

/*

  This is a much better way to write the above macro.

#define mac_times_two(a) ((a)+(a))

  Even better, because "a" is no longer evaluated twice.

#define mac_times_two(a) ((a)*2)

*/


/* Function to multiply a number by two.  Implemented by adding the
   number to itself. */
int func_times_two(int a) {
  return a+a;
}


int main(void) {

  /*Set up two identical counter variables. */
  int count_a = 0;
  int count_b = 0;

  /* The output of the next two statements is very surprising. The
     macro and function should yield the same result, but they do
     not. */
  
  /* The macro and the function do not yield the same result. */
  printf("%d %d\n",mac_times_two(2)/2, func_times_two(2)/2);

  /* You should NEVER call a macro with an argument such as "++i" --
     it may be evaluated twice, giving you an unexpected error. */
  printf("%d %d\n",mac_times_two(++count_a),func_times_two(++count_b));

  return 0;
  
}

Example 4. The dangerous macro

If you want to try the example yourself, compile it with your favorite ANSI 89 or later C compiler, and run it from the command line. The output is surprising:

3 2
4 2

In a modern twist, however, much safer inline functions can replace the function-style macros of Example 1. The inline keyword in a function declaration tells the compiler to expand the function's entire body at the point of call.

This feature is part of the 1999 ANSI C (C99) standard. Even compilers based on the earlier 1989 (C89) standard, however, often support inline functions. For example, the GNU C compiler defines the __inline__ keyword, which it can recognize even when adhering to the C89 standard.

Example 5 shows a rewritten version of the LED driver code from Example 1. The new version replaces macros with inline functions, using the syntax from the C99 standard.

/* File "LED_driver.h" -- the ANSI C99 version.

   This file is very similar to the LED driver covered earlier, but it
   uses "inline" functions instead of macros.  The "inline" keyword is
   part of the 1999 ANSI C standard. */

#ifndef LED_DRIVER_H
#define LED_DRIVER_H


struct _LED {
  volatile unsigned char* reg;
  unsigned short bit;
};

typedef struct _LED LED_Ref[1];

/* Note that "ledp" is a constant pointer -- you cannot make it point
   to another memory location.  Declaring "ledp" constant makes the
   code safer.  You can still use "ledp" to change the data that it
   points to -- only the pointer itself is constant, NOT the data. */
inline void LED_Init(struct _LED* const ledp,
                  volatile unsigned char* reg,
                  unsigned short bit) {

  ledp->reg=reg;
  ledp->bit=bit;

}

inline void LED_Toggle(struct _LED* const ledp) {
  *ledp->reg ^= ledp->bit;
}


#endif

Example 5. Replacing macros with inline functions

Unfortunately, the compiler may ignore the inline keyword. Consequently, you must examine the compiled object code, to make sure that the inlining actually took place. First, compile the Hello World program, using the new driver from Example 5. You may want to rename the files, so that you can experiment with both versions of Hello World on your system.

$ msp430-gcc -g -std=gnu99 -pedantic -W -Wall -Os -mmcu=msp430x149 \
    -o layered.elf hello-world-layered.c

Note the use of gnu99 in place of gnu89. The -g option is another important change--it causes the compiler to output symbols from the source code into the resulting object file. This makes the object code much easier to examine.

Now, obtain a "dump" of the object code:

$ msp430-objdump -sS layered.elf

You may want to redirect the output to a file, because it is very long. The object code discussed here appears at the very end of the dump.

You should see lines from your source code interspersed with disassembled machine instructions that the compiler generated to implement those lines. Example 6 shows a small portion of the disassembly on the author's system. You can see that the compiler has, indeed, expanded LED_Init inline. (Of course, the results on your system may be different.)

int main(void) {
    1140:       31 40 fa 09     mov     #2554,  r1      ;#0x09fa

  LED_Ref led; /* Create the LED object. 
                  BEWARE THAT THE OBJECT IS NOT YET INITIALIZED.*/

  P2DIR=0xFF;  /* Configure port 2; all pins are outputs in this case. */
    1144:       f2 43 2a 00     mov.b   #-1,    &0x002a ;r3 As==11
   code safer.  You can still use "ledp" to change the data that it
   points to -- only the pointer itself is constant, NOT the data. */
inline void LED_Init(struct _LED* const ledp,
                     volatile unsigned char* reg,
                     unsigned short bit) {
    1148:       0e 41           mov     r1,     r14     ;
    114a:       2e 53           incd    r14             ;

  ledp->reg=reg;
    114c:       b1 40 29 00     mov     #41,    2(r1)   ;#0x0029
    1150:       02 00 
  ledp->bit=bit;
    1152:       ae 43 02 00     mov     #2,     2(r14)  ;r3 As==10

  LED_Init(led,&P2OUT,2);  /* Initialize the LED object. */


  for(;;) {
    /* The following two lines implement a very crude delay loop.
       The actual length of the delay can vary significantly.
       This approach may not work with all compilers. */

Example 6. Portion of an object dump, showing function inlining

The MSP430x1xx Family User's Guide (PDF) contains detailed information on the MSP430 instruction (see Chapter 3). Unfortunately, this article cannot cover assembly language in detail--it is a complicated topic that requires an entire book.

The object dump also shows that the non-inline versions of LED_Init and LED_Toggle are present in the object code. In most cases, it is possible to eliminate these non-inline expansions to save space. See the discussion of inline functions in the GCC manual for details. Note that these links are to the GCC 3.2.3 documentation, because MSPGCC is based on GCC 3.2.3 at the time of this writing.

To conclude this section, here is a list of guidelines for replacing an ordinary function with a macro or an inline function:

Of course, according to these guidelines, LED_Init should be a normal function. This article leaves it as is to show the relevant coding techniques. In particular, the Example 1 version of LED_Init is a good illustration of a fairly complex macro.

Hello World, Interrupted

Modern operating systems provide concurrency mechanisms (typically processes and threads) that application developers can use in their programs. Concurrency is a very important feature. For example, an application with a GUI usually has a separate thread for interacting with the user, while other threads perform lengthy tasks in the background. This allows the application to remain responsive to the operator's inputs.

In embedded systems, concurrency is even more important. Events in the outside world (communicated to the microcontroller via switches, temperature sensors, strain gauges, and so on) often need immediate attention (such as opening a valve by activating a solenoid). Missing an event (or even being sufficiently late) can cause catastrophic failure (for example, if a car's antilock brakes failed to function).

The underlying mechanism that supports these concurrency facilities is the interrupt. Here--in outline--is how interrupts work. In response to certain events, the processor will stop what it is doing, save some state information so that it can come back to the stopped task later, and begin executing a piece of code called an interrupt service routine (ISR).

Each type of event has a different ISR associated with it. The ISR must respond to the event quickly--it cannot wait or go to sleep. After finishing, the ISR executes a return-from-interrupt instruction, which allows the processor to resume the previously suspended task. The overall state of the machine, however, may no longer be the same. For example, the operating system may schedule another thread to run as the result of the actions taken by the ISR.

What happens if you are writing an embedded program that runs directly on a microcontroller, without any operating system? The answer is that you must handle the interrupt mechanism directly and write your own ISRs.

This section illustrates how to write these routines, by replacing the crude delay loop from Example 2 with a highly accurate timer. The timer triggers an ISR. While the example does not demand such accuracy, it is often critical in embedded software. Control systems, for instance, need to sample real-world data (such as the temperature in a furnace or the position of a robotic arm) at regularly spaced intervals. You could use a timer-driven ISR routine--very similar to the following code--for this purpose.

In keeping with a layered, object-oriented approach, here is a timer driver, designed to work with the LED objects from Example 1:

/* File "LED_timer_driver.h".

   The header file for "LED_timer_driver.c", which implements an
   interrupt-based timer driver. */

#ifndef LED_TIMER_DRIVER_H
#define LED_TIMER_DRIVER_H

/* The object reference for LED timer driver objects.  The object's
   methods and its constructor take this reference as an argument, but
   they do not actually use it.  It is only there for consistency with
   LED objects (see "LED_driver.h").  A consistent approach reduces
   programmer error. */
typedef int LED_Timer_Ref[1];

/* Constructor. */
void LED_Timer_Init(int* const ignore, struct _LED* const ledp);

/* Start the timer. */
void LED_Timer_Start(int* const ignore);

/* Stop the timer. */
void LED_Timer_Stop(int* const ignore);

#endif

Example 7a. The Timer Driver Header File

Here is the .c file that implements the timer driver.

/* File "LED_timer_driver.c"

   This file implements an interrupt-based timer driver.

   It also shows the composition technique in Object-Oriented
   Programming for microcontrollers, and the classic "Singleton" design
   pattern. */

#include <io.h>
#include <signal.h>

#define TIMER_PERIOD 100  /* The timer will have a period of 100+1. */

#include "LED_driver.h"

static struct _LED* _ledp = 0;

/* The constructor. */
void LED_Timer_Init(int* ignore, struct _LED* const ledp) {
  (void)ignore;              /* Suppress the unused argument warning. */
  /* Do not initialize twice.  This makes an LED timer object a
     Singleton. */
  if (_ledp) {  
    return;
  }

  _ledp = ledp;              /* Save a pointer to the LED object. */            

  /* Note: TACCR0 and TACTL are registers which control Timer_A of the
     MSP430. */
  TACCR0 = 0;                /* Keep Timer_A off for now. */
  TACTL = TASSEL_1 | MC_1;   /* The MC_1 bit instructs Timer_A to
                             count up to the value of TACCR0, then
                             restart the count from 0 (this is
                             called "upmode"). */

  TACCTL0 = CCIE;            /* Enable the Timer_A interrupt in the
                             Timer_A control register (TACCTL0). */
}

/* Start the timer. */
void LED_Timer_Start(int* const ignore) {
  (void)ignore;              /* Suppress the unused argument warning. */
  TACCR0 = TIMER_PERIOD;     /* Set the period for Timer_A.  This also
                             causes the timer to start counting. */
}

/* Stop the timer. */
void LED_Timer_Stop(int* const ignore) {
  (void)ignore;              /* Suppress the unused argument warning. */
  TACCR0 = 0;                /* This stops the timer. */
}



/* The ISR. Note the syntax, which is an extension of the C language.
   Each C compiler that allows you to write ISRs will have slightly
   different syntax for this feature. 

   
   When Timer_A's count reaches the value in the TACCR0 register, it
   triggers this particular ISR. The MSP430 automatically clears the
   interrupt condition by the time the ISR returns.

   See the MSP430x1xx Family User's Guide for more details. */
interrupt(TIMERA0_VECTOR) timer (void) {
  LED_Toggle(_ledp);   /* Toggle the state of the LED on each Timer_A
                       interrupt. */
}

Example 7b. The timer driver itself

Note that if the calling program stops the timer, the LED may wind up in an "on" or "off" state. The LED objects from Example 1 lack a method to turn the LED off. You may implement such a method as an exercise. If you are using the development board covered in the first article, be aware that the LED is wired active low.

Example 8 is a new version of the code in Example 2. This new version uses the timer driver.

/* File "hello-world-isr.c".

   This program illustrates the composition technique in
   object-oriented microcontroller programming.  In addition, the LED
   timer driver used here includes an Interrupt Service Routine (ISR)
   to flash the LED. */

#include <io.h>
#include <signal.h>            /* Needed to handle interrupts. */ 
#include "LED_driver.h"        /* The LED device driver. */
#include "LED_timer_driver.h"  /* The LED timer driver. */

int main(void) {
  /* Create the LED and LED timer objects. 
     BEWARE THAT THE OBJECTS ARE NOT YET INITIALIZED.*/
  LED_Ref led; 
  LED_Timer_Ref timer;
  
  P2DIR=0xFF;  /* Configure port 2; all pins are outputs in this
               case. */

  LED_Init(led,&P2OUT,2);      /* Initialize the LED object. */

  LED_Timer_Init(timer, led);  /* Initialize the timer object. 
                               Note that the LED object is
                               one of the arguments. */

  eint();                      /* Enable interrupts (they are disabled
                               by default */

  LED_Timer_Start(timer);      /* Start the timer. */ 


  /* The "main" function has nothing left to do -- the ISR in the LED
     timer driver now flashes the LED.  The following MSP430-specific
     code puts the microcontroller into a partial sleep state.  See
     MSP430x1xx Family User's Guide for details.

     YOU DO NOT HAVE TO USE THIS CODE IF YOU FIND IT CONFUSING.*/

  _BIS_SR(LPM0_bits + GIE);    /* Enter partial sleep ... */                                   


  for(;;);                     /* Infinite do-nothing loop. The system
                               will never execute it, unless you
                               remove the previous line of code. */ 
}

Example 8. Using the timer driver

Place all of these new files (along with a copy of LED_driver.h) in a separate subdirectory, change to that subdirectory, and compile with the command:

$ msp430-gcc -std=gnu89 -pedantic -W -Wall -Os -mmcu=msp430x149 \
    -o isr.elf hello-world-isr.c LED_timer_driver.c

Example 8 creates an LED object, then passes it to the timer object. The latter uses only the LED object's interface. This illustrates the technique of composition, as discussed previously in Object-Oriented Design for Microcontrollers. The timer and LED objects depend only on each other's interfaces--you can vary the implementation of either, and they will still work together.

Of course, there is a performance penalty associated with these benefits. Appendix C illustrates a highly simplified variation of this design that still retains some of the advantages.

As you can also see from Example 8, an embedded program running directly on the hardware effectively supports two types of tasks. There is one low-priority, long-running task (the main function, as well as any other functions that main calls) and several high-priority tasks of much shorter duration (implemented as ISRs). Example 8 uses the long-running task only to perform initialization; after it starts the timer, main puts the microcontroller into a partial sleep state.

What if a program calls an LED object's methods from the main task as well as from the timer ISR? Because the ISR and the main task are not synchronized, it is highly likely that this will corrupt the object's internal structures or something else on the microcontroller. You need to use critical regions in your code, if you share any resource between an ISR and the main task--just like in thread programming on an ordinary PC. The next article will illustrate how to define such critical regions.

To conclude this section, here are a few important points that you must keep in mind about ISRs:

The last point is particularly important. In an environment where events come in real time from external devices, stepping with the debugger (if it works at all) will yield no insights. After all, the external events will not pause just because the debugger has stopped the program.

Even if you set breakpoints, the timing of the code often changes so dramatically that the software behaves very differently than if the breakpoints were not present.

In fact, one major benefit of connected embedded systems is that they help with debugging. A network-enabled device can send messages about its internal state. Sending such messages tends to be far less disruptive to system operation than the blunt intervention of a debugger. That makes it much easier to find errors.

This article has illustrated some server-grade design patterns--within the resources of a tiny microcontroller. You can now create efficient layered architectures with object-oriented interfaces. The next article will use these techniques to create connected embedded systems--and take a decisive step toward the pervasive computing future.

Further Reading

Overall, the object-oriented approach used here is similar to the one described in the article Object-oriented programming in C.

Of course, more elaborate techniques are available, such as those discussed in the book Object-Oriented Programming with ANSI-C (PDF). They may not always be appropriate to the microcontroller environment, however.

Microcontroller manufacturers usually provide lots of highly detailed, free documentation. For the MSP430, the MSP430x1xx Family User's Guide (PDF) is a very important resource. The guide describes all the features of the MSP430 in detail. To supplement it, read TI's code examples in C and in assembly language. The examples clearly illustrate the various facilities of this microcontroller, making the information in the user's guide much more meaningful.

In order to compile the TI's C examples with MSPGCC, you will need to make two minor changes. First, #include <signal.h>. Then, you will need to alter the ISR routine declarations to match MSPGCC conventions. Use Example 7b in this article as a guide. There are also MSPGCC-specific examples at www.mikrocontroller.net.

You will occasionally need to refer to the GCC manual and possibly the GCC C99 standards compliance page (if you use the new ANSI 1999 C standard). Note that these links are relevant to the 3.2.3 version of GCC, which is the basis for MSPGCC at the time of this writing. Another necessary reference is the MSPGCC manual.

Finally, make sure to read the classic software design book Design Patterns if you have not done so already. The ideas presented there will help you improve the structure of your own software, which is particularly important in complex embedded systems.

Appendix A: Arithmetic on a Microcontroller

This appendix describes the nature of arithmetic on a microcontroller.

Arithmetic operations are a major concern in embedded programming. Microcontrollers such as the MSP430 do not have a floating-point unit (FPU), so any floating-point calculations require a lot of support in software. The resulting code is large, and the processing cost is high. When designing programs to run on a microcontroller, you should carefully analyze any floating-point operations, and try to avoid them if possible.

Even integer operations, however, can carry a heavy penalty. The MSP430 is a 16-bit device, which is a definite advantage over 8-bit microcontrollers that are the norm. Nevertheless, there is still no native support for 32-bit integers, with which many programmers are familiar. In embedded development, it is often wise to restrict most numbers to a maximum of 16 bits. Addition and subtraction of such quantities are very fast on the MSP430, but they also remain reasonable on 8-bit architectures. It is, of course, possible to use 8-bit integers, but often this is too restrictive.

Multiplication and division of integers is also problematic on microcontrollers, which typically have no dedicated circuitry for these operations. In the MSP430 family, for example, some variants actually include a hardware multiplier, but others do not. Without hardware support, multiplication and especially division require the CPU to execute a large number of instructions--a fact that you must take into account right at the design phase of your embedded project.

The exception to this rule is multiplication or division by a power of 2. Microcontrollers (like almost all digital circuits) count in binary, so you can replace a multiplication or division by a power of 2 with much more efficient shift operations. While an optimizing compiler will likely make such replacements automatically, you should consider coding the shift explicitly if your application relies on the enhanced performance. Here are a few examples.

Multiplication Left Shift Equivalent
num * 2 num << 1
num * 4 num << 2
num * 8 num << 3
Division Right Shift Equivalent
num / 2 num >> 1
num / 4 num >> 2
num / 8 num >> 3

Table 1. Multiplication and division with shift operations

Here is a table of powers of 2 (up to 2^16). Use this table to calculate the shift equivalents to multiplication and division operations.

Power of 2 Decimal Hex Octal
0 1 0x1 01
1 2 0x2 02
2 4 0x4 04
3 8 0x8 010
4 16 0x10 020
5 32 0x20 040
6 64 0x40 0100
7 128 0x80 0200
8 256 0x100 0400
9 512 0x200 01000
10 1024 0x400 02000
11 2048 0x800 04000
12 4096 0x1000 010000
13 8192 0x2000 020000
14 16384 0x4000 040000
15 32768 0x8000 0100000
16 65536 0x10000 0200000

Table 2. Powers of 2

Note that the largest unsigned quantity expressible in any given number of bits b is 2^b-1. For an 8-bit unsigned number, this is 256-1, and for a 16-bit unsigned number it is 65,536-1 (see the table).

Here is a summary of the guidelines for arithmetic operations in microcontroller software:

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.

Copyright © 2009 O'Reilly Media, Inc.