自学内容网 自学内容网

STM32 IAP技术 bootloader设计

介绍

IAP,即在应用程序内编程,就是在Flash中预留一套升级固件的boot程序,以实现通过串口/CAN总线实现 “程序升级”。
为什么要做这个boot程序?SWD接口不够用吗?
工程师在程序开发调试阶段肯定是用SWD接口,但是在产品已经完成,进行装机后,交付给客户的设备上的SWD接口是要屏蔽掉的。客户想要升级程序的话,就要依赖这个bootloader
(一般来说还需要一个交付给客户使用的上位机程序,本篇只讲bootloader)

Flash空间分配

单片机型号为STM32F407ZET6,flash空间分配如我写的这一篇所述 STM32项目开发Flash空间分配_stm32 flash空间分配-CSDN博客

bootloader设计

主要包括四个核心内容:通信协议,程序跳转,程序接收,程序写入

通信协议

通信协议就是boot和上位机约定的一个规则:收到什么信号时接收数据包,什么时候接收完成,什么时候跳转应用,什么时候恢复出厂设置等等,这个可以按照canopen协议来,也可以自己规定

我这边规定的如下:大致思路就是先握手,握手完成就开始接收程序,接收完成之后写入flash

    if(CAN2_Receive_Flag)
    {
      CAN2_Receive_Flag = 0;
      // 握手
      if(CAN_RxHeader.StdId == 0x0F0)  
      {
        update_begin_flag = 1;                   
        CAN2_Transmit(0x0F0, Shake_Hands);  // 握手完成
      }
      // 开始接收程序
      if(CAN_RxHeader.StdId == 0x0F1)
      {
        
      }
      // 接收完成,开始写入
      if(CAN_RxHeader.StdId == 0x0F2)
      {
                
        update_finish_flag = 1;
      }
      
    }

程序跳转

包括两个步骤:不更新时从boot跳转到APP,以及更新完后跳转到APP,原理都是一模一样的,直接放代码:

    if(!update_begin_flag)
    {
      if(wait_ms >= WAITTIME)         // 等待超时
      {        
        if(((*(uint32_t*)(APP_ADDRESS+4))&0xFF000000)==0x08000000) // 检查地址
        {
            Jump_to_APP(APP_ADDRESS); // 跳转
        }                        
      }
    }

其中Jump_to_APP代码:

void Jump_to_APP(uint32_t app_address)
{
uint8_t i = 0;
if(((*(uint32_t*)app_address)&0x2FFE0000)==0x20000000)// 检查地址
{ 
    // 地址+4是复位中断服务程序地址
jump_to_app = (iapfun)*(uint32_t*)(app_address+4);
            
    // 关闭全局中断
    __set_PRIMASK(1); 
             
    // 关闭滴答定时器
    SysTick->CTRL = 0;
    SysTick->LOAD = 0;
    SysTick->VAL = 0;
    
    // 设置所有时钟到默认状态
    HAL_RCC_DeInit();    
    
    // 关闭所有中断
    for (i = 0; i < 8; i++)
    {
      NVIC->ICER[i]=0xFFFFFFFF;
      NVIC->ICPR[i]=0xFFFFFFFF;
    }
    
    // 使能全局中断
    __set_PRIMASK(0);
    
    // 这条在RTOS中比较重要,先照搬过来,不深究
    __set_CONTROL(0);
    
    __set_MSP(*(uint32_t*)app_address);// APP堆栈地址
    jump_to_app();    // 跳转
    
    // 跳转失败才会继续往下运行
    while(1)
    {
      ;
    }
  }
}

总之上面这段代码里面最关键的就是__set_MSP(*(uint32_t*)app_address);和jump_to_app = (iapfun)*(uint32_t*)(app_address+4);前者设置堆栈地址,即SP指针,后者设置程序入口。其余的都是一些安全措施,防止跳转过程收到乱七八糟的影响。

其中iapfun是需要定义的

// 用于跳转到特定地址的函数入口
typedef void (*iapfun)(void);

要实现完整的程序跳转,还要APP也配合在main函数起始位置加一条语句,并在target中进行设置:

SCB->VTOR = FLASH_BASE | 0x00020000;

这段代码将 Cortex-M 核心的中断向量表基址重定位到 Flash 存储的地址0x08020000,没有这条语句的话就算boot成功跳转了也没法运行的

程序接收

实现方式就是定义一个很大的uint8_t类型的数组,然后通过CAN总线的报文一个一个字节接收过来

      if(CAN_RxHeader.StdId == 0x0F1)
      {
        if(APPLength < APP_LEN_MAX)
        {
          for(uint8_t i = 0; i < CAN_RxHeader.DLC; i++)
          {
            APP_RX_BUF[APPLength] = CAN2_Rx_data[i];
            APPLength++;
          }
        }
        else                                      // 数组容量不足
        {
          ;
        }
      }

这部分代码很简单,主要是上位机和boot程序要配合好,还要注意在 250 kbit/s 的波特率下,CAN总线每秒可以发送约 2212 条报文(8字节),所以数据发送周期最好不要设置太大。我设置10ms发一条报文,这样一个50k字节的程序差不多64秒发送完(实际项目程序也就30k左右的样子),还算可以接受。

程序写入

这个过程就是把接收的数组写到flash的对应位置中,之前介绍过flash读写的相关内容了,可以看一下STM32 使用HAL库实现flash读写_stm32halflash程序-CSDN博客

这里直接贴一下代码

if(((*(uint32_t*)(APP_ADDRESS+4))&0xFF000000)==0x08000000) 
{
    Write_APP(APP_ADDRESS, APP_RX_BUF, APPLength);           
    update_finish_flag = 1;                                  
}

void Write_APP(uint32_t WriteAddr, uint8_t* pBuffer, uint16_t Num)
{
static FLASH_EraseInitTypeDef EraseInitStruct;
static uint32_t SECTORError;
// Unlock the ROM
HAL_FLASH_Unlock();
// Erase the ROM
EraseInitStruct.TypeErase    = FLASH_TYPEERASE_SECTORS;
  EraseInitStruct.Sector       = FLASH_SECTOR_5;
EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3;
  EraseInitStruct.NbSectors    = 1;
if (HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError) != HAL_OK)
{
Error_Handler();
  }
// Write
for(uint16_t i=0; i<Num; i++)
{
if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, WriteAddr, *pBuffer) != HAL_OK)
{
Error_Handler();
}
WriteAddr += 1;
pBuffer ++;
}
// Lock the ROM
HAL_FLASH_Lock();
}

注意一下EraseInitStruct.Sector需要根据自己写入程序的地址来选,不要擦除错了。还要注意HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, WriteAddr, *pBuffer)中使用的是FLASH_TYPEPROGRAM_BYTE,按字节写入。

总结

这样一个简单的bootloader就算设计完了,当然要实现完整的IAP功能的话还需要上位机程序的配合。如果仅仅是需要单纯的IAP,也可以使用一些可以直接发送bin文件的CAN总线调试程序。可以参考一下STM32的IAP技术,基于CAN总线的STM32F103 BootLoader设计_哔哩哔哩_bilibili这位大佬的教程,介绍的非常详细

由于工作需要我还要在上位机中设计一些特定的功能,所以我还需要自己来写(╥╯^╰╥)


原文地址:https://blog.csdn.net/weixin_57904199/article/details/144689800

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!