[STM32] - part 3 - Start your engine!
I think it’s time to go through what happens when you power on the STM32 Cortex-M microcontroller. There is a lot of resources on this topic (if you are reading this one… thank you!). I will explain this process in my own words.
You have powered on your microcontroller. Less than a second has passed and it is already munching through data, sending and receiving messages, controlling motors and blinking leds. In less than a second (waaaay less) the micro sets everything up for it, to do everything you programmed it to do. So what’s the process?
Oh, before we continue: this process is something you have full control over. I will get into how to control it in the future parts (I need to explain the linker script and the startup code to be able to do that). We good? Ok lets go!
1. Microcontroller reads states of the BOOT0 and BOOT1 pins.
This is important because the microcontroller can start running code from different memory spaces.
Usually STM micros support 3 boot modes:
- default - Main flash memory - that’s where your code goes so booting in this mode results in a normal work of the microcontroller.
- bootloader - System memory - here the manufacturer has saved the instructions that allow you to flash your code through different peripherals, for example: USB, UART, I2C, SPI etc.
- RAM - Embedded SRAM memory - during runtime you can load the code into the RAM memory to, for example use some fancy temporary bootloader. This one is rarely used.
If you work with a specific microcontroller you should be using the Reference Manual. Lets look at one of those here: Reference Manual. On the page 62 you will find Boot configuration section. There you will find a half of a page description of what I have been explaining above (and a bit more). You will notice they are using this aliasing term. It basically means that any of the memories can be accessed not only through its actual address but also through a different, corresponding one.
2. Microcontroller copies the value saved under 0x0000 0000 address into SP (Stack Pointer) register.
It’s hard for me to go through each of the programming concept. If you don’t know what stack and heap is this would be the time to go and read about it. I will try to explain the stack in a brief way.
When you write a code on a PC you might want to make stack bigger or smaller… and that’s basically what you can do with a stack. In embedded programming the concept of stack is more important, mostly because you have way less memory to work with. Because of that you might want to edit the linker script (I will write about this topic in the future). That’s when you have to think about the topic of memory sizes a bit harder. Still, the concept is easy to grasp. You have a place in the memory where the processor saves the last program request (this place is called stack and those requests can be local variable values, return addresses) and SP register holds the address to the top of this space.
It’s worth pointing out that in ARM Cortex-M devices the stack grows downwards, meaning the start address is higher than the end of your stack memory space (when pushing new value onto the stack, SP is decremented).
3. Microcontroller copies the value saved under 0x0000 0004 address into PC (Program Counter) register.
STM32 are using 32 bit registers. That means that first four bytes hold a value that will exactly fill the SP register. Next 4 byte value starts at 0x0000 0004 address. This address value isn’t random, it’s just a next register size step. Microcontroller just goes through the memory sequentially.
The Program Counter register holds the address of the currently executed intstruction (remember that the instructions are just a data saved in a dedicated memory space). Microcontroller just sets up the next step of its operation.
Since Cortex-M only supports Thumb mode (Thumb assembler instructions; it doesn’t support ARM instructions - yes, an ARM architecture doesn’t support ARM ;) ), the least significant bit of the PC should be set to 1. That’s how encoding of this information has been designed.
The address saved at 0x0000 0004 points to the first instructions microcontroller executes. The actual instruction is stored in the FLASH memory. I wrote about memory spaces in the previous part of this series. There you can find a memory layout diagram, which shows that for STM32 F3 micros series FLASH memory addresses start with 0x08, so the data saved at 0x0000 0004 could look like 0x0800 01F8.
4. Fire up those INSTRUCTIONS!
main() function can take over, several things need to be set up. The data from FLASH has to be copied into SRAM memory space, the bss segment has to be zeroed out, clock source has to be setup and after initializing standard C library the
main() function gets executed. That’s a lot. I will deconstruct it further in the future.
One thing I want to mention is a concept of the interrupt service routines / interrupt handler (ISR). That’s the last part you have to have in order to properly boot Cortex-M microcontroller. You probably know that every microcontroller has a mechanism which allows interrupting its normal work (when specific conditions are met). Those interrupts have to be handled. In Cortex-M micros (not only in those, I imagine) the addresses of the functions that determine what happens when an interrupt occurs have to be saved under a specific addresses. The possible interrupts for a specific micro are defined by the manufacturer, who also prepared slots in the memory where addresses of the handlers should be saved. How do you implement those? You define the names of the functions as a weak symbols (functions you should override in your code) in the startup code and specify where they should be saved in the compilation process in the linker script.
As I’ve said before. In order to understand the whole process startup code and linker script have to be explained. I will get to that in the next part.
main() takes control.
That’s where most of the developers start their actual work. That’s our typical
main() function in which the program starts. At this point you can start initilizing the peripherals and writing your own functionallity.