STM32-P405 Startup Code – Adding a C module

To begin exploring some concepts of the ARM Cortex-M architecture begin by creating a new directory and copy the original startup source files to it, for example:

mkdir -p $HOME/cortex/stm32p405/examples/introduction/001_startup/src
cd $HOME/cortex/stm32p405/examples/introduction/001_startup/src
cp $HOME/cortex/stm32p405/examples/introduction/000_simple_startup/src/* .

It is easier to read and write C code than ARM assembler, so we are going to create a simple C module containing a single function called main() , to do this create a file called main.c with the following contents:

int main()
        int i = 0x1234abcd;
        return 0;

Now modify the Makefile to build and link this code into the ELF file, this is done by adding a rule for *.c files which uses gcc to build an object module which is linked with the ELF file.

The modified Makefile has the following contents, the bold text shows the additions required to build and link the main.c code:

OBJ := vector_table.o main.o
CFLAGS := -nostdlib -g -mcpu=cortex-m4 -mthumb -march=armv7e-m
LDFLAGS := -g -o
ASMFLAGS := -g -o

all: $(TARGET)

%.o: %.s
    $(CROSS)as $(ASMFLAGS) $@ $<

%.o: %.c
    $(CROSS)gcc $(CFLAGS) -c $<

    $(CROSS)ld $(LDFLAGS) $@ -T $(LDSCRIPT) $(OBJ)

    $(RM) *.o *.elf

debug: $(TARGET)
    $(CROSS)gdb -x gdbinit

The reset_handler() is modified to call the main() function. After the main() function returns the reset_handler() code infinitely loops to an address called halt, the contents of the vector_table.s file is now as follows, with the new additions highlighted in bold:

.syntax unified

.cpu cortex-m4

/* Vector Table */

.word 0x20001000        /* Stack Pointer */
.word reset_handler    /* 1 Reset Handler */
.word default_handler   /* 2 NMI */
.word default_handler   /* 3 Hard Fault */
.word default_handler   /* 4 Mem Management Fault */
.word default_handler   /* 5 Bus Fault */
.word usage_handler     /* 6 Usage Fault */
.word default_handler   /* 7 RESERVED */
.word default_handler   /* 8 RESERVED */
.word default_handler   /* 9 RESERVED*/
.word default_handler   /* 10 RESERVED */
.word default_handler   /* 11 SVCall */
.word default_handler   /* 12 Debug Monitor */
.word default_handler   /* 13 RESERVED */
.word default_handler   /* 14 PendSV */
.word default_handler   /* 15 SysTick */

.global reset_handler
.type reset_handler, STT_FUNC
    bl main
    bl halt

.global default_handler
.type default_handler, STT_FUNC
    bl default_handler

.global usage_handler
.type usage_handler, STT_FUNC
    bl usage_handler

It is now possible to test the main() function is called by using the debug makefile target:

make debug
arm-none-eabi-as -g -o vector_table.o vector_table.s
arm-none-eabi-gcc -nostdlib -g -mcpu=cortex-m4 -mthumb -march=armv7e-m -c main.c
arm-none-eabi-ld -g -o startup.elf -T startup.ld vector_table.o main.o
arm-none-eabi-gdb -x gdbinit
GNU gdb (
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i686-linux-gnu --target=arm-none-eabi".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
Find the GDB manual and other documentation resources online at:
For help, type "help".
Type "apropos word" to search for commands related to "word".
0x08000056 in ?? ()
Loading section .text, size 0x70 lma 0x8000000
Start address 0x8000000, load size 112
Transfer rate: 173 bytes/sec, 112 bytes/write.
Breakpoint 1 at 0x8000040: file vector_table.s, line 29.
Note: automatically using hardware breakpoints for read-only addresses.

Breakpoint 1, reset_handler () at vector_table.s:29
29        bl main
r0             0x0    0
r1             0x0    0
r2             0x0    0
r3             0x0    0
r4             0x0    0
r5             0x0    0
r6             0x0    0
r7             0x0    0
r8             0x0    0
r9             0x0    0
r10            0x0    0
r11            0x0    0
r12            0x0    0
sp             0x20001000    0x20001000
lr             0xffffffff    -1
pc             0x8000040    0x8000040 <reset_handler>
xPSR           0x41000000    1090519040

The debugger has halted on the breakpoint for the reset_handler() function as previously, now set a breakpoint for the main() function and instruct the debugger to continue execution:

(gdb) b main
Breakpoint 2 at 0x8000056: file main.c, line 3.
(gdb) cont

Breakpoint 2, main () at main.c:3
3        int i = 0x1234abcd;

The debugger has halted at the main() function correctly. Let’s step through the first statement and examine the registers:

(gdb) step
4        return 0;    
(gdb) info reg
r0             0x0    0
r1             0x0    0
r2             0x0    0
r3             0x1234abcd    305441741
r4             0x0    0
r5             0x0    0
r6             0x0    0
r7             0x20000ff0    536874992
r8             0x0    0
r9             0x0    0
r10            0x0    0
r11            0x0    0
r12            0x0    0
sp             0x20000ff0    0x20000ff0
lr             0x8000045    134217797
pc             0x8000060    0x8000060 <main+16>
xPSR           0x41000000    1090519040


We can see that the register r3 has been set to 0x1234abcd, i.e. the result of the statement:

        int i = 0x1234abcd;

Now instruct the debugger to continue excuting, then use CTRL-C to break, as follows:

(gdb) cont
Program received signal SIGINT, Interrupt.
halt () at vector_table.s:31
31        bl halt
(gdb) list
26    .type reset_handler, STT_FUNC
27    .thumb_func
28    reset_handler:
29        bl main
30    halt:
31        bl halt
33    .global default_handler
34    .type default_handler, STT_FUNC
35    .thumb_func

We can see that after the main() function returns to the correct location, i.e. the halt infinite loop in reset_handler().

Now we have added the ability to write and link C modules we can start to explore the ARM Cortex M4 and STM32 architecture more easily.

The next section demonstrates an example of Exception Handling.