STM32串口IAP(在应用编程)例程

STM32串口IAP(在应用编程)例程

系统架构

PC端升级工具 ←UART→ STM32 Bootloader ←内部Flash→ 用户应用程序
                    ↓
              固件接收、校验、烧写

工程结构

IAP_Project/
├── Bootloader/          # Bootloader程序
│   ├── Core/
│   ├── Drivers/
│   ├── User/
│   │   ├── iap.c
│   │   ├── iap.h
│   │   ├── ymodem.c
│   │   └── ymodem.h
│   └── MDK-ARM/
├── Application/         # 用户应用程序
│   ├── Core/
│   ├── Drivers/
│   ├── User/
│   └── MDK-ARM/
├── PC_Tool/            # PC端升级工具
└── Flash_Map.md        # Flash分区说明

Flash地址规划(以STM32F103C8T6为例,64KB Flash)

区域 起始地址 结束地址 大小 功能
Bootloader 0x08000000 0x08002FFF 12KB IAP引导程序
Application 0x08003000 0x0800FFFF 52KB 用户应用程序
参数存储 0x0801F800 0x0801FFFF 2KB 升级标志、参数

C语言实现

1. Bootloader主程序(bootloader.c)

/************************************************************
 * STM32串口IAP Bootloader程序
 * MCU: STM32F103C8T6
 * 功能:通过串口1接收固件并写入Flash
 * 协议:Ymodem
 * 编译:Keil MDK-ARM V5
 ************************************************************/

#include "stm32f10x.h"
#include "stdio.h"
#include "string.h"
#include "ymodem.h"
#include "iap.h"

// 定义Flash地址
#define FLASH_APP_ADDR    0x08003000  // 应用程序起始地址
#define FLASH_PARAM_ADDR  0x0801F800  // 参数存储地址
#define FLASH_TOTAL_SIZE  0x10000     // 64KB

// 升级标志结构
typedef struct {
    uint32_t upgrade_flag;    // 升级标志
    uint32_t app_size;        // 应用程序大小
    uint32_t app_crc;         // CRC校验值
    uint32_t app_version;     // 版本号
} IAP_Param_t;

// 全局变量
IAP_Param_t iap_param;
uint8_t uart_rx_buffer[1024];

// 函数声明
void System_Init(void);
void UART1_Init(uint32_t baudrate);
void GPIO_Init(void);
void TIM2_Init(void);
void LED_Blink(uint8_t times);
void Jump_To_App(void);
uint8_t Check_App_Valid(void);
void IAP_Process(void);

/************************************************************
 * 主函数
 ************************************************************/
int main(void) {
    // 系统初始化
    System_Init();
    
    printf("\r\n\r\n=== STM32 IAP Bootloader v1.0 ===\r\n");
    printf("Bootloader Start Address: 0x%08X\r\n", 0x08000000);
    printf("Application Start Address: 0x%08X\r\n", FLASH_APP_ADDR);
    printf("Flash Total Size: %dKB\r\n", FLASH_TOTAL_SIZE/1024);
    
    // 检查应用程序是否有效
    if(Check_App_Valid()) {
        printf("Application is valid, jumping to app...\r\n");
        Delay_ms(1000);
        Jump_To_App();
    } else {
        printf("No valid application found!\r\n");
        printf("Please send new firmware via serial port...\r\n");
    }
    
    // 主循环
    while(1) {
        IAP_Process();
    }
}

/************************************************************
 * 系统初始化
 ************************************************************/
void System_Init(void) {
    // 复位所有外设
    RCC_DeInit();
    
    // 设置系统时钟
    SystemInit();
    
    // 初始化GPIO
    GPIO_Init();
    
    // 初始化串口1,波特率115200
    UART1_Init(115200);
    
    // 初始化定时器2
    TIM2_Init();
    
    // 初始化IAP参数
    memset(&iap_param, 0, sizeof(IAP_Param_t));
    
    // 读取参数
    memcpy(&iap_param, (void*)FLASH_PARAM_ADDR, sizeof(IAP_Param_t));
    
    // LED闪烁3次表示启动成功
    LED_Blink(3);
}

