As we all painfully have come to learn - firmware is rarely bug-free and new requirements are usually added over time. And so there is a need for a method to upgrade the firmware of a shipped product when defects are found or new functionality is needed. From a logistics point of view, a product recall with a factory upgrade might not be a feasible option.
By separating the application logic from the booting process, developers can design a system that allows end-users or service technicians to upgrade the application software with a newer and presumably better version later on, without the need for recalls or factory upgrades. This can be done using a bootloader. But what is a bootloader and how is it implemented and debugged on an ARM Cortex-M device like STM32 or Kinetis? The post also links to a bootloader code example for STM32F4-Discovery.
To start with, what is a bootloader? The general definition from Wikipedia is:
“A bootloader is a computer program that loads the main operating system or runtime environment for the computer after completion of the self-tests.” - Wikipedia
In ARM Cortex-M microcontroller land (for example using STM32, Kinetis, EFM32 or LPC devices) we take this as our definition instead:
“A bootloader enriches the capabilities of the microcontroller and makes them a self-programmable device”
Essentially, what happens is we create one small software program (the bootloader) that boots the system and then hand over execution to the larger application program (the application logic). But this alone really doesn’t add any extra benefits compared to including the boot logic in the application software itself.
The trick here is to add one more feature to the bootloader – the capability to download new application software versions using some communications interface (TCP/IP, UART, USB, CAN, SD-cards with a file system, or whatever is suitable) and start to use the upgraded version of the application software instead of the old one.
We thus end up with a 2-module system performing a 2-step startup; one module that is never upgraded after factory delivery (the small bootloader), and one module (the large application software) that can be upgraded any number of times “in-the-field” to add bug fixes or feature extensions, without any need for recalls and factory upgrades.
The thinking here of course is the small bootloader is easier to keep “bug-free”, while the large application software module is more likely to contain bugs or feature limitations that must be addressed with in-the-field upgrades during the lifespan of the product. Hence, the bootloader cannot be upgraded by the end-user or service technician himself, but the application software can.
And so, creating a Cortex-M system using a BOOTLOADER + APPLICATION approach requires the following:
- Constructing and building the static bootloader application
- Constructing and building the replaceable/upgradeable main software application
- Handling interaction between the bootloader and the main application in the Cortex-M target system (in particular execution control handover)
- Handling debugging of the above system, in particular during execution handover
Matters of importance here are the memory layout of the devices, as both the bootloader and application software needs to be stored in separated areas of the Flash memory in order not to interfere with each other, what communications interface to use to download new application versions using the downloader, and how to store the updated application software in Flash (i.e. flash programming). In practice, you will need two separate Cortex-M projects (for example developed using the Atollic TrueSTUDIO IDE for ARM) loaded into the same device:
- The Bootloader application needs to be stored on certain Flash memory addresses, and handle CPU booting, including responding to the power-on-reset interrupt, initializing the C runtime environment, and performing required hardware setup like enabling chip select signals, configuring DRAM refresh etc. It also needs to accept new updated software versions using some communications interface, and store the new software version in Flash for later use.
- The Application must be stored in a different area of the Flash memory, and most likely need to avoid doing configurations already made by the bootloader (remove H/W configurations made from ready-made example projects not intended for bootloader use for example), and obviously contains the application logic itself.
Debugging imposes special problems in a BOOTLOADER + APPLICATION setup. You can typically debug the bootloader using most embedded ARM Cortex IDE’s, but debug problems arise when execution is handed over from the bootloader to the application software.
The debugger (that started the debug session using the bootloader project) knows nothing about the C/C++ symbols in the binary image of the application software stored in the Flash memory of the device, and so you are kind of naked or blind when debugging the application software module after the bootloader have handed over execution control to the application software. You will not be able to do basic debug tasks like single stepping source code, set breakpoints on source code lines, or see C/C++ variables in the watch view, etc. This imposes unacceptable debug limitations to embedded developers and needs a solution.
Essentially the debugger needs to be aware of both binary images (and their ELF files containing debug info) at the same time. What we do here is really to debug two completely different IDE projects/application binary images at the same time, in the same debugger. And so, highly powerful and flexible Cortex-M debuggers like Atollic TrueSTUDIO is needed for this setup, in conjunction with a good debugger probe like ST-LINK or SEGGER J-Link/SEGGER J-Trace.
To learn more on how to develop and debug a BOOATLOADER + APPLICATION system on Cortex-M devices like STM32, Kinetis, EFM32 or LPC, read this free training presentation:
It is also possible to download a code example built for an STM32F4-Discovery board. The zip-file contains two TrueSTUDIO projects, one for the bootloader and one for the application. Download the example here!
For more information on ARM Cortex development and debugging in general, read this white paper: