摘要:STM32F103C8T6一共64K,FLASH一共64页,每页1K,bootloader分8K,FLAG分2K,APP1与APP2各27K,也就是我们的应用程序,编译出来不能超过27K的大小。
在物联网设备快速迭代和远程维护需求日益增长的今天,OTA(Over-the-Air)远程升级技术已成为智能硬件开发中不可或缺的一部分。
OTA升级,指通过无线通信方式(如Wi-Fi、蓝牙、NB-IoT等)将新固件发送到设备,实现远程软件更新。它有以下几个显著优势:
无需现场维护:节省人工成本,提高维护效率产品生命周期内灵活迭代:快速修复BUG、添加功能提高用户体验:实时响应用户反馈当然,不止于涂鸦平台,原理都一样,可以在任何支持的平台上实现,包括MCU,其它型号的MCU也都大同小异。
核心思想非常简单,我们把整个FLASH分成4部分,bootloader,APP1,APP2,FLAG。
STM32F103C8T6一共64K,FLASH一共64页,每页1K,bootloader分8K,FLAG分2K,APP1与APP2各27K,也就是我们的应用程序,编译出来不能超过27K的大小。
bootloader是一个独立的固件,在启动后负责检查FLAG区域的升级标志位以确定是否有新的固件需要升级,如果有就跳到升级部分,将APP2的部分copy到APP1,copy完之后再清空FLAG区域的升级标志,然后重启,就会运行新的APP1也就是升级后的程序了。
这里如果升级到一半断电了,下次重启还是会重新copy一次APP2到APP1,因为升级标志位还没被清空,所以这里不用担心升级的时候断电导致无法重启。
bootloader在启动的时候,如果FLAG区域的升级标志位没有置位,那就直接启动APP1就可以了。
大概原理就是这样,下面我们结合代码看下:
if(ReadFlashTest(UPFLAG) == 0x55555555) //0x55555555 { update_firmware(APP1ADDR,APP2ADDR); EarseFlash_1K(UPFLAG); WriteFlash(UPFLAG,UPFlagBuffer,4);printf("APP复制中\r\n"); NVIC_SystemReset; }else { Jump2APP(APP1ADDR); }从APP2复制到APP1,先读一页,然后擦除,再写:
void update_firmware(uint32_t SourceAddress,uint32_t TargetAddress){//先读 后擦除 后写 uint16_t i; volatile uint32_t nK ; nK = (TargetAddress - SourceAddress)/1024; for(i = 0;iJump2APP函数通过验证应用程序地址、设置堆栈指针以及跳转到应用程序的入口点,促进从引导加载程序到应用程序代码的转换。
void Jump2APP(uint32_t app_address){ volatile uint32_t JumpAPPaddr; if (((*(uint32_t*)app_address) &0x2FFE0000 ) == 0x20000000) { JumpAPPaddr = *(volatile uint32_t*)(app_address + 4); jump2app = (APP_FUNC) JumpAPPaddr; printf("APP跳转地址:%x\r\n",app_address); __set_MSP(*(__IO uint32_t*) app_address); jump2app; } else{ printf("输入地址不合法\r\n"); }}volatile uint32_t JumpAPPaddr; 声明一个变量,用于存储应用程序的入口点地址(重置处理程序的地址)。volatile关键字确保编译器不会优化对此变量的访问,因为它将从内存中读取。
if ((((uint32_t)app_address) & 0x2FFE0000 ) == 0x20000000) 在 STM32 向量表中,第一个字(位于app_address处)是初始堆栈指针(MSP,即主堆栈指针)。检查掩码值是否指示堆栈指针位于 SRAM 中。如果为真,则该地址被视为有效,因为合法应用程序的堆栈指针应该位于 SRAM 中。
JumpAPPaddr = (volatile uint32_t)(app_address + 4); app_address + 4指向向量表中的第二个字,该字保存重置处理程序(应用程序的入口点)的地址。
jump2app = (APP_FUNC) JumpAPPaddr; 将程序地址转换为函数指针。
__set_MSP((__IO uint32_t) app_address); __set_MSP是一个 CMSIS 函数,它使用从app_address (应用程序的初始堆栈指针)读取的值更新 MSP 寄存器。
jump2app; 调用函数指针jump2app,实际上将控制权转移到应用程序的入口点。这将启动应用程序代码的执行。
bootloader部分的设置,IROM1的起始地址为0x08000000,大小为0x2000。
选择Erase Sectors,Reset and Run,下载。
bootloader部分就介绍到这里。
下面介绍APP部分。
用户的代码逻辑都在APP部分,APP部分需要额外设置的比较简单,我们只需要在代码启动后重新设置一下应用程序的向量表地址。
__set_FAULTMASK(1); SCB->VTOR = FLASH_BASE | APP1; __set_FAULTMASK(0);在引导加载程序跳转到应用程序后执行(例如,通过Jump2APP)。它确保应用程序将 Cortex-M CPU 配置为其自己的向量表,设置前关闭中断,确保向量表重定位不会被中断。
这里中断向量表的起始地址就是0x08002000了。
这里需要注意的是,应用程序的下载地址需要设置为0x08002000,大小为0x6C00。
在Keil中,需要配置下编译选项fromelf.exe --bin -o "$L@L.bin" "#L",以生成bin文件。
bin文件不同于hex文件,hex文件中包含烧录地址,bin文件中不含,所以我们可以指定bin文件的烧录地址。这样,在烧录的时候,就可以直接烧录APP到0x08002000了。
也就是开始需要先烧录bootloader文件,再烧录app文件,后续量产的时候,看可以把bootloader文件与app文件合并成一个文件进行烧录。
关于如何合并,我们之前发过一篇文章可以参考。
MCU OTA方案中,BootLoader与APP如何合并?
APP1运行的时候,如果检测到服务器有新的版本号,就开始执行升级指令,就会将新版本的bin文件,分包向APP1发送,APP1接收到之后,就原封不动的将其存储到APP2区域,最后一包接收完之后,就将FLAG区域的升级标志位置位相应的标志位,然后重启,就回到上面的升级流程了。
基本原理就是这样,关于STM32一些启动过程原理与涂鸦的SDK逻辑,本文不做详细的介绍,大家自行看一下就都懂了。
以上OTA代码逻辑,在实际产品中,已经量产并持续在使用,有需要的小伙伴可以参考,欢迎大家在评论区与老宇哥进行交流。
代码源文件全部工程:
通过网盘分享的文件:tuya-OTA-lightDemo1.0.0.rar
来源:芯片之家