/************************************************************
 * GPIO初始化
 ************************************************************/
void GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能GPIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);
    
    // 配置LED引脚(PC13)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
    GPIO_SetBits(GPIOC, GPIO_Pin_13);  // 初始熄灭
    
    // 配置BOOT0引脚(PA8),设置为输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

/************************************************************
 * 串口1初始化
 ************************************************************/
void UART1_Init(uint32_t baudrate) {
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    
    // 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置USART1引脚
    // TX: PA9, RX: PA10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 配置USART1
    USART_InitStructure.USART_BaudRate = baudrate;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);
    
    // 使能串口
    USART_Cmd(USART1, ENABLE);
    
    // 使能接收中断
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    
    // 设置中断优先级
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

/************************************************************
 * 定时器2初始化(用于延时)
 ************************************************************/
void TIM2_Init(void) {
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    
    // 使能TIM2时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    // 定时器配置
    TIM_TimeBaseStructure.TIM_Period = 1000 - 1;  // 1ms
    TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1;  // 72MHz/72 = 1MHz
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    
    // 使能定时器
    TIM_Cmd(TIM2, ENABLE);
}

/************************************************************
 * LED闪烁
 ************************************************************/
void LED_Blink(uint8_t times) {
    for(uint8_t i = 0; i < times; i++) {
        GPIO_ResetBits(GPIOC, GPIO_Pin_13);  // LED亮
        Delay_ms(200);
        GPIO_SetBits(GPIOC, GPIO_Pin_13);    // LED灭
        Delay_ms(200);
    }
}

/************************************************************
 * 检查应用程序有效性
  * 检查:1. 栈顶地址是否合法
  *      2. 复位向量是否合法
  *      3. CRC校验(可选)
 ************************************************************/
uint8_t Check_App_Valid(void) {
    // 获取应用程序的栈顶地址
    uint32_t stack_top = *(volatile uint32_t*)FLASH_APP_ADDR;
    
    // 检查栈顶地址是否在RAM范围内
    if(stack_top < 0x20000000 || stack_top > 0x20005000) {
        printf("Invalid stack pointer: 0x%08X\r\n", stack_top);
        return 0;
    }
    
    // 获取复位向量地址
    uint32_t reset_vector = *(volatile uint32_t*)(FLASH_APP_ADDR + 4);
    
    // 检查复位向量是否在Flash范围内
    if(reset_vector < FLASH_APP_ADDR || reset_vector > 0x08020000) {
        printf("Invalid reset vector: 0x%08X\r\n", reset_vector);
        return 0;
    }
    
    // 检查应用程序大小
    if(iap_param.app_size == 0 || iap_param.app_size > 0x10000) {
        printf("Invalid app size: %d\r\n", iap_param.app_size);
        return 0;
    }
    
    // CRC校验(可选)
    if(iap_param.app_crc != 0) {
        uint32_t crc = Calculate_CRC(FLASH_APP_ADDR, iap_param.app_size);
        if(crc != iap_param.app_crc) {
            printf("CRC check failed: 0x%08X != 0x%08X\r\n", crc, iap_param.app_crc);
            return 0;
        }
    }
    
    printf("Application check passed!\r\n");
    printf("  Stack Pointer: 0x%08X\r\n", stack_top);
    printf("  Reset Vector: 0x%08X\r\n", reset_vector);
    printf("  App Size: %d bytes\r\n", iap_param.app_size);
    printf("  App Version: 0x%08X\r\n", iap_param.app_version);
    
    return 1;
}

/************************************************************
 * 跳转到应用程序
 ************************************************************/
