In this article, I will try to cover all the basics about:
- Linker Script
- Memory Map
- Map file
- Start up code
- Interrupt vector
- Fault Handlers
- Non fault handlers
All these topics are correlated, and it suitable to cover them in one article.
Platform:
I will be using STM32F103x6 controller which is from ARM-Cortex M3 family.
Memory Map:
From Data sheet of the controller, Memory Map section, we can deduce that the micro controller memory map is as follows:
- 0x0800 0000 > 0x0801 FFFF: Flash Memory (128 K)
- 0x1FFF F000 > 0x1FFF F800: System Memory (2 K)
- Carries the bootloader.
- The bootloader is used to reprogram the flash memory using USART.
- 0x2000 0000 > 0x2000 2800: SRam (10 K)
- 0x0000 0000 > 0x0800 0000: Aliased to one of the memories as follows
Screen shot from the controller datasheet |
Vector table is a memory section, which contains some info needed by the controller:
- Initial Stack Pointer Value (SP)
- Initial Program Counter Value (PC)
- Addresses of fault handlers
- Addresses of non-fault handlers
- Addresses of IRQ handlers
You shall place the vector table in the memory you are booting from.
|
Vector Table Offset Register:
- TBLBASE
- Vector table base
- 0: Code
- 1: RAM
- TBLOFF
- vector table offset from the bottom of the SRAM or Code memory
Both of these values are set to 0 on power on.
You have to change them if needed inside the initialization code.
From Cortex M3 Technical Ref Manual |
Linker Script
Assume that we have a small program, consists of 2 files:
Taking a further look at the addresses, you can find that:
- main.c
- delay.c
The compiler generates:
- main.o
- delay.o
Then the linker generates
- elf
lets have a look into main.o and appl.elf file, with any elf viewer (ex: "arm-none-eabi-readelf.exe"):
In the symbol table:
- main.o > functions and global variables mem addresses are not defined
- appl.elf > functions and global variables mem addresses are defined
Taking a further look at the addresses, you can find that:
- Global Variable B is placed into address "0x20000020"
- Function main is placed into address "0x08000769"
This is because:
- Global variables normally are allocated in RAM, which starts at the address "0x20000000"
- Functions, are allocated inside the code segment into the Flash, which starts at the address "0x08000000"
How can the linker know the addresses where it can allocate the variables, and the functions?
- The Linker Script contains this info. The linker script contains:
- The different memories exists into the system
- The different segments exists and where shall they be allocated
- Global variables, contains addresses info, which can be used by your C modules.
- Ex: "_sdata" which contains the start address of the data section, and is needed by start up code to initialize data section
Linker script can be divided into more than one file, for convenience.
Below is a linker script, divided into two files:
Below is a linker script, divided into two files:
- mem.ld
- Where we will get familiar to some linker script keywords:
- MEMORY
- ENTRY
- Variables definitions
- you can define variables, that can be used inside the code
- Ex: "_sdata" which contains the start address of the data section, and is needed by start up code to initialize data section
- the variable is externed into the c file (ex:startup.c), and used, then the linker links it to the variable defined into the linker
- PROVIDE
- sections.ld
- Where we will get familiar to
- How to define different sections
- vector table
- init values of data
- text
- data
- bss
- noinit
- stack
- "KEEP" keyword
- The '.' operator (location counter)
mem.ld:
/* -----------------------------------------------------------------------------------------------
// MEMORY
// Describe available memory
// MEMORY command is optional
// If MEMORY command is not used the linker assumed sufficient memory is available for different sections
// -----------------------------------------------------------------------------------------------*/
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 10K /*read-write-exec*/
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K /*read-execute*/
}
/* -----------------------------------------------------------------------------------------------
// Stack
// -----------------------------------------------------------------------------------------------
// ------------------- 0x20000000 (RAM Origin) ---------------------------------------------------
// | data | |
// |---------|---------------------------------------|
// R | bss | |
// |---------|---------------------------------------|
// A | | |
// | heap | |
// M |---------|---------------------------------------| <-- Limit of stack
// | | |
// (10K) | stack | stack size = 1K |
// | | |
// ------------------- 0x20002800 (RAM Origin + RAM LENGTH) -- <-- end of stack -----------------
// ---------------------------------------------------------------------------------------------*/
/* _estack (End of stack)*/
__stack = ORIGIN(RAM) + LENGTH(RAM);
_estack = __stack; /* STM specific definition */
/* Stack Size */
__Main_Stack_Size = 1024 ;
PROVIDE ( _Main_Stack_Size = __Main_Stack_Size ) ;
/* PROVIDE, defines the variable only if referenced and not defined
* (if defined in the cmd files or in object file, then do not define it)*/
/* Stack Limit */
__Main_Stack_Limit = __stack - __Main_Stack_Size ;
PROVIDE ( _Main_Stack_Limit = __Main_Stack_Limit );
/* There will be a link error if there is not this amount of
// RAM free at the end. */
_Minimum_Stack_Size = 256 ;
/* ------------------------------------------------------------------------------------------------
// -- Heap
// ----------------------------------------------------------------------------------------------*/
PROVIDE ( _Heap_Begin = _end_noinit ) ;
PROVIDE ( _Heap_Limit = __stack - __Main_Stack_Size ) ;
/* ------------------------------------------------------------------------------------------------
// -- ENTRY
// -- Define entry point for debuggers and simulators
// ----------------------------------------------------------------------------------------------*/
ENTRY(_start)
// MEMORY
// Describe available memory
// MEMORY command is optional
// If MEMORY command is not used the linker assumed sufficient memory is available for different sections
// -----------------------------------------------------------------------------------------------*/
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 10K /*read-write-exec*/
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K /*read-execute*/
}
/* -----------------------------------------------------------------------------------------------
// Stack
// -----------------------------------------------------------------------------------------------
// ------------------- 0x20000000 (RAM Origin) ---------------------------------------------------
// | data | |
// |---------|---------------------------------------|
// R | bss | |
// |---------|---------------------------------------|
// A | | |
// | heap | |
// M |---------|---------------------------------------| <-- Limit of stack
// | | |
// (10K) | stack | stack size = 1K |
// | | |
// ------------------- 0x20002800 (RAM Origin + RAM LENGTH) -- <-- end of stack -----------------
// ---------------------------------------------------------------------------------------------*/
/* _estack (End of stack)*/
__stack = ORIGIN(RAM) + LENGTH(RAM);
_estack = __stack; /* STM specific definition */
/* Stack Size */
__Main_Stack_Size = 1024 ;
PROVIDE ( _Main_Stack_Size = __Main_Stack_Size ) ;
/* PROVIDE, defines the variable only if referenced and not defined
* (if defined in the cmd files or in object file, then do not define it)*/
/* Stack Limit */
__Main_Stack_Limit = __stack - __Main_Stack_Size ;
PROVIDE ( _Main_Stack_Limit = __Main_Stack_Limit );
/* There will be a link error if there is not this amount of
// RAM free at the end. */
_Minimum_Stack_Size = 256 ;
/* ------------------------------------------------------------------------------------------------
// -- Heap
// ----------------------------------------------------------------------------------------------*/
PROVIDE ( _Heap_Begin = _end_noinit ) ;
PROVIDE ( _Heap_Limit = __stack - __Main_Stack_Size ) ;
/* ------------------------------------------------------------------------------------------------
// -- ENTRY
// -- Define entry point for debuggers and simulators
// ----------------------------------------------------------------------------------------------*/
ENTRY(_start)
/* Sections Definitions */
/*
* FLASH
* (.isr_vector) Vector Table
* (.inits) Initializations of .data and .bss
* (.text) Text
* RAM
* (.data) initialized variables
* (.bss) uninitialized variables (initialized to zeros)
* (.noinit) uninitialized variables (uninitialized)
* (.checkstack) to check that the minimum size of stack fits in the RAM
* */
SECTIONS
{
/* VECTOR TABLE*/
.isr_vector : ALIGN(4)
{
FILL(0xFF) /* initialize this segment to 0xFF*/
__vectors_start = ABSOLUTE(.) ; /* __vectors_start = 0x00000000*/
__vectors_start__ = ABSOLUTE(.) ; /* STM specific definition */
KEEP(*(.isr_vector)) /* Interrupt vectors */
/* KEEP: keep this section even if no one referencing it*/
/* Put Startup code and ISRs after the vector table, for convinience!*/
*(.after_vectors .after_vectors.*) /* Startup code and ISR */
} >FLASH
_sidata = .;
/* INIT values of DATA and BSS */
.inits : ALIGN(4)
{
/* data and bss initial values*/
__data_regions_array_start = .;
/*without using ABSOLUTE, the locaation counter '.' value, is relative to the curretnt section*/
/*here, the location counter "." = 0, because we did not use ABSOLUTE*/
LONG(LOADADDR(.data)); /*initial values address in FLASH*/
LONG(ADDR(.data)); /* Copy Target start address (in RAM)*/
LONG(ADDR(.data)+SIZEOF(.data)); /* Copy Target End address (in RAM)*/
__data_regions_array_end = .;
__bss_regions_array_start = .;
LONG(ADDR(.bss)); /* Copy Target start address (in RAM)*/
LONG(ADDR(.bss)+SIZEOF(.bss)); /* Copy Target start address (in RAM)*/
__bss_regions_array_end = .;
/* End of memory regions initialisation arrays. */
} >FLASH
/* CODE */
.text : ALIGN(4)
{
*(.text .text.*) /* all remaining code */
/* read-only data (constants) */
*(.rodata .rodata.* .constdata .constdata.*)
} >FLASH
/* Initialized Data */
.data : ALIGN(4)
{
FILL(0xFF)
/* This is used by the startup code to initialise the .data section */
_sdata = . ; /* STM specific definition */
__data_start__ = . ;
*(.data_begin .data_begin.*)
*(.data .data.*)
*(.data_end .data_end.*)
. = ALIGN(4);
/* This is used by the startup code to initialise the .data section */
_edata = . ; /* STM specific definition */
__data_end__ = . ;
} >RAM AT>FLASH
/*place .data section in RAM | initialize it in FLASH*/
/* uninitialized data (initialized to zeros) */
.bss (NOLOAD) : ALIGN(4)
{
__bss_start__ = .; /* standard newlib definition */
_sbss = .; /* STM specific definition */
*(.bss_begin .bss_begin.*)
*(.bss .bss.*)
*(COMMON)
*(.bss_end .bss_end.*)
. = ALIGN(4);
__bss_end__ = .; /* standard newlib definition */
_ebss = . ; /* STM specific definition */
} >RAM
/*noinit section*/
.noinit (NOLOAD) : ALIGN(4)
{
_noinit = .;
*(.noinit .noinit.*)
. = ALIGN(4) ;
_end_noinit = .;
} > RAM
/* Mandatory to be word aligned, _sbrk assumes this */
PROVIDE ( end = _end_noinit ); /* was _ebss */
PROVIDE ( _end = _end_noinit );
PROVIDE ( __end = _end_noinit );
PROVIDE ( __end__ = _end_noinit );
/*
* Used for validation only, do not allocate anything here!
*
* This is just to check that there is enough RAM left for the Main
* stack. It should generate an error if it's full.
*/
._check_stack : ALIGN(4)
{
. = . + _Minimum_Stack_Size ;
} >RAM
}
/*
* FLASH
* (.isr_vector) Vector Table
* (.inits) Initializations of .data and .bss
* (.text) Text
* RAM
* (.data) initialized variables
* (.bss) uninitialized variables (initialized to zeros)
* (.noinit) uninitialized variables (uninitialized)
* (.checkstack) to check that the minimum size of stack fits in the RAM
* */
SECTIONS
{
/* VECTOR TABLE*/
.isr_vector : ALIGN(4)
{
FILL(0xFF) /* initialize this segment to 0xFF*/
__vectors_start = ABSOLUTE(.) ; /* __vectors_start = 0x00000000*/
__vectors_start__ = ABSOLUTE(.) ; /* STM specific definition */
KEEP(*(.isr_vector)) /* Interrupt vectors */
/* KEEP: keep this section even if no one referencing it*/
/* Put Startup code and ISRs after the vector table, for convinience!*/
*(.after_vectors .after_vectors.*) /* Startup code and ISR */
} >FLASH
_sidata = .;
/* INIT values of DATA and BSS */
.inits : ALIGN(4)
{
/* data and bss initial values*/
__data_regions_array_start = .;
/*without using ABSOLUTE, the locaation counter '.' value, is relative to the curretnt section*/
/*here, the location counter "." = 0, because we did not use ABSOLUTE*/
LONG(LOADADDR(.data)); /*initial values address in FLASH*/
LONG(ADDR(.data)); /* Copy Target start address (in RAM)*/
LONG(ADDR(.data)+SIZEOF(.data)); /* Copy Target End address (in RAM)*/
__data_regions_array_end = .;
__bss_regions_array_start = .;
LONG(ADDR(.bss)); /* Copy Target start address (in RAM)*/
LONG(ADDR(.bss)+SIZEOF(.bss)); /* Copy Target start address (in RAM)*/
__bss_regions_array_end = .;
/* End of memory regions initialisation arrays. */
} >FLASH
/* CODE */
.text : ALIGN(4)
{
*(.text .text.*) /* all remaining code */
/* read-only data (constants) */
*(.rodata .rodata.* .constdata .constdata.*)
} >FLASH
/* Initialized Data */
.data : ALIGN(4)
{
FILL(0xFF)
/* This is used by the startup code to initialise the .data section */
_sdata = . ; /* STM specific definition */
__data_start__ = . ;
*(.data_begin .data_begin.*)
*(.data .data.*)
*(.data_end .data_end.*)
. = ALIGN(4);
/* This is used by the startup code to initialise the .data section */
_edata = . ; /* STM specific definition */
__data_end__ = . ;
} >RAM AT>FLASH
/*place .data section in RAM | initialize it in FLASH*/
/* uninitialized data (initialized to zeros) */
.bss (NOLOAD) : ALIGN(4)
{
__bss_start__ = .; /* standard newlib definition */
_sbss = .; /* STM specific definition */
*(.bss_begin .bss_begin.*)
*(.bss .bss.*)
*(COMMON)
*(.bss_end .bss_end.*)
. = ALIGN(4);
__bss_end__ = .; /* standard newlib definition */
_ebss = . ; /* STM specific definition */
} >RAM
/*noinit section*/
.noinit (NOLOAD) : ALIGN(4)
{
_noinit = .;
*(.noinit .noinit.*)
. = ALIGN(4) ;
_end_noinit = .;
} > RAM
/* Mandatory to be word aligned, _sbrk assumes this */
PROVIDE ( end = _end_noinit ); /* was _ebss */
PROVIDE ( _end = _end_noinit );
PROVIDE ( __end = _end_noinit );
PROVIDE ( __end__ = _end_noinit );
/*
* Used for validation only, do not allocate anything here!
*
* This is just to check that there is enough RAM left for the Main
* stack. It should generate an error if it's full.
*/
._check_stack : ALIGN(4)
{
. = . + _Minimum_Stack_Size ;
} >RAM
}
Build Process |
Map File
A map file is an output file from the linker.
It contains the start and end addresses of different memory sections as described inside the linker script.
Example:
In linker script, bss section is defined as follows:
/* uninitialized data (initialized to zeros) */
It contains the start and end addresses of different memory sections as described inside the linker script.
Example:
In linker script, bss section is defined as follows:
- It comes after the data section inside the RAM
/* uninitialized data (initialized to zeros) */
.bss (NOLOAD) : ALIGN(4)
{
__bss_start__ = .; /* standard newlib definition */
_sbss = .; /* STM specific definition */
*(.bss_begin .bss_begin.*)
*(.bss .bss.*)
*(COMMON)
*(.bss_end .bss_end.*)
. = ALIGN(4);
__bss_end__ = .; /* standard newlib definition */
_ebss = . ; /* STM specific definition */
} >RAM
{
__bss_start__ = .; /* standard newlib definition */
_sbss = .; /* STM specific definition */
*(.bss_begin .bss_begin.*)
*(.bss .bss.*)
*(COMMON)
*(.bss_end .bss_end.*)
. = ALIGN(4);
__bss_end__ = .; /* standard newlib definition */
_ebss = . ; /* STM specific definition */
} >RAM
The linker does these calculations:
- RAM start address is 0x2000000 (defined in linker script)
- data section size is equal to the size of all global vars = 0x8C (calculated by the linker)
- __bss_start__ = _sbss = location counter after placing .data section
- __bss_start__ = _sbss = RAM start addr + data section size
- __bss_start__ = _sbss = 0x2000000 + 0x8C = 0x20000008C
- __bss_end__ = _ebss = location counter after placing .bss section
- __bss_end__ = _ebss = __bss_start__ + .bss section size
- __bss_end__ = _ebss = 0x20000008C + 0xA0 = 0x20000012C
Here is a snapshot from the Map file generated from the above linker script:
Map:
0x20000000 _sdata = .
0x20000000 __data_start__ = .
*(.data_begin .data_begin.*)
.data_begin 0x20000000 0x4 ./system/src/newlib/_startup.o
*(.data .data.*)
.data.argv.4231
0x20000004 0x8 ./system/src/newlib/_syscalls.o
.data.AHBPrescTable
0x2000000c 0x10 ./system/src/cmsis/system_stm32f10x.o
0x2000000c AHBPrescTable
.data.SystemCoreClock
0x2000001c 0x4 ./system/src/cmsis/system_stm32f10x.o
0x2000001c SystemCoreClock
.data.B 0x20000020 0x4 ./src/main.o
0x20000020 B
.data.impure_data
0x20000024 0x60 d:/program files/gnu tools arm embedded/5.2 2015q4/bin/../lib/gcc/arm-none-eabi/5.2.1/../../../../arm-none-eabi/lib/armv7-m\libg_nano.a(lib_a-impure.o)
.data._impure_ptr
0x20000084 0x4 d:/program files/gnu tools arm embedded/5.2 2015q4/bin/../lib/gcc/arm-none-eabi/5.2.1/../../../../arm-none-eabi/lib/armv7-m\libg_nano.a(lib_a-impure.o)
0x20000084 _impure_ptr
*(.data_end .data_end.*)
.data_end 0x20000088 0x4 ./system/src/newlib/_startup.o
0x2000008c . = ALIGN (0x4)
0x2000008c _edata = .
0x2000008c __data_end__ = .
.igot.plt 0x2000008c 0x0 load address 0x08001248
.igot.plt 0x2000008c 0x0 ./system/src/stm32f1-stdperiph/misc.o
.bss 0x2000008c 0xa0 load address 0x08001248
0x2000008c __bss_start__ = .
0x2000008c _sbss = .
*(.bss_begin .bss_begin.*)
.bss_begin 0x2000008c 0x4 ./system/src/newlib/_startup.o
*(.bss .bss.*)
.bss.current_heap_end.3942
0x20000090 0x4 ./system/src/newlib/_sbrk.o
.bss.name.4230
0x20000094 0x1 ./system/src/newlib/_syscalls.o
*fill* 0x20000095 0x3
.bss.buf.5078 0x20000098 0x80 ./system/src/diag/Trace.o
.bss.__malloc_sbrk_start
0x20000118 0x4 d:/program files/gnu tools arm embedded/5.2 2015q4/bin/../lib/gcc/arm-none-eabi/5.2.1/../../../../arm-none-eabi/lib/armv7-m\libg_nano.a(lib_a-nano-mallocr.o)
0x20000118 __malloc_sbrk_start
.bss.__malloc_free_list
0x2000011c 0x4 d:/program files/gnu tools arm embedded/5.2 2015q4/bin/../lib/gcc/arm-none-eabi/5.2.1/../../../../arm-none-eabi/lib/armv7-m\libg_nano.a(lib_a-nano-mallocr.o)
0x2000011c __malloc_free_list
*(COMMON)
COMMON 0x20000120 0x4 ./system/src/newlib/_syscalls.o
0x20000120 errno
COMMON 0x20000124 0x4 ./src/main.o
0x20000124 A
*(.bss_end .bss_end.*)
.bss_end 0x20000128 0x4 ./system/src/newlib/_startup.o
0x2000012c . = ALIGN (0x4)
0x2000012c __bss_end__ = .
0x2000012c _ebss = .
0x20000000 __data_start__ = .
*(.data_begin .data_begin.*)
.data_begin 0x20000000 0x4 ./system/src/newlib/_startup.o
*(.data .data.*)
.data.argv.4231
0x20000004 0x8 ./system/src/newlib/_syscalls.o
.data.AHBPrescTable
0x2000000c 0x10 ./system/src/cmsis/system_stm32f10x.o
0x2000000c AHBPrescTable
.data.SystemCoreClock
0x2000001c 0x4 ./system/src/cmsis/system_stm32f10x.o
0x2000001c SystemCoreClock
.data.B 0x20000020 0x4 ./src/main.o
0x20000020 B
.data.impure_data
0x20000024 0x60 d:/program files/gnu tools arm embedded/5.2 2015q4/bin/../lib/gcc/arm-none-eabi/5.2.1/../../../../arm-none-eabi/lib/armv7-m\libg_nano.a(lib_a-impure.o)
.data._impure_ptr
0x20000084 0x4 d:/program files/gnu tools arm embedded/5.2 2015q4/bin/../lib/gcc/arm-none-eabi/5.2.1/../../../../arm-none-eabi/lib/armv7-m\libg_nano.a(lib_a-impure.o)
0x20000084 _impure_ptr
*(.data_end .data_end.*)
.data_end 0x20000088 0x4 ./system/src/newlib/_startup.o
0x2000008c . = ALIGN (0x4)
0x2000008c _edata = .
0x2000008c __data_end__ = .
.igot.plt 0x2000008c 0x0 load address 0x08001248
.igot.plt 0x2000008c 0x0 ./system/src/stm32f1-stdperiph/misc.o
.bss 0x2000008c 0xa0 load address 0x08001248
0x2000008c __bss_start__ = .
0x2000008c _sbss = .
*(.bss_begin .bss_begin.*)
.bss_begin 0x2000008c 0x4 ./system/src/newlib/_startup.o
*(.bss .bss.*)
.bss.current_heap_end.3942
0x20000090 0x4 ./system/src/newlib/_sbrk.o
.bss.name.4230
0x20000094 0x1 ./system/src/newlib/_syscalls.o
*fill* 0x20000095 0x3
.bss.buf.5078 0x20000098 0x80 ./system/src/diag/Trace.o
.bss.__malloc_sbrk_start
0x20000118 0x4 d:/program files/gnu tools arm embedded/5.2 2015q4/bin/../lib/gcc/arm-none-eabi/5.2.1/../../../../arm-none-eabi/lib/armv7-m\libg_nano.a(lib_a-nano-mallocr.o)
0x20000118 __malloc_sbrk_start
.bss.__malloc_free_list
0x2000011c 0x4 d:/program files/gnu tools arm embedded/5.2 2015q4/bin/../lib/gcc/arm-none-eabi/5.2.1/../../../../arm-none-eabi/lib/armv7-m\libg_nano.a(lib_a-nano-mallocr.o)
0x2000011c __malloc_free_list
*(COMMON)
COMMON 0x20000120 0x4 ./system/src/newlib/_syscalls.o
0x20000120 errno
COMMON 0x20000124 0x4 ./src/main.o
0x20000124 A
*(.bss_end .bss_end.*)
.bss_end 0x20000128 0x4 ./system/src/newlib/_startup.o
0x2000012c . = ALIGN (0x4)
0x2000012c __bss_end__ = .
0x2000012c _ebss = .
- When I try to add a global initialized variable int C to my code.int C;
Then build - checking the map file, I find bss start and end are shifted by four bytes
- __bss_start__ = _sbss = 0x20000090
- __bss_end__ = _ebss = 0x20000130
0x20000090 __bss_start__ = .
0x20000090 _sbss = .
...
...
...
0x20000130 __bss_end__ = .
0x20000130 _ebss = .
Vector Table
When the controller starts- it gets the vector table address from the "Vector Table Offset Register".
- Initializes the PC to the value defined in the vector table
- When fault or non fault exception happens, the controller knows the address of the exception handler from the vector table.
- When IRQ (Interrupt Request) happens, the controller knows the address of its handler from the vector table
Vector Table: Micro controller Data sheet
Here is a snapshot of the first part of the vector table as defined in the data sheet:
Microcontroller Data sheet: Vector Table |
Vector Table: In C code
In your code, you shall define your vector table according to the data sheet.
__attribute__ ((section(".isr_vector"),used))
pHandler __isr_vectors[] =
{
// Core Level - CM3
(pHandler) &_estack, // The initial stack pointer
Reset_Handler, // The reset handler
NMI_Handler, // The NMI handler
HardFault_Handler, // The hard fault handler
MemManage_Handler, // The MPU fault handler
BusFault_Handler, // The bus fault handler
UsageFault_Handler, // The usage fault handler
0, // Reserved
0, // Reserved
0, // Reserved
0, // Reserved
SVC_Handler, // SVCall handler
DebugMon_Handler, // Debug monitor handler
0, // Reserved
PendSV_Handler, // The PendSV handler
SysTick_Handler, // The SysTick handler
WWDG_IRQHandler, // Window WatchDog
PVD_IRQHandler, // PVD through EXTI Line detection
TAMPER_IRQHandler, // Tamper through the EXTI
pHandler __isr_vectors[] =
{
// Core Level - CM3
(pHandler) &_estack, // The initial stack pointer
Reset_Handler, // The reset handler
NMI_Handler, // The NMI handler
HardFault_Handler, // The hard fault handler
MemManage_Handler, // The MPU fault handler
BusFault_Handler, // The bus fault handler
UsageFault_Handler, // The usage fault handler
0, // Reserved
0, // Reserved
0, // Reserved
0, // Reserved
SVC_Handler, // SVCall handler
DebugMon_Handler, // Debug monitor handler
0, // Reserved
PendSV_Handler, // The PendSV handler
SysTick_Handler, // The SysTick handler
WWDG_IRQHandler, // Window WatchDog
PVD_IRQHandler, // PVD through EXTI Line detection
TAMPER_IRQHandler, // Tamper through the EXTI
- __isr_vector, will not be placed in .data section as any other initialized array.
- Instead it will be placed in the memory section ".isr_vector".
- The linker does this as a result of the line: __attribute__ ((section(".isr_vector"),used))
- Note that the variable _estack used in the above code, is not defined in the code, it is defined in the linker script (Check mem.ld above).
Vector Table: Linker Script
You shall define a memory section for vector table in the linker script.
You shall place it in the same place reported by the "Vector Table Offset Register" at start up (normally in the start of the flash)
.isr_vector : ALIGN(4) {
FILL(0xFF) /* initialize this segment to 0xFF*/
__vectors_start = ABSOLUTE(.) ; /* __vectors_start = 0x00000000*/
__vectors_start__ = ABSOLUTE(.) ; /* STM specific definition */
KEEP(*(.isr_vector)) /* Interrupt vectors */
/* KEEP: keep this section even if no one referencing it*/
/* Put Startup code and ISRs after the vector table, for convinience!*/
*(.after_vectors .after_vectors.*) /* Startup code and ISR */
} >FLASH
What happens when the controller resets?
- Get the PC from vector table
- In our example: PC = Reset
- Reset function calls the start up function
void __attribute__ ((section(".after_vectors"),noreturn))Reset_Handler (void){ _start ();}
- attribute: section(".after_vectors")
- Tells the linker to place this function into the memory section .after_vectors defined into the linker script
- attribute: noreturn
- Tells the compiler that this function will not return
- This makes sense, because no one calls this function, hence no need to save any values in the stack, no need to save return address inside link register
- The startup function:
- Makes some initialization
- Calls the main (Or may call the bootloader, and the bootloader calls the main)
Start up Code:
Start up code is a code that usually is provided by the compiler, however you can modify it, or even write your own.Normally start up code shall make the following:
- Initialize RCC (Reset and Clock Controller)
- PLL initialization
- initialize .data section
__initialize_data(&_sidata, &_sdata, &_edata);
- Note that _sidata, _sdata and _edata, are all defined in the linker script (check sections.ld above)
- they represent
- .inits section start addr
- .data section start addr
- .data section end addr
- The function implementation can be
- memcpy(_sidata, _sdata, _edata - _sdata);
- initialize .bss section
__initialize_bss(&__bss_start__, &__bss_end__);
- Note that __bss_start__ and __bss_end__, are defined in the linker script (check sections.ld above)
- they represent
- .bss section start addr
- .bss section end addr
- The function implementation can be
- memset (__bss_start__, 0, __bss_end__ - __bss_start___);
- Call the main
main ();
Fault Handlers
Usually, fault handlers try to:
So, you do not return from a fault handler.
Hence, they shall be no return functions.
so, use naked attribute, to prevent the compiler adding any stack push/pop stuff
__attribute__ (naked)
- Print an error on RS232 for example
- Reset the system.
So, you do not return from a fault handler.
Hence, they shall be no return functions.
so, use naked attribute, to prevent the compiler adding any stack push/pop stuff
__attribute__ (naked)
- This is also useful in the case where the fault happened in the stack pointer.
- Assume that the stack pointer is pointing to a wrong value
- you can do this using a debugger
- Set a break point before a function call
- Overwrite the stack pointer with a value out of RAM
- Step into the function call
- The fault handler of busfault will be called due to undefined address access
- If you did not define the fault handler as naked function
- Each time the fault handler will be invoked, it will try to push some data on the stack.
- Since the stack pointer carries a wrong value, the fault handler will be called again.
- This will happen again and again, and the system will never reset.
Non Fault handlers
- Non fault exceptions are not critical.
- You can define handlers for non fault exceptions if you want.
- In case you do not want to define non fault handlers, you can just define "Unhandled_exception" function, which for example prints "unhandled exception".
- You can make weak aliasing for all non fault handlers to the unhandled_exception function
void __attribute__ ((weak, alias ("unhandled_exception"))) SysTick_Handler(void);
- If you defined the function SysTick_Handler, then, the linker will link it
- If you did not, the linker will link it to the function "unhandled_exception"
- The same concept can be applied to the IRQ handlers
void __attribute__ ((weak, alias ("Default_Handler"))) WWDG_IRQHandler(void);