当前位置: 首页 > article >正文

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这位大佬的教程,介绍的非常详细

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


http://www.kler.cn/a/461404.html

相关文章:

  • 网关的主要作用
  • 【微软,模型规模】模型参数规模泄露:理解大型语言模型的参数量级
  • Linux -- 单例模式
  • No.1十六届蓝桥杯备战|第一个C++程序|cin和cout|命名空间
  • idea 的 springboot项目spring-boot-devtools 自动编译 配置热部署
  • GPU 进阶笔记(一):高性能 GPU 服务器硬件拓扑与集群组网
  • 好用的随机生成图片的网站
  • Ae:项目设置 - 音频
  • π₀:基于VLM的多任务具身操作基础模型
  • View Shadcn UI 正式版本 v2024.5.4 发布
  • C++【内存管理】
  • golang中的错误处理机制
  • Fetch处理大模型流式数据请求与解析
  • OpenLinkSaas使用手册-项目外部资源管理
  • HarmonyOS:@Require装饰器:校验构造传参
  • 深入解析 Android MediaHTTPConnection JNI 实现
  • 2024广东省职业技能大赛云计算——私有云(OpenStack)平台搭建
  • Java Web学生自习管理系统
  • 课程设计项目之基于Python实现围棋游戏代码
  • REDIS1.0
  • 【每日学点鸿蒙知识】长时任务、HarmonyAppProvision申请、preferences、Testing工具、应用保活
  • 2.ATK-DLRK3568 QT竖屏显示改为横屏显示
  • 【MySQL初级】第1-4章
  • quasar中@click.stop没有生效,点击按钮时候会跳转
  • 【2024年-9月-29日-开源社区openEuler实践记录】 Euler - Copilot - Framework:开启智能辅助编程新征程
  • Rabbitmq追问1