你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

详解 stm32 在线 IAP 升级

[复制链接]
STMCU小助手 发布时间:2022-12-14 14:24
本文主要讲解在线升级IAP的基础知识, 主要是针对IAP从原理分析, 分区划分, 到代码编写和实验验证等过程阐述这一过程. 帮助大家加深对在线升级的认识。

1.在线升级知识
什么是BootLoader?
BootLoader可以理解成是引导程序, 它的作用是启动正式的App应用程序. 换言之, BootLoader是一个程序,App也是一个程序,BootLoader程序是用于启动App程序的.

STM32中的程序在哪儿?
正常情况下, 我们写的程序都是放在STM32片内Flash中(暂不考虑外扩Flash). 我们写的代码最终会变成二进制文件, 放进Flash中 感兴趣的话可以在Keil>>>Debug>>>Memory中查看, 右边Memory窗口存储的就是代码

微信图片_20221214142442.png

接下来就可以进入正题了.

进行分区

既然我们写的程序都会变成二进制文件存放到Flash中, 那么我们就可以进一步对我们程序进行分区. 我使用的是F103RB-NUCLEO开发板,他的Flash一共128页, 每页1K.见下图:

微信图片_20221214142437.png

以它为例, 我将它分为三个区.BootLoader区、 App1区、App2区(备份区)具体划分如下图:
BootLoader区存放启动代码
App1区存放应用代码
App2区存放暂存的升级代码

微信图片_20221214142434.png

总体流程图

先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序.
然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序.
在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示:

微信图片_20221214142430.png



2. BootLoader的编写
本节主要讲解在线升级的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。

流程图分析
以我例程的BootLoader为例:

我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示

微信图片_20221214142425.png


程序编写和分析
所需STM32的资源有:
发送USART数据和printf重定向
Flash的读写
程序跳转指令,可以参考如下代码:

  1. /* 采用汇编设置栈的值 */
  2. __asm void MSR_MSP (uint32_t ulAddr)
  3. {
  4.     MSR MSP, r0   //设置Main Stack的值
  5.     BX r14
  6. }

  7. /* 程序跳转函数 */
  8. typedef void (*Jump_Fun)(void);
  9. void IAP_ExecuteApp (uint32_t App_Addr)
  10. {
  11.     Jump_Fun JumpToApp;

  12.     if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 )  //检查栈顶地址是否合法.
  13.     {
  14.         JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4);  //用户代码区第二个字为程序开始地址(复位地址)
  15.         MSR_MSP( * ( __IO uint32_t * ) App_Addr );                  //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
  16.         JumpToApp();                                                //跳转到APP.
  17.     }
  18. }
复制代码

在需要跳转的地方执行这个函数就可以了IAP_ExecuteApp(Application_1_Addr);
其他的代码请参考BootLoader源代码
3. APP的编写

本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。

流程图分析
以我例程的App1为例:

微信图片_20221214142419.png

先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题;
打印版本信息, 方便查看不同的App版本;
本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示:

程序编写和分析
所需STM32的资源有:
发送USART数据和printf重定向
Flash的读写
串口的DMA收发
YModem协议相关
Ymodem协议
百度百科[Ymodem协议]
具体流程可自行查找相关文档, 这儿提供一个我找到的 XYmodem.pdf(文末和源码一起提供).
Ymodem协议相关介绍可参考我的这篇教程 YModem介绍

代码分析
代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明

后面放了我的源代码, 详情请参考我的源代码.

主函数添加修改向量表的指令


微信图片_20221214142329.png

打印版本信息以及跳转指令

微信图片_20221214142322.png

YModem相关的文件接收部分
  1. /**
  2. * @bieaf YModem升级
  3. *
  4. * @param none
  5. * @return none
  6. */
  7. void ymodem_fun(void)
  8. {
  9.     int i;
  10.     if(Get_state()==TO_START)
  11.     {
  12.         send_command(CCC);
  13.         HAL_Delay(1000);
  14.     }
  15.     if(Rx_Flag)        // Receive flag
  16.     {
  17.         Rx_Flag=0;    // clean flag

  18.         /* 拷贝 */
  19.         temp_len = Rx_Len;
  20.         for(i = 0; i < temp_len; i++)
  21.         {
  22.             temp_buf[i] = Rx_Buf[i];
  23.         }

  24.         switch(temp_buf[0])
  25.         {
  26.             case SOH:///<数据包开始
  27.             {
  28.                 static unsigned char data_state = 0;
  29.                 static unsigned int app2_size = 0;
  30.                 if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验
  31.                 {
  32.                     if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始
  33.                     {
  34.                         printf("> Receive start...\r\n");

  35.                         Set_state(TO_RECEIVE_DATA);
  36.                         data_state = 0x01;
  37.                         send_command(ACK);
  38.                         send_command(CCC);

  39.                         /* 擦除App2 */
  40.                         Erase_page(Application_2_Addr, 40);
  41.                     }
  42.                     else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束
  43.                     {
  44.                         printf("> Receive end...\r\n");

  45.                         Set_Update_Down();
  46.                         Set_state(TO_START);
  47.                         send_command(ACK);
  48.                         HAL_NVIC_SystemReset();
  49.                     }
  50.                     else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据
  51.                     {
  52.                         printf("> Receive data bag:%d byte\r\n",data_state * 128);

  53.                         /* 烧录程序 */
  54.                         WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);
  55.                         data_state++;

  56.                         send_command(ACK);
  57.                     }
  58.                 }
  59.                 else
  60.                 {
  61.                     printf("> Notpass crc\r\n");
  62.                 }

  63.             }break;
  64.             case EOT://数据包开始
  65.             {
  66.                 if(Get_state()==TO_RECEIVE_DATA)
  67.                 {
  68.                     printf("> Receive EOT1...\r\n");

  69.                     Set_state(TO_RECEIVE_EOT2);
  70.                     send_command(NACK);
  71.                 }
  72.                 else if(Get_state()==TO_RECEIVE_EOT2)
  73.                 {
  74.                     printf("> Receive EOT2...\r\n");

  75.                     Set_state(TO_RECEIVE_END);
  76.                     send_command(ACK);
  77.                     send_command(CCC);
  78.                 }
  79.                 else
  80.                 {
  81.                     printf("> Receive EOT, But error...\r\n");
  82.                 }
  83.             }break;
  84.         }
  85.     }
  86. }
复制代码

其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接.

4. 整体测试
本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。

代码的下载
由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」。

微信图片_20221214142309.png

BootLoader的下载
BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置
按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)

微信图片_20221214142249.png

烧录代码
运行, 通过串口1打印输出, 会看到以下打印消息
说明BootLoader已经成功运行

微信图片_20221214142234.png

App1的下载
App1稍微复杂一点, 需要将代码的起始位置设置为0x08005000
同时也要修改擦除方式为Erase Sectors, 见下图

微信图片_20221214142224.png

微信图片_20221214142220.png

烧录代码
运行, 通过串口1打印输出, 会看到以下打印消息
说明BootLoader已经成功跳转到版本号为0.0.1的App1

微信图片_20221214142153.png

生成App2的.bin文件
修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件

生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件

微信图片_20221214142148.png

使用Xshell进行文件传输
打开Xshell
代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的
所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息

微信图片_20221214142114.png

你会看到App的版本成功升级到0.0.2了.
如果你到了这一步.
那么恭喜你! 你已经能够使用在线升级了!

5. 总结
通过本几节的教程,想必你已经会使用在线升级了,只要原理知道了其他的问题都可以迎刃而解了,除了使用YModem协议传输.bin文件,你还可以通过蓝牙、WIFI等其他协议传输,只要能够将.bin文件传输过去,那其他的部分原理都差不多。



转载自:混说Linux


收藏 评论1 发布时间:2022-12-14 14:24

举报

1个回答
1+1=2 回答时间:2022-12-14 21:07:32
帖子分析的挺好的,但是有些地方好像没有说,比如从boot跳转到APP1的时候是否需要关中断?在APP1初始化的时候,除了修改栈地址,还需要做其他的吗?

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版