/* 开头寒暄 */
白驹过隙,光阴如梭,转眼间半年过去了,也很久没有更新公众号文章,有愧于公众号的粉丝,现在他又来了。最近半年比较忙,也不知道在忙啥,就是没时间写公众号。
/* 正文前言 */
最近完成了一个项目,使用的是国产的32 + FreeRTOS + lwIP实现网络唤醒和其他七七八八的功能。项目代码转测后基本也没啥问题,但是我们公司的产品比较大,带来的困扰就是给主板上的MCU升级软件的时候就比较麻烦,还需要拆开设备,找到主板的烧录口,插上Jlink才能升级软件,多有不便,因此在代码基本完成后添加了IAP功能,原本这个功能很常见,也没什么可以写的,但是本文将要介绍的方式应该会比较少有人使用,所以想发个文章记录分享一下。
/* MCU基本情况介绍 */
· 项目代码:148K
· MCU Flash:512K
· MCU SRAM:144K
· 没有外挂SRAM
/* IAP的几种方案 */
· 1、更新的程序存在MCU内部的SRAM
· 2、更新的程序存在外部的SRAM
· 3、更新的程序存在未使用的Flash空间
前面两种方式应该是最简单的,进入Bootloader之后把接收到的新的程序放到Buff中即可,同时使用GNU C关键字__attribute__((at(0x........)))定义在SRAM里面,接收完成后直接把SRAM的数据写到APP程序的起始地址即可,然后跳转运行,就能实现IAP升级,但是奈何我这个项目上内部的SRAM空间不够,硬件上又没有外挂SRAM因此前两种方式都用不了,因此,就把目光放在了第三种方案上。确定方案后就准备开始动手。
/* 项目介绍 */
本项目的设备会有一个Winddows和MCU进行串口通讯,因此就设定使用和这个串口在Win下做个小软件和MCU通讯进入IAP流程,实现软件的升级。而Bootloader就实现这个串口的初始化,并使用这个串口接收新的程序即可。
/* 做IAP前期考虑的几个问题 */
· 怎么确保传输的程序不会出错?
· 即便出错了怎么保证能揪出这个错误并过滤?
· 升级失败后会不会成砖?需不需要拆设备?
· 怎么保证接收的新程序不会丢包?
考虑到上述几个问题就比较头大了,但是困难总比办法多嘛。不然现在也不会在这里分享这篇文章了。
· 怎么确保传输的程序不会出错?
增加校验机制,校验失败后返回给上位机
· 即便出错了怎么保证能揪出这个错误并过滤?
把校验结果返回给上位机,上位机收到校验错误后就重新发送这一帧数据
· 升级失败后会不会成砖?需不需要拆设备?
升级失败默认进入Bootloader中等待重新接收新的程序
· 怎么保证接收的新程序不会丢包?
接收部分使用双缓冲区或者环形缓冲,实现边收边升级,进而保证数据不会丢包。
/* 详细逻辑 */
由于本项目的代码有148K,因此无法一下全部接收再整个擦除Flash更新程序,所以想到了要把148K的代码分段发送,由于这个MCU的Flash页的大小是2K,因此就准备接收到2K新程序后就擦写一次,擦写完成后再继续接收,实现边收边写。
本项目的设计是把148K的程序,每128个字节作为一组,并在这一组增加一字节帧头、一字节帧长度、两字节的当前帧编号 + 128Bytes程序 + Sum校验。因此每个程序帧的组成大概是这样的。
F0 Len Num_H Num_L 128Bytes SumCheck
且把上面这样的一帧数据叫做程序帧,因此每个程序帧的长度就为133字节(这里有个细节要注意,最后一帧不一定是刚好的133字节,后面会说到)。简单点来说就是把148K的程序每次发送128字节,并每个程序帧做Sum校验,保证这一个程序帧数据的正确性。每次发送128字节的程序,那么每收到16帧这样的数据就能刚好组成2K的大小,此时就可以把这2K一并写到Flash中去。148K分128字节发送,需要发送148 * 1024 / 128 = 1184个程序帧,1184刚好能整除16,即最后一帧也是128字节的程序,如果不能整除16就表示最后一帧数据接收完成也不够2K,这种情况就是上面说的小细节,需要考虑进去并作出应对。(这里好好理解理解,应该能明白,毕竟没法保证是那么刚好的每次最后一帧程序帧是128个字节,因此最后一个16帧组成的程序就不是2K)
/* 正式撸代码 */
有了上面的思路之后,就准备开始撸代码了,首先列一下将要做的工作。
· 1、写Bootloader并确定Bootloader的空间
· 2、修改APP的代码,并给APP代码分配Flash空间
本项目最终Bootloader编译后是18K左右,为了预留点空间因此给Bootloader分配了32K的空间,再拿出来200K空间作为APP程序的空间,剩下的就做一个Flash标志位的空间等。那么MCU内部的Flash分配就是这样的
Bootloader:
这部分的代码很简单,初始化时钟、串口,写好串口接收的逻辑,本项目中使用的双缓冲区接收的数据,也可以使用小编之前写过的环形缓冲去接收(有需要的可以再去翻一下,源码都给出来了,并且代码已经在项目上量产应用),只要不丢包哪种方式都差不多。要注意的就是设置下Bootloader的起始地址和大小,在Keil中如下:
下面是Bootloader下几个关键函数的实现:
跳转函数:
更新2K代码:
擦写Flash Page:
APP程序:
这部分的代码改起来就更简单了,在原先的基础上设置偏移量,重新分配下起始地址和空间大小即可,在收到特定命令后写标志位然后复位进入Bootloader即可。
还有一个要修改的就是在编译完成后生成bin文件
框起来的内容是:
D:\Application\Keil_V5.36\Install\ARM\ARMCLANG\bin\fromelf.exe --bin --output ./Objects/BlackBoard.bin ./Objects/BlackBoardMCU.axf
意思是在编译完成后调用Keilde fromelf.exe生成bin文件,前面是路径(要根据你的安装路径修改)后面是生成bin文件的路径和axf文件的路径
/* 和上位机配合 */
现在万事俱备,只欠东风,更新程序的传输协议也制定好了,哪里来这样的上位机和我通讯传输数据呢,于是想到了我们公司的C#工程师,因此找了我的领导,提了这个需求,领导一听这个很有必要,就安排了一个人去做这个事情,那么上位机的问题也解决了(后台回复“MCU IAP”可以获取中性版本的上位机,拿到后根据这个协议就能实现你的MCU软件升级,会附有IAP流程图)。和他联调了两天,目前已经完成了上位机更新MCU程序。和上位机之间的通讯会先有一个握手的过程,收到MCU Update命令之后我就跳转到Bootloader执行,并接收程序,由于每一帧做校验,加上帧数比较多,更新148K的程序大概需要12秒的时间,后面还可以优化的更快,预计可以缩短一半的时间。
我和上位机之间在更新程序的时候基本都是一问一答的形式,保证了数据准确性同时也确保不会丢包,实现了稳定的更新程序,即便真存在特殊情况,比如更新的时候断电了,也不会成砖,能再次在Bootloader中重新接受新的程序进行程序更新。
最后放一张上位机的界面,傻瓜式操作。
说了这么多,相信大家对这个过程和实现方案理解的比较透彻了,想要资料的可以后台发送“MCU IAP”获取中性版本的上位机软件(界面简单易操作)及IAP流程图,详细的介绍了MCU和上位机之间的通讯过程。