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;
}
注意事项
- 中断向量表:应用程序必须正确设置中断向量表偏移
- Flash保护:Bootloader区域应该写保护
- 看门狗:升级过程中可能需要禁用看门狗
- 电源稳定:升级过程中确保电源稳定
- 超时处理:添加超时机制防止死锁
- 回滚机制:实现固件回滚功能