4. 串口自定义帧协议固件升级

任务目标

为STM32 IAP Bootloader设计一套简洁可靠的串口通信帧协议,用于上位机向Bootloader传输固件数据。协议需支持:固件下载启动、数据分包传输、固件结束确认三种基本操作。

帧格式定义

字段长度(字节)说明
帧头2固定值 0xAA 0x55,用于帧同步
命令10x01开始传输,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) {
            // 固件传输完成,跳转执行
        }
    }
}

验证方法

  1. 单元测试:在PC上使用相同代码逻辑,构造已知数据调用UART_SendFrame生成字节流,再逐字节喂给UART_ParseByte,确认返回1且解析出的cmd、len、data与发送一致。
  2. 错误注入:修改帧中任意一个数据字节,确认校验失败返回0;修改帧头字节,确认接收端能重新同步。
  3. 边界测试:发送len=0的空数据帧(CMD_END),确认能正确解析;发送len=1024的最大帧,确认无溢出。

通过标准:正常帧100%解析成功;单字节错误帧100%被拒收;错误帧后紧跟正确帧能正常恢复同步。