MSP430 Programming: Tips and Tricks

There are a number of "gotchas" which are easy to fall into when programming the MSP430 (or, really, any embedded device). Because symptoms of bugs are often more difficult to detect than on PC processes, it can be tricky to figure out what went wrong. This is especially true when dealing with things like sleep modes and interrupts — because the symptom of a misconfigured interrupt is often simply that nothing happens, you can't even catch the error with a debugger breakpoint! This page documents a number of common errors that I have seen. Hopefully it will help you keep from making them, or at least help you find them when you've made them.

The Watchdog Timer

This is one you'll find in pretty much every MSP430 tutorial or example, but yet it continues to bite people. At power up, the processor's watchdog timer, a device which resets the processor if it thinks that the firmware has locked up or entered an infinite loop, is enabled. This means that after booting, you only have a few thousand processor cycles to "service" the watchdog, which is the technical term for telling it that your application is still making progress.

The most common error to make with respect to the watchdog timer is simply ignoring it. If this happens, the device will reset itself over and over, as the watchdog timer expires after each boot.

Disabling the watchdog timer at bootup is the simplest way to deal with this. For "real" applications you likely want to configure and reenable the watchdog, but for learning it's OK to simply disable it and leave it disabled. Fortunately, disabling the watchdog is easy to do, and accomplished with this single line of code:

WDTCTL = WDTPW | WDTHOLD;

WDTCTL is the watchdog control register. WDTPW is a special constant, the "watchdog timer+ password". Any value written to the watchdog control register without this password will be ignored and trigger an error condition, so make sure you include it when manipulating the timer. The WDTHOLD bit disables the watchdog timer.

Enabling Interrupts

When adding an application's first interrupt-driven features or transitioning from busy-wait timers to clock-driven timers, it's easy to forget to enable interrupts. If this happens, sometimes the program will appear to run correctly for a while and then quit, or sometimes it will fail almost immediately, depending on how it is structured and how long various operations take to complete. When using interrupt-driven routines, remember to enable global interrupts any time they don't have to be masked for some reason.

Under MSPGCC, interrupts can be enabled with the __eint() function as follows:

#include <signal.h>

int main()
{
    __eint();
}

Older versions of MSPGCC used eint(), and it was located in signal.h.

Under other compilers, this may be accomplished differently. For example, the IAR compiler appears to use _EINT(), and some compilers may use:

_BIS_SR(GIE);

Check your compiler/development environment documentation to see how to correctly enable interrupts, if one of these operations does not work.

Object Placement

With a processor which may have as little as 2kB of FLASH and 128B of RAM, placement of code and data objects in memory is critical. The compiler will do its best to put objects "where you want them", provided that you give it enough information to do so. This means using the const keyword liberally, and using keywords like volatile and register carefully and sparingly, to allow the compiler to lay out your memory to best effect.

There are tools provided with GNU binutils to help you make sure your code conveys enough information to the compiler that it places your objects appropriately, and that the compiler hasn't done anything stupid. Tools such as objdump, nm, and size can tell you a lot about your executable layout, if you know where to look. Using size to make a quick sanity check on the various segment sizes of your executable can help you be sure that symbols were put in the right places. If you see, for example, a large BSS but don't expect a lot of non-constant data in your program, you're likely missing a const somewhere. Following up with objdump -h to identify the segment boundaries and nm -S to find the location of large symbols can tell you where the problem lies.

Note that the above commands are probably all prefixed with msp430- on your system; for example, the MSP430 objdump is likely called msp430-objdump.

For more information about executable formats and object layout, see the book Linkers and Loaders by John R. Levine, also available online in pre-publishing form here. You will probably also want to check the GNU info pages for the various binutils commands listed above; they are very powerful and sometimes complicated.