Electronic – STM32F2: Makefile, linker script and start-up file combination without commercial IDE

cgccstm32

I have been working with an STM32F2 (specifically, the STM32F217IGH6 on a development board) for about two months. By far my biggest problem had to do with the "setup", which includes makefile, linker script and start-up file.

In particular, I have been unable to properly set up my interrupt vector table and have interrupt handlers called. ST does provide examples tailored to commercial IDEs. Instead, I am using the free Yagarto recompilation of the GCC toolchain (and OpenOCD to load the image via JTAG).

Are there example projects for my board (or a close cousin of it) which contain the appropriate makefile, linker script and start-up file combination for non-commercial IDEs that are set up for interrupt handlers to be called?

Best Answer

http://github.com/dwelch67

stm32f4 and stm32vld in particular, but the others may be useful to you as well. mbed and the mzero directory under mbed (cortex-m0).

I like the keep it simple stupid approach, minimal linker scripts, minimal startup code, etc. The work is done by the code not by any particular toolchain.

Most forms of gcc and binutils (capable of thumb) will work somewhat with these examples as I use the compiler to compile not as a resource for library calls, I dont use the stock linker scripts, etc. Older gcc and binutils will not know about the newer thumb2 parts so some changes may be required.

I build my own gcc, binutils and llvm/clang as well as use codesourcery for example (now mentor graphics but you can still get the free/lite version).

Esp when starting out putting together a project for a new target you need to do some disassembly. In particular to make sure the items are where you want them, the vector table for example.

Look at stm32f4d/blinker02 for example. It starts with vectors.s the exception/vector table plus some asm support routines:

/* vectors.s */
.cpu cortex-m3
.thumb

.word   0x20002000  /* stack top address */
.word   _start      /* 1 Reset */
.word   hang        /* 2 NMI */
.word   hang        /* 3 HardFault */
.word   hang        /* 4 MemManage */
.word   hang        /* 5 BusFault */
.word   hang        /* 6 UsageFault */
.word   hang        /* 7 RESERVED */
.word   hang        /* 8 RESERVED */
.word   hang        /* 9 RESERVED*/
.word   hang        /* 10 RESERVED */
.word   hang        /* 11 SVCall */
.word   hang        /* 12 Debug Monitor */
.word   hang        /* 13 RESERVED */
.word   hang        /* 14 PendSV */
.word   hang        /* 15 SysTick */
.word   hang        /* 16 External Interrupt(0) */
.word   hang        /* 17 External Interrupt(1) */
.word   hang        /* 18 External Interrupt(2) */
.word   hang        /* 19 ...   */

.thumb_func
.global _start
_start:
    /*ldr r0,stacktop */
    /*mov sp,r0*/
    bl notmain
    b hang

.thumb_func
hang:   b .

/*.align
stacktop: .word 0x20001000*/

;@-----------------------
.thumb_func
.globl PUT16
PUT16:
    strh r1,[r0]
    bx lr
;@-----------------------
.thumb_func
.globl PUT32
PUT32:
    str r1,[r0]
    bx lr
;@-----------------------
.thumb_func
.globl GET32
GET32:
    ldr r0,[r0]
    bx lr
;@-----------------------
.thumb_func
.globl GET16
GET16:
    ldrh r0,[r0]
    bx lr

.end

No interrupts on this example, but the other things you need are here.

blinker02.c contains the main body of C code with the C entrypoint that I call notmain() to avoid calling it main (some compilers add junk to your binary when you have a main()).

will spare you a cut and paste. the makefile tells the story about compiling and linking. Note that a number of my examples compile two or more binaries from the same code. gcc compiler, llvm's clang compiler, thumb only and thumb2, different optimizations, etc.

Start by making object files from the source files.

vectors.o : vectors.s
    $(ARMGNU)-as vectors.s -o vectors.o

blinker02.gcc.thumb.o : blinker02.c
    $(ARMGNU)-gcc $(COPS) -mthumb -c blinker02.c -o blinker02.gcc.thumb.o

blinker02.gcc.thumb2.o : blinker02.c
    $(ARMGNU)-gcc $(COPS) -mthumb -mcpu=cortex-m3 -march=armv7-m -c blinker02.c -o blinker02.gcc.thumb2.o

blinker02.gcc.thumb.bin : memmap vectors.o blinker02.gcc.thumb.o
    $(ARMGNU)-ld -o blinker02.gcc.thumb.elf -T memmap vectors.o blinker02.gcc.thumb.o
    $(ARMGNU)-objdump -D blinker02.gcc.thumb.elf > blinker02.gcc.thumb.list
    $(ARMGNU)-objcopy blinker02.gcc.thumb.elf blinker02.gcc.thumb.bin -O binary

blinker02.gcc.thumb2.bin : memmap vectors.o blinker02.gcc.thumb2.o
    $(ARMGNU)-ld -o blinker02.gcc.thumb2.elf -T memmap vectors.o blinker02.gcc.thumb2.o
    $(ARMGNU)-objdump -D blinker02.gcc.thumb2.elf > blinker02.gcc.thumb2.list
    $(ARMGNU)-objcopy blinker02.gcc.thumb2.elf blinker02.gcc.thumb2.bin -O binary

the linker, ld, uses a linker script which I call memmap, these can be extremely painful, sometimes for a good reason, sometimes not. I prefer the less is more approach to the one size fits all, everything but the kitchen sink approach.

I dont use .data typically (well almost never) and this example doesnt have a need for .bss so here is the linker script, just enough to place the program (.text) where it needs to be for this processor they way I am using it.

