【STM32】基于串口的bootloader

目录

一、简单介绍

二、开发思路

​三、实战开发

App

Boot


一、简单介绍

Bootloader 是嵌入式设备中负责引导和更新的关键程序。汽车的 ECU 在进行 OTA 升级时,Bootloader 负责下载、校验和写入新固件,确保出现异常时能回退到旧版本保障行车安全;手机系统更新依赖 Bootloader来刷机、验证签名并启动系统内核;电脑中的 BIOS 或 UEFI 同样是一类 Bootloader,用于初始化硬件并加载操作系统。通过统一的下载接口和安全机制,Bootloader 让设备可以远程维护、在线更新并防止恶意篡改,从而在降低生产和维护成本的同时,保证系统在整个生命周期中的稳定与安全。

归根结底,bootloader也是一个工程,和application性质相同

笔者实现的简易bootloader的作用主要有

  • 擦除现存app
  • 接收新的app固件
  • 烧写到flash中
  • 跳转到新app

二、开发思路

笔者是基于STM32F103CBT6开发,和常见的STM32F103C8T6相比,flash变成128kb

STM32单片机的flash启动模式下,启动地址总是0x8000000,这也是自己的bootloader的起始地址

bootloader大小为12616字节,需要12个page多一点点(0.32 page)

因此把判断标志放在page 12的开头,即地址0x08003400

App的起始地址为0x08003800,(page13的开头)

内存分布为

boot检测flag信号为true表明需要升级,擦除App并烧写新的固件;否则就表明不需要升级,直接跳转到App即可

三、实战开发

App

cubemx开启时钟,UART1和UART2,LED

App工程需要设置起始地址,使用keil的话如下设置

system_stm32f1xx.c文件中设置向量表偏移地址

App闪烁LED并在串口2发送App running作为演示

App的main函数代码如下

cpp 复制代码
/* USER CODE END 0 */
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration--------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* USER CODE BEGIN Init */
  /* USER CODE END Init */
  /* Configure the system clock */
  SystemClock_Config();
  /* USER CODE BEGIN SysInit */
  /* USER CODE END SysInit */
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
   req.TypeErase = FLASH_TYPEERASE_PAGES;
   req.Banks = FLASH_BANK_1;
   req.PageAddress = APP_REQUEST_ADDRESS;
   req.NbPages = 1;
   /* go to wait state for getting next data from PC*/
   
   updateReq = *(uint8_t*)(APP_REQUEST_ADDRESS);
   HAL_UART_Receive_IT(&huart1, rx, 6);
   
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
      HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
      HAL_UART_Transmit(&huart2, tx, 14, 10);
      HAL_Delay(100);
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Boot

cubemx开启时钟,串口

boot工程起始地址就是0x8000000,就不需要偏移了

设置地址宏和升级请求标志

cpp 复制代码
#define APP_START_ADDRESS 0x08003800
#define APP_REQUEST_ADDRESS 0x08003400

updateReq = *(uint8_t*)(APP_REQUEST_ADDRESS);

判断是否需要升级

cpp 复制代码
if (updateReq == 1 || updateReq == 0xFF)
{
   state = STATE_INIT;
   HAL_FLASH_Unlock();
   HAL_FLASHEx_Erase(&req, &error);
}
else
{
   state = STATE_JUMP;
}

在主循环内进行状态的切换

笔者制作了一个配套的上位机

上位机按下boot则发送由帧头0xAA+0x22222222+CRC组成的升级请求,App检测到后就设置标志位并复位

上位机按下flash则进行新固件的传输,在boot中进行接收和flash的写入

cpp 复制代码
case STATE_FLASH:
{
   /**
    * flash code
    */
   HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, APP_START_ADDRESS + flashPackCnt * 4, *(uint32_t*) (prx + 1));
   flashPackCnt++;

   /* flash finish, notify PC by sending 0x77 */
   tx[0] = 0x77;
   HAL_UART_Transmit_IT(&huart1, tx, 1);

   /* go to wait state for getting next data from PC*/
   HAL_UART_Receive_IT(&huart1, rx, 6);
   state = STATE_WAIT;

   if ((flashPackCnt) * 4 == dataSize)
   {
      state = STATE_END;

      HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, APP_REQUEST_ADDRESS, 0);
   }

   break;
}

上位机发送0x20+四字节的固件包+CRC,单片机接收后发送0x77作为应答信号,这里笔者作为演示,就直接明文传输了,实际应用一般会有加密措施和握手等步骤

烧写完毕后跳转到新的App中

