Programming STM32-Discovery using GNU tools: Startup code

Start up code is run just after microcontroller is reset and is executed before main program. As linker script, startup code usually is implemented as universal code for all same microcontroller type. So usually you don’t need to write one from scratch. Anyway it is good to know what happens there anyway.

As we said linker script has to go along with startup code. This means that it will initialize MCU automatically according to data defined in linker. Startup code initializes variables, copies defined variables from Flash to RAM, initializes stack and then gives resources to main program. You will find that startup codes usually are written in assembly language, but this can also be done in C which is easier to read and modify if necessary. First of all in linker script we have pointed entry point to startup with following command:

ENTRY(handler_reset)

 

So in startup code this will be first function called.

 

void handler_reset(void)

{
unsigned long *source;
unsigned long *destination;
// Copying data from Flash to RAM
source = &_data_flash;
for (destination = &_data_begin; destination < &_data_end;)
{
*(destination++) = *(source++);
}
// default zero to undefined variables
for (destination = &_bss_begin; destination < &_bss_end;)
{
*(destination++) = 0;
}
// starting main program
main();
}


Startup code takes source and destination addresses from linker script as external variables:
extern unsigned long _data_flash;
extern unsigned long _data_begin;
extern unsigned long _data_end;
extern unsigned long _bss_begin;
extern unsigned long _bss_end;
extern unsigned long _stack_end;
These we defined when described sections. So the only thing left for startup code is take values from source addresses in Flash memory and copy them to destination addresses in RAM. Also variables stored in .bss section are defaulted to zero values. So no need to null them in source when used.

Next step is that startup does is to allocate exception handler vector table. Due to ARM Cortex architecture the first address in vector table is used to store address of stack end. This is convenient and efficient way to define it.

 

__attribute__ ((section(".interrupt_vector")))

void (* const table_interrupt_vector[])(void) =
{
(void *) &_stack_end, // 0 - stack
handler_reset, // 1
handler_default, // 2
handler_default, // 3
handler_default, // 4
handler_default, // 5
handler_default, // 6
0, // 7
0, // 8
0, // 9
0, // 10
handler_default, // 11
handler_default, // 12
0, // 13
handler_default, // 14
handler_default, // 15
// peripherals
handler_default, // 0
handler_default, // 1
handler_default, // 2
handler_default, // 3
handler_default, // 4
-//-
handler_default, // 59
handler_default // 60
};


In linker script we defined that “.interrupt_vector” section is starting at 0×00000000 address so stack pointer is located at 0×00000000 address of Flash. Then goes reset handler which then copies variable data to RAM and nulls undefined variables and then it is over with startup code and all resources are alocated to main() routine.
void handler_default(void)
{
while (1)
{
//loop
}
}
handler default routine simply handles unexpected interrupts and puts MCU to endless loop. This is very simplified version of linker script and startup code. Ir explains thing pretty well I guess. For starters probably it is best to find working example of Arm Cortex M3 GCC code copy linker script and startup code as they are and focus on software writing. Barely you’ll need to change these soon.