Many embedded systems are statically configured, i.e. the software cannot be easily upgraded once delivered. This can cause all sorts of problems. It is clearly very useful if the software can be upgraded “in-the-field”, preferably by the end-user himself.
Consider, for example, a situation where you find critical bugs. Or you need to make security upgrades. Or just add new features. It is very inconvenient to make a large-scale product recall. Having a bootloader in your product is the way to go to overcome these problems. But how do you create a bootloader in an ARM Cortex-M system? This blog post explains how.
To start with, what is a bootloader?
The general definition (from Wikipedia):
“A boot loader is a computer program that loads the main operating system or runtime environment for the computer after completion of the self-tests.”
Translated for microcontroller land (ARM Cortex-M0/M3/M4/M7), it can become:
“A bootloader enriches the capabilities of the microcontroller and makes it a self-programmable device”
This is our definition, and the understanding we will use throughout this blog post.
Why should you use a bootloader?
Embedded systems often have a disadvantage compared to PC’s and mobile devices. Once delivered and deployed in the field, it might be very difficult to upgrade its software with bug fixes or new functionality.
If you have delivered thousands of units, how would you improve their software at a later time, should it be needed or desired? Perhaps a security vulnerability or critical bug is found, or you just want to add new features to your product. With a static design where the software cannot be upgraded by the end-user himself in-the-field, you will have to take the product back for re-flashing, perhaps using low-level technologies like a JTAG probe.
This is not exactly the kind of process you want to put in the hands of your end-users. We all love a good JTAG probe, but your end customers may not.
What we need is a way for the end-user (or at least, someone further from the JTAG probe in your development lab) to be able to upgrade the software. In short, we need a way to make it possible to upgrade the software in the product in a user-friendly way. This is what bootloaders do.
A bootloader enables a device/product to upgrade itself in the field. And so, the bootloader solves these two main problems:
- Firmware is rarely bug-free – We need a method to upgrade a product’s firmware when defects are found.
- New requirements are often added – We need a method to upgrade a product’s firmware due to new functionality.
This blog post will show how you can create a bootloader for ARM Cortex-M devices, using Atollic TrueSTUDIO. In particular, these topics will be explained:
- Constructing and building the bootloader
- Constructing and building the main application
- Interaction between the bootloader and the main application
The anatomy of a bootloader
An embedded system that includes a bootloader to make it in-the-field programmable, have a few particular design elements.
First of all, the system contains the bootloader itself, a small software application that runs when the system boots. The bootloader is not in-the-field programmable, and is thus a static part of the delivered product – and so, the bootloader needs to be bug-free as it cannot easily be upgraded later.
The bootloader also has the capability to do one of two things; to start the normal application program, or to install a new version of the application program. The normal and most common use-case, of course, is to just start the application program after the bootloader has booted the system. However, in certain situations, the bootloader instead installs an updated version of the application software.
And so, the bootloader needs to include three distinctly different functionalities:
- Boot the hardware upon reset, and at the bare minimum, initialize the system up to the level for the bootloader to perform all its duties.
- In the normal use-case, transfer execution to the application program
- In certain situations, “somehow get” a new flash binary for a new application program, and then flash that into the system.
How does the bootloader install an updated application flash binary? In other words, what does the phrase “somehow get” in the paragraph above mean? The truth is that the bootloader might get a new application software flash binary using any of many different interfaces.
For example, a bootloader can install new application software using:
- A UART/serial cable
- A virtual serial port using a USB cable
- A USB Flash memory
- A remote TCP/IP connection
- A CAN network
- Or any other way that makes sense and is technically suitable
Solution #1 and #2 above are conceptually the same, although the physical solution is different. In both cases, you need to design a proprietary communications protocol to transfer the new application software down to the target system. You also need to design a PC GUI to allow the users to select what application file to download, and start the download. Because this solution requires a proprietary GUI application on the computer side, it must be considered how to distribute and support that to the customer base as well.
Solution #3 and #4 above uses standardized solutions that remove the need for a proprietary communication protocol, and hence a proprietary GUI application on the PC side is not necessary. With alternative #3, the user simply installs a USB memory stick into the device, and the bootloader can install the new application software from the file system. This solution does, however, require a USB host stack, with support for a file system. Solution #4, on the other hand, requires a TCP/IP stack, for example using the FTP file transfer protocol. And so, with both solution #3 and #4, the bootloader needs some non-trivial middleware components. The advantage is there is no need for any proprietary communications protocols or proprietary GUI applications, to enable the bootloader to install new software.
Solution #5 is rather similar to solution #1 and #2, in that you will have to build a proprietary file transfer protocol, and most likely, a PC GUI application too. Solution #6 can be more or less anything.
Once the bootloader has a capability to download/install a new application software version, it also needs to flash it into the flash memory of the device. Hence, the bootloader also needs to include a flash programming function, implementing a suitable flash programming algorithm.
The memory map
Let’s make a more practical example using an STM32F4-DiscoveryKit hardware. We want to separate the bootloader from the application in flash memory. In particular, make sure they are on separate flash pages.
We will allocate the first three 16K flash pages for the bootloader:
- This gives us the memory range 0x08000000 - 0x0800C000
- The reset vector is located at 0x08000000
All other memory will be reserved for the application (which most likely give some free space at the top). Our memory map will look like this:
Constructing the bootloader
Let the Atollic TrueSTUDIO project wizard auto-generate a bootloader project template for the STM32F4-DiscoveryKit. This gives us a few files in the bootloader project folder:
Open the linker configuration file (stm32f4_flash.ld) and make sure it locates the code starting at address 0x08000000. This is the default choice by the TrueSTUDIOproject wizard, and you should not have to change anything at this point.
To avoid confusing the debugger when debugging the bootloader and the application in the same debug session:
- Use different symbolic names for critical functions that exist in both the bootloader and the application
- This simplifies breakpoint handling, etc.
For instance, the entry point for the bootloader will be the Reset_Handler
function by default. You probably have another version with the same name in the application also.
- Rename Reset_Handler to Boot_Reset_Handler and update all references!
- Rename main() to boot_main() and update all references!
Something like this:
What will the bootloader do on startup? First, it updates the firmware - if this should be done at this time (the code for this is not implemented in this example). Secondly, it sets up the runtime environment for the application:
- Locates and sets the application stack pointer address. (Stored at first entry in application vector table.)
- Locates the application entry point. (Stored at second entry in application vector table.)
- Configures the vector table offset register. (Exceptions/IRQ now finds its handlers here!)
- Starts the application.
The bootloader main() function now looks something like this:
It can be noted that the code to detect if a firmware update should be made - and the code to do it - may be a major software functionality. It may include UART or USB protocols, USB or TCP/IP stacks, Flash filesystems, LCD GUI development, and more. In this blog post, the details on how to do that will not be covered, as it differs widely across different products and projects.
Constructing the application
Let the Atollic TrueSTUDIO project wizard auto-generate an application software project template for the STM32F4-DiscoveryKit. This gives us a few files in the application project folder:
The auto-generated linker configuration file (stm32f4_flash.ld) will, by default, place the code at the start of flash at 0x08000000. We need to change this, as this is where the bootloader code is located. In the memory map listed above, the application software should be located at 0x0800C000, which we enter into the linker configuration file instead.
In this example, we have chosen to let the bootloader set up the basic environment (stack pointer, vector table, etc.) for the application. Beware of any application code that might circumvent this behavior!
For instance, code in the SystemInit() function in this example:
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
Except for problems similar to the one highlighted above, the application should now run just fine. The bootloader has done its job and is no longer involved in the runtime execution. What else is there to consider?
With a code framework available for both the bootloader and the application, we need to worry about debugging. As it turns out, there are special complexities involved when debugging a system with both a bootloader and an application.
In fact, there are several different debugging use-cases that have to be handled differently from a debug configuration and debug launching point of view:
- Debugging the bootloader
- Debugging the application
- Debugging the bootloader and application program together
- When the application is programmed by the debugger
- When the application is programmed by the bootloader
I will not go into how these scenarios are debugged in this blog post, but this application note explains this if you are interested to learn more on bootloader and application debugging techniques:
Do you want to learn more on Atollic TrueSTUDIO or ARM Cortex-M development and debugging? Read this whitepaper: