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

IAP固件升级经验分享

[复制链接]
攻城狮Melo 发布时间:2023-4-2 17:48
什么是IAP升级?

IAP,即In Application Programming,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写。简单来说,就是开发者代码出bug了或者添加新功能了,能够利用预留的通讯接口,对代码进行升级


UART、SPI、IIC、USB等等,当然还有wifi、4G、蓝牙等无线通讯手段,都可以作为IAP升级的方式,今天主要介绍如何使用串口对固件进行升级


STM32的代码启动过程

要想设计IAP,首先需要对MCU的代码启动过程有个了解,先来看看STM32的代码启动过程是怎样的吧


在《Cortex-M3权威指南》有讲述:芯片复位后首先会从向量表里面取出两个值(下图来自Cortex-M3权威指南):

  • 从0x0000 0000地址取出MSP(主堆栈寄存器)的值

  • 从0x0000 0004地址取出PC(程序计数器)的值

  • 然后取出第一条指令执行


微信图片_20230402174619.png

启动文件源代码分析
  1. ;******************** (C) COPYRIGHT 2011 STMicroelectronics ********************
  2. ;* File Name          : startup_stm32f10x_hd.s
  3. ;* Author             : MCD Application Team
  4. ;* Version            : V3.5.0
  5. ;* Date               : 11-March-2011
  6. ;* Description        : STM32F10x High Density Devices vector table for MDK-ARM
  7. ;*                      toolchain.
  8. ;*                      This module performs:
  9. ;*                      (上电复位后会做下面的几件事情)
  10. ;*                      - Set the initial SP(设置堆栈,就是设置MSP的值)
  11. ;*                      - Set the initial PC == Reset_Handler(设置PC的值)
  12. ;*                      - Set the vector table entries with the exceptions ISR address(设置中断向量表的地址)
  13. ;*                      - Configure the clock system and also configure the external (设置系统时钟;如果芯片外部由挂载SRAM,还需要配置SRAM,默认是没有挂外部SRAM的)
  14. ;*                        SRAM mounted on STM3210E-EVAL board to be used as data
  15. ;*                        memory (optional, to be enabled by user)
  16. ;*                      - Branches to __main in the C library (which eventually      (调用C库的__main函数,然后调用main函数执行用户的)
  17. ;*                        calls main()).
  18. ;*                      After Reset the CortexM3 processor is in Thread mode,
  19. ;*                      priority is Privileged, and the Stack is set to Main.
  20. ;* <<< Use Configuration Wizard in Context Menu >>>   
  21. ;*******************************************************************************



  22. ; ------------------分配栈空间----------------
  23. Stack_Size      EQU     0x00000400      ;EQU指令是定义一个标号;标号名是Stack_Size; 值是0x00000400(有点类似于C语言的#define)。Stack_Size标号用来定义栈的大小
  24.                 AREA    STACK, NOINIT, READWRITE, ALIGN=3  ;AREA指令是定义一个段;这里定义一个 段名是STACK,不初始化,数据可读可写,2^3=8字节对齐的段(详细的说明可以查看指导手册)
  25. Stack_Mem       SPACE   Stack_Size   ;SPACE汇编指令用来分配一块内存;这里开辟内存的大小是Stack_Size;这里是1K,用户也可以自己修改
  26. __initial_sp      ;在内存块后面声明一个标号__initial_sp,这个标号就是栈顶的地址;在向量表里面会使用到

  27.                                           
  28. ; <h> Heap Configuration
  29. ;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
  30. ; </h>
  31. ; ------------------分配堆空间----------------
  32. ;和分配栈空间一样不过大小只是512字节
  33. Heap_Size       EQU     0x00000200
  34.                 AREA    HEAP, NOINIT, READWRITE, ALIGN=3

  35. __heap_base        ;__heap_base堆的起始地址
  36. Heap_Mem        SPACE   Heap_Size      ;分配一个空间作为堆空间,如果函数里面有调用malloc等这系列的函数,都是从这里分配空间的
  37. __heap_limit       ;__heap_base堆的结束地址

  38.                 PRESERVE8 ;PRESERVE8 指令作用是将堆栈按8字节对齐
  39.                 THUMB;THUMB作用是后面的指令使用Thumb指令集

  40. ; ------------------设置中断向量表----------------
  41. ; Vector Table Mapped to Address 0 at Reset
  42.                 AREA    RESET, DATA, READONLY      ;定义一个段,段名是RESET的只读数据段
  43.                 ;EXPORT声明一个标号可被外部的文件使用,使标号具有全局属性
  44.                 EXPORT  __Vectors          ;声明一个__Vectors标号允许其他文件引用         
  45.                 EXPORT  __Vectors_End      ;声明一个__Vectors_End标号允许其他文件引用
  46.                 EXPORT  __Vectors_Size     ;声明一个__Vectors_Size标号允许其他文件引用


  47.                
  48. ;DCD 指令是分配一个或者多个以字为单位的内存,并且按四字节对齐,并且要求初始化

  49. ;__Vectors 标号是 0x0000 0000 地址的入口,也是向量表的起始地址
  50. __Vectors       DCD     __initial_sp               ;* Top of Stack     定义栈顶地址;单片机复位后会从这里取出值给MSP寄存器,
  51.                                                    ;* 也就是从0x0000 0000 地址取出第一个值给MSP寄存器 (MSP = __initial_sp)
  52.                                                    ;* __initial_sp的值是链接后,由链接器生成

  53.                 DCD     Reset_Handler              ;* Reset Handler    定义程序入口的值;单片机复位后会从这里取出值给PC寄存器,
  54.                                                    ;* 也就是从0x0000 0004 地址取出第一个值给PC程序计数器(pc = Reset_Handler)
  55.                                                    ;* Reset_Handler是一个函数,在下面定义
  56.                 ;后面的定义是中断向量表的入口地址了这里就不多介绍了,想要了解的可以参考《STM32中文手册》和《Cortex-M3权威指南》
  57.                 DCD     NMI_Handler                ; NMI Handler      
  58.                 DCD     HardFault_Handler          ; Hard Fault Handler
  59.                 DCD     MemManage_Handler          ; MPU Fault Handler
  60.                 DCD     BusFault_Handler           ; Bus Fault Handler
  61.                 DCD     UsageFault_Handler         ; Usage Fault Handler


  62.                 .....由于文件太长这里省略了部分向量表的定义,完整的可以查看工程里的启动文件

  63.                 DCD     DMA2_Channel1_IRQHandler   ; DMA2 Channel1
  64.                 DCD     DMA2_Channel2_IRQHandler   ; DMA2 Channel2
  65.                 DCD     DMA2_Channel3_IRQHandler   ; DMA2 Channel3
  66.                 DCD     DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
  67. __Vectors_End                                    ;__Vectors_End向量表的结束地址

  68. __Vectors_Size  EQU  __Vectors_End - __Vectors   ;定义__Vectors_Size标号,值是向量表的大小

  69.                 AREA    |.text|, CODE, READONLY  ;定义一个代码段,段名是|.text|,属性是只读

  70. ;PROC指令是定义一个函数,通常和ENDP成对出现(标记程序的结束)               
  71. ; Reset handler
  72. Reset_Handler   PROC                                      ;定义 Reset_Handler函数;复位后赋给PC寄存器的值就是Reset_Handler函数的入口地址值。也是系统上电后第一个执行的程序

  73.                 EXPORT  Reset_Handler             [WEAK]  ;*[WEAK]指令是将函数定义为弱定义。所谓的弱定义就是如果其他地方有定义这个函数,
  74.                                                           ;*编译时使用另一个地方的函数,否则使用这个函数
  75.                                                 
  76.                                                           ;*IMPORT   表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似
  77.                 IMPORT  __main                            ;*__main 和 SystemInit 函数都是外部文件的标号
  78.                 IMPORT  SystemInit                        ;* SystemInit 是STM32函数库的函数,作用是初始化系统时钟
  79.                 LDR     R0, =SystemInit
  80.                 BLX     R0              
  81.                 LDR     R0, =__main                       ;* __main是C库的函数,主要是初始化堆栈和代码重定位,然后跳到main函数执行用户编写的代码
  82.                 BX      R0
  83.                 ENDP
  84.                
  85. ; Dummy Exception Handlers (infinite loops which can be modified)
  86. ;下面定义的都是异常服务函中断服务函数
  87. NMI_Handler     PROC
  88.                 EXPORT  NMI_Handler                [WEAK]
  89.                 B       .
  90.                 ENDP
  91. .....由于文件太长这里省略了部分函数的定义,完整的可以查看工程里的启动文件
  92. SysTick_Handler PROC
  93.                 EXPORT  SysTick_Handler            [WEAK]
  94.                 B       .
  95.                 ENDP

  96. Default_Handler PROC

  97.                 EXPORT  WWDG_IRQHandler            [WEAK]
  98.                 EXPORT  PVD_IRQHandler             [WEAK]
  99.                 .....由于文件太长这里省略了部分中断服务函数的定义,完整的可以查看工程里的启动文件
  100.                 EXPORT  DMA2_Channel2_IRQHandler   [WEAK]
  101.                 EXPORT  DMA2_Channel3_IRQHandler   [WEAK]
  102.                 EXPORT  DMA2_Channel4_5_IRQHandler [WEAK]

  103. WWDG_IRQHandler
  104. PVD_IRQHandler
  105. TAMPER_IRQHandler
  106. .....由于文件太长这里省略了部分标号的定义,完整的可以查看工程里的启动文件
  107. DMA2_Channel1_IRQHandler
  108. DMA2_Channel2_IRQHandler
  109. DMA2_Channel3_IRQHandler
  110. DMA2_Channel4_5_IRQHandler
  111.                 B       .

  112.                 ENDP

  113.                 ALIGN    ;四字节对齐

  114. ;*******************************************************************************
  115. ; User Stack and Heap initialization
  116. ;*******************************************************************************
  117. ;下面函数是初始化堆栈的代码
  118.                  IF      :DEF:__MICROLIB     
  119.                  ;如果定义了__MICROLIB宏编译下面这部分代码,__MICROLIB在MDK工具里面定义
  120.                  ;这种方式初始化堆栈是由 __main 初始化的
  121.                  EXPORT  __initial_sp   ;栈顶地址 (EXPORT将标号声明为全局标号,供其他文件引用)
  122.                  EXPORT  __heap_base    ;堆的起始地址
  123.                  EXPORT  __heap_limit   ;堆的结束地址
  124.                
  125.                  ELSE
  126.                  ;由用户初始化堆
  127.                  ;否则编译下面的
  128.                  IMPORT  __use_two_region_memory      ;__use_two_region_memory 由用户实现
  129.                  EXPORT  __user_initial_stackheap
  130.                  
  131. __user_initial_stackheap
  132.                  
  133.                  LDR     R0, =  Heap_Mem              ;堆的起始地址
  134.                  LDR     R1, =(Stack_Mem + Stack_Size);栈顶地址
  135.                  LDR     R2, = (Heap_Mem +  Heap_Size);堆的结束地址
  136.                  LDR     R3, = Stack_Mem              ;栈的结束地址
  137.                  BX      LR

  138.                  ALIGN

  139.                  ENDIF

  140.                  END

  141. ;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****
复制代码


STM32的启动步骤如下:

  • 1、上电复位后,从 0x0000 0000 地址取出栈顶地址赋给MSP寄存器(主堆栈寄存器),即MSP = __initial_sp。这一步是由硬件自动完成的

  • 2、从0x0000 0004 地址取出复位程序的地址给PC寄存器(程序计数器),即PC = Reset_Handler。这一步也是由硬件自动完成调用SystemInit函数初始化系统时钟

  • 3、跳到C库的__main函数初始化堆栈(初始化时是根据前面的分配的堆空间和栈空间来初始化的)和代码重定位(初始RW 和ZI段),然后跳到main函数执行应用程序


IAP设计思路

大体分为两部分设计,bootloader、APP代码设计,bootloader用于检查APP区代码是否需要更新,以及跳转到APP区执行APP程序

调研了一下群里的小伙伴,下面这个流程比较通用一些,大概是下图所示升级流程:


升级流程图


微信图片_20230402175006.png


Flash分区

是以STM32F103RET6为主控做的flash分区,主要功能:


微信图片_20230402174614.png


  • boot区:0x0800 0000 到 0x0800 b7FF 地址的flash块划分给bootloader,用于升级固件,大小是46kb

  • 用户参数区:0x0800 B800 到 0x0800 BFFF 的flash块划分为用户参数区(parameters),用于存储用户的一些参数,大小是2Kb

  • APP区:0x0800 C000 到 0x0804 3FFF 的flash块划分为APP区 ,(application)用于存放用户功能应用代码,大小是224Kb

  • APP缓存区:0x0804 4000 到 0x0807 BFFF 的flash块划分为APP缓存区 (update region),用于暂存下发的固件,大小跟应用程序区一样 224kb

  • 未定义:0x0807 C000 到 0x0807 FFFF 的flash块划分未定义区,可以根据具体用途定义,大小是16Kb


代码实现

硬件:

  • fallingstar-board(已开源,打板验证)

软件:

  • 内部flash读写

  • 串口DMA+空闲中断


内部flash读写操作

这部分比较简单,直接上代码:

读flash操作:

  1. /************************************************************
  2.   * @brief   读取2字节数据
  3. * @param[in]   uint32_t faddr
  4.   * @return  NULL
  5.   * @github  
  6.   * @date    2021-xx-xx
  7.   * @version v1.0
  8.   * @note    NULL
  9.   ***********************************************************/
  10. uint16_t BSP_FLASH_ReadHalfWord(uint32_t raddr)
  11. {
  12. return *(__IO uint16_t*)raddr;
  13. }
  14. /************************************************************
  15.   * @brief      读取n(uint16_t)字节数据
  16. * @param[in]   uint32_t ReadAddr
  17. * @param[out]  uint16_t *pBuffer
  18. * @param[in]   uint16_t len
  19.   * @return  NULL
  20.   * @github  
  21.   * @date    2021-xx-xx
  22.   * @version v1.0
  23.   * @note    NULL
  24.   ***********************************************************/
  25. void BSP_FLASH_Read (uint32_t ReadAddr, uint16_t *pBuffer, uint16_t len )   
  26. {
  27. uint16_t i;

  28. for(i=0;i<len;i++)
  29. {
  30.   pBuffer[i]=BSP_FLASH_ReadHalfWord(ReadAddr);   //读取2个字节.
  31.   ReadAddr+=2;                   //偏移2个字节.
  32. }
  33. }
复制代码

写操作,注意写之前要保证是没有写过的区域即可:

  1. /************************************************************
  2.   * @brief   写入n(uint16_t)字节数据
  3. * @param[in]   uint32_t ReadAddr
  4. * @param[out]   uint16_t *pBuffer
  5. * @param[in]   uint16_t len
  6.   * @return  NULL
  7.   * @github  
  8.   * @date    2021-xx-xx
  9.   * @version v1.0
  10.   * @note    NULL
  11.   ***********************************************************/
  12. void BSP_FLASH_Write_NoCheck ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t len )   
  13. {        
  14. uint16_t i;

  15. for(i=0;i<len;i++)
  16. {
  17.   HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
  18.    WriteAddr+=2;                                    //地址增加2.
  19. }  
  20. }
  21. /************************************************************
  22.   * @brief     写入n(uint16_t)字节数据
  23. * @param[in]  uint32_t WriteAddr
  24. * @param[in]  uint16_t *pBuffer
  25. * @param[in]  uint16_t len
  26.   * @return  NULL
  27.   * @github  
  28.   * @date    2021-xx-xx
  29.   * @version v1.0
  30.   * @note    NULL
  31.   ***********************************************************/
  32. void BSP_FLASH_Write(uint32_t WriteAddr,uint16_t * pBuffer,uint16_t len )
  33. {
  34.   uint32_t SECTORError = 0;
  35. uint16_t sector_off;    //扇区内偏移地址(16位字计算)
  36. uint16_t sector_remain; //扇区内剩余地址(16位字计算)   
  37.   uint16_t i;   
  38. uint32_t secor_pos;    //扇区地址
  39. uint32_t offaddr;   //去掉0X08000000后的地址

  40. if(WriteAddr<FLASH_BASE||(WriteAddr>=(FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址

  41. HAL_FLASH_Unlock();         //解锁

  42. offaddr=WriteAddr-FLASH_BASE;    //实际偏移地址.
  43. secor_pos=offaddr/STM_SECTOR_SIZE;   //扇区地址  0~127 for STM32F103RBT6
  44. sector_off=(offaddr%STM_SECTOR_SIZE)/2;  //在扇区内的偏移(2个字节为基本单位.)
  45. sector_remain=STM_SECTOR_SIZE/2-sector_off;  //扇区剩余空间大小   
  46. if(len<=sector_remain)sector_remain=len;//不大于该扇区范围

  47. while(1)
  48. {
  49.   BSP_FLASH_Read(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
  50.    
  51.   for(i=0;i<sector_remain;i++)//校验数据
  52.   {
  53.    if(STMFLASH_BUF[sector_remain+i]!=0XFFFF)
  54.     break;//需要擦除     
  55.   }
  56.   if(i<sector_remain)//需要擦除
  57.   {
  58.    //擦除这个扇区
  59.       /* Fill EraseInit structure*/
  60.       EraseInitStruct.TypeErase     = FLASH_TYPEERASE_PAGES;
  61.       EraseInitStruct.PageAddress   = secor_pos*STM_SECTOR_SIZE+FLASH_BASE;
  62.       EraseInitStruct.NbPages       = 1;
  63.       HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);
  64.    for(i=0;i<sector_remain;i++)//复制
  65.    {
  66.     STMFLASH_BUF[i+sector_off]=pBuffer[i];   
  67.    }
  68.    BSP_FLASH_Write_NoCheck(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区  
  69.   }
  70.   else
  71.    BSP_FLASH_Write_NoCheck(WriteAddr,pBuffer,sector_remain);//写已经擦除了的,直接写入扇区剩余区间.        
  72.   if(len==sector_remain)
  73.    break;//写入结束了
  74.   else//写入未结束
  75.   {
  76.    secor_pos++;    //扇区地址增1
  77.    sector_off=0;    //偏移位置为0   
  78.     pBuffer+=sector_remain;   //指针偏移
  79.    WriteAddr+=sector_remain; //写地址偏移   
  80.     len-=sector_remain; //字节(16位)数递减
  81.    
  82.    if(len>(STM_SECTOR_SIZE/2))
  83.     sector_remain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
  84.    else
  85.     sector_remain=len;//下一个扇区可以写完了
  86.   }  
  87. };
  88. HAL_FLASH_Lock();//上锁
  89. }
复制代码

串口DMA+空闲中断接收不定长数据

在上篇文章的基础上,我们对结构体做点修改,增加bin文件数据总长度记录,以及bin文件接收完成标志(小伙伴们可以采用其他办法,不必拘泥于我教程中的方式):

  1. #define Max_RecLen 1024*3

  2. typedef struct{
  3. uint8_t RxBuffer[Max_RecLen];  //DMA接收缓冲区
  4. uint16_t RecDat_len;      //单包数据长度
  5. uint32_t Cur_WriteAddr;     //APP缓冲区地址
  6. uint16_t BinLen;        //bin文件数据长度
  7. uint8_t rec_endFlag;      //单包数据接收结束标志
  8. uint8_t Binrec_endFlag;         //bin文件接收结束标志
  9. uint16_t DMA_TIMCNT;       //bin文件下发超时计数器

  10. }UserUartDMA_Typedef;
复制代码

然后在串口中断中:

  1. /**
  2.   * @brief This function handles USART1 global interrupt.
  3.   */
  4. void USART1_IRQHandler(void)
  5. {
  6.   /* USER CODE BEGIN USART1_IRQn 0 */
  7. uint32_t idle_flag_temp = 0;
  8. uint16_t len_temp = 0;
  9.   /* USER CODE END USART1_IRQn 0 */
  10.   HAL_UART_IRQHandler(&huart1);
  11.   /* USER CODE BEGIN USART1_IRQn 1 */
  12. idle_flag_temp = __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);

  13. if(idle_flag_temp)
  14. {
  15.   __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE);
  16.   HAL_UART_DMAStop(&huart1);
  17.   
  18.   UserUartDma.DMA_TIMCNT = 0;
  19.   UserUartDma.Binrec_endFlag=0;
  20.   len_temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
  21.   
  22.   UserUartDma.RecDat_len = Max_RecLen - len_temp;  
  23.   UserUartDma.BinLen+=UserUartDma.RecDat_len;
  24.   UserUartDma.rec_endFlag = 1;
  25.   
  26. }
  27.   __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
  28.   HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen);
  29.   /* USER CODE END USART1_IRQn 1 */
  30. }
复制代码

IAP代码设计
  • bootloader代码设计

扇区擦除,用于写入前擦除相应扇区

  1. /******************************************************
  2. * Brief     : 擦除APP区
  3. * Parameter :
  4. *           *startaddr:APP起始地址
  5. *      *pages  :要擦除的page = APPSIZE/PAGESIZE
  6. * Return    :None.
  7. *******************************************************/
  8. void APPReigion_Erase(uint32_t startaddr,uint16_t pages)
  9. {
  10.   uint32_t SECTORError = 0;

  11.   //擦除APP区
  12.   /* Fill EraseInit structure*/
  13.   EraseInitStruct.TypeErase     = FLASH_TYPEERASE_PAGES;
  14.   EraseInitStruct.PageAddress   = startaddr;
  15.   EraseInitStruct.NbPages       = pages;
  16.   HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);
  17. }
