加载中
加载中
表情图片
评为精选
鼓励
加载中...
分享
加载中...
文件下载
加载中...
修改排序
加载中...
本文在AI协助下创作,作者声明AI的作用仅限于局部修饰性、辅助性工作,文章内容的严肃性由作者负责。
所有教程由网友发布,仅供参考,请谨慎采纳。科创不对教程的科学性、准确性、可靠性负责。
STM32L4+Bootloader+USART+IAP
李艺良
全桥整流2025/04/29原创 秋名山最速传说 IP:广东

前言:

最近都没有怎么写文章了,折腾下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。

image.png


2、STM一般程序(没有IAP)的启动流程如下

image.png

(1-2)STM32复位后先从0x08000004的地址取出复位中断向量的地址,然后转跳到中断复位程序,然后中断复位程序再转跳到main函数。

(3-5)main函数是个while死循环,当出现中断请求后PC指针指向中断向量表,然后轮询找到具体的中断函数并进入,结束中断回调后从LR取出地址赋给PC后返回调用点(SP保存被中断函数的上下文信息


3、加入了Bootloader的启动流程如下

image.png

加入了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。可以根据编译的大小来确定划分的空间。

image.png

计算公式:

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和中断向量表起始地址重映射,我们先用串口接收数据,使用

C
HAL_UART_Receive(UART_HandleTypeDef huart, uint8_t pData, uint16_t Size, uint32_t Timeout)

如果5s内有升级指令就擦除Flash上需要写入的区域(因为APP大小选定了,因此擦除的页面数也是定的),使用

C
HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef pEraseInit, uint32_t PageError)

把串口接收到是数据转换成双字DOUBLEWORD类型,使用

C
data_64 = (uint64_t )(&datatemp[i*8]);//方法随意,能转就行

因为我们总共接收到的是1024字节,每次写入DOUBLEWORD(8个字节)需要写入1024/8=128次(我这里之前计算错了,计算出256次,然后就解了两天bug,后面会说说我是怎么解bug的)。最后一次不足1024字节的接收等待超时后再写入一次。Bootloader转跳到APP前要关闭中断

C
HAL_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启动文件,只不过不用手改汇编罢了给了个配置界面)如下


image.png

在APP的main函数首行设置中断向量表偏移

C
SCB->VTOR = NVIC_VectTab_FLASH | BOOT_SIZE;

APP其他部分不用修改,点进User中,加入生成bin文件的命令,编译后会在.\Bin文件夹下生成bin文件

Plain Text
fromelf.exe --bin --output=Bin\@L.bin !L

image.png


7、打开串口助手,设置发送1024字节后等待100ms,然后复位开发板,发送“update”等待擦除成功后发送bin文件

image.png


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 Text
SAVE C:\改为你自己的路径\Desktop\flash_dump.txt 0x08000000,0x0800FFFF

image.png

从Keil上面看0x08000000处的数据是这样的

image.png

我们打开其中一个文件看看,这是什么玩意(其实Keil直接导出的数据经过了压缩):

image.png

这玩意不是给人类直接阅读的,我们转成bin格式的文件看看

image.png

数据算是可读了,我们把转换好的bin文件保存为txt文本,然后对比

image.png

APP程序开始时是正确的(这也是我后面踩坑的地方,我看着前面的数据是正确的后面的那些就没有管了,也没有用CRC校验,后面可以加上CRC校验一下稳一点),看看下面的是否能对上

image.png

两者数据还真不一样,0x08008000往后正确的行数一共有128行,对应128*8=1024个字节的数据,也就是说前面的数据1024字节的数据是正确的(这也能解释为什么我在main函数开头while(1)是可以转跳进去的,因为此时的bin代码量不足1024字节,烧进去的代码都是正确的),那么问题多半出在Bootloader的写Flash上面了,排查写Flash的代码后发现,我一次是写了2K字节的数据1f602,修改成写1K字节数据,程序正常运行,排错结束。由于L4要使用双字节写片上Flash,和F1的单字节写片上Flash不一样,同时我使用的是正点原子的串口助手,根据参考文献改Bootloader的时候大脑宕机遇到了这个问题。(参考代码给出的是正确的代码,错误的代码没有放上来了)


