文章目录
简介
这是一个简单的单片机的 Bootloader
程序示例(基于STM32H743VIT6 & STM32CubeIDE)。提供了 boot
和 app
程序,提供了固件生成和固件下载需要的工具和源码。
例程地址:https://github.com/NaisuXu/STM32_Bootloader_Demo
API 说明
Format
本 Bootloader 示例通讯基于 UART ( 115200, 8N1
)。大于一字节数据以小端形式排列。
Type | Byte0~Byte1 | Byte2 | Byte3 | Byte4~Byte5 | … ( None when length == 0 ) |
---|---|---|---|---|---|
Request | 0xA5 0x5A | Rolling counter | CMD | Length (Data) | Data |
Response | 0xA5 0x5A | Rolling counter | CMD | Length (Data) | Data |
Negative Response | 0xA5 0x5A | Rolling counter | 0x00 | Length (Data) | Data |
Rolling counter
用于丢帧检测,不过例程中实际并未使用。
如果需要通讯通讯可靠,通常还需要在数据帧的结尾加上校验数据,这里偷了个懒。
Get Information
Request: A5 5A 00 11 00 00
Response: A5 5A 00 11 01 00 Mode
Mode
: 0=FBL, 1=APP
Go into FBL
Go into FBL(APP) or stay in FBL(BOOT).
Request: A5 5A 00 10 03 00 46 42 4C
Response: A5 5A 00 10 00 00
Update
BEGIN:
Request: A5 5A 00 0D 08 00 app_size(4bytes) app_crc(4bytes)
Response: A5 5A 00 0D 04 00 pack_max_size(4bytes)
WRITE:
Request: A5 5A 00 0E 00 04 data(1024bytes)
Response: A5 5A 00 0E 00 00
END:
Request: A5 5A 00 0F 00 00
Response: A5 5A 00 0F 00 00
pack_max_size
: for this demo is fixed at 1024.
程序说明
boot_proj
这是例程的 boot
程序,程序使用C/C++混合开发。
Boot 和 APP 的 Flash 分布如下:
/* Boot占用STM32H743VIT6 Flash Bank1 第一个扇区,即用户程序起始地址 */
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
/* App占用STM32H743VIT6 Flash Bank1 第二个扇区开始的三个扇区 */
FLASH (rx) : ORIGIN = 0x08020000, LENGTH = 384K
程序启动后会检查是否有合法的 APP 程序,然后注册 API 接口回调函数,启动串口监听:
void boot_setup(void){
__enable_irq();
check_if_app_ready();
uproto.add(0x0D, uart2pc_rx_cb_update_begin);
uproto.add(0x0E, uart2pc_rx_cb_update_write);
uproto.add(0x0F, uart2pc_rx_cb_update_end);
uproto.add(0x10, uart2pc_rx_cb_stay_in_boot);
uproto.add(0x11, uart2pc_rx_cb_get_info);
uart2pc_rx.start();
}
主循环中就是处理串口收发以及 APP 跳转。当有合法 APP 以及满足跳转条件后跳转 APP :
#define STARTUP_DELAY_MS (50)
void boot_loop(void){
for(;;){
uart2pc_rx.poll();
uproto.poll();
uart2pc_tx.poll();
if(goto_app_flag && (bsp_sys_get_time_ms() > STARTUP_DELAY_MS)){
goto_app();
}
}
}
本例程中 APP 合法的检查条件有下面三条:
- APP 起始四字节数据符合栈指针地址范围;
- APP 栈指针之后四字节数据符合程序入口指针范围;
- 在 BOOT 的 NVM 数据中有 APP 就绪标志;
// 检查堆栈指针和程序入口是否合法
static bool check_app_sp_pc(void){
uint32_t main_stack_pointer = *((volatile uint32_t*)APP_PART_START_ADDR);
if((main_stack_pointer < RAM_START_ADDR) || (main_stack_pointer > (RAM_START_ADDR + RAM_PART_SIZE))) return false;
uint32_t app_entry_addr = *((volatile uint32_t*)(APP_PART_START_ADDR + 4));
if((app_entry_addr < APP_PART_START_ADDR) || (app_entry_addr > (APP_PART_START_ADDR + APP_PART_SIZE))) return false;
return true;
}
#define APP_READY_FLAG (0xA5A55A5A)
// 检查是否有合法APP
static void check_if_app_ready(void){
#ifndef DEBUG_MODE
uint32_t flag = *((volatile uint32_t*)BOOT_NVM_START_ADDR);
if(flag != APP_READY_FLAG) return;
#endif
if(!check_app_sp_pc()) return;
goto_app_flag = true;
}
除了上面三个条件外 BOOT 还设置了一个延时时间,在该时间内也不会进行跳转,以防 APP 程序不当处理或跑飞导致无法进入 BOOT 再次刷程序。
从 BOOT 跳转到 APP 主要就是设置堆栈指针然后跳转到程序入口:
static void goto_app(void){
__disable_irq();
SCB_DisableICache(); // 程序中启用了 ICache ,这里需要关闭
// 失能所有用到的外设
LL_DMA_DeInit(DMA1, LL_DMA_STREAM_0);
LL_DMA_DeInit(DMA1, LL_DMA_STREAM_1);
LL_USART_DeInit(USART1);
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
// 清中断
for (int i = 0; i < 8; i++){
NVIC->ICER[i]=0xFFFFFFFF;
NVIC->ICPR[i]=0xFFFFFFFF;
}
uint32_t main_stack_pointer = *(volatile uint32_t*)APP_PART_START_ADDR;
typedef void (*app_entry_func_tpte)(void);
uint32_t app_entry_addr = *(volatile uint32_t*)(APP_PART_START_ADDR + 4);
app_entry_func_tpte app_entry_func = (app_entry_func_tpte)app_entry_addr;
__set_MSP(main_stack_pointer); // 设置栈指针
app_entry_func(); // 跳转到程序入口
}
接收固件基于上文的串口 API ,擦除固件区域,然后再分包接受固件数据并写入Flash,最后检查数据,并设置标志。
本示例中的固件数据是经过了异或运算的,需要再次运算进行还原。异或运算提供一定程度的加密。本示例中只是使用一个字节的异或,实际中可以采用一个数组循环进行异或加强加密能力。
本示例固件生成工具中会计算原始固件的 CRC32
校验数据,但 BOOT 中偷了个懒并未进行校验核对。
可以注释掉 boot/config.h
中的 #define DEBUG_MODE
来启用读保护,防止固件被简单读取。
app_proj
这是例程的 app
程序,程序使用C/C++混合开发。
APP 程序非常简单,和 BOOT 部分代码是重复了,唯一不同的是跳转 BOOT 接口中擦除 BOOT 的 NVM (主要是其中的 APP 就绪标志)。
APP程序可以单独调试,需要如下设置:
app_proj/STM32H743VITX_FLASH.ld 文件:
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 384K
app/config.h 文件:
SCB->VTOR = 0x08000000;
APP程序也可以在有BOOT下调试,需要如下设置:
boot/config.h 文件:
#define DEBUG_MODE
app_proj/STM32H743VITX_FLASH.ld 文件:
FLASH (rx) : ORIGIN = 0x08020000, LENGTH = 384K
app/config.h 文件:
SCB->VTOR = APP_PART_START_ADDR;
另外说一句 BOOT 本身就是起始于 0x08000000
,直接调试就行。
fw_gen.html
该工具用于将 app_proj
生成的 .bin
文件转换成用于通过 boot
升级的 .nxfw
文件。
程序中读取上传的 .bin
文件,将数据通过异或运算后填充,最后在结尾处填充 CRC32
校验数据。
fw_update.html
该工具用于更新固件到设备中,本身并没有什么特别难的地方,只是对上文 API 的简单操作。
该工具用到了 Web Serial API
需要 Windows 下最新的 Edge 或者 Chrome 浏览器支持。该技术相关使用说明可以参考下面文章:
《使用 Web Serial API 在浏览器中实现串口通讯(纯前端)》:https://blog.csdn.net/Naisu_kun/article/details/132522118
使用演示
固件生成
固件更新
带有 BOOT 时调试 APP
其他说明
Bootloader 程序的原理和实现方式是有很多种的,这里只是比较简单常见的一种实现而已。