复制代码

升级标志获取与擦除

  1. /******************************************************
  2. * Brief     : 写入升级标志
  3. * Parameter :
  4. *           *addr:标志存放地址
  5. *      *pdata:写入的数据
  6. * Return    :None.
  7. *******************************************************/
  8. void APP_UpdateFlag_Write(uint32_t addr,uint16_t *pdata)
  9. {
  10.   BSP_FLASH_Write(addr,pdata,1);
  11. }
  12. /******************************************************
  13. * Brief     : 获取升级标志
  14. * Parameter :
  15. *           *addr:标志存放地址
  16. * Return    :None.
  17. *******************************************************/
  18. uint16_t APP_UpdateFlag_Read(uint32_t addr)
  19. {
  20.   uint16_t flag_temp;
  21.   BSP_FLASH_Read(addr,&flag_temp,1);

  22.   return flag_temp;
  23. }
复制代码

接下来就是APP缓冲区数据写入,APP区与APP缓冲区数据倒腾了

  1. /******************************************************
  2. * Brief     : Bin文件写入app缓冲区
  3. * Parameter :
  4. *           StartAddr: 起始地址
  5. *           *pBin_DataBuf: 要传输的数据
  6. *           packBufLength:单包数据长度
  7. * Return    : None.
  8. *******************************************************/
  9. void IAP_WriteBin(uint32_t StartAddr,uint8_t * pBin_DataBuf,uint32_t packBufLength)
  10. {
  11. uint16_t pack_len, packlen_Ctr=0, dataTemp;
  12. uint8_t * pData = pBin_DataBuf;
  13.   
  14. for (pack_len = 0; pack_len < packBufLength; pack_len += 2 )
  15. {         
  16.   dataTemp =  ( uint16_t ) pData[1]<<8;
  17.   dataTemp += ( uint16_t ) pData[0];   
  18.   pData += 2;                                                      //偏移2个字节
  19.   ulBuf_Flash_App [ packlen_Ctr ++ ] = dataTemp;     
  20. }

  21.    BSP_FLASH_Write ( UserUartDma.Cur_WriteAddr, ulBuf_Flash_App, packlen_Ctr );
  22.    UserUartDma.Cur_WriteAddr += (packlen_Ctr*2);                                           //偏移packlen_Ctr  16=2*8.所以要乘以2.
  23.    packlen_Ctr = 0;
  24. }
  25. /******************************************************
  26. * Brief     : Bin文件从app缓冲区写入app区
  27. * Parameter :
  28. *           SrcStartAddr: app缓冲区起始地址
  29. *           DstStartAddr: APP区起始地址
  30. *           BinLength:bin文件长度

  31. * Return    : None.
  32. *******************************************************/
  33. void IAP_WriteBinToAPPReigon(uint32_t SrcStartAddr,uint32_t DstStartAddr,uint32_t BinLength)
  34. {
  35. uint16_t data_temp = 0;
  36. uint32_t count=0;


  37. HAL_FLASH_Unlock();         //解锁

  38. APPReigion_Erase(APP_START_ADDR,APPSIZE/PAGESIZE);
  39. HAL_Delay(10);

  40. for(count=0;count<BinLength;count=count+2)
  41. {
  42.    BSP_FLASH_Read (SrcStartAddr, &data_temp, 1);
  43.    BSP_FLASH_Write_NoCheck(DstStartAddr, &data_temp,1);
  44.    SrcStartAddr+=2;
  45.    DstStartAddr+=2;
  46. }
  47. //BSP_FLASH_Write_NoCheck(DstStartAddr, ,1);
  48. HAL_FLASH_Lock();//上锁
  49. }