void Jump_To_App(void) {
    // 定义函数指针
    void (*app_reset_handler)(void);
    
    // 获取应用程序的栈顶地址
    uint32_t app_stack = *(volatile uint32_t*)FLASH_APP_ADDR;
    
    // 获取应用程序的复位向量地址
    uint32_t app_reset = *(volatile uint32_t*)(FLASH_APP_ADDR + 4);
    
    // 禁用所有中断
    __disable_irq();
    
    // 设置新的栈指针
    __set_MSP(app_stack);
    
    // 跳转到应用程序
    app_reset_handler = (void (*)(void))app_reset;
    app_reset_handler();
}

/************************************************************
 * IAP处理流程
 ************************************************************/
void IAP_Process(void) {
    uint8_t key = 0;
    
    printf("\r\nWaiting for commands...\r\n");
    printf("Press '1' to enter upgrade mode\r\n");
    printf("Press '2' to jump to application\r\n");
    printf("Press '3' to show system info\r\n");
    
    // 等待按键
    while(1) {
        if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET) {
            key = USART_ReceiveData(USART1);
            printf("Received: %c\r\n", key);
            
            switch(key) {
                case '1':
                    // 进入升级模式
                    IAP_Upgrade();
                    break;
                    
                case '2':
                    // 跳转到应用程序
                    Jump_To_App();
                    break;
                    
                case '3':
                    // 显示系统信息
                    Show_System_Info();
                    break;
                    
                default:
                    printf("Unknown command!\r\n");
                    break;
            }
            
            break;
        }
    }
}

/************************************************************
 * IAP升级函数
 ************************************************************/
void IAP_Upgrade(void) {
    printf("\r\nEntering IAP upgrade mode...\r\n");
    printf("Please use Ymodem protocol to send firmware...\r\n");
    printf("Timeout: 60 seconds\r\n");
    
    // 清除升级标志
    iap_param.upgrade_flag = 0;
    Save_IAP_Params();
    
    // 开始Ymodem接收
    uint32_t file_size = 0;
    uint32_t file_crc = 0;
    uint8_t file_name[32] = {0};
    
    if(Ymodem_Receive(&file_size, &file_crc, file_name) == 0) {
        printf("Receive success!\r\n");
        printf("File Name: %s\r\n", file_name);
        printf("File Size: %d bytes\r\n", file_size);
        printf("File CRC: 0x%08X\r\n", file_crc);
        
        // 验证接收到的固件
        if(Verify_Firmware(file_size, file_crc)) {
            printf("Firmware verify success!\r\n");
            
            // 设置升级参数
            iap_param.upgrade_flag = 1;
            iap_param.app_size = file_size;
            iap_param.app_crc = file_crc;
            iap_param.app_version = Get_Firmware_Version();
            
            // 保存参数
            Save_IAP_Params();
            
            printf("Upgrade completed! System will reset...\r\n");
            
            // 延时后复位
            Delay_ms(1000);
            NVIC_SystemReset();
        } else {
            printf("Firmware verify failed!\r\n");
        }
    } else {
        printf("Receive failed or timeout!\r\n");
    }
}

/************************************************************
 * 显示系统信息
 ************************************************************/
void Show_System_Info(void) {
    printf("\r\n=== System Information ===\r\n");
    printf("MCU: STM32F103C8T6\r\n");
    printf("Flash Size: 64KB\r\n");
    printf("Bootloader Size: 12KB\r\n");
    printf("App Size: 52KB\r\n");
    printf("App Address: 0x%08X\r\n", FLASH_APP_ADDR);
    printf("Bootloader Version: 1.0\r\n");
    printf("App Version: 0x%08X\r\n", iap_param.app_version);
    printf("App Size: %d bytes\r\n", iap_param.app_size);
    printf("App CRC: 0x%08X\r\n", iap_param.app_crc);
}

/************************************************************
 * 串口中断处理
 ************************************************************/
void USART1_IRQHandler(void) {
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        uint8_t data = USART_ReceiveData(USART1);
        
        // 可以在这里处理中断接收的数据
        // 例如:Ymodem协议处理
        
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }
}

