【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运行了

相关推荐
杰尼君3 小时前
STM32CubeMX笔记(11)-- AD模块使用
笔记·stm32·嵌入式硬件
lingzhilab5 小时前
零知IDE——STM32F407VET6与ADS1115模数转换器实现多通道数据采集显示系统
stm32·单片机·开源
xxy.c8 小时前
基于IMX6ULL的时钟,定时器(EPIT,GPT)
单片机·嵌入式硬件·fpga开发
happygrilclh9 小时前
stm32L496 flash 分配
stm32·单片机·嵌入式硬件
古译汉书10 小时前
嵌入式铁头山羊STM32-各章节详细笔记-查阅传送门
数据结构·笔记·stm32·单片机·嵌入式硬件·个人开发
自由的好好干活11 小时前
从0开始使用LabVIEW操作数据采集卡-概述和新建新建项目
嵌入式硬件·labview
一枚码农~13 小时前
STM32红外与LED控制实战
单片机·嵌入式硬件
Heavy sea14 小时前
STM32定时器(寄存器与HAL库实现)
stm32·单片机
路过羊圈的狼15 小时前
STM32的HAL库驱动ADS124S08进行PT100温度采集
stm32·嵌入式硬件·mongodb