单片机的 Bootloader 是一种特殊的程序,负责在单片机上电后初始化硬件、更新用户应用程序(固件),并将控制权移交给用户程序。以下是其运行机制和关键流程的详细说明:
1、单片机 Bootloader 的核心作用
固件更新:通过通信接口(如 UART、USB、CAN、I2C、SPI 等)接收新固件,写入 Flash 存储。
程序跳转:根据条件(如按键触发、标志位)决定运行 Bootloader 还是用户程序。
硬件初始化:配置时钟、外设、通信模块等。
安全校验:对固件进行 CRC 校验或数字签名验证,防止错误或恶意代码写入。.
2、单片机 Bootloader 的运行流程
1.上电启动
硬件复位
单片机上电后,CPU 从固定地址(如0x00000000)开始执行代码,通常指向Bootloader 的入口。
部分单片机(如 STM32)支持从不同存储区域启动(通过 BOOT 引脚选择启动 Bootloader 或用户程序)。
初始化硬件
配置时钟、GPIO、通信接口(如 UART/USB)、Flash 控制器等。
2.判断运行模式
Bootloader 模式:
检测触发条件(如按键按下、特定引脚电平、通信端口收到特定指令)。
若触发条件满足,进入固件更新流程。
用户程序模式:
若未触发,直接跳转到用户程序起始地址(如0x08000000,需与应用程序的链接脚本一致)。
3、固件更新流程
3.1 等待接收固件数据
通知上位机(PC 或主机设备)开始传输固件。
固件通常以二进制(.bin)或十六进制(.hex)格式传输,包含起始地址、数据长度、校验信息。
3.2 擦除 Flash
擦除用户程序存储区。
3.3 写入 Flash
按数据包逐块写入 Flash,同时进行 CRC 校验。
若校验失败,重传或报错。
3.4 写入 Flash
写入结束标志,复位或跳转到用户程序。
3.5 跳转到用户程序
关闭 Bootloader 使用的外设。
设置APP起始地址。
4、 Bootloader 设计的关键技术
4.1 存储空间划分
如一个芯片FLASH大小为512K
Bootloader 区: 占用 Flash 起始部分(如 0x00000000~0x00010000),大小按需制定,需在编译时指定。
用户程序区: 从 Bootloader 区之后开始(如 0x00010000~0x0007F000)。
配置区: 在 Flash 或 EEPROM 中存储标志,通常运用最后一页(4K)作为配置区,大小按需制定。在配置内可以存储固件升级标志、固件升级方式、固件大小、固件检验值、固件版本号等。(如0x0007F000~ 0x00080000)
4.2 安全机制
CRC 校验: 确保数据传输完整性。
数字签名: 使用非对称加密(如 RSA)验证固件来源。
写保护: 防止误写 Bootloader 区域(通过 Flash 保护位)。
5. 单片机 Bootloader 实现实例
以新塘M480芯片为例。升级方式可以通过串口、CAN、蓝牙、U盘等信道。
当前处于APP重新中:
步骤1:上位机触发固件升级。
步骤2:擦除配置区。对于Flash来说最小擦写单位为 块(Block) 或 扇区(Sector) 擦除(如 4KB、64KB)。故最小擦除4K。将配置区数据写入配置区。
步骤3:设置中断向量映射地址,将向量地址映射为0x00000000。
步骤4:进行系统复位。
切换到bootload中
步骤5:初始化外设。
步骤6:读取配置区标志。
步骤7:根据配置区配置进行后续工作,是进行固件升级还是跳入APP执行。
步骤8:在进行固件升级的情况下获取固件升级包内容。
步骤9:接收到一定数量的配置信息,如4K(FLASH最小擦写单位),在写入之前可以先擦除需要写的内存,写入后需要再读出来进行验证。
步骤10:完成最后一包数据的写入。
步骤11:为跳转到APP做准备。清除外设配置、设置中断向量映射地址,将向量地址映射到APP地址。
步骤12:进行系统复位。
伪代码操作
\\APP中的运行代码
失能中断;
iap_flag = 0xAA; //设置升级标志
擦除FLASH(配置页地址,配置页大小);
写入FLASH((配置页地址,配置页大小,配置数据);
向量映射(0x00000000);
系统复位;
使能中断;
\\boot中的运行代码
初始化外设;
失能中断;
读取FLASH(配置页地址,配置页大小,配置数据)
使能中断;
各类校验等操作;
发送升级原因、升级包数量、固件大小等;
发送升级包请求;
获取到一定数量的升级包;
将升级包擦写读FLASH;
完成最后一包的写入;
发送升级结果;
清除外设设置;
向量映射(APP开始地址);
系统复位;
6、烧录文件类型
1.hex 文件
Intel HEX 格式,包含程序代码和烧录地址信息。
由 IDE(如 Keil、STM32CubeIDE)编译生成。
适用工具:ST-Link Utility、J-Flash等。
2.bin 文件
纯二进制文件,仅包含代码数据,需手动指定烧录地址。
适用工具:STM32CubeProgrammer、J-Link Commander、串口烧录工具、NuMicro ICP Programming Tool等。
3. 注意事项
烧录地址:
.bin文件需指定正确的起始地址(如 Flash 起始地址 0x08000000)。
.hex文件已包含地址信息,无需手动设置。
通常Bootloader代码与APP实现代码分开实现。则boot.bin与app.bin需要进行合并。
IMAGE.bin = boot.bin + app.bin;
DATA.bin = 配置信息;
若需要通过各类信道升级固件。
UPDATE.bin = 配置信息 + app.bin;
若配置区长度固定也可以写入文件低地址,应用程序区写高地址。由读取升级固件实现方式决定。
7、合并文件实现
利用python实现
这里主要阐述原理
if __name__ == "__main__": #主函数
#利用sys模块引入参数 import sys
sys.argv[0] #第一个参数
sys.argv[1] #第二个参数
sys.argv[n+1] #第n个参数
#获取到需要的参数如:需要合成的文件1、需要合成的文件2、每页大小、输出的文件名、版本号等、CRC校验等、输出的文件路径等。
文件处理路径生成
def vfile_path(path_config):
path = os.path.normpath(os.path.abspath(输出路径)) # 路径规范化
print(f"输出路径: {path}")
完整路径 = path + "\\" + 输出文件名
try:
Path( 完整路径).resolve()
except OSError as e: #判断路径是否合法
print(f"路径非法: {e.strerror}")
dir = os.path.dirname(完整路径) #路径提取
if not os.path.exists(dir):
os.makedirs(parent_dir,mode=0o777, exist_ok=False) # 递归创建目录
#print(f"创建父目录: {parent_dir}")
return parent_dir
输出文件名拼接
info = [列表元素1,列表元素2,列表元素3,.....,列表元素n]
文件名="".join(info)
合成二进制文件
with open(合成的文件1, 'rb') as app,open(输出的文件名, 'wb') as image: #读取第一个文件的内容,打开app文件,使用'wb'代表以二进制覆盖的形式写入
os.path.isfile(合成的文件1) #判断文件是否存在
os.path.getsize(合成的文件1) #判断文件是大小
#若写入的文件较大,可以利用divmod(文件大小、分段大小)分段写入
for _ in range(分段):
image.write(app.read(分段大小)) #读取合成的文件1 写入输出的文件。
if 最后一段余大小 > 0:
image.write(app.read(最后一段余大小)) #读取合成的文件1 写入输出的文件。
fp_image.write(b"\xff" * (int(页大小,10) - 最后一段余大小)) #补充最后一页内容
重复上述步骤
with open(合成的文件2, 'rb') as app,open(输出的文件名, 'ab') as image: #读取第二个文件的内容,打开app文件,使用参数 "ab"代表以二进制追加的形式写入
写入具体内到二进制文件
通过zlib.crc32函数能够获取数据的CRC校验
endianness = sys.byteorder #通过返回值可以判断大小端 big大端 little小端
通过 struct.pack可以将数据打包位二进制数据流
with open(输出文件,'wb') as fp:
fp.write(struct.pack打包的数据流)
若后续需要再写入其他数据这以'ab'增加的形式写入数据