Embedded software is never finished. The software always requires some tweak, adjustment, a feature addition or bug fix. It is just the way the world works. A product could be sent back to the manufacturer for updates or a technician could be sent to the product. Either way it is expensive! Some products are built in such a way that accessing the microcontroller’s programming port would require a complete tear down of the system. A bootloader is the perfect solution for either situation.
Bootloaders have become a necessity in nearly every embedded system but unfortunately they are given little to no attention. The ability to update firmware is an afterthought rather than a forethought for most developers. The result of this oversight is a last minute ditch effort to build any update functionality into the system. Bugs creep into the system and due to a lack of testing undoubtedly find their way into the field. There are 7 tips that can be followed to ensure that a bootloader doesn’t just update firmware but does so correctly every time.
Tip 1 – Add a bootloader early
Start on the bootloader early in the design, preferably after the proof of concept stage when the project becomes officially funded. Integrating the bootloader into the system early not only allows thorough testing but also puts a limit on the changes to the application that may be necessary during integration.
Tip 2 – Check the reset vector
One of the purposes of a bootloader is to get the system to a known state and make a decision to either jump to the application or wait for programming instructions. In the event that the bootloader decides that the application should be started, the jump to the application vector should not be a blind one. The system flash could have previously been erased, leaving the reset vector with a unique location of 0xFFFFFFFF. What will happen to the system if this reset vector is executed?
The simplest check that can be performed on an application prior to starting it is to make sure that the reset vector has been programmed to a value, i.e., it’s not erased. A programmed reset vector gives a minimum level of confidence that the program won’t jump off into the weeds. An example can be seen below in Figure 1. A developer may even want to get fancier and provide a bounds check on possible valid reset vectors. A boundary check would help ensure that not only has the reset vector been programmed but it has been pointed to a valid location.
Tip 3 – Perform a CRC check
A reset vector check is a great start to validating that an application is present but the reset vector alone doesn’t tell the whole story. What would happen if the firmware update were aborted part way through? The reset vector could have been written to flash but the application itself corrupted. Adding a CRC check to the application is a great way to validate the application. The bootloader can perform a quick CRC check of the application space at start-up and if the check succeeds the bootloader can assume that the application space is intact.
Tip 4 – Lock the bootloader
Updating the bootloader in the field is an extremely dangerous endeavor. A power glitch, stray cosmic ray or a dirty look could cause the bootloader section of memory to become corrupted and unusable. The result is an embedded system that is bricked until it can be rescued by a more sophisticated programming tool. A bootloader should be a small, simple and robust application that doesn’t require feature upgrades or bug fixes. The bootloader should be placed in protected memory sections in order to ensure that it cannot be accidentally be erased or overwritten in any way, shape or form.
Tip 5 – Break flash into multiple application slots
Embedded systems sometimes have complex requirements such as updating the firmware while the application is still running or being able to revert to a previous version in the event of a failed update. These requirements would be difficult to implement without breaking the flash space up into multiple application segments. For example, flash space could be broken up to include the bootloader and the original firmware in protected memory and then two spare application sections for updated firmware. The bootloader would program one of the spare slots and if success could then switch to using that application. In the event of a failure the bootloader could always fall back to the original or the application stored in the backup slot.
Tip 6 – Use a hand shaking protocol
The compiler will generate a representation of the application that is broken down into records. These records can be generated in a variety of formats but one common feature is that each record represents a smaller chunk of the application and contains a checksum. The developer can create a protocol that sends one record at a time, verifies the record, writes it to flash and then responds to the update tool with a confirmation. Once the tool receives the confirmation the next record can be sent. Using a handshake breaks the flashing process up into manageable chunks and in the event of an error the system can handle it immediately rather than at the end of the update cycle.
Tip 7 – Use a delta generator
A traditional microcontroller bootloader will completely erase the application flash space during the update process. Time can be lost updating firmware if there were only minor changes to the software. An approach that is often taken in larger computer systems is to determine the delta or difference between application files and only update those files or memory sections that have changed. Delta generators can also be used to minimize the time required to update a microcontroller application.