【STM32】基于串口的bootloader
一、简单介绍
Bootloader 是嵌入式设备中负责引导和更新的关键程序。汽车的 ECU 在进行 OTA 升级时,Bootloader 负责下载、校验和写入新固件,确保出现异常时能回退到旧版本保障行车安全;手机系统更新依赖 Bootloader来刷机、验证签名并启动系统内核;电脑中的 BIOS 或 UEFI 同样是一类 Bootloader,用于初始化硬件并加载操作系统。通过统一的下载接口和安全机制,Bootloader 让设备可以远程维护、在线更新并防止恶意篡改,从而在降低生产和维护成本的同时,保证系统在整个生命周期中的稳定与安全。
归根结底,bootloader也是一个工程,和application性质相同
笔者实现的简易bootloader的作用主要有
- 擦除现存app
- 接收新的app固件
- 烧写到flash中
- 跳转到新app
二、开发思路
笔者是基于STM32F103CBT6开发,和常见的STM32F103C8T6相比,flash变成128kb
STM32单片机的flash启动模式下,启动地址总是0x8000000,这也是自己的bootloader的起始地址
bootloader大小为12616字节,需要12个page多一点点(0.32 page)
因此把判断标志放在page 12的开头,即地址0x08003400
App的起始地址为0x08003800,(page13的开头)
内存分布为
boot检测flag信号为true表明需要升级,擦除App并烧写新的固件;否则就表明不需要升级,直接跳转到App即可
三、实战开发
App工程需要设置起始地址,使用keil的话如下设置
system_stm32f1xx.c文件中设置向量表偏移地址
在App中只运行一个闪烁LED作为演示
boot工程起始地址就是0x8000000,就不需要偏移了
设置地址宏和升级请求标志
#define APP_START_ADDRESS 0x08003800
#define APP_REQUEST_ADDRESS 0x08003400updateReq = *(uint8_t*)(APP_REQUEST_ADDRESS);
判断是否需要升级
if (updateReq == 1 || updateReq == 0xFF)
{state = STATE_INIT;HAL_FLASH_Unlock();HAL_FLASHEx_Erase(&req, &error);
}
else
{state = STATE_JUMP;
}
在主循环内进行状态的切换
笔者制作了一个配套的上位机
上位机按下boot则发送由帧头0xAA+0x22222222+CRC组成的升级请求,App检测到后就设置标志位并复位
上位机按下flash则进行新固件的传输,在boot中进行接收和flash的写入
case STATE_FLASH:
{/*** flash code*/HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, APP_START_ADDRESS + flashPackCnt * 4, *(uint32_t*) (prx + 1));flashPackCnt++;/* flash finish, notify PC by sending 0x77 */tx[0] = 0x77;HAL_UART_Transmit_IT(&huart1, tx, 1);/* go to wait state for getting next data from PC*/HAL_UART_Receive_IT(&huart1, rx, 6);state = STATE_WAIT;if ((flashPackCnt) * 4 == dataSize){state = STATE_END;HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, APP_REQUEST_ADDRESS, 0);}break;
}
上位机发送0x20+四字节的固件包+CRC,单片机接收后发送0x77作为应答信号
烧写完毕后跳转到新的App中
case STATE_JUMP:
{deinitEverything();uint32_t stacktop = *((__IO uint32_t *)APP_START_ADDRESS);__set_MSP(stacktop);app_func_t app_func = (app_func_t)(*((__IO uint32_t *)(APP_START_ADDRESS + 4)));app_func();
}
升级完毕后调试读取内存,0x8003800处的内容和编译的App的hex内容一致
由于升级请求被擦除,下一次复位就直接跳转至App运行了