复制代码


最后,要设置APP程序的运行地址

  1. /******************************************************
  2. * Brief     : 设置栈顶指针
  3. * Parameter :
  4. *           ulAddr:
  5. * Return    : None.
  6. *******************************************************/

  7. __asm void MSR_MSP ( uint32_t ulAddr )
  8. {
  9.     MSR MSP, r0                       //set Main Stack value
  10.     BX r14
  11. }

  12. /******************************************************
  13. * Brief     : IAP执行
  14. * Parameter :
  15. *           ulAddr_App: APP起始地址
  16. * Return    : None.
  17. *******************************************************/
  18. void IAP_ExecuteApp ( uint32_t ulAddr_App )
  19. {
  20. pIapFun_TypeDef pJump2App;

  21. if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 )   //检查栈顶地址是否合法.
  22. {
  23.   pJump2App = ( pIapFun_TypeDef ) * ( __IO uint32_t * ) ( ulAddr_App + 4 ); //用户代码区第二个字为程序开始地址(复位地址)  
  24.   MSR_MSP( * ( __IO uint32_t * ) ulAddr_App ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
  25.   pJump2App ();                                             //跳转到APP.
  26. }
  27. }  
复制代码

IAP相关的代码就这些,主要是数据的倒腾,其他倒也没什么复杂的

