December 4, 2012 18 Comments
This is the first in a series of posts that will be going over various aspects of using the new Stellaris Launchpad with GCC. This post is going to be a rundown of how the various compiler flags, linker scripts, libraries and drivers work together to give us a working program for our dev board.
A couple of weeks ago the folks over at Recursive Labs posted about using the ARM gcc toolchain to build binaries for the TI Stellaris Launchpad. The directions that RL laid out were pretty straight forward, they made use of the Summon ARM Toolchain (SAT) to get a working copy of GCC and newlib. After that it was a matter of wading through the maze of TI supplied Makefiles to get the proper flags to build our projects.
Multilibs and the Awesomeness of SAT
I’ve never used SAT before to build and ARM toolchain and I have to say that its really fantastic, and the folks that have been working on it are under no certain terms, considered a hero by me. When you build the toolchain it will build it with Multilib support by default, which means it will compile a version of newlib (libc, libm) and libgcc for various ARM processors (Cortex-M0, M3, and our M4).
Not only that, it will build libraries for our Cortex-M4 in both softfp and hard floating point unit flavors so we can bounce back and forth between the two just by changing a gcc flag.
There are a bunch flags that are being passed to the compiler that at first glance didn’t make a bunch of sense. After a little digging around I’ve found what most out what most of the flags stand for.
CFLAGS += -mthumb #Using the Thumb Instruction Set CFLAGS += -mcpu=cortex-m4 #The CPU Variant CFLAGS += -mfloat-abi=softfp #Which floating point ABI to use CFLAGS += -mfpu=fpv4-sp-d16 #The type of FPU we are using CFLAGS += -Os #Compile with Size Optimizations CFLAGS += -ffunction-sections #Create a separate function section CFLAGS += -fdata-sections #Create a separate data section CFLAGS += -MD #Create dependency files (*.d) CFLAGS += -std=c99 #Comply with C99 CFLAGS += -Wall #
Be analEnable All Warnings CFLAGS += -pedantic # Be extra analMore ANSI Checks CFLAGS += -Dgcc #Flag used in driverlib for compiler specific flags CFLAGS += -DPART_LM4F120H5QR #Flag used in driverlib for specifying the silicon version. CFLAGS += -DTARGET_IS_BLIZZARD_RA1 #Used in driverlib to determine what is loaded in rom.
Another thing that could use some de-mystification is the loader scripts that tell the linker where to put all the data, text, and bss sections of the executable you build when its flashed to the device. There is one included with each of the demo projects and if you open them all up, you’ll notice that they are all the same. So you can pick one and re-use it with each of your projects.
After all of the sources are compiled and you have a stack of object files the next step is to link them together using
arm-none-eabi-ld. This is also the step where the stdlib functions get brought into the mix as well. Unlike compiling a normal program there are a couple extra things that need to be done here.
LDFLAGS += -T blinky.ld #Path to Linker Script LDFLAGS += --entry ResetISR #Name of the application entry point LDFLAGS += --gc-sections #Tell the linker to ignore functions that aren't used.
Using GCC as the Linker
Normally when you compile a program, gcc will perform a lot of magic for you behind the scenes by compiling and linking your program all in one step. Here we are doing it in three steps, compiling all of the source files, linking all the objects together into and ELF executable, and then copying the relevant sections out of the ELF executable and dumping them in a raw binary format that can be flashed to the device.
If you were interested in skipping the middle step that would be possible but the flags can get kind of messy. Since we need to pass in a few arguments to the linker itself (listed below), we do that by prefacing all the arguments with
-Wl,<ARG>. It would look something like this.
arm-none-eabi-gcc $(CFLAGS) -c blinky.c arm-none-eabi-gcc $(CFLAGS) -c startup_gcc.c arm-none-eabi-gcc $(CFLAGS) -Wl,--script=blinky.ld -Wl,--entry ResetISR \ -Wl,--gc-sections -o blinky.out blinky.o startup_gcc.o libm.a libc.a libgcc.a
Is there any advantage to doing this? I don’t think so, but I thought it was worth a blurb to show the difference.
Using StellarisWare’s DriverLib
When you are writing your program and want to use the functionality within driverlib to setup the peripherals you will need to compile driverlib with gcc. After you have built your toolchain and added the bin directory to your PATH variable cd into the Stellarisware root folder and type
make. This will go through and build all the projects and driverlib, allowing you to link the function calls into your programs.
With that out of the way, now we’re going to discover why you don’t actually need to use any of it!
ROM, MAP, and Driverlib
One thing that confused me for a bit in using Stellarisware was the difference between calls to driverlib functions being prefixed with
MAP_. Confusion that would have been easily remedied if I had bothered to read the manual, but thats no fun. So to save anyone else a trip to the documentation center here is a brief rundown of what’s happening.
The Stellaris Launchpad comes preloaded with a copy of driverlib in ROM so that when you are creating your programs you don’t need to link in a copy of driverlib yourself. What the
ROM_ prefixes are meant to accomplish is increase the portability of applications you write for the LaunchPad. Different versions of the hardware will have different subsets of driverlib loaded into ROM.
//Has the defines for the constants passed to the //functions as well as the stubs for the plain function calls #include "driverlib/sysctl.h" //Has the definitions for ROM_XXX function calls #include "driverlib/rom.h" //Has the definitions for MAP_XXX function calls //and relies on the information in rom.h to work. #include "driverlib/rom_map.h" /* This function call to set the clock rate will require that you link * Driverlib as part of the compilation process. */ SysCtlClockSet(SYSCTL_SYSDIV_4|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN); /* This function call to set the clock rate will not require that you * link in driverlib during compilation. But will fail to compile if * the particular device you are using does not have this function loaded into ROM. */ ROM_SysCtlClockSet(SYSCTL_SYSDIV_4|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN); /* This function call to set the clock rate will not require that you * link in driverlib during compilation, unless the device you are * using doesn't have the function in ROM, then it will link against driverlib. */ MAP_SysCtlClockSet(SYSCTL_SYSDIV_4|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN);
One thing that may be important to note about using this feature would be the
-DTARGET_IS_BLIZZARD_RA1 flag that is passed to gcc during compilation, this tells the preprocessor what device the code is being compiled for and maps in the correct function calls for
ROM_ calls. Not passing this in will result in compilation errors.
Another thing to note that has burned me a few times is if you ever nest these function calls like this
//Won’t Work MAP_UARTConfigSetExpClk(UART0_BASE, MAP_SysCtlClockGet(), 115200, UART_CONFIG_PAR_NONE); //Should Work MAP_UARTConfigSetExpClk(UART0_BASE, (MAP_SysCtlClockGet()) , 115200, UART_CONFIG_PAR_NONE);
You need to make sure that you wrap the inner calls to
ROM_ functions in
() other wise the preprocessor will get them mucked up and will result in a compilation error, or, and this is what happened to me, will result in a runtime error where the UART will stop working for apparently no reason.
So if you plan on moving your code to a different development platform down the road, it would be be a smart idea to use the
MAP_ version of the function calls to save yourself some hassle down the road. If not, at least use the
ROM_ calls to save yourself some code size. If you end up needing to modify the code in driverlib you can just make a call to the regular function and it will link in your modified version.
If you want further reading check out the Driverlib User’s Guide (
SW-DRL-UG-9453.pdf) in the doc/ folder of your Stellarisware directory.
All the of the code presented here can be found on my github account located here. There is some extra functionality included in the code that I haven’t covered yet, so if you want a sneak peak at whats coming, take a look.
wget https://github.com/eehusky/Stellaris-GCC/archive/b1-intro.tar.gz tar xf b1-intro.tar.gz cd Stellaris-GCC-b1-intro/prog0
This is my first attempt at “blogging”, so if anyone has a comments/criticisms please drop a comment below. Any feedback would be greatly appreciated.
Stellaris-GCC: Newlib-SysCalls-Stacks-Heaps-Oh My!
Stellaris-GCC: The Hard FPU
Stellaris-GCC: SD-Cards and File Systems