4. 串口自定义帧协议固件升级
任务目标
为STM32 IAP Bootloader设计一套简洁可靠的串口通信帧协议,用于上位机向Bootloader传输固件数据。协议需支持:固件下载启动、数据分包传输、固件结束确认三种基本操作。
帧格式定义
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| 帧头 | 2 | 固定值 0xAA 0x55,用于帧同步 |
| 命令 | 1 | 0x01开始传输,0x02传输数据,0x03结束传输 |
| 长度 | 2 | 数据字段长度(小端序),范围0~1024 |
| 数据 | N | 有效载荷,最大1024字节 |
| 校验 | 1 | 从命令到数据末尾所有字节的累加和(忽略进位) |
完整实现
// uart_protocol.h
#ifndef UART_PROTOCOL_H
#define UART_PROTOCOL_H
#include <stdint.h>
#define FRAME_HEADER0 0xAA
#define FRAME_HEADER1 0x55
#define CMD_START 0x01
#define CMD_DATA 0x02
#define CMD_END 0x03
#define MAX_DATA_LEN 1024
#define FRAME_OVERHEAD 6 // 帧头2 + 命令1 + 长度2 + 校验1
typedef struct {
uint8_t cmd;
uint16_t len;
uint8_t data[MAX_DATA_LEN];
} Frame_t;
// 发送帧(自动添加帧头与校验)
void UART_SendFrame(uint8_t cmd, uint8_t* data, uint16_t len);
// 接收解析:返回1表示收到完整帧,0表示未完成
uint8_t UART_ParseByte(uint8_t byte, Frame_t* frame);
#endif
// uart_protocol.c
#include "uart_protocol.h"
// 模拟串口发送(实际使用时替换为具体UART驱动)
extern void UART_WriteByte(uint8_t byte);
static uint8_t rx_buf[MAX_DATA_LEN + FRAME_OVERHEAD];
static uint16_t rx_index = 0;
static uint8_t rx_state = 0; // 0=等待帧头0, 1=等待帧头1, 2=接收中
void UART_SendFrame(uint8_t cmd, uint8_t* data, uint16_t len) {
uint8_t checksum = cmd;
uint16_t i;
UART_WriteByte(FRAME_HEADER0);
UART_WriteByte(FRAME_HEADER1);
UART_WriteByte(cmd);
UART_WriteByte(len & 0xFF);
UART_WriteByte((len >> 8) & 0xFF);
for (i = 0; i < len; i++) {
UART_WriteByte(data[i]);
checksum += data[i];
}
checksum += (len & 0xFF) + ((len >> 8) & 0xFF);
UART_WriteByte(checksum);
}
uint8_t UART_ParseByte(uint8_t byte, Frame_t* frame) {
uint8_t checksum = 0;
uint16_t i;
switch (rx_state) {
case 0:
if (byte == FRAME_HEADER0) rx_state = 1;
break;
case 1:
if (byte == FRAME_HEADER1) {
rx_state = 2;
rx_index = 0;
} else {
rx_state = (byte == FRAME_HEADER0) ? 1 : 0;
}
break;
case 2:
rx_buf[rx_index++] = byte;
if (rx_index < 4) break; // 命令+长度尚未收齐
// 解析命令与长度
frame->cmd = rx_buf[0];
frame->len = rx_buf[1] | (rx_buf[2] << 8);
// 长度合法性检查
if (frame->len > MAX_DATA_LEN) {
rx_state = 0;
return 0;
}
// 数据+校验是否收齐
if (rx_index == 4 + frame->len + 1) {
// 复制数据
for (i = 0; i < frame->len; i++) {
frame->data[i] = rx_buf[4 + i];
}
// 计算校验
checksum = frame->cmd;
checksum += (frame->len & 0xFF) + ((frame->len >> 8) & 0xFF);
for (i = 0; i < frame->len; i++) {
checksum += frame->data[i];
}
rx_state = 0;
// 校验比对
return (checksum == rx_buf[4 + frame->len]) ? 1 : 0;
}
break;
}
return 0;
}
调用示例
// 发送端:开始传输(携带固件总长度)
uint32_t firmware_size = 32768;
uint8_t size_buf[4] = {
firmware_size & 0xFF,
(firmware_size >> 8) & 0xFF,
(firmware_size >> 16) & 0xFF,
(firmware_size >> 24) & 0xFF
};
UART_SendFrame(CMD_START, size_buf, 4);
// 发送端:传输一包数据(如每包256字节)
uint8_t packet[256];
// ... 填充packet为固件数据 ...
UART_SendFrame(CMD_DATA, packet, 256);
// 发送端:结束传输
UART_SendFrame(CMD_END, NULL, 0);
// 接收端:在UART中断中逐字节调用
Frame_t frame;
void UART_IRQHandler(void) {
uint8_t byte = UART_ReceiveByte();
if (UART_ParseByte(byte, &frame)) {
// 收到完整帧,按frame.cmd处理
if (frame.cmd == CMD_START) {
// 解析固件长度,准备写入Flash
} else if (frame.cmd == CMD_DATA) {
// 将frame.data写入Flash对应地址
} else if (frame.cmd == CMD_END) {
// 固件传输完成,跳转执行
}
}
}
验证方法
- 单元测试:在PC上使用相同代码逻辑,构造已知数据调用
UART_SendFrame生成字节流,再逐字节喂给UART_ParseByte,确认返回1且解析出的cmd、len、data与发送一致。 - 错误注入:修改帧中任意一个数据字节,确认校验失败返回0;修改帧头字节,确认接收端能重新同步。
- 边界测试:发送len=0的空数据帧(CMD_END),确认能正确解析;发送len=1024的最大帧,确认无溢出。
通过标准:正常帧100%解析成功;单字节错误帧100%被拒收;错误帧后紧跟正确帧能正常恢复同步。