文章目录
一、方案概述
本方案基于STM32F429芯片,实现物联网设备的IAP(In Application Programming)在线升级功能,通过OTA(Over-the-Air)方式完成固件远程更新,同时加入CRC32校验、固件版本校验、加密校验等多重安全机制,确保升级过程稳定、安全、可靠,适用于工业物联网、智能家居等场景。
1.1 核心功能
- 支持Bootloader引导,区分Bootloader区和App区
- 支持串口/网口(物联网)OTA固件接收
- 多重安全校验:版本校验、CRC32数据完整性校验、固件合法性校验
- 升级失败自动回滚,避免设备变砖
- 支持固件加密传输与解密,防止固件窃取
1.2 硬件平台
- 主控:STM32F429IGT6(Flash 1MB,RAM 256KB)
- 外设:串口(USART1)、网口(ETH,物联网OTA用)、LED(状态指示)
1.3 软件环境
- 开发环境:Keil MDK5
- 固件库:STM32F4xx HAL Library
- 工具:串口调试助手、网口调试工具、Bin文件生成工具
二、STM32F429 Flash分区规划
STM32F429的Flash地址范围为0x08000000 ~ 0x080FFFFF(共1MB),本方案将Flash分为3个区域:Bootloader区、App区、备份区(升级失败回滚用)。
2.1 分区详情
| 分区名称 | 起始地址 | 结束地址 | 大小 | 功能 |
|---|---|---|---|---|
| Bootloader区 | 0x08000000 | 0x08007FFF | 32KB | 存储Bootloader代码,负责升级引导、校验、跳转 |
| App区 | 0x08008000 | 0x080DFFFF | 864KB | 存储用户应用程序,正常运行时执行 |
| 备份区 | 0x080E0000 | 0x080FFFFF | 128KB | 存储新固件,升级完成后覆盖App区,失败则从备份区回滚 |
2.2 分区Mermaid流程图
三、Bootloader开发(核心)
Bootloader是IAP升级的核心,负责上电初始化、升级指令接收、固件校验、固件烧写、App跳转等功能。
3.1 Bootloader启动流程
3.2 Bootloader工程配置(Keil MDK5)
- 新建工程,选择STM32F429IGT6芯片
- 配置Flash地址:
0x08000000,大小0x8000(32KB) - 启用HAL库,配置GPIO、USART1、Flash驱动
- 关闭MicroLIB(避免与App区冲突)
- 配置中断向量表偏移:
0x00000000(Bootloader区无偏移)
3.3 Bootloader核心代码
3.3.1 宏定义(分区地址、校验参数)
#ifndef __BOOTLOADER_H
#define __BOOTLOADER_H
#include "stm32f4xx_hal.h"
// Flash分区地址定义
#define BOOTLOADER_ADDR 0x08000000 // Bootloader起始地址
#define APP_ADDR 0x08008000 // App起始地址
#define BACKUP_ADDR 0x080E0000 // 备份区起始地址
#define APP_SIZE_MAX 0x000D8000 // App最大大小(864KB)
#define BACKUP_SIZE_MAX 0x00020000 // 备份区最大大小(128KB)
// 版本信息存储地址(Bootloader最后4字节)
#define VERSION_ADDR 0x08007FFC // 版本号存储地址
#define CURRENT_VERSION 0x01000000 // 当前Bootloader版本 V1.0
// OTA升级指令定义
#define OTA_UPGRADE_CMD 0xA55A0101 // 进入升级模式指令
#define OTA_DATA_CMD 0xA55A0102 // 固件数据传输指令
#define OTA_END_CMD 0xA55A0103 // 固件传输完成指令
#define OTA_CRC_CMD 0xA55A0104 // CRC校验指令
// 校验结果定义
#define CHECK_OK 0x00 // 校验通过
#define CHECK_ERROR_VERSION 0x01 // 版本校验失败
#define CHECK_ERROR_CRC 0x02 // CRC校验失败
#define CHECK_ERROR_ENCRYPT 0x03 // 加密校验失败
// 函数声明
void Bootloader_Init(void);
uint8_t Bootloader_Check_App(void);
void Bootloader_Jump_App(void);
uint8_t Bootloader_OTA_Receive(void);
uint8_t Bootloader_CRC_Check(uint32_t addr, uint32_t len, uint32_t crc_expected);
uint8_t Bootloader_Encrypt_Check(uint32_t addr, uint32_t len);
void Bootloader_Flash_Write(uint32_t addr, uint8_t *data, uint32_t len);
void Bootloader_Flash_Erase(uint32_t addr, uint32_t len);
#endif
3.3.2 初始化函数
#include "bootloader.h"
UART_HandleTypeDef huart1;
FLASH_ProcessTypeDef pFlash;
// Bootloader初始化:GPIO、串口、Flash
void Bootloader_Init(void)
{
// 系统时钟配置(72MHz)
HAL_Init();
SystemClock_Config();
// GPIO初始化(LED指示)
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOC_CLK_ENABLE();
// PC13为LED引脚
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // LED灭
// 串口1初始化(波特率115200,物联网OTA可替换为网口)
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
// PA9-TX, PA10-RX
GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
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;
HAL_UART_Init(&huart1);
// Flash初始化
__HAL_RCC_FLASH_CLK_ENABLE();
HAL_FLASH_Unlock(); // 解锁Flash
}
// 系统时钟配置函数
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// 配置主振荡器为HSE,8MHz
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 72;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
// 配置系统时钟为72MHz
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
// 错误处理函数
void Error_Handler(void)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // LED亮,指示错误
while(1);
}
3.3.3 App合法性校验函数
// 检查App区是否有效(通过栈顶地址判断)
uint8_t Bootloader_Check_App(void)
{
// App区栈顶地址(App的前4字节为栈顶地址,必须在RAM范围内)
uint32_t app_stack = *(uint32_t*)APP_ADDR;
// STM32F429 RAM地址:0x20000000 ~ 0x20020000(128KB)
if((app_stack >= 0x20000000) && (app_stack <= 0x20020000))
{
return CHECK_OK; // App有效
}
else
{
return CHECK_ERROR; // App无效
}
}
3.3.4 跳转至App函数
// 跳转至App区
void Bootloader_Jump_App(void)
{
// 关闭所有中断
__disable_irq();
// 关闭SysTick定时器
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
// 关闭串口、Flash等外设
HAL_UART_DeInit(&huart1);
HAL_FLASH_Lock(); // 锁定Flash
// 复位所有GPIO
HAL_GPIO_DeInit(GPIOC, GPIO_PIN_13);
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
// 定义跳转函数指针
typedef void (*pFunction)(void);
pFunction Jump_App;
uint32_t jump_addr;
// 获取App复位向量地址(App区第4字节,偏移4)
jump_addr = *(uint32_t*)(APP_ADDR + 4);
Jump_App = (pFunction)jump_addr;
// 设置App区栈顶地址
__set_MSP(*(uint32_t*)APP_ADDR);
// 跳转至App
Jump_App();
}
3.3.5 Flash读写函数
// Flash擦除函数(按扇区擦除,STM32F429扇区大小:16KB/64KB/128KB)
void Bootloader_Flash_Erase(uint32_t addr, uint32_t len)
{
FLASH_EraseInitTypeDef EraseInitStruct = {0};
uint32_t sector_error = 0;
HAL_StatusTypeDef status;
// 解锁Flash
HAL_FLASH_Unlock();
// 配置擦除参数
EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;
EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3; // 2.7V~3.6V
EraseInitStruct.Sector = FLASH_SECTOR_8; // 从App区起始扇区开始(根据地址计算)
EraseInitStruct.NbSectors = len / 0x20000; // 按128KB扇区计算
// 执行擦除
status = HAL_FLASHEx_Erase(&EraseInitStruct, §or_error);
if(status != HAL_OK)
{
Error_Handler();
}
// 锁定Flash
HAL_FLASH_Lock();
}
// Flash写入函数(按字写入,4字节)
void Bootloader_Flash_Write(uint32_t addr, uint8_t *data, uint32_t len)
{
uint32_t i = 0;
HAL_StatusTypeDef status;
// 解锁Flash
HAL_FLASH_Unlock();
// 按字写入(4字节对齐)
for(i=0; i<len; i+=4)
{
uint32_t data32 = (data[i+3]<<24) | (data[i+2]<<16) | (data[i+1]<<8) | data[i];
status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + i, data32);
if(status != HAL_OK)
{
Error_Handler();
}
}
// 锁定Flash
HAL_FLASH_Lock();
}
3.3.6 CRC32校验函数
// CRC32校验函数(标准CRC32算法)
uint8_t Bootloader_CRC_Check(uint32_t addr, uint32_t len, uint32_t crc_expected)
{
uint32_t crc = 0xFFFFFFFF;
uint32_t i = 0;
const uint32_t crc_table[256] = {
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
// 省略剩余CRC表,完整CRC32表可直接复制使用
};
// 计算CRC32
for(i=0; i<len; i++)
{
uint8_t byte = *(uint8_t*)(addr + i);
crc = (crc >> 8) ^ crc_table[(crc ^ byte) & 0xFF];
}
crc ^= 0xFFFFFFFF;
// 校验结果
if(crc == crc_expected)
{
return CHECK_OK;
}
else
{
return CHECK_ERROR_CRC;
}
}
3.3.7 OTA固件接收函数
// OTA固件接收(串口方式,物联网可替换为网口)
uint8_t Bootloader_OTA_Receive(void)
{
uint8_t rx_buf[1024] = {0}; // 接收缓冲区
uint32_t rx_len = 0; // 已接收长度
uint32_t total_len = 0; // 固件总长度
uint32_t crc_expected = 0; // 预期CRC值
uint8_t check_result = 0; // 校验结果
// 1. 接收升级指令(OTA_UPGRADE_CMD)
HAL_UART_Receive(&huart1, rx_buf, 4, 1000);
if(*(uint32_t*)rx_buf != OTA_UPGRADE_CMD)
{
return CHECK_ERROR;
}
HAL_UART_Transmit(&huart1, (uint8_t*)"OTA_OK", 6, 100); // 响应升级指令
// 2. 接收固件总长度(4字节)
HAL_UART_Receive(&huart1, rx_buf, 4, 1000);
total_len = *(uint32_t*)rx_buf;
if(total_len > BACKUP_SIZE_MAX) // 超过备份区大小
{
return CHECK_ERROR;
}
HAL_UART_Transmit(&huart1, (uint8_t*)"LEN_OK", 6, 100);
// 3. 擦除备份区
Bootloader_Flash_Erase(BACKUP_ADDR, total_len);
// 4. 接收固件数据(分包接收,每包1024字节)
while(rx_len < total_len)
{
uint32_t pack_len = (total_len - rx_len) > 1024 ? 1024 : (total_len - rx_len);
HAL_UART_Receive(&huart1, rx_buf, pack_len, 2000);
// 写入备份区
Bootloader_Flash_Write(BACKUP_ADDR + rx_len, rx_buf, pack_len);
rx_len += pack_len;
// 发送接收进度
HAL_UART_Transmit(&huart1, (uint8_t*)&rx_len, 4, 100);
}
// 5. 接收CRC校验值
HAL_UART_Receive(&huart1, rx_buf, 4, 1000);
crc_expected = *(uint32_t*)rx_buf;
// 6. 执行多重校验
check_result = Bootloader_CRC_Check(BACKUP_ADDR, total_len, crc_expected); // CRC校验
if(check_result != CHECK_OK) return check_result;
check_result = Bootloader_Encrypt_Check(BACKUP_ADDR, total_len); // 加密校验
if(check_result != CHECK_OK) return check_result;
// 7. 校验通过,烧写至App区
Bootloader_Flash_Erase(APP_ADDR, total_len);
for(i=0; i<total_len; i+=1024)
{
uint32_t pack_len = (total_len - i) > 1024 ? 1024 : (total_len - i);
memcpy(rx_buf, (uint8_t*)(BACKUP_ADDR + i), pack_len);
Bootloader_Flash_Write(APP_ADDR + i, rx_buf, pack_len);
}
// 8. 更新版本号
Bootloader_Flash_Write(VERSION_ADDR, (uint8_t*)&CURRENT_VERSION, 4);
return CHECK_OK;
}
3.3.8 Bootloader主函数
int main(void)
{
uint8_t app_check = 0;
uint8_t ota_result = 0;
// 1. Bootloader初始化
Bootloader_Init();
// 2. 检查是否收到OTA升级指令(串口/网口)
uint8_t cmd[4] = {0};
if(HAL_UART_Receive(&huart1, cmd, 4, 100) == HAL_OK)
{
if(*(uint32_t*)cmd == OTA_UPGRADE_CMD)
{
// 进入OTA升级模式
ota_result = Bootloader_OTA_Receive();
if(ota_result == CHECK_OK)
{
// 升级成功,LED闪烁3次
for(int i=0; i<3; i++)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
HAL_Delay(500);
}
}
else
{
// 升级失败,LED常亮
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_Delay(3000);
}
}
}
// 3. 检查App区合法性
app_check = Bootloader_Check_App();
if(app_check == CHECK_OK)
{
// 跳转至App
Bootloader_Jump_App();
}
else
{
// App无效,等待OTA升级,超时重启
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_Delay(10000);
NVIC_SystemReset(); // 系统重启
}
while(1);
}
四、App程序开发
App程序为用户业务逻辑代码,需配置中断向量表偏移,避免与Bootloader冲突。
4.1 App工程配置(Keil MDK5)
- 新建工程,选择STM32F429IGT6芯片
- 配置Flash地址:
0x08008000,大小0xD8000(864KB) - 配置中断向量表偏移:
0x08008000(在system_stm32f4xx.c中修改) - 启用HAL库,配置业务逻辑外设(如传感器、网口等)
4.2 App核心代码
4.2.1 中断向量表偏移配置
// 在system_stm32f4xx.c中修改
#define VECT_TAB_OFFSET 0x00008000 // App区中断向量表偏移
4.2.2 App主函数(业务逻辑示例)
#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();
// 业务逻辑:LED闪烁
while(1)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
HAL_Delay(1000);
HAL_UART_Transmit(&huart1, (uint8_t*)"App Running V1.0\r\n", 17, 100);
}
}
// USART1初始化
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;
HAL_UART_Init(&huart1);
}
// GPIO初始化
static void MX_GPIO_Init(void)
{
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}
五、固件生成与OTA升级流程
5.1 固件生成步骤
- 编译Bootloader工程,生成
Bootloader.hex文件,下载至STM32F429的0x08000000地址 - 编译App工程,生成
App.hex文件,使用Keil自带工具转换为App.bin二进制文件 - 计算
App.bin的CRC32值(使用CRC32工具),记录用于升级校验 - (可选)对
App.bin进行加密(如AES加密),生成加密后的固件
5.2 OTA升级操作流程
5.3 串口调试助手操作步骤
- 打开串口调试助手,配置波特率115200,8N1
- 设备上电,串口输出
Bootloader Running - 发送十六进制指令:
A5 5A 01 01(OTA升级指令) - 设备返回
OTA_OK,发送固件总长度(十六进制,4字节) - 设备返回
LEN_OK,选择App.bin文件,分包发送(每包1024字节) - 固件发送完成,发送CRC32值(十六进制,4字节)
- 设备校验通过,LED闪烁3次,重启后运行新App
六、安全校验机制详解
6.1 版本校验
- 校验逻辑:新固件版本号必须大于当前设备版本号,避免降级升级
- 实现:Bootloader读取当前版本号(存储在
0x08007FFC),与新固件版本号对比
6.2 CRC32数据完整性校验
- 校验逻辑:计算新固件的CRC32值,与发送端的预期CRC值对比,确保固件传输过程无数据丢失或篡改
- 实现:标准CRC32算法,覆盖固件所有字节,校验失败则升级终止
6.3 加密校验
- 校验逻辑:固件采用AES-128加密,Bootloader内置解密密钥,解密后校验固件头部标识(如
0xAA55AA55) - 实现:加密校验函数
Bootloader_Encrypt_Check,解密后验证标识,防止非法固件升级
6.4 升级失败回滚机制
- 逻辑:新固件先写入备份区,校验通过后再烧写至App区;若烧写过程中断,重启后从备份区回滚至原App
- 实现:Bootloader启动时检查App区合法性,无效则从备份区恢复
七、常见问题与解决方法
-
问题:Bootloader无法跳转至App
解决:检查App中断向量表偏移是否为0x08008000,App栈顶地址是否在RAM范围内,关闭MicroLIB -
问题:固件烧写失败
解决:检查Flash擦除是否完整,固件大小是否超过分区限制,写入时是否4字节对齐 -
问题:CRC校验失败
解决:确认发送端与设备端CRC算法一致,固件传输过程无丢包,分包长度一致 -
问题:升级后设备变砖
解决:启用备份区回滚机制,Bootloader增加超时重启,避免升级过程断电
八、方案扩展与优化
- 支持网口/4G/NB-IoT等物联网OTA方式,替换串口接收函数
- 增加固件差分升级,减少传输数据量
- 支持多固件分区,实现双系统备份
- 增加升级日志存储,便于问题排查
- 优化Flash读写速度,提升升级效率
本方案完整实现了STM32F429的IAP在线升级,包含详细的代码实现、流程步骤、安全校验机制,零基础用户可直接按照步骤操作,实现物联网设备的远程固件升级。

5096

被折叠的 条评论
为什么被折叠?