结束语:

三个月没写文章了,折腾了两天把STM32的IAP搞了出来。最近感觉写RTOS能力还不够强,代码量一上来就很难排查错误了,也不知道要怎么Debug,不像裸机直接单步执行结果就很显然了,感觉RTOS回到了STC89C52那个纯靠串口printf数据排错的时候,但应该有标准的排错方法的,不至于有调试器但在RTOS上不知道怎么用。

即将告别大学生活正式步入社会(虽然整个大四都在实习,也上班一年了),回想起长者的话:一个人的命运,当然要靠自我奋斗,但也要考虑历史的进程。回看大学这四年默然惆怅,虽然大学生活基本如我高考完规划的那样一一进行,总觉得少了些什么,人文学科的跨学科学习仍有欠缺,批判性思维尚未建立,嵌入式的学习也没有那么精通反倒是有些茫然。


Bootloader参考代码:

attachment iconIAP_Test.zip30.21MBZIP2次下载

APP参考代码:

attachment iconLET_Tg.zip10.44MBZIP3次下载

Keil导出的文件转bin文件代码:

你可以直接把这段代码丢给GPT然后把Keil导出的文件也一起上传上去,就可以跑出结果了,也可以本地跑。同时也尝试了几个在线的转换工具,不知道为什么转换出来的结果均有误,原始导出文件我放下面了有兴趣的朋可以试试。

attachment iconKeil导出的文件转bin文件.py1.42KBPY0次下载

BIN转HEX工具:

https://viewing.cc/hex-bin-converter

导出的文件,分别是Keil导出的原始文件,转BIN格式的文件,BIN转HEX保存的文件:

attachment iconIAP+DAP.txt180.03KBTXT1次下载

attachment iconIAP_DAP.bin64.00KBBIN0次下载

attachment iconIAP_DAP_R.txt136.00KBTXT0次下载

文本对比工具:

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:总感觉现在的表达能力下降得很厉害,平时上班一直搞技术也很少聊天,主要还是干活,太少和朋友吹水了(本人在深圳欢迎一起玩耍)?不知道坛友们有没有类似的情况?能否支个招1f604


[修改于 17天0时前 - 2025/04/30 11:38:21]

来自:电子信息 / 电子技术包含人工智能产物:作者负责、AI协助动手实践:实验报导严肃内容:教程/课程
3
 
9
新版本公告
~~空空如也
全桥整流 作者
13天17时前 IP:中国
943686

顶贴


引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
谁叫小明
9天20时前 IP:广东
943827

有参考价值,给我们公司软件佬学习学习


+1
科创币
全桥整流
2025-05-11
佬去哪高就了
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
zRed洲虹
9天2时前 IP:中国
943845

感谢分享,期待楼主更新rtos标准调试流程,之前折腾的时候就是靠printf和串口debug,略微头大,后面又用回裸机编程了hhh(当时要实现的功能确实不复杂)


+1
科创币
全桥整流
2025-05-11
rtos调试流程我也没搞太明白,有点玄学,目前在研究安富莱的调试文档
引用
评论
1
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
所属分类
上级专业
同级专业
全桥整流
进士 学者 机友 笔友
文章
29
回复
287
学术分
1
2020/01/26注册,53分30秒前活动

秋名山最速传说! 邮箱:331924204@XXXXXX

主体类型:个人
所属领域:无
认证方式:身份证号
IP归属地:广东
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

笔记
{{note.content}}
{{n.user.username}}
{{fromNow(n.toc)}} {{n.status === noteStatus.disabled ? "已屏蔽" : ""}} {{n.status === noteStatus.unknown ? "正在审核" : ""}} {{n.status === noteStatus.deleted ? '已删除' : ''}}
  • 编辑
  • 删除
  • {{n.status === 'disabled' ? "解除屏蔽" : "屏蔽" }}
我也是有底线的