顶贴
前言:
最近都没有怎么写文章了,折腾下STM32L4的IAP,自己写写Bootloader。查阅了网上的资料,IAP的升级包一般是使用USART发送到MCU,也有使用CAN和USB的方式,但这个具体的通信方式不是太重点,本文的主要内容是写片上FLASH和重新设置中断向量表起始地址(确保程序从新的固件起始点开始执行),L4系列的MCU写Flash参考我之前写的文章:STM32L4内部Flash双字节对齐读写短字符串 - 科创网
正文:
1、在使用Keil的软件配置的Target中可以看到ROM(Flash)和RAM(内存)的划分,代表着从0x08000000为基地址偏移0x40000到0x08040000的区域划分给程序储存区,RAM的那两个就不重复讲解了,代表那两个区域划分给了内存。我使用的MCU是STM32L431RCT6,Flash的大小是256KB,RAM是128KB。
2、STM一般程序(没有IAP)的启动流程如下
(1-2)STM32复位后先从0x08000004的地址取出复位中断向量的地址,然后转跳到中断复位程序,然后中断复位程序再转跳到main函数。
(3-5)main函数是个while死循环,当出现中断请求后PC指针指向中断向量表,然后轮询找到具体的中断函数并进入,结束中断回调后从LR取出地址赋给PC后返回调用点(SP保存被中断函数的上下文信息)。
3、加入了Bootloader的启动流程如下
加入了Bootloader的大部分启动流程和一般程序启动流程是一样,主要的区别是多了从Bootloader的main函数开头转跳到新的中断向量表。以及如果VTOR(中断向量表基地址)没有改,发生中断时MCU会根据默认向量表地址(一般是0x08000000)读取中断处理函数地址,从而跳回Bootloader区域。可以简单理解成,在Flash和RAM中各开辟了两个区域,Flash的区域1和RAM的区域1给Bootloader使用,主要的功能是接收数据和PC转跳。Flash的区域2和RAM的区域2给APP使用,主要功能是执行业务代码。为了让中断在APP中正确响应,需要在跳转到APP前,将SCB->VTOR设置到APP的中断向量表地址。
4、程序的设计逻辑是Flash划分32KB,RAM划分20KB给Bootloader,这个划分给Bootloader的空间其实算是比较大了,但由于我测试的APP程序只是个闪灯程序Flash和RAM占有很少,因此Bootloader区域的划分倒没必要太极限。Flash划分32KB,RAM划分20KB给APP。可以根据编译的大小来确定划分的空间。
计算公式:
Flash 区域大小 = Code 大小 + RO-data 大小
RAM 区域大小 = RW-data 大小 + ZI-data 大小
可得:
Flash 区域大小 = 9096 + 572 = 9668 字节(9.47 KB)
RAM 区域大小 = 64 + 2184 = 2248 字节(2.2 KB)
在MCU上电后的5s时间内如果串口接收到“update”指令后进入APP烧写功能,否则直接尝试执行APP程序(通过对应Flash区域是否有数据判断是否有APP程序,如果有就执行,如果没有就串口打印错误提示)。由于正点原子的串口文件发送延迟是发送1024字节后延迟100ms,因此MCU每接收到1024字节就写入一次直到全部写完,最后发送的字节个数可能不足1024个,需要等待串口接受超时后再多写一次Flash。
5、Bootloader程序的主要核心是写片上FLASH和中断向量表起始地址重映射,我们先用串口接收数据,使用
CHAL_UART_Receive(UART_HandleTypeDef huart, uint8_t pData, uint16_t Size, uint32_t Timeout)
如果5s内有升级指令就擦除Flash上需要写入的区域(因为APP大小选定了,因此擦除的页面数也是定的),使用
CHAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef pEraseInit, uint32_t PageError)
把串口接收到是数据转换成双字DOUBLEWORD类型,使用
Cdata_64 = (uint64_t )(&datatemp[i*8]);//方法随意,能转就行
因为我们总共接收到的是1024字节,每次写入DOUBLEWORD(8个字节)需要写入1024/8=128次(我这里之前计算错了,计算出256次,然后就解了两天bug,后面会说说我是怎么解bug的)。最后一次不足1024字节的接收等待超时后再写入一次。Bootloader转跳到APP前要关闭中断
CHAL_RCC_DeInit();//时钟反初始化
HAL_DeInit();//HAL库反初始化
SysTick->CTRL &=~SysTick_CTRL_ENABLE_Msk;//时钟反初始化
SysTick->CTRL &=~SysTick_CTRL_ENABLE_Msk;
转跳
C__set_MSP(*(uint32_t*)ApplicationAddress); //调用__set_MSP重新设定栈顶代地址,把栈顶地址设置为APP代码指向的栈顶地址。
jump2app=(iapfun)*(uint32_t*)(ApplicationAddress+4);//获得新的复位函数入口地址,让jump2这个函数指针指向复位函数入口地址。
jump2app(); //跳转到新的复位函数
6、Bootloader构建好了接下来便是构建APP程序了,我们APP的Flash区和RAM区需要在Bootloader的Flash区和RAM区后面(其实也不一定,具体的PC转跳是Bootloader决定的),因此APP的Keil5的Targe设置(其实这个就是改.s启动文件,只不过不用手改汇编罢了给了个配置界面)如下
在APP的main函数首行设置中断向量表偏移
CSCB->VTOR = NVIC_VectTab_FLASH | BOOT_SIZE;
APP其他部分不用修改,点进User中,加入生成bin文件的命令,编译后会在.\Bin文件夹下生成bin文件
Plain Textfromelf.exe --bin --output=Bin\@L.bin !L
7、打开串口助手,设置发送1024字节后等待100ms,然后复位开发板,发送“update”等待擦除成功后发送bin文件
8、文件发送成功,APP程序正常运行,断电后再上电Bootloader等待5s后默认转跳到APP程序,至此功能验证基本完成。
艰难的故障排查:
如前文所言,在升级过程中曾遇到过升级失败的问题,具体现象为Bootloader程序在PC转跳后进入了HardFault_Handler,我本来以为是Flash读写没对齐,使用之前的__align(8)方法进行对齐但不奏效。
随后修改APP程序使其一进入main函数后马上while(1)锁死(不进行HAL库和时钟初始化)并烧录,从PC指针上看是可以转跳到main函数里面的。
然后我以为是APP程序的问题,尝试Bootloader全片擦写烧录,APP程序部分擦写烧录,这种情况下APP程序是可以运行的,也没有进入HardFault_Handler。
此时我怀疑是两者的Flash文件不一致,先把上面能跑的代码的MCU Flash拷贝一份,使用如下命令拷贝片上Flash并保存,方便后面进行对比。
Plain TextSAVE C:\改为你自己的路径\Desktop\flash_dump.txt 0x08000000,0x0800FFFF
从Keil上面看0x08000000处的数据是这样的
我们打开其中一个文件看看,这是什么玩意(其实Keil直接导出的数据经过了压缩):
这玩意不是给人类直接阅读的,我们转成bin格式的文件看看
数据算是可读了,我们把转换好的bin文件保存为txt文本,然后对比
APP程序开始时是正确的(这也是我后面踩坑的地方,我看着前面的数据是正确的后面的那些就没有管了,也没有用CRC校验,后面可以加上CRC校验一下稳一点),看看下面的是否能对上
两者数据还真不一样,0x08008000往后正确的行数一共有128行,对应128*8=1024个字节的数据,也就是说前面的数据1024字节的数据是正确的(这也能解释为什么我在main函数开头while(1)是可以转跳进去的,因为此时的bin代码量不足1024字节,烧进去的代码都是正确的),那么问题多半出在Bootloader的写Flash上面了,排查写Flash的代码后发现,我一次是写了2K字节的数据,修改成写1K字节数据,程序正常运行,排错结束。由于L4要使用双字节写片上Flash,和F1的单字节写片上Flash不一样,同时我使用的是正点原子的串口助手,根据参考文献改Bootloader的时候大脑宕机遇到了这个问题。(参考代码给出的是正确的代码,错误的代码没有放上来了)
结束语:
三个月没写文章了,折腾了两天把STM32的IAP搞了出来。最近感觉写RTOS能力还不够强,代码量一上来就很难排查错误了,也不知道要怎么Debug,不像裸机直接单步执行结果就很显然了,感觉RTOS回到了STC89C52那个纯靠串口printf数据排错的时候,但应该有标准的排错方法的,不至于有调试器但在RTOS上不知道怎么用。
即将告别大学生活正式步入社会(虽然整个大四都在实习,也上班一年了),回想起长者的话:一个人的命运,当然要靠自我奋斗,但也要考虑历史的进程。回看大学这四年默然惆怅,虽然大学生活基本如我高考完规划的那样一一进行,总觉得少了些什么,人文学科的跨学科学习仍有欠缺,批判性思维尚未建立,嵌入式的学习也没有那么精通反倒是有些茫然。
Bootloader参考代码:
APP参考代码:
Keil导出的文件转bin文件代码:
你可以直接把这段代码丢给GPT然后把Keil导出的文件也一起上传上去,就可以跑出结果了,也可以本地跑。同时也尝试了几个在线的转换工具,不知道为什么转换出来的结果均有误,原始导出文件我放下面了有兴趣的朋可以试试。
BIN转HEX工具:
https://viewing.cc/hex-bin-converter
导出的文件,分别是Keil导出的原始文件,转BIN格式的文件,BIN转HEX保存的文件:
文本对比工具:
https://www.jyshare.com/front-end/8006/
参考文章:
https://blog.csdn.net/weixin_42434684/article/details/144459430
https://blog.csdn.net/professionalmcu/article/details/103208852
ps:欢迎各位提建议和意见,回复均有KCB
pps:总感觉现在的表达能力下降得很厉害,平时上班一直搞技术也很少聊天,主要还是干活,太少和朋友吹水了(本人在深圳欢迎一起玩耍)?不知道坛友们有没有类似的情况?能否支个招
[修改于 17天0时前 - 2025/04/30 11:38:21]
有参考价值,给我们公司软件佬学习学习
200字以内,仅用于支线交流,主线讨论请采用回复功能。