MSP430 programming using GAS

The executable as produced by GCC uses 218 bytes of program code and 2 bytes of RAM. There were no variables in the C program, so this variable comes from the MSP430 C framework. (Actually, it is used to keep the watchdog alive while starting up the program.)

Can we get the executable even smaller? The time has come for us to try some assembler programming. As we will see, when using the GNU assembler to work with include files that have been converted from use with the Texas Instruments tool suites to the GNU C compiler, there are a few aspects that might not have been solved as smoothly as possible. Still, one of the points of this exercise is to use the standard distribution as it was installed by the Ubuntu packages.

$ cd msp430/cycle_led_asm/
$ less cycle_led.asm

As we can see, the code is pretty much based on the previous C version. Some comments on things that are worthy of notice:

#include <msp430g2553.h>

We have actually run in to difficulties right away. Normally, we would expect to be able to use the assemblers include directive, but if we change this line to

.include "msp430g2553.h"

and then run the assembler

$ msp430-as -I /usr/msp430/include cycle_led.asm
/usr/msp430/include/msp430g2553.h: Assembler messages:
/usr/msp430/include/msp430g2553.h:55: Error: unknown opcode `extern'
/usr/msp430/include/msp430g2553.h:135: Error: unknown opcode `sfrb(ie1,'
/usr/msp430/include/msp430g2553.h:142: Error: unknown opcode `sfrb(ifg1,'
...

we get a horrible batch of error messages. Regardless of the fact that the header file states that it supports assembler development, when using the GNU tools, this simply isn’t the case. The GNU assembler ignores the #ifdef __cplusplus on line 54 of the msp430g2553.h header file, and happily tries to parse the C++ code that follows, obviously resulting in errors. We need to preprocess the code in some way, so CPP should be able to solve our problems. We change the include statement back to the ‘#’ version.

$ cpp -I /usr/msp430/include cycle_led.asm > cycle_led.s
$ less cycle_led.s
...
# 1 "/usr/msp430/include/intrinsics.h" 1
# 47 "/usr/msp430/include/intrinsics.h"
void __nop (void);
void __dint (void);
void __eint (void);

We still have some C code in there. If we look in intrinsics.h it seems that we managed to overcome the #ifdef __cplusplus problem, but for some reason the symbol __ASSEMBLER__ is not defined. The way that the include file is written, missing this symbol definition leads to inclusion of C code. It turns out that CPP needs some more help to understand that this is an assembler file.

$ cpp -I /usr/msp430/include -x assembler-with-cpp cycle_led.asm > cycle_led.s
$ less cycle_led.s
...

Things are looking much better now. This looks like a pure assembler file, so maybe we can get it to assemble?

$ msp430-as cycle_led.s
cycle_led.asm: Assembler messages:
cycle_led.asm:32: Error: garbage after immediate: #_BIS_SR(((0x0080)+(0x0040)+(0x0020)+(0x0010)))

We can find the _BIS_SR() definition in msp430g2553.h, line 114. But looking at line 105, it seems that we are inside an #else branch using defines meant for C. On line 97, the condition used is #ifndef __STDC__. So even though we have used the -x assembler-with-cpp flag to CPP, it still has __STDC__ defined. Digging deep into the reference documentation for CPP, we find that the way to leave __STDC__ undefined is to use the -traditional-cpp flag.

$ cpp -I /usr/msp430/include -x assembler-with-cpp -traditional-cpp cycle_led.asm > cycle_led.s
$ msp430-as cycle_led.s
/usr/msp430/include/msp430g2553.h: Assembler messages:
/usr/msp430/include/msp430g2553.h:730: Error: junk at end of line, first unrecognized character is `/'
...

Back in the msp430g2553.h file, for some reason line 730 contains a C++ style //-comment. Apparently, CPP with -traditional-cpp does not remove C++ style comments. This means that we end up with a two-step solution – run CPP once with the -traditional-cpp to avoid including C code that we get from __STDC__ being defined, and once without -traditional-cpp to remove C++ comments.

$ cpp -I /usr/msp430/include -x assembler-with-cpp -traditional-cpp cycle_led.asm > cycle_led.asmx
$ cpp -I /usr/msp430/include -x assembler-with-cpp cycle_led.asmx > cycle_led.s
$ msp430-as cycle_led.s

Phew! We had to do a lot of work to get around some of the tool limitations and some decisions in the header files that were not really optimized for the GNU toolchain. (We could execute CPP just once by undefining __STDC__ on the command line, but that results in a warning that is not possible to disable, so I find the result not to be as clean as this solution.)

Moving on to some more comments about the program itself.

    .text
main:
    mov     #(WDTPW | WDTHOLD), &WDTCTL

The .text segment will be placed in flash memory, so this is where we should be outputting our program to.

    bis.b   #(GREEN_LED | RED_LED), &P1DIR
    bic.b   #BUTTON, &P1DIR

BIS and BIC are really useful bit set and clear instructions. When accessing hardware, there is always the question about whether to set bits directly at the device register address, or to read the device register into a processor register, modify it, and write it back. An instruction that affects a device register should use the absolute addressing mode, so it will be slower and take up more program memory than the corresponding processor register access function, but the latter needs two extra instructions to read in and write back the contents.

...
isr_button_press:
    bit.b   #BUTTON, &P1IFG
...

As in the C program, there is an interrupt routine that handles the cycling through the LED states when the button is pushed.

    .section .vectors, "a"
...
    .word   isr_end     ; 0xFFFC Non-maskable
    .word   main        ; 0xFFFE Reset

GCC is set up to generate the interrupt vector for us automatically, so when programming in assembler we need to set it up ourselves. The .vectors section will be placed at the interrupt vector area at the top of flash memory. It also needs to have the “a” attribute in order to actually be loaded by mspdebug. All that remains is to build. After that, we can download the code to the Launchpad using mspdebug, the same way as we did for the C program.

$ make
cpp -I /usr/msp430/include -x assembler-with-cpp -traditional-cpp cycle_led.asm > cycle_led.asmx 
cpp -I /usr/msp430/include -x assembler-with-cpp cycle_led.asmx > cycle_led.s 
msp430-as -mmcu=msp430g2553 -mcpu=430 -mmpy=none -o cycle_led.o cycle_led.s
msp430-ld -L /usr/msp430/lib/ldscripts/msp430g2553 -o cycle_led.elf cycle_led.o

   text	   data	    bss	    dec	    hex	filename
    114	      0	      0	    114	     72	cycle_led.elf

rm cycle_led.s cycle_led.asmx

We end up with an executable of 114 bytes, close to half the size of the C program. Well done!