STM32-P405 Startup Code – In Detail

This example demonstrates how to build an ELF file which can be used, via GDB and JTAG, to flash and execute code on the STM32-P405 board. A typical workflow is as follows:

  1. Compile object files (from *.c and/or *.s source files)
  2. Link the object files and output an ELF file
  3. Invoke GDB and connect to the OpenOCD GDB interface
  4. Load the ELF file onto the target board
  5. Set a breakpoint via GDB

At this point the program can be executed or debugged via GDB.

First create a directory which will contain the Makefile and source code:

mkdir -p $HOME/arm/cortex/stm32p405/examples/introduction/000_simple_startup/src
cd $HOME/arm/cortex/stm32p405/examples/introduction/000_simple_startup/src

Then create a file called startup.ld which contains the following:

MEMORY
{
    flash_mem : ORIGIN = 0x08000000, LENGTH = 0x4000
}

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

This file is commonly known as a linker script, it’s purpose is to tell the linker where to place the code in terms of memory address. This script tells the linker to load the .text segment at memory location 0X08000000 with a maximum length of 16KB (0X4000) , these values were derived by examining the Supplier User Manual  (Olimex Ltd.) and also the OEM Reference Manual (ST Microelectronics) as follows:

The OEM Reference Manual (Section 2.4, Boot Configuration, Table 2 – Boot Modes) shows the pin settings which control the boot behaviour, the board can be configured to boot from:

  • Main Flash Memory
  • System Memory
  • Embedded SRAM

To select a particular boot setting two pins are used BOOT0 and BOOT1, cross refencing with the Supplier User Manual (Section 3.4.2, Using USB cable and bootloader software) shows that the default setting for BOOT0 is B0_L, i.e. BOOT0 is set to zero. According to the OEM User Manual this setting instructs the board to boot from the main flash memory

I.e. the board will start booting from the memory address of the main flash memory, this means that the code contained within the ELF file needs to be loaded at this address.

Warning: Overwriting the flash memory will erase and preloaded software supplied by the manufacturer so be sure you have a copy of this software and a method of reflashing it.

To determine the memory address of the main flash memory consult the OEM User Manual, a description of the flash memory map of the board is provided (Section 2.4, Boot Configuration, Table 3 – Memory mapping vs. Boot mode/physical remap)

This table shows the flash memory is located at 0X08000000.

The flash memory is organised as a set of logical sectors, the layout of these sectors is provided (Section 3, Embedded Flash memory interface, Table 5 – Flash module organization) and shows that sector 0 is 16KB (0X4000) in length.

The boot configuration set via the boot pins also remaps the flash memory located at 0X0800000 to 0X00000000, this means, for example, reading or writing memory location 0X00000004 will actually read the location 0X08000004 which is located in flash memory.

The boot sequence for this board is as follows:

  1. On power up or reset map memory location 0X08000000 to 0X00000000
  2. Read the first 32 bit word at 0X00000000 and set the stackpointer register to this value
  3. Read the second 32 bit word (0X00000004) and start executing code at the memory location of this value

 

If, for example, the memory starting at location contained the following:

Address        Value
0X00000000     0x20001000
0X00000004     0X08000040

then after reset the following would occur:

  1. The stack pointer register is set to 0x20001000
  2. The program counter register is set to 0x08000040
  3. The processor begins excuting the instructions from 0x08000040

The second value is referred to as the reset vector or reset handler, or reset exception.

The contents of the memory after this contain addresses for other type of exceptions, for instance if the processor tries to execute an instruction is doesn’t recognise it will jump to an exception handler, which could be programmed to print out a warning message and then halt the processor.

It’s possible to build an ARM object file containing the wrong type of instructions for this particular processor. Trying to execute this code would result in an exception being called. This exception is called the Usage fault exception.

These exception handlers are decribed in the Cortex M4 Programming Manual (Section 2.3, Exception Model, Table 16 – Properties of the different exception types)

This table can be used to determine the values required to be set starting at memory location 0X0000000 (remapped to 0X0800000), for example

Location      Value         Exception
0x00000000    0x20001000    N/A - Initial Stack pointer value
0x00000004    0x????????    Reset - Called on reset
0x00000008    0x????????    NMI - Non Maskable Interrupt
0x0000000C    0x????????    Hard Fault
0x00000010    0x????????    Mem Management Fault
0x00000014    0x????????    Bus Fault
0x00000018    0x????????    Usage Fault
0x0000001C    0x????????    Reserved
0x00000020    0x????????    Reserved
0x00000024    0x????????    Reserved
0x00000028    0x????????    Reserved
0x0000002C    0x????????    SVCall
0x00000030    0x????????    Reserved
0x00000034    0x????????    Reserved
0x00000038    0x????????    PendSV
0x0000003C    0x????????    SysTick

Apart from the first value (the initial stack pointer), the values 0x???????? must be determined by the compiler and linker and written to the ELF file.

To do this create a file called vector_table.s (in the same directory as startup.ld) which contains the following code:

.syntax unified

.cpu cortex-m4
.thumb

