During a business trip to a new energy factory, I was tasked with upgrading the firmware to resolve a bug. The microcontroller needed to have a .hex file reprogrammed, but the product was already sealed, making it impossible to open. Therefore, I had to use the CAN bus to update the firmware via a Bootloader. Essentially, this involves sending the .bin/.hex file to the microcontroller through CAN communication and storing it in the designated Flash memory. This process is similar to updating an app on a mobile device.
Taking the STM8 microcontroller as an example, how can we implement a Bootloader? Today, I will share this with you.
A Bootloader is a piece of code used to update its own application software and run independently. It is commonly used for product upgrades and bug fixes. To download a .hex file to the STM8 microcontroller, one typically uses STVP and STLINK, with the SWIM pin serving as the download interface. But what if the product is sealed on the user’s end, and we cannot use SWIM to download? This is where the Bootloader comes into play. STM supports Bootloader implementation via CAN and UART, allowing data to be stored in the STM8’s Flash memory through CAN or UART communication.
Dividing Flash Memory Regions
The interrupt vector jump address for the STM8 is fixed, directing to the offset address of 0x8000. Therefore, the Bootloader should be stored starting from the 0x8000 space. For instance, if we allocate 4k for the Bootloader code, the starting address will be from 0x8000 to 0x8FFF; consequently, the application code can start from 0x9000.
Modifying the Interrupt Vector Table
The STM8’s interrupt vector table is implemented through a specific piece of code and needs to be modified according to the aforementioned memory allocation. The starting address for the STM8 application program is 0x8400, with the default vector table shown in the following code:
__root const long reintvec[]@".intvec"=
{
0x82008080,0x82008404,0x82008408,0x8200840c,
0x82008410,0x82008414,0x82008418,0x8200841c,
0x82008420,0x82008424,0x82008428,0x8200842c,
0x82008430,0x82008434,0x82008438,0x8200843c,
0x82008440,0x82008444,0x82008448,0x8200844c,
0x82008450,0x82008454,0x82008458,0x8200845c,
0x82008460,0x82008464,0x82008468,0x8200846c,
0x82008470,0x82008474,0x82008478,0x8200847c,
};
Since we have modified the application program’s starting address to 0x9000, the method for modifying the vector table is as follows: change all elements except for the first one from 8 to 9. The modified code is as follows:
__root const long reintvec[]@".intvec"=
{
0x82008080,0x82009404,0x82009408,0x8200940c,
0x82009010,0x82009014,0x82009018,0x8200901c,
0x82009020,0x82009024,0x82009028,0x8200902c,
0x82009030,0x82009034,0x82009038,0x8200903c,
0x82009040,0x82009044,0x82009048,0x8200904c,
0x82009050,0x82009054,0x82009058,0x8200905c,
0x82009060,0x82009064,0x82009068,0x8200906c,
0x82009070,0x82009074,0x82009078,0x8200907c,
};
The ICF file is stored in the installation directory of the programming environment. Each model/series of microcontroller corresponds to a specific ICF file. We need to modify the ICF file according to the flash memory allocation. Here, we set the Bootloader’s end address to 0x9FFF, so we modify it as follows:
define region NearFuncCode = [from 0x8000 to 0x8FFF];
define block INTVEC with size = 0x80 { ro section .intvec };
place at start of NearFuncCode { block INTVEC };
The so-called jump refers to the ability to switch from the application program to the Bootloader for upgrading; once the upgrade is complete, it needs to jump back to the application program, starting from the specified address. The Bootloader jump to the application program is coded as follows:
asm("LDW X, SP ");
asm("LD A, $FF");
asm("LD XL, A ");
asm("LDW SP, X ");
asm("JPF $9000");
The application program’s jump to the Bootloader is coded as follows:
asm("LDW X, SP ");
asm("LD A, $FF");
asm("LD XL, A ");
asm("LDW SP, X ");
asm("JPF $8000");
Once the above settings are complete, you can write the interaction code. The interaction code is essentially a set of protocols that define how the application program jumps to the Bootloader and how data is handled and stored within the Bootloader, which requires support from the host computer. If it is only for proprietary products, you can define your own protocol. Standard protocols can also be used, such as UDS (Unified Diagnostic Services) in the automotive industry.
After implementing the Bootloader, you can use CAN/UART to upgrade the firmware of the product. This covers the key aspects of the Bootloader. Did you learn something from this article? Feel free to leave a comment for discussion.
Collection | Linux Articles Summary
Collection | Programming Life
My Knowledge Sharing Circle
