STM32 在线升级 IAP(远程固件升级)方案

一、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, &sector_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远程升级方案包含:

  1. Bootloader:支持串口、YModem协议
  2. 应用程序:支持远程升级请求
  3. 服务器端:Python实现的升级服务器
  4. 安全机制:加密、签名验证
  5. 测试工具:完整的测试流程
相关推荐
都在酒里6 小时前
FreeRTOS 手动移植教程(四):队列 —— 任务间通信的最佳起点
stm32·单片机·rtos
都在酒里6 小时前
FreeRTOS 手动移植教程(二):任务管理——多任务创建、优先级抢占与删除
stm32·单片机·嵌入式硬件·rtos
都在酒里7 小时前
FreeRTOS 手动移植教程(五):信号量 —— 任务同步与中断通知的优雅解决方案
stm32·单片机·rtos·嵌入式软件
2601_961194029 小时前
考研政治历年真题及解析pdf
stm32·单片机·嵌入式硬件·物联网·考研·pdf
今日待办9 小时前
STM32H747I-DISCO 开发指南【数字麦克风使用】
stm32·单片机·嵌入式硬件
嵌入式ZYXC9 小时前
第7章:原理图设计与阅读——从“能看懂”到“会画”的关键一跃
stm32·单片机·嵌入式硬件·物联网
破晓单片机9 小时前
007、STM32单片机分享:宠物喂食器系统
stm32·单片机·嵌入式硬件
嵌入式小站9 小时前
STM32 零基础可移植教程 16:ADC + DMA 连续采样,为什么不用 CPU 一直搬数据
stm32·单片机·嵌入式硬件
抓虾爪10 小时前
STM32F407VGT6一站式配齐丨粤科源兴ST分销商,同系列F4/F7/H7均可配套
stm32·单片机·嵌入式硬件