Bootloader Tutorial, Part 1: Basics

Björn Schmitz

21/03/2019

Anyone with microcontroller firmware in the field usually relies on being able to update it. Many common microcontrollers have pre-installed bootloaders that can be used for this purpose. However, these are often insufficient for your specific needs. Sometimes the bootloader isn't flexible enough, sometimes the performance isn't sufficient, and sometimes a higher level of reliability is required. Furthermore, the required hardware peripherals (GPIOs or communication interfaces) may not be accessible. It may also be necessary for your application's firmware to continue running during the update.

So it might be a good idea to not settle for the pre-installed bootloader, but instead write your own code. In this part of the series, I'll cover the necessary basics: what a bootloader needs to do and how the code is placed in memory.

Since writing and erasing the flash is highly hardware-dependent and the protocol for sending the update file is highly application-dependent, I will not go into this here and will try to limit myself to the general basics.

Where to put the code?

Let's start with the basics. Anyone working on an update function for microcontroller firmware will inevitably have to deal with flash memory. This is usually made up of pages, sectors, blocks, and banks (I deliberately use the English terms here, as the German terms are rarely used in everyday life). How exactly these components can be written, read, and erased varies greatly between microcontroller manufacturers. For some manufacturers—such as ST—even the individual controller families differ significantly from one another. The smallest erasable unit is crucial here.

Let's take the STM32L072RB as an example. The memory (excluding EEPROM) can be read word-by-word, half-word-by-byte, and byte-by-byte, but can only be written word-by-word and half-word-by-word. The smallest erasable unit is a page of 128 bytes. Anyone wanting to write a bootloader for this microcontroller must therefore consider the following:

  1. Parts of the bootloader and the main application cannot be located in the same page, because otherwise the bootloader would never be able to delete this part of the main application without deleting part of itself. It is therefore advisable to place the start address of the main application at the beginning of a page.
  2. At least n pages must always be reserved for the bootloader.

The following figure shows a possible memory distribution. The physical address 0x0800 0000 is the lowest address of the program memory for the STM32L072RB mentioned above. Typically, the beginning of the program memory is accessed first after a reset. The bootloader is therefore executed first after a reset. If a valid address is present (see "Securing the Firmware"), this address can be accessed. The main application can be placed anywhere in principle. However, to utilize the space as efficiently as possible, it is recommended to place it at the beginning of a page.

Even though this is the norm, the bootloader doesn't necessarily have to be located at the beginning of the memory. In principle, the main application can jump to any address where the bootloader is located at will. However, it should be noted that if the main application is corrupted, the bootloader can no longer be accessed. Therefore, if an update fails, no further updates via the bootloader are possible.

Your contact person:

M.Sc. Björn Schmitz, Software Developer
E-mail: schmitz@medtech-ingenieur.de
Phone:  +49 9131 691 240
 

Do you need support with the development of your medical device? We're happy to help! MEDtech Ingenieur GmbH offers hardware development, software development, systems engineering, mechanical development, and consulting services from a single source. Contact us.

make contact

Is the firmware stored in memory?

The linker is responsible for placing the individual firmware components in memory. In order to know which program parts should be assigned to which addresses, and how much memory is actually available, the linker requires a linker script. If the bootloader and main application are independent firmware projects, they both require their own linker file. These must be coordinated to ensure that both components work together as intended. The line in which the flash size and start address are specified is particularly important. The start address of the linker must be outside the bootloader (start address_main application > (start address_bootloader + length_bootloader)). The following figure shows a comparison of the two linker scripts. As you can see, the two scripts essentially only need to differ in one line.

Securing the firmware

After flashing the main application, it can be executed by the bootloader. But what happens if the power supply or the communication interface is interrupted during an update process? In this case, incomplete firmware is located in memory, which is unlikely to behave as intended. To rule this out, the bootloader should perform a check of the main application before each jump to the main application. A CRC (cyclic redundancy check) is suitable for this, which is stored at a specific address outside of the bootloader and main application. An address at the end of the program memory can be selected as the storage location, for example. The CRC must then be cleared before each update. After an update, the bootloader then calculates a CRC for the entire memory area of the main application. This should then also be stored in the flash (or in the EEPROM if available). The following figure shows possible behavior of a bootloader.

How do you start the bootloader

The flowchart in the previous chapter mentioned a "condition for update." This is mandatory if the bootloader is run immediately after a reset, to tell the bootloader that it is not allowed to jump to the main application, even if it is present. Pre-installed bootloaders often use a microcontroller pin for this purpose, which must be at a specific level during a reset for the bootloader to enter update mode. This is acceptable for many applications, but often impractical, especially if there is no host controller in the system that has control over the bootloader and reset lines.

In MEDtech Ingenieur's projects, markers in non-volatile memory have proven to be a useful alternative. This is a value that is written to a specific address in flash or EEPROM before the update starts. The update process could, for example, proceed as follows:

  1. In the initial state, only the bootloader is located in the flash memory. Both the update marker and the CRC are missing. This means that although the update condition is not met, the bootloader enters update mode due to the faulty CRC.
  2. The bootloader flashes the main application, saves the CRC and executes it.
  3. A message is sent to the main application to initiate the update process. It then saves the update marker to a free flash memory location and performs a software reset.
  4. During reboot, the bootloader checks the update marker. If it is set, the bootloader enters update mode. At the same time, the update marker is reset. This ensures that the main application can be accessed again after a timeout or a reset without a prior update.
  5. Steps 2 and 3 are repeated

The update marker does not necessarily have to be located in non-volatile memory. If a RAM region is defined in the two linker scripts that contains a variable used by both firmware projects, this variable can replace the update marker. However, it should be noted that a software reset cannot be used to enter the bootloader. Starting the bootloader can only be achieved by jumping to the corresponding address, as described in the following chapter. It is important that all registers of the microcontroller are set to a state in which the bootloader is fully functional before jumping.

Jump to the main application

After successfully completing an update and passing the CRC check, the main application should typically be executed. This is usually done by jumping to the main application. In principle, this process is similar to a normal function call, but the following points should be noted:

  1. No interrupts should be triggered during the jump to the main application, so they should be deactivated globally.
  2. Typically, the actual start address of the main application is not accessed, as this is where the interrupt vector table is usually located (see the linker scripts shown above). On Arm Cortex-M CPUs, this is usually 4 bytes in size, so an address four bytes larger than the start address is accessed.
  3. The main stack pointer must be set to the start address of the main application.
  4. All microcontroller peripherals (UART, ADC, GPIOs) should be checked to see if they need to be reset to their initial state.

The following figure shows an example of a jump function that can be used to execute the main application. The function was written for an STM32, but can be applied to other ARM Cortex-M-based microcontrollers and, with slight modifications, to other architectures.

/** * @breif Function jump to main application */ static void JumpToMainApplication(void) { // function pointer that should point to main application void (*MainJump)(void); // set start address of main application volatile uint32_t addr = MAIN_APPLICATION_START_ADDRESS; /* * increment main application address by 4, this is the * address behind the interrupt vector table where the * actual code start */ MainJump = (void (*) (void)) (*((uint32_t*)(addr+4))); // set stack main pointer __set_MSP(*(uint32_t *)addr); //jump to main application MainJump(); }

outlook

In the next part of the series, I'll introduce the concept of backup firmware. This was developed by MEDtech Ingenieur and serves as a backup in the event of a failed update using the pre-installed standard bootloader. In the third and final part, I'll introduce the concept of live updates. This allows the firmware to update itself without disrupting normal operation.

Continue to Part 2


Written by Björn Schmitz

I've been part of the MEDtech engineering team since July 2017, primarily working as a firmware developer. In a very short time, I've been able to work on many exciting projects in the medical technology field, as well as in other areas.


More articles

  • 09/09/2025
  • General, Software

In previous blog posts, I have introduced two essential components of a simple and universally applicable software architecture: events with dispatchers, listeners, and data pools. These already allow for many simple use cases ...

Read more
  • 12/11/2024
  • General, Software, Testing, Tools

In safety-critical software projects, software quality is paramount. Especially for Class C software, which must be certified according to strict standards such as IEC 62304 (medical technology), it is essential that ...

Read more
  • 08/08/2024
  • General, Electrical Stimulation, Software, Testing

Nowadays, apps in the healthcare sector are very important. Apps that can read and process data from medical sensors are particularly useful. Flutter is an open-source framework from Google that is excellent for ...

Read more
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.

Strictly Necessary Cookies

Strictly Necessary Cookie should be enabled at all times so that we can save your preferences for cookie settings.