目前小飞哥对整包bin文件传输完成判断,单包采用DMA+空闲中断的方式,整包文件传输采用的是串口中断在5s内没有数据过来,认为一包数据接收完成,置位接收标志,这部分小伙伴可以自行判断

  1. static void Systick_Config(void)
  2. {
  3.   HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000); //1ms
  4.   HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
  5.   HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
  6. }

  7. void HAL_SYSTICK_Callback(void)
  8. {
  9. UserUartDma.DMA_TIMCNT++;
  10. if(UserUartDma.DMA_TIMCNT>5000)
  11. {
  12.   UserUartDma.Binrec_endFlag = 1;
  13.   UserUartDma.DMA_TIMCNT = 0;
  14. }
  15.   TIMX_IRQHandler_user();
  16. }
复制代码

bin文件传输完成后,写入需要更新标志,重新跳转至boot区,检查是否APP代码需要更新:


  1. uint32_t Task_02()
  2. {

  3. if(UserUartDma.rec_endFlag)
  4. {
  5.   PRINT_INFO("update firmware\n");
  6.   PRINT_INFO("APP 长度:%d字节\n", UserUartDma.RecDat_len);  
  7.   
  8.   UserUartDma.rec_endFlag = 0;
  9.   IAP_WriteBin(UserUartDma.Cur_WriteAddr,UserUartDma.RxBuffer,UserUartDma.RecDat_len);
  10.   
  11.   memset(UserUartDma.RxBuffer,0,UserUartDma.RecDat_len);
  12.   UserUartDma.RecDat_len = 0;
  13. }
  14. if(UserUartDma.BinLen!=0)
  15. {
  16.   if(UserUartDma.Binrec_endFlag)
  17.   {
  18.     UserUartDma.Binrec_endFlag = 0;
  19.    
  20.     APP_UpdateFlag_Write(APP_Len_ADDR,&UserUartDma.BinLen);   //写入升级标志
  21.     APP_UpdateFlag_Write(APP_UpdateFlag_ADDR,&APP_UPDATE_FLAG);   //写入升级标志
  22.     UserUartDma.BinLen = 0;  
  23.     IAP_ExecuteApp(FLASH_BASE);   //跳转8000000,重新启动
  24.     HAL_Delay(2000);
  25.   }

  26. }
  27. }