cpp 复制代码
case STATE_JUMP:
{
   deinitEverything();
   uint32_t stacktop = *((__IO uint32_t *)APP_START_ADDRESS);
   __set_MSP(stacktop);

   app_func_t app_func = (app_func_t)(*((__IO uint32_t *)(APP_START_ADDRESS + 4)));

   app_func();
}

boot的main函数代码如下

cpp 复制代码
/* USER CODE END 0 */
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration--------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* USER CODE BEGIN Init */
  /* USER CODE END Init */
  /* Configure the system clock */
  SystemClock_Config();
  /* USER CODE BEGIN SysInit */
  /* USER CODE END SysInit */
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
   updateReq = *(uint8_t*)(APP_REQUEST_ADDRESS);
   erase.TypeErase = FLASH_TYPEERASE_PAGES;
   erase.Banks = FLASH_BANK_1;
   erase.PageAddress = APP_START_ADDRESS;
   erase.NbPages = 40;
   req.TypeErase = FLASH_TYPEERASE_PAGES;
   req.Banks = FLASH_BANK_1;
   req.PageAddress = APP_REQUEST_ADDRESS;
   req.NbPages = 1;
   uint32_t error;
   if (updateReq == 1 || updateReq == 0xFF)
   {
      state = STATE_INIT;
      HAL_FLASH_Unlock();
      HAL_FLASHEx_Erase(&req, &error);
   }
   else
   {
      state = STATE_JUMP;
   }
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
   switch (state)
   {
      case STATE_INIT:
      {
         /* prepare for size info by interrupt */
         HAL_UART_Receive_IT(&huart1, rx, 6);
         /* tell host the chip is in boot now */
         tx[0] = 0x77;
         HAL_UART_Transmit_IT(&huart1, tx, 1);
         /* go to idle */
         state = STATE_IDLE;
         break;
      }
      case STATE_IDLE:
      {
         break;
      }
      case STATE_SIZE:
      {
         break;
      }
      case STATE_ERASE_BEGIN:
      {
         break;
      }
      case STATE_WAIT:
      {
         break;
      }
      case STATE_FLASH:
      {
         /**
          * flash code
          */
         HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, APP_START_ADDRESS + flashPackCnt * 4, *(uint32_t*) (prx + 1));
         flashPackCnt++;
         /* flash finish, notify PC by sending 0xAA */
         tx[0] = 0x77;
         HAL_UART_Transmit_IT(&huart1, tx, 1);
         /* go to wait state for getting next data from PC*/
         HAL_UART_Receive_IT(&huart1, rx, 6);
         state = STATE_WAIT;
         if ((flashPackCnt) * 4 == dataSize)
         {
            state = STATE_END;
            HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, APP_REQUEST_ADDRESS, 0);
         }
         break;
      }
      case STATE_END:
      {
         
         break;
      }
      case STATE_JUMP:
      {
         deinitEverything();
         uint32_t stacktop = *((__IO uint32_t *)APP_START_ADDRESS);
         __set_MSP(stacktop);
         app_func_t app_func = (app_func_t)(*((__IO uint32_t *)(APP_START_ADDRESS + 4)));
         app_func();
      }
      
      default:
         break;
   }
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

调试读取内存,0x8003800处的内容和编译的App的hex内容一致

由于升级请求被擦除,下一次复位就直接跳转至App运行了

相关推荐
国科安芯11 小时前
航天器多路并联大功率电源系统设计与ASP4644均流特性分析
单片机·嵌入式硬件·fpga开发·安全性测试
茯苓gao13 小时前
嵌入式开发笔记:CANopen相关移位运算与通信协议术语详解
网络·嵌入式硬件·学习·信息与通信
梁朝辉16 小时前
STM32H750VBT6中ADCINP与INN什么区别
stm32·单片机·嵌入式硬件
省四收割者17 小时前
一文详解信号完整性(1)
python·嵌入式硬件·数学建模·信息与通信·信号处理·智能硬件
崇山峻岭之间18 小时前
单片机无感FOC驱动之ADC
单片机·嵌入式硬件
Ligocious18 小时前
1.点亮一颗小小的LED
单片机·嵌入式硬件
KaifuZeng1 天前
通信与接口协议面试八、CAN通信
单片机·嵌入式硬件·面试·通信与接口协议
智源单片机设计1 天前
基于单片机的直流电机双闭环调速系统设计
单片机·嵌入式硬件
kebidaixu1 天前
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片详解
stm32
kebidaixu1 天前
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片调试实录:四个关键问题与解决方案
stm32