关于IAP之前写过几篇,刚开始计划,没搞成就换了工作,遗憾,现在刚好又需要使用到IAP的功能,所以继续更新,必成。
一个物联网的项目,主要就是MCU+4G模块,MCU通过AT指令使用4G模块,利用MQTT协议连接阿里云平台,嵌入式和后端通过阿里云平台交互数据;
型号是HK32F030R8T6,M0内核,内部Flash大小64K,SRAM大小10K;
IAP升级的流程是:平台把要更新的代码分成一个个包,通过阿里云平台给4G模块,4G模块再转发到MCU的串口,所以总体就是一个串口IAP的功能;
KEIL中配置ROM,起始地址0x0800 0000,Size是0x4000,预留16K,如下图:
KEIL中配置ROM,起始地址0x0800 4000,Size是0xB800,也就是46K,加上上面的16K一共62K,剩余的2K用作保存数据;
RAM的起始地址为0x2000 0100,Size是0x2700,0x100+0x2700=0x2800也就是10K的RAM,至于为什么不从0x2000 0000开始,是因为M0不能将中断向量表映射到FLASH的0x0800 4000,但是可以映射到RAM,所以这部分0x100就是给APP的中断向量用的,如下图:
0x2000 0000到0x2000 0100这段内存,实际使用的大小是48个32位的空间也就是48*4=0xC0,所以配置RAM的起始地址你也可以写成0x2000 00C0;
Cortex-M0内核中断向量共有48个,在“startup_stm32f0xx.s”函数中__Vectors到__Vectors_End可以看到;
主要是实现BOOT到APP跳转部分的代码,此外解决了由于BOOT中使用外设中断(现在只用了定时器)导致跳转到APP后无法执行的问题,在BOOT中关总中断,在APP中开总中断的方式是不够的,要具体到使用的外设才能正常跳转;
- #define MAIN_USER_FLASH_BEGIN 0x8004000 //用户程序存储地址
-
- typedef void (*RESET_FUNCTION )(void); //复位函数模型
-
- //从BOOT程序跳转到APP程序
- void boot_jump_app(void)
- {
- uint32_t jump_addr=*((__IO uint32_t *)(MAIN_USER_FLASH_BEGIN+4));
- RESET_FUNCTION Reset=(RESET_FUNCTION)jump_addr;
- __set_MSP(*(__IO uint32_t*)MAIN_USER_FLASH_BEGIN);
- Reset();
- }
-
- int main()
- {
- board_init(); //有使用到TIM2
-
- TIM_Cmd(TIM2,DISABLE); //关闭定时器
- TIM_DeInit(TIM2); //复位定时器
- __disable_irq(); //关中断
-
- boot_jump_app(); //从BOOT程序跳转到APP程序
-
- while(1);
- }
配置中断向量表、开总中断;
编译后的APP程序,48个中断向量就在bin文件的起始位置,所以把APP程序下载到0x0800 4000的位置后,理论上要将这个中断向量映射到0x0800 4000,这也是M3的做法,但是M0不能将中断向量表映射到FLASH的0x0800 4000,不过可以映射到RAM,所以就复制0x0800 4000开始的中断向量到0x2000 0000,然后将中断向量映射到RAM;
由于映射时使用了SYSCFG_MemoryRemapConfig函数,所以要开启SYSCFG时钟;
- //用户程序存放地址(也是编译后的下载文件存放中断向量的flash地址)
- #define APPLICATION_ADDRESS ((uint32_t)0x08004000)
-
- //RAM中的中断向量
- __IO uint32_t VectorTable[48] __attribute__((at(0x20000000)));
-
- int main()
- {
- u8 i;
-
- //必须开启SYSCFG时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
-
- //从flash中复制中断向量到ram
- for(i = 0; i < 48; i++){
- VectorTable[i] = *(__IO uint32_t*)(APPLICATION_ADDRESS + (i<<2));
- }
- //中断向量映射到RAM(开始地址处)
- SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);
-
- __enable_irq();
-
- board_init();
- while(1);
- }
在上面的基础上让APP在运行一段时间后,跳转到BOOT,只需要在APP中定时个10s确定APP已经可以正常运行了,然后执行软件复位;
现象呢就是BOOT运行几秒跳转APP,APP运行几秒BOOT,这样循环,测试的程序就是定时器加LED,运行明了而且测试使用外设中断的情况;
- //软件复位函数
- void System_Reset(void)
- {
- __disable_irq(); //关中断
- NVIC_SystemReset(); //软件复位
- }
之前文章有,而且这些知识非常基本,与IAP本身的功能也不是很相关,不花篇幅;
IAP_4_串口通讯准备_小老虎_IOT的博客-CSDN博客
IAP_5_读取内部Flash的数据_小老虎_IOT的博客-CSDN博客_如何读取flash中的数据
IAP_6_内部Flash写_小老虎_IOT的博客-CSDN博客
BOOT程序:
检测flash中更新标志位是否为更新,如果更新,进入串口收发、接收bin文件的流程,如果不需要更新,直接跳转到APP;
更新的情况,在接收完bin文件之后,清除更新标志位,复位,再次进入BOOT程序判断不需要更新,跳转到APP;
APP程序:
正常运行程序,收到IAP指令后,将flash中的更新标志位置位,然后跳转到BOOT;
bin文件为30K左右,太大了不可能一次性发送的,MQTT也不支持,就算平台可以一次发送单片机也没办法一次性接收缓存bin文件再写入flash的,所以要分包,分包不大不小最好,现在是以512字节分包;
最后一个包可能是不足512字节的,所以每个包带上传输的数据量,方便操作;
要保证发送和接收数据的一致性,bin文件有任何错误都可能导致升级失败,最可怕的是如果不校验你压根不知道升级失败了;
写入flash是很耗时间的,平台要等单片机处理完,发送ACK1后再发送下一个包;
同时,如果出现CRC错误,可以发送ACK0,让平台重发;
以及,如果丢包,超时未收到ACK1,也要进行重发;
APP端在收到IAP升级的指令后,往往不能直接跳转到BOOT,比如APP中还有数据正在处理中,想等待一次完整的处理完成;
BOOT中也有一些初始化的操作,可能比较耗时,比如连接阿里云平台等;
所以跳转到BOOT并准备好了以后,发送“READY TO UPDATE”,再让平台发送更新的程序。
如果知道bin文件一共被分成了多少个包,每成功处理完一个包计数,最后一个包处理完成,也就是更新完成了;
所以,可以事先发送bin文件的信息,比如版本,包总数,再进行分包发送;
当平台收到最后一个包返回的ACK1后,可以记为更新成功;