/* 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
.thumb_func
reset_handler:
    bl reset_handler

.global default_handler
.type default_handler, STT_FUNC
.thumb_func
default_handler:
    bl default_handler

.global usage_handler
.type usage_handler, STT_FUNC
.thumb_func
usage_handler:
    bl usage_handler

This assembler file defines three functions: reset_handler(), default_handler() and usage_handler().

The lines beginning with .word are used to instruct the assembler to place the memory address of a particular function and this location  except the first instance which is the address stack pointer will be set to upon boot.

To make this more clear compile the code with the following command:

arm-none-eabi-as -g -o vector_table.o vector_table.s

This will create an object file called vector_table.o

This object file can be examined with tools from the binutils package, for example:

arm-none-eabi-nm vector_table.o
00000044 T default_handler
00000040 T reset_handler
00000048 T usage_handler

This command lists the symbols found in the file, from the output it can be seen there are three functions reset_handler(), default_handler() and usage_handler() as expected, the first column show the relative locations of these functions. I.e. the function reset_handler is located 0x40 bytes from the beginning of the object.

Recall that the code should be placed in flash memory at memory location 0x08000000 to be able to boot on this board, i.e. the reset_handler() function should really be located at 0x08000040. In order to do so the linker script above is used to instruct the linker to create an ELF file with functions listed above relocated to the correct memory address.

To do this execute the following command:

arm-none-eabi-ld -g -o startup.elf -T startup.ld vector_table.o

This will create an ELF file called startup.elf , this file can be examined with the objdump command, as follows:

arm-none-eabi-objdump -D startup.elf | less

startup.elf:     file format elf32-littlearm


Disassembly of section .text:

08000000 <reset_handler-0x40>:
 8000000:       20001000        andcs r1, r0, r0
 8000004:       08000041        stmdaeq r0, {r0, r6}
 8000008:       08000045        stmdaeq r0, {r0, r2, r6}
 800000c:       08000045        stmdaeq r0, {r0, r2, r6}
 8000010:       08000045        stmdaeq r0, {r0, r2, r6}
 8000014:       08000045        stmdaeq r0, {r0, r2, r6}
 8000018:       08000049        stmdaeq r0, {r0, r3, r6}
 800001c:       08000045        stmdaeq r0, {r0, r2, r6}
 8000020:       08000045        stmdaeq r0, {r0, r2, r6}
 8000024:       08000045        stmdaeq r0, {r0, r2, r6}
 8000028:       08000045        stmdaeq r0, {r0, r2, r6}
 800002c:       08000045        stmdaeq r0, {r0, r2, r6}
 8000030:       08000045        stmdaeq r0, {r0, r2, r6}
 8000034:       08000045        stmdaeq r0, {r0, r2, r6}
 8000038:       08000045        stmdaeq r0, {r0, r2, r6}
 800003c:       08000045        stmdaeq r0, {r0, r2, r6}

08000040 <reset_handler>:
 8000040: f7ff fffe bl 8000040 <reset_handler>

08000044 <default_handler>:
 8000044: f7ff fffe bl 8000044 <default_handler>

08000048 <usage_handler>:
 8000048: f7ff fffe bl 8000048 <usage_handler>

This commad shows the content and dissassembly of all bytes in the startup.elf file. It can be seen that the reset_handler() function is now at memory location 0x08000040 as expected. The locations from 0x08000000-0x080003c represent the vector table specified in vector_table.s. Ignoring the text  stmdaeq r0, {r0, R2, r6] and andcs r0, r1, r0 (this is the attempt of objdump to dissassemble code which is actually really data) the file vector_table.s and the output from the objdump command can be compared:

vector_table.s             objdump output

/* Vector Table */

.word 0x20001000       8000000: 20001000
.word reset_handler        8000004: 08000041
.word default_handler      8000008: 08000045
.word default_handler      800000C: 08000045
.word default_handler      8000010: 08000045
.word default_handler      8000014: 08000045
.word usage_handler        8000018: 08000049

From this comparison it can bee seen that the memory location 0x08000000 contains the value 0x20020000 which is expected as this is the value of the first 32 bit word set in the vector_table.s file. However the value at 0x0800004 is 0x0800041 as shown below:

vector_table.s             objdump output

/* Vector Table */

.word 0x20020000           8000000: 20020000
.word reset_handler    8000004: 08000041
.word default_handler      8000008: 08000045
.word default_handler      800000C: 08000045
.word default_handler      8000010: 08000045
.word default_handler      8000014: 08000045
.word usage_handler        8000018: 08000049

The address of the resest_handler() function is given above as 0x08000040, so why has the linker set the location 0x08000004 to 0x08000041 ?

The reason is that the Cortex M4 uses the Thumb instruction set, this is a set of opcodes that are encoded into 16 bit values rather then 32 bit. The ARM architecture requires the bit 0 is set to 1 when branching to a Thumb function. The processor still branches to address 0x08000040, but setting bit 0 to 1 informs the processor that it must decode Thumb opcodes rather than “normal” 32 bit opcodes. Some ARM processors can execute both types of opcode and this signal to the processor allows interoperability between both sets of opcodes.

The stack pointer is set to 0x20001000 as this is the first address of the address of the embedded SRAM on the board with 4k bytes added, this gives a usable stacksize of 4k; a size which has been arbitarily chosen and may be changed according to an application’s requirements.

The SRAM is mapped from memory location 0x20000000 (which can be determined from the OEM Reference ManualSection 2.4, Boot Configuration, Table 3 – Memory mapping vs. Boot mode/physical remap)

To automate the above steps the next article demonstates how to create a build system for the startup code: STM32-P405 – Startup Code Build System

 

Advertisements