2. IAP核心库(iap.c)

/************************************************************
 * IAP核心功能实现
  * 包含:Flash操作、CRC校验、固件验证等
 ************************************************************/

#include "stm32f10x.h"
#include "iap.h"
#include <string.h>

// 内部函数声明
static uint32_t Get_Sector(uint32_t address);
static uint8_t Erase_Sector(uint32_t sector);

/************************************************************
 * 擦除Flash扇区
  * 参数:start_addr - 起始地址
  *       size       - 擦除大小
  * 返回:0-成功,1-失败
 ************************************************************/
uint8_t IAP_Erase(uint32_t start_addr, uint32_t size) {
    FLASH_Status status = FLASH_COMPLETE;
    uint32_t end_addr = start_addr + size;
    uint32_t current_addr = start_addr;
    
    // 解锁Flash
    FLASH_Unlock();
    
    // 清除所有标志位
    FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
    
    // 擦除扇区
    while(current_addr < end_addr) {
        uint32_t sector = Get_Sector(current_addr);
        
        if(Erase_Sector(sector) != 0) {
            FLASH_Lock();
            return 1;
        }
        
        // 更新地址到下一个扇区
        if(sector == FLASH_Sector_11) {
            current_addr = 0x0800C000;  // Sector 11结束地址
        } else {
            current_addr += 0x800;  // 每个扇区2KB
        }
    }
    
    // 锁定Flash
    FLASH_Lock();
    
    return 0;
}

/************************************************************
 * 写入数据到Flash
  * 参数:dest_addr - 目标地址
  *       src_data  - 源数据
  *       size      - 数据大小
  * 返回:0-成功,1-失败
 ************************************************************/
uint8_t IAP_Write(uint32_t dest_addr, uint8_t *src_data, uint32_t size) {
    FLASH_Status status = FLASH_COMPLETE;
    uint16_t *p_data = (uint16_t*)src_data;
    uint32_t data_len = (size + 1) / 2;  // 转换为半字数
    
    // 解锁Flash
    FLASH_Unlock();
    
    // 清除所有标志位
    FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
    
    // 写入数据
    for(uint32_t i = 0; i < data_len; i++) {
        // 编程半字
        status = FLASH_ProgramHalfWord(dest_addr + i*2, p_data[i]);
        
        if(status != FLASH_COMPLETE) {
            FLASH_Lock();
            return 1;
        }
        
        // 验证写入的数据
        if(*(volatile uint16_t*)(dest_addr + i*2) != p_data[i]) {
            FLASH_Lock();
            return 1;
        }
    }
    
    // 锁定Flash
    FLASH_Lock();
    
    return 0;
}

/************************************************************
 * 读取Flash数据
  * 参数:src_addr  - 源地址
  *       dest_data - 目标缓冲区
  *       size      - 读取大小
 ************************************************************/
void IAP_Read(uint32_t src_addr, uint8_t *dest_data, uint32_t size) {
    memcpy(dest_data, (void*)src_addr, size);
}

/************************************************************
 * 计算CRC32校验
  * 参数:data - 数据指针
  *       len  - 数据长度
  * 返回:CRC32值
 ************************************************************/
uint32_t Calculate_CRC(uint8_t *data, uint32_t len) {
    uint32_t crc = 0xFFFFFFFF;
    
    // 简单CRC32实现(实际应使用硬件CRC)
    for(uint32_t i = 0; i < len; i++) {
        crc ^= data[i];
        for(uint8_t j = 0; j < 8; j++) {
            if(crc & 1) {
                crc = (crc >> 1) ^ 0xEDB88320;
            } else {
                crc = crc >> 1;
            }
        }
    }
    
    return ~crc;
}

/************************************************************
 * 验证固件
  * 参数:expected_size - 期望大小
  *       expected_crc  - 期望CRC
  * 返回:0-验证成功,1-验证失败
 ************************************************************/
