写一个STM32 BootLoader-1
写一个STM32 BootLoader-1
1. 概述
某些定制的产品交付使用之后,用户方难免会提出一些功能更改的要求,所以也就避免不了对MCU里面的程序进行更新。一般情况下,例如STM32,会在内部FLASH前面放一段BOOTLOADER的代码。在启动时,如果没有某个操作(如串口输入或按键输入)的话,则正常跳转到用户程序中进行执行。如果有相关操作的话,则运行BOOTLOADER程序,在BOOTLOADER里面嵌有操作FLASH的程序,通过该程序可以将新的用户程序更新进FLASH里面。
ST官方有基于UART进行更新用户程序的BOOTLOADER工程。该工程启动的时候,如果串口收到输入,则进入更新用户程序状态,上位机利用Ymodem协议,把编译好的用户程序通过串口写入到STM32中。如果启动过程中,串口没有收到数据,则正常跳转到用户程序中去执行。
笔者之前项目使用的BOOTLOADER是在官方发布的工程基础上修改的。基本流程是这样:在启动三秒内,如果串口没有输入的话,则跳转到用户程序;如果串口有输入的话,则运行Ymodem协议软件进行用户程序更新。
但是最近有些项目在执行过程中遇到了一些新的需求。比如启动时间,用户方要求一上电就默认进入用户程序,而不必等三秒;再者,也出现过这样的情况,在某些强干扰的情况下,启动过程中,串口接收到了一些干扰信号,导致误触发了下载更新流程。于是萌发了自己给STM32写一个BOOTLOADER的想法。
2. 定义基本功能
1、串口下载不用采用Ymodem协议了,采用自定义的协议,该协议同时跑在BOOTLOADER和用户程序中,通过指令操作选择执行BOOTLOADER或者用户程序。
2、因为BOOTLOADER是在FLASH开头执行的程序,想从用户程序切换回去,最简单的方式是让软件进行重启,这样让软件可以从FLASH开头进行运动。
3、重启后必须有一种机制来选择是执行BOOTLOADER或者用户程序,可以通过在内存设置标志位的方式。例如在用户程序执行的过程中,如果收到进入BOOTLOADER的指令,则设置该标志位,然后重启,重启后BOOTLOADER开头读取该标志位,通过该标志位来选择是进入BOOTLOADER或者用户程序。因此该标志位必须不受软件复位的影响,同时在上电复位时能复位到一个明确的状态。
4、串口需要接收不等长的数据包。因为更新用户程序时,数据量较大,一帧数据要尽可能加长。同时也要兼顾到其他短包的指令
3. 通讯协议
帧头采用0x41,帧尾采用0x2b,如下图所示
如果在数据中遇到0x41,0x2b,则采用转义的方式:
0x41 => 0x2d 0x21
0x2b => 0x2d 0x0b
0x2d => 0x2d 0x0d
协议中数据的组织形式为
typedef struct {
uint8_t id; // 板子ID号,如果系统有多个板子的话,防止更新错程序
uint8_t cmd; // 协议命令
uint8_t subcmd; // 协议子命令
uint8_t len; // 后面数据的长度,以字节为单位
uint8_t dat[65]; // 数据,烧写的数据长度要保证4的倍数,最长的数据为64个字节,最后一个字节是CRC校验
// CRC校验从dat[0]开始
}
4. BOOTLOADER和用户程序切换
将标志位设置在内部的FLASH中应该是一种比较好的选择,因为FLASH中的数据不会因为系统复位而发生改变。但是FLASH是按页进行擦除的,在写入数据之前需要对整页进行擦除操作。因为要写入一个数据的原因,而导致要去操作整页FLASH,这样对于嵌入式系统来说有点浪费资源
在STM32系统的芯片中,有个更好的办法,即利用STM32中的备份寄存器就可以完成这个操作。STM32F1中有42个两字节的备份寄存器。这些寄存器里面写入的内容不会受到系统软件复位的影响,同时,当系统断电后重新上电时,又可以初始化到一个明确的状态,所以很适合把操作切换的标志位放在这个寄存器里面。
5. 内部FLASH分配(以STM32F103VET6为例)
STM32F103VET6 FLASH的组织形式如下图
本设计中,将Page0~Page3共8KB的空间分配给BOOTLOADER,用户程序从Page4开始,也就是从地址0x08002000开始,后面所有的FLASH都分配给用户程序。
6. 参考
- https://blog.csdn.net/MQ0522/article/details/123422553
作者:Cesaroy