MEMORY
{
    ram : ORIGIN = 0x08000000, LENGTH = 0x1000
}

SECTIONS
{
    .text : { *(.text*) } > ram
}

I have a memory region to define that, there is nothing special about the name ram you can call it foo or bar or bob or ted it doesnt matter it just links the memory items to sections. The sections define things like .text, .data, .bss, .rodata and where they go in the memory map.

when you build this, you see I disassemble everything (objdump -D) you see this

Disassembly of section .text:

08000000 <_start-0x50>:
 8000000:       20002000        andcs   r2, r0, r0
 8000004:       08000051        stmdaeq r0, {r0, r4, r6}
 8000008:       08000057        stmdaeq r0, {r0, r1, r2, r4, r6}
 800000c:       08000057        stmdaeq r0, {r0, r1, r2, r4, r6}
 8000010:       08000057        stmdaeq r0, {r0, r1, r2, r4, r6}

The key thing to note is the address on the left is where we wanted it, the vectors.s code is first in the binary (Because it is first on the ld command line, unless you do something in the linker script the items will show up in the binary in the order they are on the ld command line). For booting properly you must insure your vector table is in the right place. The first item is my stack address, that is fine. The second item is the address to _start and it should be an odd number. the use of .thumb_func before a label causes this to happen so you dont have to do other ugly looking things.

08000050 <_start>:
 8000050:       f000 f822       bl      8000098 <notmain>
 8000054:       e7ff            b.n     8000056 <hang>

08000056 <hang>:
 8000056:       e7fe          

so the 0x08000051 and 0x08000057 are the proper vector entries for _start and hang. start calls notmain()

08000098 <notmain>:
 8000098:       b510            push    {

That looks good (They dont show the odd numbered address in the disassembly).

All is well.

Skip to the example blinker05, this one does support interrupts. and needs some ram, so .bss is defined.

MEMORY
{
    rom : ORIGIN = 0x08000000, LENGTH = 0x100000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1C000
}

SECTIONS
{
    .text : { *(.text*) } > rom
    .bss  : { *(.bss*) } > ram
}

remember ram and rom are arbitrary names, bob and ted, foo and bar all work just fine.

Not going to show the whole vectors.s because the cortex-m3 has a zillion entries in the vector table if you make a complete one (varies from core to core and maybe within the same core depending on the options chosen by the chip vendor) The relevant portions are here after disassembly:

08000000 <_start-0x148>:
 8000000:       20020000        andcs   r0, r2, r0
 8000004:       08000149        stmdaeq r0, {r0, r3, r6, r8}
 8000008:       0800014f        stmdaeq r0, {r0, r1, r2, r3, r6, r8}
...
8000104:       0800014f        stmdaeq r0, {r0, r1, r2, r3, r6, r8}
 8000108:       08000179        stmdaeq r0, {r0, r3, r4, r5, r6, r8}
 800010c:       0800014f        stmdaeq r0, {r0, r1, r2, r3, r6, r8}

takes some trial and error to place that handler exactly at the right spot, check with your chip where it needs to be it isnt necessarily at the same place as this one, and with so many interrupts you may be looking for a different interrupt anyway. the cortex-m processors, unlike normal arms, make it so you dont NEED trampoline code for interrupts, they preserve a certain number of registers and manage switching of processor modes through the link register contents. so long as the hardware and the abi for the compiler are close enough it all works. In this case I did the handler in C, unlike other platforms and the past you dont need to do anything special with the compiler/syntax just make a function (but dont do stupid things in the function/handler)

//-------------------------------------------------------------------
volatile unsigned int intcounter;
//-------------------------------------------------------------------
// CAREFUL, THIS IS AN INTERRUPT HANDLER
void tim5_handler ( void )
{
    intcounter++;
    PUT32(TIM5BASE+0x10,0x00000000);
}
// CAREFUL, THIS IS AN INTERRUPT HANDLER
//-------------------------------------------------------------------

The makefile for blinker05 should resemble the blinker02 example, mostly cut and paste for most of these. turn the individual source files into objects then link. I do build for thumb, thumb2 using gcc and clang. you can change the all: line at the time to only include the gcc items if you dont have/want clang (llvm) involved. I use binutils to assemble and link the clang output btw.

All of these projects use free, off the shelf, open source, tools. no IDE's, command line only. Yes I only mess with Linux and not Windows, but these tools are available to windows users as well, change things like rm -f something to del something in the makefile, things like that when building on windows. That or run linux on vmware or virtualbox or qemu. Not using an IDE means you pick your text editor as well, I wont get into that, I have my favorite(s). Note that an extremely annoying feature of the gnu make program is that it requires actual tabs in the makefile, I hate invisible tabs with a passion. So one text editor for makefiles that leaves tabs, the other for source code that makes spaces. I dont know about windows, I normally used borland make or microsoft make instead of gnu when developing there (even when using gcc and binutils).

I hope this helps, it is not the exact chip/board but a cortex-m4 well m4 not m3, close enough for this discussion. see the mbed or stm32vld dir for an actual cortex-m3 (not enough differences from the m4 for makefiles and boot code, etc), but not made by st. The cortex-m3 core's should be the same across vendors, the cortex-m3 and cortex-m4 are both ARMv7m and are closer rather than different. The cortex-m0 is an ARMv6m, hardly has thumb2 instructions enough to bother with, the compilers have not caught up with it so just use thumb only (pretend you are building for an ARMv4T (thumb only) if need be). My thumbulator simulator is thumb only, no thumb2, it might be useful to you as well, I think I made it perform interrupts in some form or fashion.