uint8_t Verify_Firmware(uint32_t expected_size, uint32_t expected_crc) {
    // 检查固件大小
    if(expected_size > (FLASH_TOTAL_SIZE - (FLASH_APP_ADDR - 0x08000000))) {
        printf("Firmware too large: %d > %d\r\n", 
               expected_size, FLASH_TOTAL_SIZE - (FLASH_APP_ADDR - 0x08000000));
        return 1;
    }
    
    // 计算CRC校验
    uint32_t actual_crc = Calculate_CRC((uint8_t*)FLASH_APP_ADDR, expected_size);
    
    if(actual_crc != expected_crc) {
        printf("CRC mismatch: 0x%08X != 0x%08X\r\n", actual_crc, expected_crc);
        return 1;
    }
    
    return 0;
}

/************************************************************
 * 获取固件版本
  * 从固件特定地址读取版本号
  * 返回:版本号
 ************************************************************/
uint32_t Get_Firmware_Version(void) {
    // 版本号存储在应用程序的特定位置
    // 这里假设版本号存储在0x08003020地址
    uint32_t version_addr = FLASH_APP_ADDR + 0x20;
    
    if(version_addr < FLASH_APP_ADDR || version_addr > 0x08020000) {
        return 0;
    }
    
    return *(volatile uint32_t*)version_addr;
}

/************************************************************
 * 保存IAP参数到Flash
 ************************************************************/
void Save_IAP_Params(void) {
    // 先擦除参数扇区
    IAP_Erase(FLASH_PARAM_ADDR, 2048);
    
    // 写入参数
    IAP_Write(FLASH_PARAM_ADDR, (uint8_t*)&iap_param, sizeof(IAP_Param_t));
}

/************************************************************
 * 根据地址获取扇区号
 ************************************************************/
static uint32_t Get_Sector(uint32_t address) {
    if(address < 0x08004000) {
        return FLASH_Sector_0;
    } else if(address < 0x08008000) {
        return FLASH_Sector_1;
    } else if(address < 0x0800C000) {
        return FLASH_Sector_2;
    } else if(address < 0x08010000) {
        return FLASH_Sector_3;
    } else if(address < 0x08020000) {
        return FLASH_Sector_4;
    } else {
        return FLASH_Sector_4;  // 默认
    }
}

/************************************************************
 * 擦除指定扇区
 ************************************************************/
static uint8_t Erase_Sector(uint32_t sector) {
    FLASH_Status status = FLASH_COMPLETE;
    
    status = FLASH_ErasePage(FLASH_ADDR_START_SECTOR0 + (sector * 0x400));
    
    if(status != FLASH_COMPLETE) {
        return 1;
    }
    
    return 0;
}

3. IAP头文件(iap.h)

#ifndef __IAP_H
#define __IAP_H

#include "stm32f10x.h"

// 函数声明
uint8_t IAP_Erase(uint32_t start_addr, uint32_t size);
uint8_t IAP_Write(uint32_t dest_addr, uint8_t *src_data, uint32_t size);
void IAP_Read(uint32_t src_addr, uint8_t *dest_data, uint32_t size);
uint32_t Calculate_CRC(uint8_t *data, uint32_t len);
uint8_t Verify_Firmware(uint32_t expected_size, uint32_t expected_crc);
uint32_t Get_Firmware_Version(void);
void Save_IAP_Params(void);

// 延时函数
void Delay_ms(uint32_t ms);
void Delay_us(uint32_t us);

#endif

4. Ymodem协议实现(ymodem.c)

/************************************************************
 * Ymodem协议实现
  * 支持:文件传输、CRC校验、断点续传
 ************************************************************/

#include "stm32f10x.h"
#include "ymodem.h"
#include "iap.h"
#include <string.h>

// 内部缓冲区
static uint8_t ymodem_buffer[YMODEM_PACKET_SIZE];
static uint32_t flash_write_addr = FLASH_APP_ADDR;

