一、IAP 系统架构设计
1.1 Flash 分区规划(以 STM32F407VG 512KB 为例)
0x0800 0000 ┌─────────────┐
│ Bootloader │ 16KB
0x0800 4000 ├─────────────┤
│ App 1 │ 240KB
0x0804 0000 ├─────────────┤
│ App 2 │ 240KB
0x0807 C000 ├─────────────┤
│ 升级标志区 │ 4KB
0x0808 0000 └─────────────┘
1.2 系统组成
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 服务器/PC │────▶│ 通信模块 │────▶│ STM32 IAP │
│ (固件) │ │(WiFi/4G/UART)│ │ Bootloader │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Application │
│ (用户程序)│
└─────────────┘
二、Bootloader 设计
2.1 Bootloader 主程序
c
// bootloader.h
#ifndef __BOOTLOADER_H
#define __BOOTLOADER_H
#include "stm32f4xx_hal.h"
#include <string.h>
#include <stdint.h>
// Flash 分区定义
#define FLASH_BASE_ADDR 0x08000000
#define BOOTLOADER_SIZE (16 * 1024) // 16KB
#define APP_SIZE (240 * 1024) // 240KB
#define FLAG_SIZE (4 * 1024) // 4KB
// 地址定义
#define BOOTLOADER_START FLASH_BASE_ADDR
#define BOOTLOADER_END (BOOTLOADER_START + BOOTLOADER_SIZE - 1)
#define APP1_START (BOOTLOADER_START + BOOTLOADER_SIZE)
#define APP1_END (APP1_START + APP_SIZE - 1)
#define APP2_START (APP1_START + APP_SIZE)
#define APP2_END (APP2_START + APP_SIZE - 1)
#define FLAG_START (APP2_START + APP_SIZE)
#define FLAG_END (FLAG_START + FLAG_SIZE - 1)
// 升级标志
typedef struct {
uint32_t magic; // 魔数 0xAA55A5A5
uint32_t app_address; // 目标应用程序地址
uint32_t app_size; // 应用程序大小
uint32_t app_crc; // CRC32校验值
uint8_t status; // 升级状态 0:空闲, 1:准备升级, 2:升级中, 3:升级完成
uint8_t reserved[3]; // 保留
} Update_FlagTypeDef;
#define FLAG_MAGIC 0xAA55A5A5
#define FLAG_STATUS_IDLE 0
#define FLAG_STATUS_READY 1
#define FLAG_STATUS_UPDATING 2
#define FLAG_STATUS_DONE 3
// 跳转地址定义
typedef void (*pFunction)(void);
#define APP_STACK_PTR_ADDR (APP1_START)
#define APP_RESET_HANDLER_ADDR (APP1_START + 4)
// 函数声明
void Bootloader_Init(void);
void Bootloader_Run(void);
uint8_t Bootloader_CheckUpdate(void);
void Bootloader_JumpToApp(uint32_t app_addr);
void Bootloader_StartUpdate(void);
void Bootloader_FinishUpdate(void);
uint8_t Bootloader_EraseApp(uint32_t app_addr, uint32_t size);
uint8_t Bootloader_WriteFlash(uint32_t addr, uint8_t *data, uint32_t len);
uint32_t Bootloader_CalculateCRC(uint8_t *data, uint32_t len);
#endif
2.2 Bootloader 主程序实现
c
// bootloader.c
#include "bootloader.h"
#include "uart.h"
#include "flash.h"
#include "crc.h"
#include "rtc.h"
// 升级标志
Update_FlagTypeDef update_flag;
uint8_t is_updating = 0;
uint32_t current_write_addr = 0;
uint32_t total_bytes_received = 0;
// 初始化Bootloader
void Bootloader_Init(void)
{
// 初始化外设
HAL_Init();
SystemClock_Config();
UART_Init(115200);
RTC_Init();
CRC_Init();
printf("Bootloader v1.0 Initialized\r\n");
printf("Build Date: %s %s\r\n", __DATE__, __TIME__);
// 读取升级标志
uint8_t *flag_addr = (uint8_t *)FLAG_START;
memcpy(&update_flag, flag_addr, sizeof(Update_FlagTypeDef));
if (update_flag.magic != FLAG_MAGIC) {
// 首次使用,初始化标志
memset(&update_flag, 0, sizeof(Update_FlagTypeDef));
update_flag.magic = FLAG_MAGIC;
update_flag.status = FLAG_STATUS_IDLE;
Flash_Write(FLAG_START, (uint8_t *)&update_flag, sizeof(Update_FlagTypeDef));
}
}
// 主运行循环
void Bootloader_Run(void)
{
uint8_t cmd = 0;
while (1) {
// 1. 检查是否需要升级
if (Bootloader_CheckUpdate() == 1) {
printf("Update detected, starting update process...\r\n");
Bootloader_StartUpdate();
continue;
}
// 2. 检查串口命令
if (UART_Available()) {
cmd = UART_Receive();
switch (cmd) {
case 'U': // 开始升级
printf("Enter update mode\r\n");
Bootloader_EnterUpdateMode();
break;
case 'J': // 跳转到APP
printf("Jump to application\r\n");
Bootloader_JumpToApp(APP1_START);
break;
case 'S': // 显示状态
Bootloader_ShowStatus();
break;
case 'E': // 擦除APP区域
Bootloader_EraseAppArea();
break;
case 'R': // 重启
printf("Rebooting...\r\n");
HAL_Delay(100);
NVIC_SystemReset();
break;
}
}
// 3. 如果空闲时间过长,自动跳转到APP
static uint32_t idle_time = 0;
if (idle_time++ > 1000000) { // 约10秒
printf("Auto jump to application\r\n");
Bootloader_JumpToApp(APP1_START);
}
HAL_Delay(10);
}
}
// 检查是否需要升级
uint8_t Bootloader_CheckUpdate(void)
{
if (update_flag.status == FLAG_STATUS_READY) {
return 1;
}
return 0;
}
// 进入升级模式
void Bootloader_EnterUpdateMode(void)
{
printf("Entering update mode...\r\n");
// 设置升级标志
update_flag.status = FLAG_STATUS_UPDATING;
update_flag.app_address = APP1_START; // 默认更新到APP1区域
update_flag.app_size = 0;
update_flag.app_crc = 0;
Flash_Write(FLAG_START, (uint8_t *)&update_flag, sizeof(Update_FlagTypeDef));
is_updating = 1;
current_write_addr = APP1_START;
total_bytes_received = 0;
printf("Ready to receive firmware\r\n");
printf("Send firmware in YMODEM protocol\r\n");
}
// 开始升级
void Bootloader_StartUpdate(void)
{
// 擦除目标APP区域
if (Bootloader_EraseApp(update_flag.app_address, APP_SIZE) != HAL_OK) {
printf("Erase failed!\r\n");
update_flag.status = FLAG_STATUS_IDLE;
Flash_Write(FLAG_START, (uint8_t *)&update_flag, sizeof(Update_FlagTypeDef));
return;
}
is_updating = 1;
current_write_addr = update_flag.app_address;
total_bytes_received = 0;
printf("Start receiving firmware...\r\n");
printf("Target address: 0x%08lX\r\n", update_flag.app_address);
}
// 接收固件数据
void Bootloader_ReceiveData(uint8_t *data, uint32_t len)
{
if (!is_updating) return;
// 写入Flash
if (Bootloader_WriteFlash(current_write_addr, data, len) == HAL_OK) {
current_write_addr += len;
total_bytes_received += len;
// 更新进度
if (total_bytes_received % 1024 == 0) {
printf("Received: %lu bytes\r\n", total_bytes_received);
}
} else {
printf("Write failed at 0x%08lX\r\n", current_write_addr);
is_updating = 0;
}
}
// 完成升级
void Bootloader_FinishUpdate(uint32_t file_size, uint32_t file_crc)
{
if (!is_updating) return;
update_flag.app_size = file_size;
update_flag.app_crc = file_crc;
update_flag.status = FLAG_STATUS_DONE;
// 计算实际CRC
uint32_t calculated_crc = Bootloader_CalculateCRC((uint8_t *)update_flag.app_address, file_size);
printf("Firmware update completed!\r\n");
printf("File size: %lu bytes\r\n", file_size);
printf("Expected CRC: 0x%08lX\r\n", file_crc);
printf("Calculated CRC: 0x%08lX\r\n", calculated_crc);
if (calculated_crc == file_crc) {
printf("CRC check passed!\r\n");
update_flag.status = FLAG_STATUS_DONE;
} else {
printf("CRC check failed!\r\n");
update_flag.status = FLAG_STATUS_IDLE;
}
// 保存标志
Flash_Write(FLAG_START, (uint8_t *)&update_flag, sizeof(Update_FlagTypeDef));
is_updating = 0;
// 延时后重启
printf("System will reboot in 3 seconds...\r\n");
HAL_Delay(3000);
NVIC_SystemReset();
}
// 跳转到应用程序
void Bootloader_JumpToApp(uint32_t app_addr)
{
printf("Jumping to application at 0x%08lX...\r\n", app_addr);
// 检查栈指针是否有效
uint32_t stack_pointer = *(volatile uint32_t *)app_addr;
if ((stack_pointer & 0x2FFE0000) != 0x20000000) {
printf("Invalid stack pointer: 0x%08lX\r\n", stack_pointer);
return;
}
// 检查复位向量是否有效
uint32_t reset_handler = *(volatile uint32_t *)(app_addr + 4);
if (reset_handler < app_addr || reset_handler > (app_addr + APP_SIZE)) {
printf("Invalid reset handler: 0x%08lX\r\n", reset_handler);
return;
}
// 禁用所有中断
__disable_irq();
// 设置向量表偏移
SCB->VTOR = app_addr;
// 设置栈指针
__set_MSP(*(volatile uint32_t *)app_addr);
// 跳转到应用程序
pFunction jump_to_app = (pFunction)(*(volatile uint32_t *)(app_addr + 4));
jump_to_app();
// 不会执行到这里
while (1);
}
// 显示状态
void Bootloader_ShowStatus(void)
{
printf("=== Bootloader Status ===\r\n");
printf("Bootloader Address: 0x%08lX\r\n", BOOTLOADER_START);
printf("APP1 Address: 0x%08lX\r\n", APP1_START);
printf("APP2 Address: 0x%08lX\r\n", APP2_START);
printf("Flag Address: 0x%08lX\r\n", FLAG_START);
printf("\r\n");
printf("Update Flag:\r\n");
printf(" Magic: 0x%08lX\r\n", update_flag.magic);
printf(" Status: %d", update_flag.status);
switch(update_flag.status) {
case FLAG_STATUS_IDLE: printf(" (IDLE)\r\n"); break;
case FLAG_STATUS_READY: printf(" (READY)\r\n"); break;
case FLAG_STATUS_UPDATING: printf(" (UPDATING)\r\n"); break;
case FLAG_STATUS_DONE: printf(" (DONE)\r\n"); break;
}
printf(" App Address: 0x%08lX\r\n", update_flag.app_address);
printf(" App Size: %lu bytes\r\n", update_flag.app_size);
printf(" App CRC: 0x%08lX\r\n", update_flag.app_crc);
}
2.3 Flash 操作函数
c
// flash.c
#include "flash.h"
#include "stm32f4xx_hal.h"
// Flash解锁
void Flash_Unlock(void)
{
HAL_FLASH_Unlock();
}
// Flash加锁
void Flash_Lock(void)
{
HAL_FLASH_Lock();
}
// 擦除Flash扇区
uint8_t Flash_EraseSector(uint32_t sector)
{
FLASH_EraseInitTypeDef erase;
uint32_t sector_error = 0;
erase.TypeErase = FLASH_TYPEERASE_SECTORS;
erase.Banks = FLASH_BANK_1;
erase.Sector = sector;
erase.NbSectors = 1;
erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;
if (HAL_FLASHEx_Erase(&erase, §or_error) != HAL_OK) {
return HAL_ERROR;
}
return HAL_OK;
}
// 擦除应用程序区域
uint8_t Bootloader_EraseApp(uint32_t app_addr, uint32_t size)
{
uint32_t start_sector = 0;
uint32_t end_sector = 0;
uint32_t i;
// 计算起始扇区
if (app_addr >= APP1_START && app_addr < APP1_END) {
start_sector = FLASH_SECTOR_4; // APP1起始扇区
} else if (app_addr >= APP2_START && app_addr < APP2_END) {
start_sector = FLASH_SECTOR_8; // APP2起始扇区
} else {
return HAL_ERROR;
}
// 计算需要擦除的扇区数
end_sector = start_sector + (size / 0x40000); // 每个扇区256KB
if (size % 0x40000) end_sector++;
Flash_Unlock();
for (i = start_sector; i < end_sector; i++) {
if (Flash_EraseSector(i) != HAL_OK) {
Flash_Lock();
return HAL_ERROR;
}
printf("Erased sector %lu\r\n", i);
}
Flash_Lock();
return HAL_OK;
}
// 写入Flash
uint8_t Bootloader_WriteFlash(uint32_t addr, uint8_t *data, uint32_t len)
{
uint32_t i;
uint32_t *pdata = (uint32_t *)data;
// 地址必须4字节对齐
if (addr % 4 != 0) {
return HAL_ERROR;
}
// 长度必须是4的倍数
if (len % 4 != 0) {
return HAL_ERROR;
}
Flash_Unlock();
for (i = 0; i < len; i += 4) {
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + i, *pdata) != HAL_OK) {
Flash_Lock();
return HAL_ERROR;
}
pdata++;
}
Flash_Lock();
return HAL_OK;
}
// 计算CRC32
uint32_t Bootloader_CalculateCRC(uint8_t *data, uint32_t len)
{
uint32_t crc = 0xFFFFFFFF;
uint32_t i;
for (i = 0; i < len; i++) {
crc = crc ^ data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x00000001) {
crc = (crc >> 1) ^ 0xEDB88320;
} else {
crc = crc >> 1;
}
}
}
return ~crc;
}
2.4 YModem 协议实现
c
// ymodem.c
#include "ymodem.h"
#include "uart.h"
#define SOH 0x01
#define STX 0x02
#define EOT 0x04
#define ACK 0x06
#define NAK 0x15
#define CAN 0x18
#define C 0x43
typedef struct {
uint8_t header;
uint8_t block_num;
uint8_t block_num_inv;
uint8_t data[1024];
uint16_t crc16;
} YModem_Packet;
// 接收一个YModem包
static uint8_t YModem_ReceivePacket(YModem_Packet *packet, uint32_t timeout)
{
uint8_t header = 0;
uint32_t start_time = HAL_GetTick();
// 等待包头
while (HAL_GetTick() - start_time < timeout) {
if (UART_Available()) {
header = UART_Receive();
if (header == SOH || header == STX) {
packet->header = header;
break;
} else if (header == EOT) {
return 2; // 文件结束
} else if (header == CAN) {
return 3; // 取消传输
}
}
}
if (packet->header == 0) {
return 0; // 超时
}
// 接收块号
packet->block_num = UART_Receive();
packet->block_num_inv = UART_Receive();
// 验证块号
if (packet->block_num + packet->block_num_inv != 0xFF) {
return 0;
}
// 接收数据
uint16_t data_len = (packet->header == SOH) ? 128 : 1024;
for (int i = 0; i < data_len; i++) {
while (!UART_Available());
packet->data[i] = UART_Receive();
}
// 接收CRC16
while (!UART_Available());
packet->crc16 = UART_Receive() << 8;
while (!UART_Available());
packet->crc16 |= UART_Receive();
// 验证CRC
uint16_t calc_crc = CRC16_Calculate(packet->data, data_len);
if (calc_crc != packet->crc16) {
return 0;
}
return 1; // 成功
}
// YModem接收文件
uint8_t YModem_ReceiveFile(uint8_t *buffer, uint32_t *file_size, uint32_t *file_crc, uint32_t timeout)
{
YModem_Packet packet;
uint8_t result = 0;
uint16_t block_num = 0;
uint32_t total_size = 0;
uint32_t crc32 = 0xFFFFFFFF;
uint8_t file_name[128] = {0};
uint8_t file_size_str[16] = {0};
printf("Waiting for YModem transfer...\r\n");
// 发送'C'启动传输
UART_SendByte(C);
// 接收第一个包(文件名和文件大小)
result = YModem_ReceivePacket(&packet, timeout);
if (result != 1) {
return 0;
}
// 解析文件名
uint8_t *ptr = packet.data;
uint8_t i = 0;
while (*ptr != 0 && i < sizeof(file_name) - 1) {
file_name[i++] = *ptr++;
}
file_name[i] = 0;
// 解析文件大小
ptr++; // 跳过0
i = 0;
while (*ptr != 0 && i < sizeof(file_size_str) - 1) {
file_size_str[i++] = *ptr++;
}
file_size_str[i] = 0;
*file_size = atoi((char *)file_size_str);
printf("Receiving file: %s, size: %lu bytes\r\n", file_name, *file_size);
// 发送ACK
UART_SendByte(ACK);
// 发送'C'准备接收数据
UART_SendByte(C);
// 接收数据包
block_num = 1;
while (1) {
result = YModem_ReceivePacket(&packet, timeout);
if (result == 2) { // EOT
UART_SendByte(NAK); // 第一次回应NAK
result = YModem_ReceivePacket(&packet, timeout);
if (result == 2) { // 第二次EOT
UART_SendByte(ACK);
break;
}
} else if (result == 1) { // 数据包
if (packet.block_num != (block_num & 0xFF)) {
// 包序号错误
UART_SendByte(CAN);
UART_SendByte(CAN);
return 0;
}
// 计算数据长度
uint16_t data_len = (packet.header == SOH) ? 128 : 1024;
// 写入缓冲区
if (buffer) {
memcpy(buffer + total_size, packet.data, data_len);
}
// 更新CRC
for (int i = 0; i < data_len; i++) {
crc32 = crc32 ^ packet.data[i];
for (int j = 0; j < 8; j++) {
if (crc32 & 1) {
crc32 = (crc32 >> 1) ^ 0xEDB88320;
} else {
crc32 = crc32 >> 1;
}
}
}
total_size += data_len;
block_num++;
// 发送ACK
UART_SendByte(ACK);
} else {
// 错误
UART_SendByte(CAN);
UART_SendByte(CAN);
return 0;
}
// 显示进度
if (total_size % 1024 == 0) {
printf("Received: %lu/%lu bytes\r\n", total_size, *file_size);
}
}
*file_crc = ~crc32;
return 1;
}
三、Application 设计
3.1 应用程序的链接脚本修改
ld
/* STM32F407VG_FLASH.ld - 修改链接脚本 */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K
FLASH (rx) : ORIGIN = 0x08004000, LENGTH = 240K /* APP1区域 */
/* 注意:ORIGIN 必须与 Bootloader 中的 APP1_START 一致 */
}
/* 入口点 */
ENTRY(Reset_Handler)
/* 定义堆栈大小 */
_Min_Heap_Size = 0x200; /* 512字节 */
_Min_Stack_Size = 0x400; /* 1KB */
SECTIONS
{
/* 程序代码 */
.text :
{
. = ALIGN(4);
*(.isr_vector) /* 中断向量表 */
*(.text) /* 代码段 */
*(.rodata) /* 只读数据 */
. = ALIGN(4);
} >FLASH
/* 其他段保持不变... */
}
3.2 应用程序的中断向量表重定位
c
// system_stm32f4xx.c
#include "stm32f4xx.h"
// 在SystemInit函数中添加向量表重定位
void SystemInit(void)
{
/* 设置中断向量表偏移到0x08004000 */
SCB->VTOR = FLASH_BASE | 0x4000;
/* 其他初始化代码... */
}
3.3 应用程序的IAP接口
c
// iap_interface.h
#ifndef __IAP_INTERFACE_H
#define __IAP_INTERFACE_H
#include "stm32f4xx.h"
// IAP命令定义
#define IAP_CMD_ENTER_BOOTLOADER 0x01
#define IAP_CMD_START_UPDATE 0x02
#define IAP_CMD_GET_STATUS 0x03
#define IAP_CMD_RESET 0x04
// 共享内存地址(使用备份寄存器或SRAM)
#define SHARED_MEMORY_BASE ((uint32_t)0x2000F000)
#define IAP_FLAG_ADDRESS (SHARED_MEMORY_BASE)
#define UPDATE_FLAG_ADDRESS (BOOTLOADER_START + BOOTLOADER_SIZE - 4)
// 函数声明
void IAP_JumpToBootloader(void);
void IAP_StartUpdate(uint32_t app_address);
uint8_t IAP_CheckUpdate(void);
void IAP_Reboot(void);
#endif
c
// iap_interface.c
#include "iap_interface.h"
#include "stm32f4xx_hal.h"
// 跳转到Bootloader
void IAP_JumpToBootloader(void)
{
// 设置跳转标志
*((volatile uint32_t *)IAP_FLAG_ADDRESS) = 0xDEADBEEF;
// 软重启
NVIC_SystemReset();
}
// 在Bootloader中检查跳转标志
uint8_t Bootloader_CheckJumpFlag(void)
{
uint32_t flag = *((volatile uint32_t *)IAP_FLAG_ADDRESS);
if (flag == 0xDEADBEEF) {
// 清除标志
*((volatile uint32_t *)IAP_FLAG_ADDRESS) = 0;
return 1;
}
return 0;
}
// 检查是否需要升级
uint8_t IAP_CheckUpdate(void)
{
Update_FlagTypeDef flag;
// 从Flash读取升级标志
uint8_t *flag_addr = (uint8_t *)FLAG_START;
// 检查是否在Bootloader区域
if ((uint32_t)flag_addr >= BOOTLOADER_START &&
(uint32_t)flag_addr <= BOOTLOADER_END) {
return 0;
}
memcpy(&flag, flag_addr, sizeof(Update_FlagTypeDef));
if (flag.magic == FLAG_MAGIC && flag.status == FLAG_STATUS_READY) {
return 1;
}
return 0;
}
3.4 应用程序的看门狗和心跳
c
// app_wdt.c
#include "app_wdt.h"
#include "stm32f4xx_hal.h"
#include "iap_interface.h"
IWDG_HandleTypeDef hiwdg;
// 初始化独立看门狗
void WDT_Init(void)
{
hiwdg.Instance = IWDG;
hiwdg.Init.Prescaler = IWDG_PRESCALER_256; // 256分频
hiwdg.Init.Reload = 4095; // 约1秒超时
hiwdg.Init.Window = 4095;
if (HAL_IWDG_Init(&hiwdg) != HAL_OK) {
Error_Handler();
}
}
// 喂狗
void WDT_Feed(void)
{
HAL_IWDG_Refresh(&hiwdg);
}
// 应用程序心跳线程
void App_HeartbeatTask(void)
{
static uint32_t last_feed = 0;
uint32_t current_time = HAL_GetTick();
// 每100ms喂一次狗
if (current_time - last_feed > 100) {
WDT_Feed();
last_feed = current_time;
}
// 检查是否需要升级
if (IAP_CheckUpdate()) {
printf("Update detected, jumping to bootloader...\r\n");
IAP_JumpToBootloader();
}
}
参考代码 STM32在线升级IAP www.youwenfan.com/contentcsv/103159.html
四、远程升级服务器端
4.1 Python 升级服务器
python
# upgrade_server.py
import socket
import struct
import time
import hashlib
import serial
import threading
from datetime import datetime
class STM32Updater:
def __init__(self, port='COM3', baudrate=115200):
self.ser = serial.Serial(port, baudrate, timeout=1)
self.file_data = None
self.file_size = 0
self.file_crc = 0
def calculate_crc32(self, data):
"""计算CRC32"""
crc = 0xFFFFFFFF
for byte in data:
crc = crc ^ byte
for _ in range(8):
if crc & 1:
crc = (crc >> 1) ^ 0xEDB88320
else:
crc = crc >> 1
return ~crc & 0xFFFFFFFF
def send_command(self, cmd, timeout=1):
"""发送命令到设备"""
self.ser.write(cmd.encode())
time.sleep(0.1)
# 等待响应
start_time = time.time()
while time.time() - start_time < timeout:
if self.ser.in_waiting:
response = self.ser.read(self.ser.in_waiting).decode()
return response
return None
def enter_bootloader(self):
"""进入Bootloader模式"""
print("Entering bootloader mode...")
response = self.send_command('U')
if response and 'update mode' in response:
return True
return False
def send_ymodem_packet(self, packet_num, data, is_last=False):
"""发送YModem数据包"""
if is_last:
# 发送EOT
self.ser.write(b'\x04')
time.sleep(0.1)
return True
packet_size = len(data)
if packet_size <= 128:
header = b'\x01' # SOH
else:
header = b'\x02' # STX
packet_size = 1024
# 填充数据
if len(data) < packet_size:
data = data + b'\x1a' * (packet_size - len(data))
# 构建包
packet = header
packet += struct.pack('B', packet_num & 0xFF)
packet += struct.pack('B', (~packet_num) & 0xFF)
packet += data[:packet_size]
# 计算CRC16
crc = 0
for byte in data[:packet_size]:
crc = crc ^ (byte << 8)
for _ in range(8):
if crc & 0x8000:
crc = (crc << 1) ^ 0x1021
else:
crc = crc << 1
crc = crc & 0xFFFF
packet += struct.pack('>H', crc)
# 发送包
self.ser.write(packet)
# 等待ACK
response = self.ser.read(1)
return response == b'\x06'
def send_file_ymodem(self, filename):
"""使用YModem协议发送文件"""
with open(filename, 'rb') as f:
file_data = f.read()
self.file_size = len(file_data)
self.file_crc = self.calculate_crc32(file_data)
print(f"Sending file: {filename}")
print(f"File size: {self.file_size} bytes")
print(f"CRC32: 0x{self.file_crc:08X}")
# 发送文件名和大小
filename_bytes = filename.encode() + b'\x00'
filesize_str = str(self.file_size).encode() + b'\x00'
first_packet_data = filename_bytes + filesize_str
first_packet_data = first_packet_data.ljust(128, b'\x00')
# 发送第一个包
if not self.send_ymodem_packet(0, first_packet_data):
print("Failed to send first packet")
return False
# 发送数据包
packet_num = 1
bytes_sent = 0
while bytes_sent < self.file_size:
# 获取数据块
chunk_size = min(1024, self.file_size - bytes_sent)
chunk = file_data[bytes_sent:bytes_sent + chunk_size]
# 发送包
if not self.send_ymodem_packet(packet_num, chunk):
print(f"Failed to send packet {packet_num}")
return False
bytes_sent += chunk_size
packet_num += 1
# 显示进度
progress = (bytes_sent / self.file_size) * 100
print(f"Progress: {progress:.1f}% ({bytes_sent}/{self.file_size})", end='\r')
print() # 换行
# 发送结束包
if not self.send_ymodem_packet(packet_num, b'', is_last=True):
print("Failed to send EOT")
return False
print("File sent successfully!")
return True
def update_firmware(self, firmware_file):
"""更新固件"""
# 1. 进入Bootloader
if not self.enter_bootloader():
print("Failed to enter bootloader mode")
return False
time.sleep(1)
# 2. 清空串口缓冲区
self.ser.reset_input_buffer()
# 3. 发送固件
if not self.send_file_ymodem(firmware_file):
print("Failed to send firmware")
return False
# 4. 等待重启
print("Waiting for device to reboot...")
time.sleep(5)
# 5. 检查设备是否正常启动
response = self.send_command('J', timeout=3)
if response and 'application' in response:
print("Update successful! Device is running new firmware.")
return True
else:
print("Update may have failed. Please check device.")
return False
def get_device_info(self):
"""获取设备信息"""
response = self.send_command('S')
if response:
print("Device Info:")
print(response)
return True
return False
def reboot_device(self):
"""重启设备"""
print("Rebooting device...")
self.send_command('R')
time.sleep(2)
return True
# HTTP服务器用于远程升级
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
class UpdateHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/status':
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
status = {
'status': 'idle',
'version': '1.0.0',
'last_update': '2024-01-01 00:00:00'
}
self.wfile.write(json.dumps(status).encode())
elif self.path == '/update':
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
html = '''
<html>
<body>
<h1>Firmware Update</h1>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="firmware">
<input type="submit" value="Update">
</form>
</body>
</html>
'''
self.wfile.write(html.encode())
def do_POST(self):
if self.path == '/upload':
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
# 解析文件上传
# 这里简化处理,实际需要解析multipart/form-data
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
response = {'status': 'success', 'message': 'Firmware uploaded'}
self.wfile.write(json.dumps(response).encode())
def run_http_server(port=8080):
"""运行HTTP服务器"""
server = HTTPServer(('0.0.0.0', port), UpdateHandler)
print(f'HTTP server running on port {port}')
server.serve_forever()
# 主程序
if __name__ == '__main__':
import sys
if len(sys.argv) < 3:
print("Usage: python upgrade_server.py <COM_PORT> <FIRMWARE_FILE>")
print("Example: python upgrade_server.py COM3 app.bin")
sys.exit(1)
com_port = sys.argv[1]
firmware_file = sys.argv[2]
# 创建更新器
updater = STM32Updater(port=com_port)
try:
# 获取设备信息
updater.get_device_info()
# 更新固件
if updater.update_firmware(firmware_file):
print("Firmware update completed successfully!")
else:
print("Firmware update failed!")
except Exception as e:
print(f"Error: {e}")
finally:
updater.ser.close()
4.2 固件打包工具
python
# firmware_packager.py
import struct
import hashlib
import zlib
import os
from datetime import datetime
class FirmwarePackager:
def __init__(self):
self.header_size = 128
self.version_major = 1
self.version_minor = 0
self.version_patch = 0
def create_firmware_header(self, file_size, crc32, app_address=0x08004000):
"""创建固件头"""
header = bytearray(self.header_size)
# 魔数
struct.pack_into('<I', header, 0, 0xAA55A5A5)
# 固件版本
struct.pack_into('<BBB', header, 4,
self.version_major,
self.version_minor,
self.version_patch)
# 时间戳
timestamp = int(datetime.now().timestamp())
struct.pack_into('<I', header, 8, timestamp)
# 固件大小
struct.pack_into('<I', header, 12, file_size)
# CRC32
struct.pack_into('<I', header, 16, crc32)
# 应用程序地址
struct.pack_into('<I', header, 20, app_address)
# 硬件兼容性
hw_compatible = [
'STM32F407',
'STM32F407VG',
'STM32F407VE'
]
hw_str = ','.join(hw_compatible)
hw_bytes = hw_str.encode('ascii')
header[24:24+len(hw_bytes)] = hw_bytes
# 固件描述
description = "STM32 Application Firmware"
desc_bytes = description.encode('ascii')
header[64:64+len(desc_bytes)] = desc_bytes
# 计算头部CRC
header_crc = zlib.crc32(header[:124])
struct.pack_into('<I', header, 124, header_crc)
return header
def package_firmware(self, input_file, output_file, app_address=0x08004000):
"""打包固件"""
# 读取原始固件
with open(input_file, 'rb') as f:
firmware_data = f.read()
# 计算CRC
crc32 = zlib.crc32(firmware_data) & 0xFFFFFFFF
# 创建头部
header = self.create_firmware_header(len(firmware_data), crc32, app_address)
# 写入输出文件
with open(output_file, 'wb') as f:
f.write(header)
f.write(firmware_data)
print(f"Firmware packaged successfully!")
print(f"Input: {input_file}")
print(f"Output: {output_file}")
print(f"Size: {len(firmware_data)} bytes")
print(f"CRC32: 0x{crc32:08X}")
print(f"App Address: 0x{app_address:08X}")
return True
def verify_firmware(self, firmware_file):
"""验证固件文件"""
with open(firmware_file, 'rb') as f:
data = f.read()
if len(data) < self.header_size:
print("Invalid firmware file: too small")
return False
# 解析头部
magic, = struct.unpack_from('<I', data, 0)
if magic != 0xAA55A5A5:
print("Invalid magic number")
return False
version = struct.unpack_from('<BBB', data, 4)
timestamp, = struct.unpack_from('<I', data, 8)
file_size, = struct.unpack_from('<I', data, 12)
crc32, = struct.unpack_from('<I', data, 16)
app_address, = struct.unpack_from('<I', data, 20)
header_crc, = struct.unpack_from('<I', data, 124)
# 验证头部CRC
calc_header_crc = zlib.crc32(data[:124]) & 0xFFFFFFFF
if calc_header_crc != header_crc:
print("Header CRC mismatch")
return False
# 验证数据CRC
firmware_data = data[self.header_size:self.header_size + file_size]
if len(firmware_data) != file_size:
print("File size mismatch")
return False
calc_crc = zlib.crc32(firmware_data) & 0xFFFFFFFF
if calc_crc != crc32:
print("Data CRC mismatch")
return False
print(f"Firmware verification passed!")
print(f"Version: {version[0]}.{version[1]}.{version[2]}")
print(f"Timestamp: {datetime.fromtimestamp(timestamp)}")
print(f"Size: {file_size} bytes")
print(f"App Address: 0x{app_address:08X}")
print(f"CRC32: 0x{crc32:08X}")
return True
if __name__ == '__main__':
import sys
if len(sys.argv) < 3:
print("Usage: python firmware_packager.py <input.bin> <output.bin> [app_address]")
print("Example: python firmware_packager.py app.bin app_packed.bin 0x08004000")
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2]
app_address = 0x08004000
if len(sys.argv) > 3:
app_address = int(sys.argv[3], 16)
packager = FirmwarePackager()
packager.package_firmware(input_file, output_file, app_address)
五、编译和部署
5.1 Bootloader 编译配置
makefile
# Bootloader Makefile
TARGET = bootloader
MCU = STM32F407VG
# 链接脚本
LDSCRIPT = STM32F407VG_FLASH_BOOT.ld
# 编译选项
CFLAGS = -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard
CFLAGS += -DUSE_HAL_DRIVER -DSTM32F407xx
CFLAGS += -DBOOTLOADER_START=0x08000000
CFLAGS += -DAPP1_START=0x08004000
CFLAGS += -DAPP2_START=0x08040000
CFLAGS += -Os -g3 -Wall
# 链接选项
LDFLAGS = -T$(LDSCRIPT) -Wl,-Map=$(TARGET).map,--cref
LDFLAGS += -Wl,--gc-sections
# 源文件
SRCS = bootloader.c flash.c uart.c crc.c ymodem.c system_stm32f4xx.c
OBJS = $(SRCS:.c=.o)
all: $(TARGET).bin
$(TARGET).bin: $(TARGET).elf
arm-none-eabi-objcopy -O binary $< $@
$(TARGET).elf: $(OBJS)
arm-none-eabi-gcc $(CFLAGS) $(LDFLAGS) -o $@ $^
5.2 Application 编译配置
makefile
# Application Makefile
TARGET = application
MCU = STM32F407VG
# 链接脚本 - 注意起始地址
LDSCRIPT = STM32F407VG_FLASH_APP.ld
# 编译选项
CFLAGS = -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard
CFLAGS += -DUSE_HAL_DRIVER -DSTM32F407xx
CFLAGS += -DAPP_START=0x08004000
CFLAGS += -Os -g3 -Wall
# 链接选项
LDFLAGS = -T$(LDSCRIPT) -Wl,-Map=$(TARGET).map,--cref
LDFLAGS += -Wl,--gc-sections
LDFLAGS += -Wl,--defsym=APP_START=0x08004000
# 源文件
SRCS = main.c system_stm32f4xx.c iap_interface.c app_wdt.c
OBJS = $(SRCS:.c=.o)
all: $(TARGET).bin
$(TARGET).bin: $(TARGET).elf
arm-none-eabi-objcopy -O binary $< $@
# 添加固件头
python firmware_packager.py $(TARGET).bin $(TARGET)_packed.bin 0x08004000
$(TARGET).elf: $(OBJS)
arm-none-eabi-gcc $(CFLAGS) $(LDFLAGS) -o $@ $^
5.3 下载脚本
python
# download.py
import serial
import time
import sys
import struct
def send_firmware(port, firmware_file, baudrate=115200):
"""通过串口下载固件"""
try:
ser = serial.Serial(port, baudrate, timeout=1)
print(f"Connected to {port}")
# 等待设备就绪
time.sleep(1)
ser.reset_input_buffer()
# 发送进入Bootloader命令
print("Entering bootloader mode...")
ser.write(b'U')
time.sleep(0.5)
# 读取响应
response = ser.read(ser.in_waiting)
if b'update mode' not in response:
print("Failed to enter bootloader mode")
return False
print("Bootloader mode entered successfully")
# 读取固件文件
with open(firmware_file, 'rb') as f:
firmware = f.read()
# 发送固件大小
file_size = len(firmware)
print(f"Firmware size: {file_size} bytes")
# 使用YModem协议发送
# 这里简化,实际应使用完整YModem
# 发送数据
chunk_size = 1024
total_chunks = (file_size + chunk_size - 1) // chunk_size
for i in range(total_chunks):
start = i * chunk_size
end = min(start + chunk_size, file_size)
chunk = firmware[start:end]
# 发送块
ser.write(chunk)
# 等待ACK
ack = ser.read(1)
if ack != b'\x06':
print(f"Failed at chunk {i}")
return False
# 显示进度
progress = (i + 1) * 100 // total_chunks
print(f"Progress: {progress}%", end='\r')
print("\nFirmware sent successfully!")
# 发送完成标志
ser.write(b'EOT')
time.sleep(0.1)
# 等待重启
time.sleep(3)
# 验证固件
print("Verifying firmware...")
ser.write(b'S')
time.sleep(0.5)
response = ser.read(ser.in_waiting)
print(f"Device response: {response}")
ser.close()
return True
except Exception as e:
print(f"Error: {e}")
return False
if __name__ == '__main__':
if len(sys.argv) < 3:
print("Usage: python download.py <COM_PORT> <FIRMWARE_FILE>")
sys.exit(1)
port = sys.argv[1]
firmware = sys.argv[2]
if send_firmware(port, firmware):
print("Download successful!")
else:
print("Download failed!")
六、测试流程
6.1 测试步骤
bash
# 1. 编译Bootloader
cd bootloader
make clean
make
# 2. 编译Application
cd ../application
make clean
make
# 3. 下载Bootloader
st-flash write bootloader.bin 0x08000000
# 4. 测试Bootloader
python test_bootloader.py COM3
# 5. 下载Application
python download.py COM3 application_packed.bin
# 6. 远程升级测试
python upgrade_server.py COM3 application_v2_packed.bin
6.2 自动化测试脚本
python
# test_ota.py
import unittest
import serial
import time
import os
class TestOTA(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.ser = serial.Serial('COM3', 115200, timeout=1)
time.sleep(1)
def test_01_bootloader_connection(self):
"""测试Bootloader连接"""
self.ser.write(b'S')
time.sleep(0.5)
response = self.ser.read(self.ser.in_waiting)
self.assertIn(b'Bootloader', response)
def test_02_jump_to_app(self):
"""测试跳转到应用程序"""
self.ser.write(b'J')
time.sleep(0.5)
response = self.ser.read(self.ser.in_waiting)
self.assertIn(b'application', response)
time.sleep(2)
def test_03_enter_update_mode(self):
"""测试进入升级模式"""
# 从应用程序跳回Bootloader
self.ser.write(b'U')
time.sleep(0.5)
response = self.ser.read(self.ser.in_waiting)
self.assertIn(b'update mode', response)
def test_04_firmware_update(self):
"""测试固件升级"""
# 发送测试固件
with open('test_firmware.bin', 'rb') as f:
firmware = f.read()
# 发送固件
self.ser.write(firmware[:1024]) # 发送第一部分
# 检查响应
time.sleep(0.1)
response = self.ser.read(self.ser.in_waiting)
self.assertNotIn(b'Error', response)
@classmethod
def tearDownClass(cls):
cls.ser.close()
if __name__ == '__main__':
unittest.main()
七、安全增强
7.1 固件加密
c
// firmware_crypto.c
#include "firmware_crypto.h"
#include "aes.h"
// AES加密固件
uint8_t Firmware_Encrypt(uint8_t *input, uint8_t *output, uint32_t len, uint8_t *key)
{
AES_ctx ctx;
uint32_t i;
// 填充到16字节对齐
uint32_t padded_len = ((len + 15) / 16) * 16;
uint8_t *padded_input = malloc(padded_len);
memcpy(padded_input, input, len);
memset(padded_input + len, 0, padded_len - len);
// 初始化AES
AES_init_ctx(&ctx, key);
// 加密
for (i = 0; i < padded_len; i += 16) {
AES_ECB_encrypt(&ctx, padded_input + i);
}
memcpy(output, padded_input, padded_len);
free(padded_input);
return padded_len;
}
// 固件签名验证
uint8_t Firmware_VerifySignature(uint8_t *firmware, uint32_t len, uint8_t *signature)
{
// 使用HMAC-SHA256验证
uint8_t hash[32];
SHA256_Calculate(firmware, len, hash);
// 验证签名
if (memcmp(hash, signature, 32) == 0) {
return 1;
}
return 0;
}
7.2 安全启动
c
// secure_boot.c
#include "secure_boot.h"
// 安全启动验证
uint8_t SecureBoot_Verify(uint32_t app_addr)
{
Firmware_Header *header = (Firmware_Header *)app_addr;
// 1. 检查魔数
if (header->magic != FIRMWARE_MAGIC) {
return 0;
}
// 2. 检查版本
if (header->version_major < MIN_SUPPORTED_VERSION) {
return 0;
}
// 3. 验证签名
uint8_t *firmware_data = (uint8_t *)(app_addr + sizeof(Firmware_Header));
if (!Firmware_VerifySignature(firmware_data, header->size, header->signature)) {
return 0;
}
// 4. 验证CRC
uint32_t calc_crc = CRC32_Calculate(firmware_data, header->size);
if (calc_crc != header->crc) {
return 0;
}
return 1;
}
这个完整的STM32 IAP远程升级方案包含:
- Bootloader:支持串口、YModem协议
- 应用程序:支持远程升级请求
- 服务器端:Python实现的升级服务器
- 安全机制:加密、签名验证
- 测试工具:完整的测试流程