KiRorY
STM32G0系OTA开发相关问题的解决方案

STM32G0系OTA开发相关问题的解决方案

   实习过程中对STM32G0系开发板OTA功能开发时遇到的一些问题和解决办法,主要涉及Bootloader程序的跳转和APP分区思路问题。 + Bootloader 跳转到APP区后发生卡死的情况。 + 双APP分区时只能跳转到其中某一个分区,无法跳转到另一分区的情况。

Bootloader程序跳转后程序卡死问题

  先简单说明程序跳转的操作,就是设置PC指针位置+堆栈指针位置,PC指针指向地址应该是 \([程序烧录地址] + 4\)。CBT6后缀的开发板HAL库代码可以参考下面几句:

1
2
3
4
5
typedef void (*load)(void);

__set_MSP(*(uint32_t*)addr); //addr为跳转目标地址,即APP程序烧写地址
load = (load)* (uint32_t*)(addr + 4);
load();
当然,我们可以直接使用上面的程序进行跳转,前提是addr所在地址位置存在于RAM内。实际执行时发现,程序成功跳转运行,但是会在某些地方卡死无法进行下去。原因很简单,我们重定向了PC指针和堆栈指针,但没有去重定位其中断向量指针。这导致APP程序在执行时遇到中断函数时,回头跑到Bootloader的中断向量表中去了,自然会导致程序指针跑飞。默认状态下,程序的中断向量表指针指向0x0,但实际的程序对应中断向量表的起始位置就是程序的起始位置。可以参考下面的示意图: 示意图

解决方法

   Cortex M系列芯片中,除了M0以外,其它芯片都保留有VTOR寄存器用于重定位中断向量指针。G0系列开发板使用的是M0+芯片,可以使用该寄存器。因此在程序跳转前,给VTOR寄存器赋值相应地址就行。向量表地址根据上面的示意图,就是程序开始位置。完整跳转函数代码如下:

1
2
3
4
5
6
7
8
9
10
typedef void (*load)(void);
void jump(uint32_t addr){
if((*(uint32_t*)addr >= 0x20000000) && (*(uint32_t*)addr < 0x20000000 + ram_size)){ //这里的0x20000000是RAM的起始地址,这里就是判断跳转地址数据是否在RAM中
__set_MSP(*(uint32_t*)addr);
load = (load)* (uint32_t*)(addr + 4);

SCB->VTOR = addr; //此为新增语句
load();
}
}
   在其它项目中, 笔者也遇到了需要在M0系列芯片执行OTA的情况。在不能直接通过相应寄存器的情况下就会稍麻烦一些,我们需要手动重定位中断向量的位置。大致思路是在跳转前将整个中断向量表拷贝到RAM中,然后将向量表指针地址重映射为RAM的起始地址(按照上面的例子就是映射为0x20000000)。这边会用到stm32相应库中的一个remap函数。然而很可惜因为笔者开发的板子并不是使用stm标准库,没有相应函数,因此该功能暂时找不到其它方法实现。如果有相应需求的话可以去查找M0系列Bootloader实现的相关资料,笔者没有实际检验过,故不在这边讲述。

双APP分区下Bootloader只能跳转到其中某一个分区问题

  双APP分区是笔者为了在没有eeprom的情况下实现OTA首先构想的方法,也就是分Bootloader + flag + AB区。OTA更新思路如下: 1. 程序启动默认进入A区; 2. 发生OTA请求,将新程序写入B区; 3. 写入成功则将flag区对应地址标志位置为有效; 4. 让程序进入死循环,由看门狗重启系统; 5. Bootloader验证程序通过后跳入B区执行,此时A区作为新的下载区。

上述方法的优点就是始终保证有一个分区是有可执行程序的,这样即使OTA失败甚至写入被强制中断,也有一个有效区可以执行。除此之外有效利用程序flash空间,减少单个区域擦写次数,延长flash寿命。

  不过上述方法忽略了程序本身的地址指针问题。在使用各类工具编译完成后,我们得到的二进制执行文件中,其实保留了程序的起始地址等信息。这段地址信息一般是在bin文件的开头部分(其实也就是前一个问题中提到的中断向量表地址)。当硬件执行bin程序时,会先读取这些地址信息,跳转到对应位置去执行。实际上我们给相同程序设置不同的烧写位置,其编译生成的bin文件是不同的。看下面的对比可以更直观一些: bin文件对比 从划分区间的数据段区别能明显看出头部数据段不同。如果我们单纯将同一个程序(编译时设定烧入同一flash区域)烧入到不同的区域,即使Bootloader将程序跳转到正确位置,在程序读取开头字段时,中断就又会跳转回原先的区域。这意味着在编写新程序时,我们需要提前知晓烧录目标地址进行编译,否则就会出现区域跳转错误的现象。这对于后续OTA升级非常不方便,因此无法直接使用该方法。

解决方法

1.针对头字段在程序中进行修改[不具备可复用性]

  这种方法就是在程序内,根据flag区域所保存的OTA目标地址,修改待OTA程序的头字段。上图中对比数据也可以知道,头字段会在特定区间重复一个字段值。该字段值大致是\(程序烧写目标地址+某偏移量\)。但具体这个偏移量是多少,笔者暂时没有找到规律,不同的程序似乎不一样。考虑到项目尽可能要剔除这种不稳定因素,所以该方案暂时没有采用。

2.修改方案: 程序区+下载区

  简单来说就是要避开向不同区域的跳转,尽量每次启动固定向一个区域跳转即可。设定A区为APP,运行OTA时将程序下载入B区,然后向flag区写入OTA标志信息和CRC校验码信息,通过看门狗重启。在Bootloader程序中去验证B区程序的正确性,然后再复制到A区中执行,随后重置flag区信息。

  当然,上面的方案会有运行稳定性方面的问题:需要进行OTA时Bootloader的启动效率会下降,同时也很难保证复制程序的正确性。所以最好能够使用外置eeprom或者flash来解决OTA程序暂存的问题。

本文作者:KiRorY
本文链接:https://kirory.xyz/2024/12/18/STM32G0系OTA开发相关/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可