// 内部函数声明
static uint8_t Receive_Packet(uint8_t *buffer, uint16_t *length, uint8_t seq);
static uint8_t Send_ACK(uint8_t seq);
static uint8_t Send_NAK(void);
static uint8_t Send_C(void);

/************************************************************
 * Ymodem接收
  * 参数:file_size - 接收到的文件大小
  *       file_crc  - 文件的CRC32
  *       file_name - 文件名
  * 返回:0-成功,1-失败
 ************************************************************/
uint8_t Ymodem_Receive(uint32_t *file_size, uint32_t *file_crc, uint8_t *file_name) {
    uint8_t seq = 0;
    uint16_t length = 0;
    uint8_t result = 0;
    uint32_t total_size = 0;
    uint8_t first_packet = 1;
    
    printf("Waiting for Ymodem transmission...\r\n");
    
    // 发送'C'开始传输
    if(Send_C() != 0) {
        printf("Timeout waiting for transmission start\r\n");
        return 1;
    }
    
    // 擦除应用程序区域
    printf("Erasing application area...\r\n");
    if(IAP_Erase(FLASH_APP_ADDR, 52*1024) != 0) {
        printf("Flash erase failed!\r\n");
        return 1;
    }
    
    flash_write_addr = FLASH_APP_ADDR;
    
    while(1) {
        // 接收数据包
        result = Receive_Packet(ymodem_buffer, &length, seq);
        
        if(result == 0) {
            // 成功接收
            Send_ACK(seq);
            
            if(first_packet) {
                // 第一个包包含文件名和文件大小
                Parse_File_Info(ymodem_buffer, file_name, file_size);
                first_packet = 0;
                printf("File info received: %s, %d bytes\r\n", file_name, *file_size);
            } else if(length > 0) {
                // 数据包
                if(IAP_Write(flash_write_addr, ymodem_buffer, length) != 0) {
                    printf("Flash write failed at 0x%08X\r\n", flash_write_addr);
                    return 1;
                }
                
                flash_write_addr += length;
                total_size += length;
                
                // 显示进度
                if((seq % 10) == 0) {
                    printf("Received: %d/%d bytes\r\n", total_size, *file_size);
                }
            } else {
                // 空包,传输结束
                break;
            }
            
            seq = (seq + 1) & 0xFF;
        } else if(result == 1) {
            // 接收失败,重试
            Send_NAK();
        } else {
            // 超时或其他错误
            printf("Transmission error!\r\n");
            return 1;
        }
    }
    
    // 接收结束包
    Send_ACK(seq);
    Send_ACK(0);
    
    *file_crc = Calculate_CRC((uint8_t*)FLASH_APP_ADDR, total_size);
    
    printf("Transmission completed! Total: %d bytes\r\n", total_size);
    
    return 0;
}

/************************************************************
 * 解析文件信息
 ************************************************************/
void Parse_File_Info(uint8_t *buffer, uint8_t *file_name, uint32_t *file_size) {
    uint8_t *ptr = buffer;
    
    // 文件名
    while(*ptr != 0) {
        *file_name++ = *ptr++;
    }
    *file_name = '\0';
    
    // 文件大小
    ptr++;  // 跳过0
    *file_size = 0;
    
    while(*ptr != 0) {
        if(*ptr >= '0' && *ptr <= '9') {
            *file_size = *file_size * 10 + (*ptr - '0');
        }
        ptr++;
    }
}

/************************************************************
 * 接收一个数据包
 ************************************************************/