复制代码

MCU复位之后,初始化执行过程中,对APP升级标志进行检测:

  1. PRINT_INFO("-----IAP Menu--------------\n");
  2. PRINT_INFO("-----Download APP BIN------\n");
  3. PRINT_INFO("-----Restart To RUN APP----\n");
  4. PRINT_INFO("\n\n\n");

  5. if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))
  6. {
  7.   PRINT_INFO("-----Restart.....5-------\n");
  8.   HAL_Delay(1000);
  9.   PRINT_INFO("-----Restart.....4-------\n");
  10.   HAL_Delay(1000);
  11.   PRINT_INFO("-----Restart.....3-------\n");
  12.   HAL_Delay(1000);
  13.   PRINT_INFO("-----Restart.....2-------\n");
  14.   HAL_Delay(1000);
  15.   PRINT_INFO("-----Restart.....1-------\n");
  16.   HAL_Delay(1000);
  17.   
  18.   PRINT_INFO("the device need update\n");
  19.   IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR));
  20.   PRINT_INFO("firmware write OK\n");
  21.   PRINT_INFO("please double click the button the execute the application\n");

  22.   HAL_FLASH_Unlock();         //解锁
  23.   APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE);  //擦除APP缓冲区
  24.   APPReigion_Erase(Flash_UNUSED,1);          //清除APP更新标志     
  25.   HAL_FLASH_Lock();          //上锁
  26.   
  27.   PRINT_INFO ( "开始执行 APP\n" );
  28.   //执行FLASH APP代码
  29.   IAP_ExecuteApp(APP_START_ADDR);
  30. }
复制代码

倒计时5秒后,代码更新,并执行

  1. PRINT_INFO("-----IAP Menu--------------\n");
  2. PRINT_INFO("-----Download APP BIN------\n");
  3. PRINT_INFO("-----Restart To RUN APP----\n");
  4. PRINT_INFO("\n\n\n");

  5. if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))
  6. {
  7.   PRINT_INFO("-----Restart.....5-------\n");
  8.   HAL_Delay(1000);
  9.   PRINT_INFO("-----Restart.....4-------\n");
  10.   HAL_Delay(1000);
  11.   PRINT_INFO("-----Restart.....3-------\n");
  12.   HAL_Delay(1000);
  13.   PRINT_INFO("-----Restart.....2-------\n");
  14.   HAL_Delay(1000);
  15.   PRINT_INFO("-----Restart.....1-------\n");
  16.   HAL_Delay(1000);
  17.   
  18.   PRINT_INFO("the device need update\n");
  19.   IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR));
  20.   PRINT_INFO("firmware write OK\n");
  21.   PRINT_INFO("please double click the button the execute the application\n");

  22.   HAL_FLASH_Unlock();         //解锁
  23.   APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE);  //擦除APP缓冲区
  24.   APPReigion_Erase(Flash_UNUSED,1);          //清除APP更新标志     
  25.   HAL_FLASH_Lock();          //上锁
  26.   
  27.   PRINT_INFO ( "开始执行 APP\n" );
  28.   //执行FLASH APP代码
  29.   IAP_ExecuteApp(APP_START_ADDR);
  30. }
  31. else//不需要更新,执行APP
  32. {
  33.   IAP_ExecuteApp(APP_START_ADDR);
  34. }
复制代码

bin文件生成


微信图片_20230402174611.png


输入:

C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o  IAP.bin  UART_CircleQueueTest\UART_CircleQueueTest.axf


参数意义:

  • C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe:fromelf.exe文件目录地址,一般keil下自带有

  • --bin -o  IAP.bin :


输出bin文件名称

  • UART_CircleQueueTest\UART_CircleQueueTest.axf:

axf文件目录及文件

要注意boot区的地址范围,根据前面的设计,46K,拿出计算器....


微信图片_20230402174607.png

微信图片_20230402174604.png

  • APP代码设计

APP代码是用户功能代码,实现业务逻辑,本次测试用的比较简单,接收到APP代码之后,会自动重启,更新APP区代码,如下:


  • boot区代码执行效果

微信图片_20230402174601.png

  • APP1代码执行效果


微信图片_20230402174557.png


  • 接下来再进行一次更新


微信图片_20230402174553.png


注意APP BIN文件的flash地址设置


微信图片_20230402174550.png

OK,至此,设计部分就完成了,涉及的内容还是非常多的

本文仅仅用于原理性介绍及IAP功能演示,与工程中实际使用的还是有很大区别的,后面会继续给大家出工程应用方便的教程,会涉及到签名、验签、加解密、校验固件完整性等等


转载自:Embedded小飞哥
如有侵权请联系删除

收藏 评论0 发布时间:2023-4-2 17:48

举报

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