2024 - 2026 Custom Bootloader Notes
@Ishan Deshpande
Initial Thoughts
Trying to get a bootloader up and running for our suite of STM32 microcontrollers.
https://embetronicx.com/tutorials/microcontrollers/stm32/bootloader/bootloader-basics/
https://embetronicx.com/tutorials/microcontrollers/stm32/reset-sequence-in-arm-cortex-m4/
https://www.youtube.com/watch?v=OkUQ3iMmiYQ&list=PLM7yYW7w7MWms-Um-dHxbftXRUFT74u9l&index=1
https://interrupt.memfault.com/blog/how-to-write-a-bootloader-from-scratch
What is a bootloader?
Piece of code that runs on reset
Benefits are for easy firmware updates and security
For us, this means we can write code to flash our applications via CAN or UART, which will make the update process far easier than using JTAG.
Needs to exist in a separate spot of memory than the main application (requires modifying the .LD file)
A typical application has FLASH and RAM segments, which are allocated as follows (quote ChatGPT):
FLASH/NVM:
Text segment: Holds the program code and read-only data (e.g., constants).
Initialized data: Stored here initially and copied to RAM on startup.
RAM:
Data segment: Holds initialized global and static variables.
BSS segment: Holds uninitialized global and static variables (zeroed out on startup).
Heap: Used for dynamic memory allocation.
Stack: Used for function calls and local variable storage.
We’ll need these data segments for our bootloader as well, so we’ll need to add them to the linker script which defines the memory mapping.
https://embetronicx.com/tutorials/microcontrollers/stm32/bootloader/bootloader-basics/
https://embetronicx.com/tutorials/microcontrollers/stm32/reset-sequence-in-arm-cortex-m4/
https://www.youtube.com/watch?v=OkUQ3iMmiYQ&list=PLM7yYW7w7MWms-Um-dHxbftXRUFT74u9l&index=1
https://interrupt.memfault.com/blog/how-to-write-a-bootloader-from-scratch
What is a bootloader?
Piece of code that runs on reset
Benefits are for easy firmware updates and security
For us, this means we can write code to flash our applications via CAN or UART, which will make the update process far easier than using JTAG.
Needs to exist in a separate spot of memory than the main application (requires modifying the .LD file)
The bootloader itself will have some sort of generic command mechanism allowing a user to read/write appflash memory (with some restrictions probably). This way the same interface can be used with both a UART and CAN parser (eventually). But I’m sticking with UART for now.
Bootloader Design Details
After countless hours of messing with linker scripts and ARM assembly, I’ve successfully crafted a bootloader structure that seems to make sense.
Linker Script Generator
A linker script is a file that tells the linker where to place all of the object code in memory. It expects a bunch of input sections which are essentially “tagged” pieces of code or functions or data that are passed in by the compiler. Depending on the rules laid out in the linker script, it reorganizes these input sections and places them at discrete memory addresses on the chip.
stm/generate_ld.sh
is the linker script generator script I’ve written for this project. It takes in a .cfg file for a processor, which includes details specific to the STM32 processor you are using. As I write this, the stm32f446.cfg file looks like this:
MCU_NAME=stm32f446ret
RAM_SIZE=128
TOTAL_FLASH_SIZE=512
VEC_TAB_OFS=0x1d0
The linker script generator fixes the BOOT_SIZE (bootloader size) at 128KiB. It uses the LINKER_TEMPLATE.ld
file as a baseline, in which it replaces discrete sections or variable for both BOOT and FLASH linker scripts.
The linker script generator then spits out two files, STM32x4xx_APP.ld and STM32x4xx_BOOT.ld. We have two separate linker scripts because even though it’s all going on one processor, I’ve constructed two separate build processes for the application and for the bootloader.
Build Process
The bootloader is set up as a separate project. The rationale behind this was that developers should not have to reflash the bootloader every time they want to update their application. It incurs extra memory costs (flash has a limited number of erase cycles) and could potentially destroy the bootloader if interrupted or if flashed incorrectly, meaning that developers would need to JTAG into their board to fix it (destroying the point of this entirely).
We pass a separate set of sources/includes, linker script (STM32*_BOOT.ld) and a separate flash address for the bootloader, but use the same base Makefile as our application as the flags and compilation process for gcc stays mostly the same.
The application has the typical startup flow. Note that the flash address is not passed in for the application, as the Makefile sets the default FLASH_ADDRESS to 0x08020000.
Memory Layout
Address Range | Section |
---|---|
0x20000000 - 0x20020000 (128KiB) | RAM: The bootloader and the application use a shared stack here. |
0x08000000 - 0x08020000 (128KiB) | BOOT: The bootloader code & data and the boot vector table is stored here. Our STM32 processors expect the vector table to be at 0x08000000 on reset so that it knows where it can find the Reset_Handler. |
0x08020000 - (0x08020000 + APP_FLASH_SIZE) (APP_FLASH_SIZE) | FLASH: We call this FLASH in the linker script even though BOOT is technically also stored in flash memory. This includes the application code & data and the application vector table. The bootloader and the application have two separate vector tables, which will make a bit more sense in context with the program startup flow flowchart. |
Program Startup Flow
A few notes on this:
We reset the stack pointer so that we don’t accidentally leave application data on the stack when executing the bootloader, and so that we don’t accidentally leave bootloader data on the stack when executing the application
The nice thing about this structure is that the user can still define a Reset_Handler that runs on reset, but it will only run after the bootloader checks for a flash message. Thus the user will still have the impression that their reset handler runs immediately on reset and they do not have to deal with any logic for “jumping to the bootloader”.
Welcome to the University Wiki Service! Please use your IID (yourEID@eid.utexas.edu) when prompted for your email address during login or click here to enter your EID. If you are experiencing any issues loading content on pages, please try these steps to clear your browser cache.