STM32之BootLoader
STM32 的 BootLoader 为系统的固件更新提供了一种灵活、方便的解决方案。通过合理划分存储区域、编写 BootLoader 代码和应用程序代码,可以实现系统的在线固件更新功能。在实际应用中,需要注意 Flash 操作、中断向量表和通信协议等问题,以确保系统的稳定性和可靠性。
✅作者简介:热爱科研的嵌入式开发者,修心和技术同步精进
❤欢迎关注我的知乎:对error视而不见
代码获取、问题探讨及文章转载可私信。
☁ 愿你的生命中有够多的云翳,来造就一个美丽的黄昏。
🍎获取更多嵌入式资料可点击链接进群领取,谢谢支持!👇
一、引言
在嵌入式系统开发中,经常需要对设备的固件进行更新。传统的更新方式可能需要将设备连接到特定的编程器或调试器,操作繁琐且不够灵活。BootLoader (引导加载程序)则为解决这一问题提供了很好的方案,它允许我们通过各种通信接口(如串口、USB、网络等)在系统运行时对固件进行更新,大大提高了系统的可维护性和灵活性。本文将详细介绍 STM32 的 BootLoader 相关知识,包括原理、实现步骤和代码示例。
二、BootLoader 原理
2.1 基本概念
BootLoader 是在系统上电后运行的第一段代码,它的主要任务是初始化硬件环境,然后将应用程序加载到内存中并启动执行。在具备固件更新功能的系统中,BootLoader 还需要提供与外部通信的接口,接收新的固件数据并将其存储到指定的存储区域。
2.2 工作流程
- 系统上电:当 STM32 系统上电后,CPU 会从固定的地址(如 0x08000000)开始执行代码,即 BootLoader 的起始地址。
- 硬件初始化:BootLoader 首先对系统的硬件进行初始化,包括时钟、GPIO、串口等外设。
- 判断是否需要更新固件:可以通过检测特定的引脚状态、按键输入或接收到的特定指令来判断是否需要进行固件更新。
- 更新固件:如果需要更新固件,BootLoader 通过通信接口(如串口)接收新的固件数据,并将其存储到指定的存储区域(如内部 Flash)。
- 启动应用程序:如果不需要更新固件或固件更新完成后,BootLoader 将应用程序的起始地址加载到 CPU 的程序计数器(PC)中,从而启动应用程序的执行。
三、实现步骤
3.1 划分存储区域
在 STM32 的内部 Flash 中,需要划分出 BootLoader 区域和应用程序区域。例如,可以将前 16KB 的 Flash 空间分配给 BootLoader,剩余的空间用于存储应用程序。
3.2 编写 BootLoader 代码
硬件初始化
首先进行硬件初始化,包括时钟、串口等外设的初始化。以下是使用 STM32 HAL 库进行时钟和串口初始化的示例代码:
#include "stm32f4xx_hal.h"
UART_HandleTypeDef huart1;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
// 后续代码...
while (1)
{
// 主循环
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** 初始化 RCC 振荡器
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** 初始化 RCC 时钟
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}
static void MX_GPIO_Init(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
}
void Error_Handler(void)
{
while(1)
{
}
}
判断是否更新固件
可以通过检测特定引脚的状态来判断是否需要更新固件。例如,当某个引脚为低电平时,进入固件更新模式:
#define UPDATE_PIN GPIO_PIN_0
#define UPDATE_PORT GPIOA
if (HAL_GPIO_ReadPin(UPDATE_PORT, UPDATE_PIN) == GPIO_PIN_RESET)
{
// 进入固件更新模式
UpdateFirmware();
}
else
{
// 启动应用程序
StartApplication();
}
固件更新
在固件更新模式下,通过串口接收新的固件数据,并将其存储到应用程序区域。以下是一个简单的固件更新函数示例:
#define APPLICATION_ADDRESS 0x08004000
void UpdateFirmware(void)
{
uint8_t buffer[256];
uint32_t address = APPLICATION_ADDRESS;
uint32_t receivedBytes = 0;
// 擦除应用程序区域
HAL_FLASH_Unlock();
FLASH_Erase_Sector(FLASH_SECTOR_1, VOLTAGE_RANGE_3);
HAL_FLASH_Lock();
while (1)
{
if (HAL_UART_Receive(&huart1, buffer, sizeof(buffer), HAL_MAX_DELAY) == HAL_OK)
{
receivedBytes = sizeof(buffer);
HAL_FLASH_Unlock();
for (uint32_t i = 0; i < receivedBytes; i += 4)
{
uint32_t data = *(uint32_t *)&buffer[i];
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data);
address += 4;
}
HAL_FLASH_Lock();
}
else
{
break;
}
}
}
启动应用程序
将应用程序的起始地址加载到 CPU 的程序计数器(PC)中,从而启动应用程序的执行:
typedef void (*pFunction)(void);
pFunction Jump_To_Application;
uint32_t JumpAddress;
void StartApplication(void)
{
if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
{
JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
Jump_To_Application = (pFunction) JumpAddress;
__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
Jump_To_Application();
}
}
3.3 编写应用程序
在编写应用程序时,需要注意将应用程序的起始地址设置为 BootLoader 划分的应用程序区域的起始地址。可以在 Keil 等开发工具中进行相关设置。
四、注意事项
- Flash 擦除和编程:在进行 Flash 擦除和编程操作时,需要注意保护数据的完整性,避免出现数据丢失或损坏的情况。
- 中断向量表:应用程序的中断向量表需要进行相应的偏移,以确保中断能够正确响应。可以通过修改向量表偏移寄存器(VTOR)来实现。
- 通信协议:在固件更新过程中,需要定义好通信协议,确保数据的准确传输。例如,可以使用简单的帧头、数据长度、数据内容和校验码等组成的协议。
五、总结
STM32 的 BootLoader 为系统的固件更新提供了一种灵活、方便的解决方案。通过合理划分存储区域、编写 BootLoader 代码和应用程序代码,可以实现系统的在线固件更新功能。在实际应用中,需要注意 Flash 操作、中断向量表和通信协议等问题,以确保系统的稳定性和可靠性。
更多推荐



所有评论(0)