干货 | 小谈bootloader和app两重升级

EEWORLD

电子资讯 犀利解读

技术干货 每日更新

一般设计来说,bootloader作为引导程序和升级APP,改动并不大而且也相对稳定。但是如果项目中遇到非得更新bootloader的,比如协议的更新替换,这时候就有设计此方案的必要性了。

不多说,直接进入正题。flash分区图和程序图如下,接下来的描述都是基于这图来进行分析

大概说下思路:简单的说就是设计三段程序,bootloader0、bootloader1、APP。

这里假设使用的是ST的STM32F103C8T6,该款单片机有64K flash,每个扇区为1K,一共64个扇区。在这些扇区的分配中,bootloader0占用1K,也就是一个扇区空间(0x8000000~0x80003FFF);bootloader1占用22K,也就是22个扇区空间(0x8000400~0x80005BFF);标志区占用1K,也同样为一个扇区空间(0x80005C00~0x80006000);APP部分包括APP和DATA,还有40K空间可利用。这个就flash的分区思路。

此次再来说下1K标志区的存储,在这里我只用了10个字节存储,当然如果有一些必要的标志数据啥的,也可以存储在此空间,在这里我用结构体表示这些变量成员,并把标志区的flash地址转换成此结构体指针。

此次再来说明程序流程图。正常模式下,系统开始运行是从bootloader0开始的,然后跳转到bootloader1,再者跳转到APP,这就是一个流程。bootloader0的程序也很简单,无非就是判断标志区的标志变量是否改变,从而选择跳转到Bootloader1或者APP区域。bootloader0的main函数如下,按照上述的流程图可以明白,只要是标志区的跳转标记是正常模式或者bootloader_flag未被置位,说明是没有升级过bootloader1或者已经升级成功的,进而就跳转到bootloader1。否则都直接进入APP,这样可以防止APP里面升级bootloader失败了而且不小心断电了,不用经过Bootloader1进入APP再次升级bootloader1。

当然在启动文件里面,不需要进入系统时钟初始化直接进去main,所以我们同样也要修改下启动文件。

最后生成的bootloader0空间比较小,如果觉得这1K扇区用的比较浪费,也同样可以存储一些你觉得必要的数据。

那么接下来先说明下16个字节升级参数,这16个字节是每次制作升级bin包的时候插入到此Bin包的头16个字节。说明如下:

bin包的发送上,我的上位机是这样处理的,先每次间隔两秒发送16个字节参数,后续的bin包拆分512个字节依次200ms发送。(下面会简单介绍下上位机的思路。)

接下来再看Bootloader1,此段代码作为引导进入APP以及升级APP的代码,其作用也不言而喻。回到原来那个流程图,当APP收到APP升级的16个字节升级参数(下面会说明)的时候,APP会把标志区的跳转标志设置为升级模式并且把bootloader1置位并且复位,这时候程序重新运行的时候就一直在bootloader1中运行,一直等待升级APP,当再次收到16个字节升级参数的时候,记录升级参数的CRC,bin包的总长度、根据升级包的总数量擦出APP区域的对应空间。后续在接受512个字节的时候依次写入flash,到最后一包的时候,check下CRC是否对应上,对的上就更新标志区的标志变量并且复位,check不对就上报信息给上位机并且重发。截图中的是明文协议。因为之前用的是明文协议(方便但是弊端比较多,所以才会要更新为暗文协议。)

再者说明APP部分,APP部分主要是一者升级APP,设置标志区的标志变量并且复位跳转至Bootloader1进行APP升;二者是升级Bootloader1。区分是升级APP还是Bootloader主要在于区分16个字节升级参数,16个字节升级参数里面带有一个字节判断APP还是bootloader,以此来决定哪个类型升级。

底层上已经大概说明完毕,不管是用xyzmodemx协议、modbus协议、自定义协议也好,只要能考虑好升级的安全性和全面性,还有代码逻辑思路清晰,其实都可以。

然使用多线程升级更加方便。一部分截图如下。对于生产员工来说,只需要点击两个空间,等待进度条100%即可。

在控件逻辑上,打开升级文件,使用QFileDialog类并把bin包信息通过全局变量记录下来。

再点击发送升级文件控件,逻辑上是开启两个定时器,一个2s定时,一个200ms定时,当然两个定时都可以根据情况修改;这两个定时的作用是,2s定时的是发送两次16个升级参数,后面的200ms定时是依次发送512包的bin包数据。

在2s定时的槽函数里面进行发送16个升级参数,发送完关闭2s定时并且开始200ms定时。

发送bin包里面的头16个字节,可以带参多次发送。(这个是对应2S定时槽函数。)

在发送bin包的每次512个字节的时候,先记录总的512个字节总段数,每次发送完一包,依次使用seek函数来设置当前文件的位置,并且使用read设置要读的字节长度,直到发送至总段数到了。(这个是对应200ms定时槽函数)代码如下:

至此,对于两重升级的就已经结束了。

网友freebsder跟帖回复:你这个还缺环节没说。相对地址链接到绝对地址的时候得根据flash实际划分调整。另外,现在IAP可能很方便,以前还得考虑升级部分flash代码不会在升级过程中被新覆盖从而导致不可用,或者弄到sram中执行避免覆盖。等等吧。

本文作者RCSN回复:第一个倒是没注意这个问题,对于三个分区地址来说,是写死了。比如bootloader1占用的是22K,其空间地址范围就在0x8000400~0x80005BFF擦除与写入,当然当来了一个bootloader的升级文件不足22K的时候,会根据其大小擦除相对应的flash空间,剩余的就全不写入与擦出。当然你这个“相对地址链接到绝对地址的时候得根据flash实际划分调整”可以借鉴,除了可以重新调整flash实际划分还需要重新分配好中断向量偏移地址。

第二个确实也有想过,考虑过bkp,但是考虑到ram空间不够,所以如果每次确定收到16个字节升级参数check正确,那么我就去擦除相对应的flash空间的起始地址,CRC check不对上发信息给上位机重发这个bin文件。再者一些重要的用户数据区,规划好确定其分区地址,每次升级的时候,避免不去升级擦除掉这些用户数据区导致不可用的方式。

(0)

相关推荐