static uint8_t Receive_Packet(uint8_t *buffer, uint16_t *length, uint8_t seq) {
    uint8_t header[3];
    uint16_t timeout = 0;
    uint32_t i;
    
    // 接收包头
    for(i = 0; i < 3; i++) {
        timeout = 0;
        while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET) {
            if(++timeout > 10000) {  // 10秒超时
                return 2;  // 超时
            }
            Delay_us(1000);
        }
        header[i] = USART_ReceiveData(USART1);
    }
    
    // 检查包类型
    if(header[0] == SOH) {
        *length = 128;
    } else if(header[0] == STX) {
        *length = 1024;
    } else if(header[0] == EOT) {
        *length = 0;
        return 0;
    } else {
        return 1;  // 错误
    }
    
    // 检查序列号
    if(header[1] != seq || header[2] != (0xFF - seq)) {
        return 1;  // 序列号错误
    }
    
    // 接收数据
    for(i = 0; i < *length; i++) {
        timeout = 0;
        while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET) {
            if(++timeout > 10000) {
                return 2;
            }
            Delay_us(1000);
        }
        buffer[i] = USART_ReceiveData(USART1);
    }
    
    // 接收CRC
    uint16_t crc = 0;
    timeout = 0;
    while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET) {
        if(++timeout > 10000) {
            return 2;
        }
        Delay_us(1000);
    }
    crc = USART_ReceiveData(USART1) << 8;
    
    timeout = 0;
    while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET) {
        if(++timeout > 10000) {
            return 2;
        }
        Delay_us(1000);
    }
    crc |= USART_ReceiveData(USART1);
    
    // 计算CRC校验
    uint16_t calc_crc = Calculate_CRC16(buffer, *length);
    
    if(crc != calc_crc) {
        return 1;  // CRC错误
    }
    
    return 0;
}

/************************************************************
 * 发送ACK
 ************************************************************/
static uint8_t Send_ACK(uint8_t seq) {
    USART_SendData(USART1, ACK);
    while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
    return 0;
}

/************************************************************
 * 发送NAK
 ************************************************************/
static uint8_t Send_NAK(void) {
    USART_SendData(USART1, NAK);
    while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
    return 0;
}

/************************************************************
 * 发送'C'
 ************************************************************/
static uint8_t Send_C(void) {
    uint16_t timeout = 0;
    
    while(1) {
        USART_SendData(USART1, 'C');
        while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
        
        // 等待响应
        timeout = 0;
        while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET) {
            if(++timeout > 1000) {  // 1秒超时
                break;
            }
            Delay_us(1000);
        }
        
        if(timeout <= 1000) {
            uint8_t response = USART_ReceiveData(USART1);
            if(response == SOH || response == STX) {
                return 0;  // 收到数据包
            }
        }
        
        Delay_ms(100);
    }
    
    return 1;
}

/************************************************************
 * 计算CRC16
 ************************************************************/
uint16_t Calculate_CRC16(uint8_t *data, uint16_t length) {
    uint16_t crc = 0;
    uint16_t i, j;
    
    for(i = 0; i < length; i++) {
        crc = crc ^ ((uint16_t)data[i] << 8);
        for(j = 0; j < 8; j++) {
            if(crc & 0x8000) {
                crc = (crc << 1) ^ 0x1021;
            } else {
                crc <<= 1;
            }
        }
    }
    
    return crc;
}

5. 用户应用程序(application.c)需要修改的地方

/************************************************************
 * 用户应用程序(需要修改的部分)
  * 1. 修改中断向量表偏移
  * 2. 添加版本号
  * 3. 添加快捷方式进入Bootloader
 ************************************************************/

#include "stm32f10x.h"

// 定义应用程序起始地址
#define APPLICATION_ADDRESS    0x08003000

// 定义版本号(存储在特定地址)
#define FIRMWARE_VERSION       0x01020304
__attribute__((section(".version"))) const uint32_t firmware_version = FIRMWARE_VERSION;

/************************************************************
 * 修改中断向量表偏移
  * 在SystemInit()之后调用
 ************************************************************/
void Vector_Table_Config(void) {
    // 设置中断向量表偏移
    NVIC_SetVectorTable(NVIC_VectTab_FLASH, APPLICATION_ADDRESS - 0x08000000);
}

