STM32的IAP升级是一个比较重要和常规的功能,特别是在物联网产品的应用上尤为重要,能够非常方便地完成应用程序的更新和升级。正点原子的开发板都有例程可以参考,基本的IAP功能能够实现。
本次IAP升级的应用的场景恰好就是物联网终端产品的应用,最终实现上位机平台通过GPRS网络方式完成终端应用程序的远程升级。物联网终端使用的是STM32F103RC,256Kflash和48KRAM,并有64M的外扩FLASH芯片W25Q64,GPRS通讯模块使用芯讯通SIM800C。应用程序(app)大小约45k,使用FreeRTOS操作系统。基本设计思路是:
将256K的FLASH分为三个部分:
第一个部分64K(0x8000000~0x8010000),用于bootLoader程序存放位置;
第二个部分128K(0x8010000~0x8030000),用于正常app程序存放位置;
第三个部分64K(0x8030000~0x8040000),用于初版app程序存放位置。
正常情况下设备始终运行在第二个部分正常app程序,当上位机平台有升级请求时,会下发远程升级基本信息指令到物联网终端,基本信息指令内容包含升级bin文件的URL地址,升级bin文件MD5校验值。终端收到远程升级指令后,将升级基本信息和升级标志写入FLASH中,执行复位重启,程序复位后首先进入第一个部分BootLoader程序中,读取到升级标志后立即进入到IAP升级任务。
IAP升级任务主要工作是:读取FLASH中URL地址和MD5校验值,然后配置GPRS相关功能进行bin文件下载,最后进行MD5校验,成功之后写入正常app程序区完成升级,若升级失败则程序跳转到初版app程序执行,以防止设备变成砖头。
整个功能设计之中的重点是如何保证升级的可靠性和安全性。那么如何能保证安全可靠呢?首先想到的就是bin文件接收到的内容必须要校验,对于这个45K大小的文件最后选择了MD5校验的方式,关于MD5计算也是第一次使用,在了解其原理和方法之后发现并不容易实现。首先上位机计算一个45K大小文件的MD5值是比较容易的,但针对单片机的内容和空间却是存在很大问题的,只能分段计算MD5。所以在读取bin文件数据时,使用的方式是每次读取1K内容,然后计算MD5中间值,然后把每次读取的1K数据内容存储到外扩FLASH中,读完所有数据之后再校验总的MD5值。
bin文件的下载使用了GPRS模块的http访问网页的方式,避免了实时数据交互的繁琐。
SIM800C HTTP访问网页数据AT指令流程:
(1)AT+HTTPINIT:初始化HTTP。
正常 返回OK
(2)AT+HTTPPARA="CID","1":设置承载上下文标识,和上面一致。
正常返回OK
(3)AT+HTTPPARA="URL","www.baidu.com/img/baidu_logo.gif":设置你要访问的那个网站,要加上“http://”协议
正常返回OK
(4)AT+HTTPACTION=0:激活HTTP请求,0表示get方式、1表示post方式、2表示head。
这个时候要耐心等,在OK之后会返回HTTP的状态。
(5)AT+HTTPREAD=1,1489:读取HTTP相应数据。1表示从第一个字节开始读,读1489个字节。
(6)AT+HTTPTERM:访问完了,终止HTTP服务。
返回OK
(7)AT+SAPBR=0,1:最后关闭承载。
最后在完成bin文件的接收以及写入FLASH之后,发现bootloader跳转到app程序之后并不能运行。检查了app程序的中断向量地址设置和bin文件生成配置都没有问题:
最后在网上看到一些资料说是,freeRTOS系统程序的IAP升级并不能完全像裸机一样实现!主要问题出在iap_load_app这个函数这里,这里完全是参照正点原子IAP例程编写的,需要修改!由于使用了RTOS,跳转之前必须关闭掉中断,清除中断相关寄存器,并且要关闭系统滴答定时器,程序才能成功跳转。重写iap_load_app函数如下:
void iap_load_app(uint32_t app_address)
{
typedef void (*_func)(void);
//__disable_irq();
__set_FAULTMASK(1);
/* MCU peripherals re-initial. */
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = ~(GPIO_PIN_13|GPIO_PIN_14);
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;//输入
GPIO_InitStruct.Pull = GPIO_NOPULL;//
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_All;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/* reset systick */
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
/* disable all peripherals clock. */
RCC->AHBENR = 0;
RCC->APB1ENR = 0;
RCC->APB2ENR = 0; /* Switch to default cpu clock. */
RCC->CFGR = 0;
} /* MCU peripherals re-initial. */
/* Disable MPU */
#if (__MPU_PRESENT == 1U)
HAL_MPU_Disable();// MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;
#endif
/* disable and clean up all interrupts. */
{
int i;
for(i = 0; i < 8; i++)
{
/* disable interrupts. */
NVIC->ICER[i] = 0xFFFFFFFF; /* clean up interrupts flags. */
NVIC->ICPR[i] = 0xFFFFFFFF;
}
} /* Set new vector table pointer */
SCB->VTOR = app_address; /* reset register values */
__set_BASEPRI(0);
__set_FAULTMASK(0); /* set up MSP and switch to it */
__set_MSP(*(uint32_t*)app_address);
__set_PSP(*(uint32_t*)app_address);
__set_CONTROL(0); /* ensure what we have done could take effect */
__ISB();
//__disable_irq(); /* never return */
__set_FAULTMASK(1);
((_func)(*(uint32_t*)(app_address + 4)))();
}