As before, my current setup uses the 9.3.1 version of GCC available on TI's website called msp430-elf-gcc. This version of GCC is a rewrite distinct from the older 4.6.3 version known as gcc-msp430 which is available in the Ubuntu repositories. Installing the 9.3.1 version on Ubuntu with the terminal-based installer was straightforward. One gotcha was installing the compiler at a slightly different path like /opt/ti/msp430-gcc/ than the default /opt/msp430-gcc/. The makefile described below hardcodes the linker include directory, so the compiler needs to be installed at the same place on every machine or VM that might compile the project. This was relevant when I tried to install GCC on an ARM-based Radxa ROCK 3C single board computer that I want to use for MSP430 programming eventually. The newer 9.3.1 version of GCC isn't available in the Debian repositories for that board either, and TI's installer is only for x86 systems, so I was able to compile GCC from scratch and get it working on the board. It only needed a few pre-requisite packages to be installed first. The compile time was pretty long which makes sense given that the ARM-based board is much less powerful than the average x86 system. It's probably possible to cross-compile GCC on an x86 system, but setting that up and getting the options right would have taken a while too. After compiling, the source directory had increased to 8.8GB, and the compiled files were about three times larger than the x86 version. Maybe tweaking some of the compile options would bring the compiled size down. After this was all finished, compiling four different MSP430 projects with the newly built compiler on the board produced identical output files to the x86 version.
Another big change was switching to GNU make instead of using plain bash scripts to compile. make has been a major source of frustration to me for a long time, so I have always avoided it if at all possible. The -n option lets you see what commands make would run, so it's easy to dump those to a bash script and avoid a lot of the headaches that make entails. One of my least favorite make experiences was trying to build someone else's project on Windows when the project had originally been developed for Linux. Some of the makefile lines ended in a semi-colon which is unnecessary and ignored with make on Linux but made make on Windows hard crash with no error message. That was a real pain to figure out. The main advantage of make is that it only compiles source files that have been updated since their object files were generated. This is supposed to save time by avoiding unnecessary compilation but makes no practical difference when the chip's flash is only 16K so the entire project compiles in about one second.
The example makefiles that come with GCC have a few lines that seem to assume that the makefile is always located relative to the directory where GCC is installed:
GCC_DIR = $(abspath $(dir $(lastword $(MAKEFILE)))/../../msp430-gcc/bin)
This doesn't make a lot of sense when the installer puts GCC in /opt/msp-430/ by default since you wouldn't store source files in /opt/. Figuring out how to get make to call the correct assembler was the next thing to figure out since the Tiny Calculator source is written entirely in assembly. By default, make was calling the GNU as assembler rather than the as or GCC specific to MSP430. The trick is to define a variable named AS to tell make what do for assembly files, although confusingly this only works if the file ends in .s or .S rather than .asm or .s43 which IAR uses for assembly files for example. It would be more consistent for make to expect a recipe for assembly files rather than calling GNU as with no options which is the default. The C compiler to use is defined by the variable CC and the options to pass it are defined by CFLAGS. You might expect that the arguments for the assembler are therefore defined by AFLAGS, but they are instead defined by ASFLAGS. The last piece of the puzzle is defining a variable that lists the names of object files for make to generate from the source files. This is easy enough, but I decided to create a short script in Python instead to read the list of source files from a text file and return the names of the corresponding object files to make. It's convenient to list the source files one per line with comments in the text file rather than cramming the object file names all onto one line in the makefile.
The biggest challenge was getting a debugger and simulator working together. A big advantage of using IAR to compile is that the IDE includes the simulator and debugger, so it's easy to step through the source line by line watching the register values change. This is really important for the Tiny Calculator project since the majority of the code is for the floating point routines which don't rely on simulating any particular hardware. The new version of GCC contains a copy of GDB for the MSP430 that includes a simulator, but I couldn't get it to work correctly even after adjusting various GDB options. It allows a program to write to memory but reads back nonsense values. This was caused at first by a weird quirk of GNU as where supplying an address to an instruction with relative addressing treats the address as an offset instead of an address. This is odd and inconsistent. Here's an example:
foo: ;assigned 0xC000 by linker for example
bar=0xC000
MOV foo,R4
MOV bar,R4
Both foo and bar have the value 0xC000 here but the two MOV instructions encode different addresses. The first MOV is encoded with a calculated offset so that the instruction loads data starting at foo which is reasonable and useful. The second MOV, on the other hand, loads data from 0xC000 bytes past the instruction rather than calculating an offset to 0xC000 which is probably not what the programmer intended. After I figured this out, I used a different addressing mode but the GDB simulator still gives random, incorrect values when reading from RAM. This is especially obvious with RET instructions where the processor reads garbage out of RAM for the return address and jumps into uninitialized memory.
The next thing to try was mspdebug. Although not part of the GNU tools, it has been well-known among MSP430 users for a long time since it's helpful with a number of tasks such as device programming. Like GDB, it also has a simulator mode. Running a test file showed that the simulator works fine, but there's no convenient way to see the source and registers while debugging line by line like GDB has with it's layout regs mode. mspdebug has a server mode that lets GDB connect and access its simulator. The first try produced the error "Reply contains an invalid hex digit 59" after connecting and trying to run the test program. This is apparently a common error with GDB that can have a variety of causes. The version info for the mspdebug downloaded from the Ubuntu repositories showed 0.22, so I built the newest version 0.25 from source which fixed the error. Stepping through the program in layout regs mode in GDB garbled the screen since mspdebug was outputting lots of lines about bytes reads every step. There's no option in mspdebug to turn the output off, so I added &>/dev/null to the invocation and finally got a working simulator and debugger combination. Moving everything to a makefile as described above caused the &>/dev/null to be ignored (a known issue with make that doesn't seem to have an answer) so the extraneous output was back but disappeared after changing &>/dev/null to > /dev/null (no explanation for this either.) A last convenience was defining a new GDB command in ~/.gdbinit called qq that quits without asking for confirmation.
Here's the final template makefile I can use for restarting my Tiny Calculator project and for any other new projects:
TARGET=project-name
#OBJECTS=main.o asm.o
#generate list of object files with script
#create list manually as above if script unavailable
OBJECTS=$(shell make-filelist filelist.txt)
MAP=$(TARGET).map
MAKEFILE=Makefile
RM= rm -rf
GCC_DIR = /opt/msp430-gcc/bin
SUPPORT_FILE_DIRECTORY = /opt/msp430-gcc/include
DEVICE = MSP430G2553
AS = $(GCC_DIR)/msp430-elf-as
CC = $(GCC_DIR)/msp430-elf-gcc
GDB = $(GCC_DIR)/msp430-elf-gdb
OBJCOPY = $(GCC_DIR)/msp430-elf-objcopy
ASFLAGS = -I $(SUPPORT_FILE_DIRECTORY) -mmcu=$(DEVICE) -g
CFLAGS = -I $(SUPPORT_FILE_DIRECTORY) -mmcu=$(DEVICE) -O2 -Wall -g
LFLAGS = -L $(SUPPORT_FILE_DIRECTORY) -Wl,-Map,$(MAP),--gc-sections
GDB = msp430-elf-gdb
MSPDEBUG = mspdebug sim
GDBFLAGS = --quiet --ex "target remote localhost:2000" --ex "layout src" \
--ex "layout regs" --ex "break main" --ex continue
MSPDEBUGFLAGS = "opt quiet true" gdb
all: $(TARGET)
$(OBJCOPY) -O ihex $(TARGET).out $(TARGET).hex
$(TARGET): $(OBJECTS)
#Links here after each file processed by CC or AS
$(CC) $(CFLAGS) $(LFLAGS) $? -o $(TARGET).out
clean:
$(RM) $(OBJECTS)
$(RM) $(MAP)
$(RM) *.out
$(RM) *.hex
debug: all
$(MSPDEBUG) "prog $(TARGET).out" $(MSPDEBUGFLAGS) > /dev/null &
$(GDB) --se $(TARGET).out $(GDBFLAGS)
No comments:
Post a Comment