固件更新是每个场合都需要的硬通货,如果有自己的一套不错的更新框架并能通过简单的修改就能移植到各类单片机上会让后续工作更加方便。iap升级基于芯片的通讯接口就能更新程序,避免了没有仿真器的情况与烧录接口外露的窘迫,并可以通过网络完成云端升级,通讯接口,协议灵活。
同样以stm32cube+hal库进行开发,仅需添加几个关键文件就可以实现功能,并在更新失败时也能手动复位从而避免设备变砖(前提是不破坏boot程序,就像windows装系统一样,系统挂了或者蓝屏,可以在开机时手动按f8进入bios选项。如果bios都进不去,那就真没救了)。
1.设计思路
升级的功能与装系统相似,在开机最初时,运行的实际是主板的bios,对于设备来说,也是一个程序,在bios运行的那几秒,判断键盘是否有升级需求,从而进入bios页面,不启动windows。设备同样也可以在最初上电时给几秒倒计时,如果有升级需求就不去启动原有的app程序。这样有个好处就是即使app功能不正常,一进入app就挂了,也可以在boot阶段强制进入升级,从而避免这种意外情况。
2.功能实现
升级基于两个程序完成,boot程序以及app程序,app程序作为固件引入,上位机通过串口将固件分包,依次写入内部flash,重启后就启动app,也就是刚写入flash的固件。
3.通讯交互流程
为了避免固件在传输时产生乱码,从而改变了固件包内容。在每一次发送完固件包时,单片机会将接收到的包数据做一次校验,和上位机下发的校验一致的话,才会将收到的包写入flash。
4.hal库中的flash写入
flash需要先擦除,再写入,并且由于芯片系列的不同有的按页为最小单位擦除,而有的以扇区为最小单位擦除,不同系列,扇区的分布大小也不一致,所以需要按对应芯片的具体情况去实现。
在对应hal库中也有对应的选项。
在每次擦除地址为扇区地址时擦除一遍,然后其他的地址写入的话就不用重新擦除了。当然如果简单一点直接把可能需要用的地方全擦一遍也可以。
5.app的跳转以及keil中的设置
void jump_to_app(void)
{
typedef void (*app_func_t)(void);
uint32_t app_addr = OTA_CODE_START_ADD;
uint32_t stk_addr = *((__IO uint32_t *)app_addr);
app_func_t app_func = (app_func_t)(*((__IO uint32_t *)(app_addr + 4)));
if ((((uint32_t)app_func & 0xff000000) != 0x08000000) || ((stk_addr & 0x2ff00000) != 0x20000000))
{
return;
}
//rt_kprintf("Jump to application running ... \n");
// rt_thread_mdelay(200);
__disable_irq(); /*关闭总中断*/
HAL_DeInit(); /*函数通过写复位寄存器,将所有模块复位。*/
for(int i = 0; i < 128; i++)
{
HAL_NVIC_DisableIRQ(i); /*失能中断*/
HAL_NVIC_ClearPendingIRQ(i);/*清除中断标志位*/
}
/* 关闭滴答定时器,复位到默认值 */
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
HAL_RCC_DeInit(); /*将RCC时钟配置重置为默认重置状态。*/
__set_CONTROL(0); /*设置CONTROL寄存器的值。在RTOS工程,这条语句很重要,设置为特权级模式,使用MSP指针*/
__set_MSP(stk_addr); /* 设置MSP跳转的地址;设置主堆栈指针 */
app_func(); /* Jump to application running */
}
其中这一句在很多例子中都有出现,初学时不明白,后来才知道这一句仅仅是在验证bin文件的规则,bin文件的开头一定是满足这个规则的,如果不满足说明地址不合法也就不会启动app,而会继续在boot中运行。
if ((((uint32_t)app_func & 0xff000000) != 0x08000000) || ((stk_addr & 0x2ff00000) != 0x20000000))
{
return;
}
bin文件开头会存放两个地址,具体可看
https://blog.csdn.net/qq_33894122/article/details/84098729
6.功能验证
这里使用qt自制了一个小工具,并打包成了一个单独的exe文件,如下
点击帮助可以提示升级流程
打开设备的链接串口号之后,打开一个bin文件,如下就是一个测试用的app固件,功能是led闪烁同时串口发送app1。。字符串
然后强制进入升级,强制意思是即使app出问题,也可以在boot运行的一秒内强制进入升级,而不会直接运行app导致没有升级的机会。如果此时app正在运行,那么这个命令仅只会让系统重启,在boot才会响应这个命令。然后告诉上位机处于boot模式里面。
点击开始升级,则会自动完成升级操作,左下方会显示当前升级的包以及进度,所有信息也会在左侧文本框记录。数据日志是下位机上传的数据,用来方便调试以及跟踪问题。
左侧就是单片机运行app1后的打印字符。
然后将app2的固件下载。
重新来一遍
程序就已经更新了。
由于通讯暂定的是1024字节为1包,最大255包,所以目前升级的最大固件255kb,用个比较大的固件来测试一下,200多kb,一共215包。
升级完毕后
以上功能验证正常,并且在中途断电或者意外出现,都不会影响boot程序,只需要在强制进入boot时,复位一下即可。
keil工程代码以及上位机在关注公众号(芯片家)后发送"我要升级"就可以获取下载链接了。亲测效果很棒