Build a sample C project

CMSIS

The Cortex Microcontroller Software Interface Standard (CMSIS) is a basic API provided to use the microcontroller’s core and its peripherals and make it easier for developpers to port code from one Cortex device to another or to reuse someone’s code.

The footprint is said to be limited to about 1KB.

ARM is providing the basic CMSIS package and the Cortex vendors are providing more complete versions for their devices. A basic introduction about CMSIS can be found here.

As I’m using the LPC1756 for this project, I downloaded the NXP LPC17xx CMSIS compliant firware driver library. You should find the latest version here.

NXP suggests the following project file structure for every CMSIS based projects (excerpt from NXP CMSIS document) :

I personally find it way too heavy, and decided to go for some simplifications…

So, for my sample/template project, I just kept the core and the driver header/source files and discarded the rest.

I still keep the complete CMSIS package at hand, especially for the examples and when I need it, to use the SPI, UART and ETHERNET frameworks (I didn’t include them in the sample project).

Import and build the sample project

I’ve prepared a sample Eclipse project that can be used as a basic template.

You can download the archive here. Extract the project directory into your workspace directory.

From within Eclipse, import the project (import / existing project…). You should have the C_template project shown like this :

Now you should be able to build the project by choosing one of the available build configuration…

Build configurations

There are 3 available build configurations : debug_RAM, debug_ROM & release_ROM.

The debug_RAM configuration allows you to run the code directly in RAM, using the JTAG debugger. This can be interesting if your debugger isn’t programming the ROM (some don’t without an option! in that case, people use a serial ICSP solution for ROM programming), or considering that RAM code download and execution is faster (debugging may also be faster?).

Choose a build configuration and run it… If everything’s fine, you should see something like this in the console output :

The text field refers to the size of the code, while the data field refers to the size required for handling global variables. The bss value shown doesn’t seem to be relevant.

If you encounter an error in the building process, you’ll have to investigate !

Did you rename the sample project ? In that case, you should modify the include directories field in the project C/C++ settings for every build configuration.

The project Settings allow you to entirely configure your toolchain from within Eclipse, thanks to the great GNU ARM plugin.

Inner mechanics

Here are some notes about how things work…

main()

main.c and main.h are the project main sample code/header files.

The main program is using the SysTick counter to provide a basic delay(ms) function and turn on/off a LED on the P2.7 pin every 100 ms.

The SysTick interrupt handler is declared as .fastcode, then will be executed in RAM (.fastcode will be discussed later). The point here is just to test the .fastcode, the interrupt handler is called every 1ms and shouldn’t need to be executed in RAM…

I’m using the bit-banding capabilities to directly address the P2.7 value bit, without having to read/write and bit-mask the entire register. The bit-banding macros are defined in main.h. See the LPC17xx user manual for more info. about bit-banding.

The vector table offset value is set to the beginning of ROM or RAM, depending on the ROM/RAM building mode. In RAM mode, the VMA and the LMA for the .data section are identical, and this is the test that I’m using here (_sidata is LMA, _sdata if VMA – discussed later-).

CMSIS and device configuration

In the CMSIS Core directory, you’ll find the lpc17xx_libcfg.h file which allows you to configure the CMSIS library and tell which peripheral/module you want to use. You’ll also find the lpc17xx_system.c file which defines setup variables like the device clock frequency, and other setting variables. The device and library configuration can be done using these two files. startup_lpc17xx.c contains the device vector table and the startup code, so you should have a look at it…

The CMSIS_drivers files are the ones used for the CMSIS library modules you declared in lpc17xx_libcfg.h. Note that lpc17xx_libcfg_default.c and lpc17xx_libcfg_default.h are provided as config templates and shouldn’t be compiled (exclude from build in Eclipse).

linker scripts

LPC1756_ROM.ld and LPC1756_RAM.ld are the linker scripts for ROM and RAM building configurations.

The linker scripts are firstly telling the linker what is the device memory (flash, SRAM) configuration. That allows him to give you a warning at build time when you are exceeding the memory capabilities.

Here is the memory section declaration for the LPC1756 :

MEMORY
{
 /* LPC1756 : 256k ROM + 32k SRAM */

 /* On-chip ROM is a readable (r), executable region (x) */
 /* On-chip SRAM is a readable (r), writable (w) and */
 /* executable region (x) */

 /* Main ROM region - 256k for LPC1756 */
 IROM (rx) : ORIGIN = 0x00000000, LENGTH = 256k
 /* local static RAM - 16k for LPC1756 */
 IRAM0 (rwx) : ORIGIN = 0x10000000, LENGTH = 16k
 /* AHB SRAM - 16k for LPC1756 - often used for USB */
 IRAM1 (rwx) : ORIGIN = 0x2007C000, LENGTH = 16k
}

And here is the LPC1758 version :

MEMORY
{
 /* LPC1758 : 512k ROM + 64k SRAM */
 /* Main ROM region - 512k for LPC1758 */
 IROM (rx) : ORIGIN = 0x00000000, LENGTH = 512k
 /* local static RAM - 32k for LPC1758 */
 IRAM0 (rwx) : ORIGIN = 0x10000000, LENGTH = 32k
 /* AHB SRAM - 16k+16k for LPC1758 - often used for USB and ETHERNET */
 IRAM1 (rwx) : ORIGIN = 0x2007C000, LENGTH = 16k
 IRAM2 (rwx) : ORIGIN = 0x20080000, LENGTH = 16k
}

You can refer to the LPC17xx datasheet and user manual for more details about the devices memory maps.

The linker script then tells the linker where in memory it should place every defined section.

Have a look at the linker scripts and see how the sections are mapped in ROM and RAM configurations…

.text is the section for the code and .data is for the global variables declarations.

LST and MAP files

When you build a configuration, in the corresponding binary output directory, you will find an .lst and a .map file.

Here is the .lst file for a release_ROM build :

It shows you how each linker script section is mapped to the device memory, which is quite useful…

Note that under 0x 1000 0000, we’re in the ROM memory space, and above 0x 1000 0000, it’s the SRAM and peripherals memory space.

The VMA (Virtual Memory Address) is the address at which the section is intended to be run (or found at runtime for data).

The LMA (Loading Memory Address) is the address at which the code/data is loaded/written.

So, in RAM mode, everything (VMA and LMA) will be in SRAM. In ROM mode, the code will have VMA and LMA in ROM, and data sections will have LMA in ROM and VMA in RAM.

The .fastcode section is here to tell the linker to make some code execute in RAM, instead of ROM (discussed later).

After the sections memory mapping descriptions, the .lst file allows you to see the binary disassembly.

The .map file is perhaps less important, but can be helpful since it shows you all the functions of every .o module before the linker creates the binary.

Startup file

startup_LPC17xx.c is the startup file that fills the device’s exceptions and interrupts vector table and defines the reset_handler function which is run when the device is started/reset.

The startup code copies the data segments initialization values from ROM to RAM, fills the bss sections with zeros and if needed, copies .fastcode sections from ROM to RAM.

Then it just calls the main() function…

.fastcode feature

The .fastcode feature is described in the excellent Building bare-metal ARM systems with GNU guide. It allows you to tell the linker that you want to execute given functions from SRAM at runtime, instead of ROM.

Executing code from SRAM is faster than from ROM and consumes less power, so it should be used for the small parts of code that are performance critical.

You just have to put the following statements before your functions declarations :

__attribute__ ((section(".fastcode")))
__attribute__((noinline))

Having a look at the resulting .lst file, you can check that the VMA for your .fastcode is in RAM, while the LMA is in ROM. The startup script will copy your code from ROM to RAM at runtime when the device is reset.

Optimization levels

For release configuration, you can try various optimization levels. I personnaly use -O2, but if you are short on memory, -Os (minimal binary size) is a must !

Available libraries

The CodeSourcery G++ Lite toolchain comes with thumb2 libraries ready to be used in your project.

Look for an eabi directory where you installed your CodeSourcery G++ Lite, and you’ll find the include files and the libraries.

Here are the main libraries :

libc is the newlib C library

libm is the newlib math library

libstdc++ is the standard C++ library

libsupc++ is the C++ support library (exceptions…)

CodeSourcery comes along with included documentation/reference for those libraries.

further improvements

The CMSIS configuration files are not easy to use (system_LPC17xx.c was apparently intended to be modified by a configuration wizard?).

The include directories must be changed when the project is renamed.

A C++ version of the template could be interesting, but I don’t need it now and I guess I would have to modify every CMSIS source/header file to put this everywhere :

#ifdef __cplusplus
extern "C" {
#endif

If you want to make a C++ version, I suggest you use the GNUARM Eclipse plugin to create a blank CodeSourcery-based C++ project and add CMSIS into it…

If you manage to do it, please share it…

The next step will be : Hardware prototyping