/************************************************************
 * 跳转到Bootloader
  * 可以通过按键、命令等方式触发
 ************************************************************/
void Jump_To_Bootloader(void) {
    // 禁用所有中断
    __disable_irq();
    
    // 设置升级标志
    uint32_t *upgrade_flag = (uint32_t*)0x0801F800;
    *upgrade_flag = 0xA5A5A5A5;
    
    // 跳转到Bootloader
    void (*bootloader_reset)(void) = (void (*)(void))0x08000000;
    bootloader_reset();
}

/************************************************************
 * 主函数
 ************************************************************/
int main(void) {
    // 系统初始化
    SystemInit();
    
    // 设置中断向量表偏移
    Vector_Table_Config();
    
    // 初始化外设
    // ...
    
    // 主循环
    while(1) {
        // 应用程序代码
        // ...
        
        // 检查是否需要进入Bootloader
        if(/* 触发条件 */) {
            Jump_To_Bootloader();
        }
    }
}

6. 链接器脚本修改(application.ld)

/* STM32F103C8T6应用程序链接脚本 */
MEMORY
{
    RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 20K
    FLASH (rx)     : ORIGIN = 0x08003000, LENGTH = 52K  /* 注意起始地址 */
}

SECTIONS
{
    .isr_vector :
    {
        . = ALIGN(4);
        KEEP(*(.isr_vector))
        . = ALIGN(4);
    } >FLASH
    
    .text :
    {
        . = ALIGN(4);
        *(.text)
        *(.text*)
        . = ALIGN(4);
    } >FLASH
    
    /* 版本号段 */
    .version :
    {
        . = ALIGN(4);
        KEEP(*(.version))
        . = ALIGN(4);
    } >FLASH
    
    /* 其他段定义... */
}

参考代码 基于stm32的串口IAP例程源码 www.youwenfan.com/contentcst/183290.html

使用步骤

1. 编译和烧写Bootloader

1. 使用Keil打开Bootloader工程
2. 修改Flash起始地址为0x08000000
3. 编译并烧写到STM32

2. 编译应用程序

1. 使用Keil打开Application工程
2. 修改以下设置:
   - IROM1起始地址:0x08003000
   - 大小:0xD000 (52KB)
3. 修改中断向量表偏移:
   - 在SystemInit()后调用:NVIC_SetVectorTable(...)
4. 编译生成hex/bin文件

3. 使用PC端工具升级

# 使用SecureCRT、Xshell等工具
# 或使用Python脚本:

import serial
import time

def send_firmware(port, baudrate, filename):
    ser = serial.Serial(port, baudrate, timeout=5)
    
    # 发送'1'进入升级模式
    ser.write(b'1')
    time.sleep(1)
    
    # 使用Ymodem协议发送文件
    with open(filename, 'rb') as f:
        data = f.read()
        # 这里需要实现Ymodem协议
        # ...
    
    ser.close()

# 使用
send_firmware('COM3', 115200, 'app.bin')

扩展功能

1. 网络升级(可选)

// 通过以太网、WiFi升级
void Ethernet_IAP(void) {
    // 接收固件数据
    // 写入Flash
    // 跳转到应用程序
}

2. 固件加密

// AES加密固件
void Decrypt_Firmware(uint8_t *data, uint32_t size) {
    // 解密算法
    // ...
}

3. 固件压缩

// LZ77压缩算法
uint32_t Decompress_Firmware(uint8_t *src, uint8_t *dst) {
    // 解压缩算法
    // ...
    return decompressed_size;
}

注意事项

  1. 中断向量表:应用程序必须正确设置中断向量表偏移
  2. Flash保护:Bootloader区域应该写保护
  3. 看门狗:升级过程中可能需要禁用看门狗
  4. 电源稳定:升级过程中确保电源稳定
  5. 超时处理:添加超时机制防止死锁
  6. 回滚机制:实现固件回滚功能

专注于matlab/simulink,电子电路,编程