嵌入式通用通信帧协议设计

嵌入式通用通信帧协议设计

嵌入式系统开发中,如串口UART、蓝牙、轻量级网络等通信链路往往需要自定义封包协议来保证数据传输的准确性。本文分享一套简洁、通用、易实现的帧协议设计方案,适配多类嵌入式通信场景。

一、帧协议结构(小端序)

协议采用 "帧头 + 标识 + 数据 + 校验 + 帧尾" 的经典结构,总长度灵活且解析逻辑简单,各字段定义如下:

字段 字节数 定义 / 取值 核心作用
帧头 2 0xAA55(小端存储) 帧同步标识,快速定位帧起始位置
帧计数 1 0~255(溢出归零) 区分帧的新旧顺序,辅助检测丢包、重传
帧 ID 2 自定义(如指令码 / 设备 ID) 标识帧的功能类型(如读指令 / 写指令 / 响应包),适配多指令 / 多设备通信
数据长度 2 0~65535 标识 "数据位" 的字节数,避免解析时内存溢出,限定数据区范围
数据位 不定 业务自定义数据 承载实际业务数据(如传感器值、控制指令参数)
CRC16 校验 2 小端存储 校验范围:帧计数~数据位,采用 CRC16-CCITT 多项式,检测传输过程中的字节错误
帧尾 2 0x55AA(小端存储) 帧结束标识,辅助确认帧边界,降低字节流通信的帧解析错误率

二、CRC16-CCITT的C代码

c 复制代码
/**
 * @brief       CCITT CRC16算法
 * @param       data :传入的数据指针
 * @param       length  : 数据长度
 * @retval      crc计算结果
 */
uint16_t crc16_CCITT(const uint8_t *data, uint32_t length) 
{
	uint16_t crc16_polynomial = 0x1021;
	uint16_t crc16_start_value = 0xFFFF;
    uint16_t crc = crc16_start_value;
    
    for (uint32_t i = 0; i < length; i++) 
	{
        crc ^= (uint16_t)data[i] << 8;
        for (uint8_t bit = 0; bit < 8; bit++) 
		{
            if (crc & 0x8000) crc = (crc << 1) ^ crc16_polynomial;
			else crc = crc << 1;
        }
    }
    return crc;
}

三、帧解析的C代码

c 复制代码
/* 帧格式定义 */
#define FRAME_HEADER      0xAA55
#define FRAME_TAIL        0x55AA
#define MIN_FRAME_LEN     9  /* 最小帧长度 */


/**
 * @brief       用户命令帧结构体(适配自定义通用协议)
 */
typedef struct {
    uint8_t  seq_num;       /* 帧计数(1字节) */
    uint16_t frame_id;      /* 帧ID(2字节,小端) */
    uint16_t data_len;      /* 数据段长度(2字节,小端) */
    uint8_t* data;          /* 数据段指针 */
    uint16_t crc16;         /* CRC16校验值(2字节,小端) */
} UserCommandFrame;




/**
 * @brief       解析命令帧
 * @param       rx_buffer   : 接收缓冲区指针
 * @param       size        : 数据长度
 * @param       cmd         : 解析后的命令结构体指针
 * @retval      true:解析成功, false:解析失败
 */
bool parse_command_frame(uint8_t *rx_buffer, uint16_t size, \
    UserCommandFrame *cmd)
{
    /* 1. 基本长度检查 */
    if (size < MIN_FRAME_LEN || rx_buffer == NULL || cmd == NULL) {
        return false;
    }
    
    /* 2. 帧头检查 (0xAA55, 小端存储: 0x55 0xAA) */
    if (rx_buffer[0] != 0x55 || rx_buffer[1] != 0xAA) {
        return false;
    }
    
    /* 3. 解析固定字段 */
    cmd->seq_num  = rx_buffer[2];                          /* 帧计数 */
    cmd->frame_id = rx_buffer[3] | (rx_buffer[4] << 8);    /* 帧ID(小端) */
    cmd->data_len = rx_buffer[5] | (rx_buffer[6] << 8);    /* 数据长度(小端) */
    
    /* 4. 总长度验证: 帧头2 + 帧计数1 + 帧ID2 + 数据长度2 + 数据 + CRC2 + 帧尾2 */
    if (size != 11 + cmd->data_len) {
        return false;
    }
    
    /* 5. 帧尾检查 (0x55AA, 小端存储: 0xAA 0x55) */
    if (rx_buffer[size-2] != 0xAA || rx_buffer[size-1] != 0x55) {
        return false;
    }
    
    /* 6. 数据段指针赋值 */
    cmd->data = &rx_buffer[7];
    
    /* 7. 解析CRC16 */
    uint16_t data_end = 7 + cmd->data_len;
    cmd->crc16 = rx_buffer[data_end] | (rx_buffer[data_end + 1] << 8);
    
    /* 8. CRC校验 (计算从帧计数到数据结束的数据) */
    uint16_t cal_crc = crc16_CCITT(&rx_buffer[2], 5 + cmd->data_len);
    
    if (cal_crc != cmd->crc16) {
        return false;
    }
    
    return true;  /* 解析成功 */
}