ONLamp.com
oreilly.comSafari Books Online.Conferences.

advertisement


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.

Pages: 1, 2, 3, 4, 5, 6

Next Pagearrow





Sponsored by: