Bootloaders 101: making your embedded design future proof
There are few projects where embedded firmware developers have more time than they need for a project. In fact, embedded firmware development is more like a gas: they take up all the available space. Often times this means that test and quality review are under pressure at the end of a project, which increases risk. So while project managers wring their hands over meeting schedule deadlines, embedded designers and test engineers are also at odds for development time versus quality of test coverage.
But what if everyone could have what they wanted? What if hardware builds could continue to progress without being code complete? What if firmware test could continue even during production builds, and what if the whole team knew that if worst came to worst they could always do a field upgrade to make critical changes? Everyone might actually be able to sleep at night. I've got good news: this is possible with the use of an embedded bootloader.
First, let’s get our lexicons in sync. A bootloadable is code that will be loaded into memory and become the main application code running on the microprocessor during normal operation. It resides on the microprocessor forever, either in ROM (Read Only Memory) programmed in the factory or in a reserved portion of on-board Flash memory. The bootloadable can be updated via a “bootloader”. It is this on-board bootloader (Figure 1) that makes it possible for a product's firmware to be updated in the field.
A bootloader resides in protected program memory on a given microcontroller. It is usually the first software to run after power up or reset and is highly processor- and board-specific. The bootloader could be considered a “dumb” piece of code in that it doesn’t understand what application needs to be performed or even what the device function is. Rather, it is specially designed to understand communication from the outside world via any number of communication protocols (UART, I2C, SPI, CAN, Ethernet) and to understand the memory map of the microcontroller. When the bootloader does its job, it communicates with the outside world, or host, reads the data file sent by the host, and updates the Microprocessor in which it resides to run the new application code provided.
Depending on the embedded system, bootloaders can begin their operation upon receiving a boot signal from a human (i.e., manual reset) or a peripheral device (i.e., the system host). This bootloader signal will first validate whether the bootloader itself is valid, determine whether the current device application is valid or not, communicate with the host to load the new application being presented, and then execute the application Flash rewrite as directed.
Most modern microcontrollers have the ability to reprogram their own Flash. A typical bootloader can accomplish this in microseconds. With larger memory sizes, however, this time increases quickly and can sometimes take several seconds for an update. Once bootloaded, the bootloader must validate the bootloadable image and turn over control to the bootloadable code. Pointers to interrupt vectors must also be set. Typically, a bootloader will perform a soft reset to allow the application to take over. Figure 2 shows an example bootloader logic flow.
Because the bootloader is programmed into a device at manufacturing and is not the primary application necessary for device operation, it can be considered “overhead” code. The project manager and embedded designer, however, must decide if the risk protection afforded by a bootloader is worth the code space it consumes.
As a result of this code space use, many designers try to keep the memory footprint of the bootloader as small as possible. This maximizes the available memory for application code. As a minimum, bootloaders must supply the following: a communication channel, a method to erase and reprogram Flash memory, and a method to validate and execute newly programmed application code.
Additionally, a bootloader should be able to detect, report, and handle errors that occur during the bootload operation, such as power failure, loss of communication, and Flash write error. Flash error protection is usually done by storing a checksum or Cyclic Redundancy Code (CRC) for the application.
When the bootload operation is started, these bits are cleared. If the new application is downloaded and installed successfully, the checksum is updated. If, however, an error occurs during download (such as loss of communication or power failure), the bootloader detects invalid check bits and does not begin application operation. Instead, it communicates with the host and waits for a valid retry of the bootload operation.
Another key consideration is to prevent code from overwriting the bootloader area itself. If a bootloader attempts to, or accidently does, overwrite itself and an error event occurs, the bootloader can be left inoperable. The next time the processor boots, the bootloader most likely will be corrupt. The effects of this could be wide ranging. It is possible that the bootloader would not be able to open the communication channel to the host or that the system might not turn over control to the last valid bootloadable image.