第 3 篇:UART 协议设计------定义你自己的通讯协议
1. 帧格式设计
┌──────┬──────┬──────┬──────┬──────┬──────┬──────┐
│ 帧头 │ 地址 │ 命令 │ 长度 │ 数据 │ CRC16 │ 帧尾 │
│ 2字节 │ 1字节│ 1字节 │ 1字节 │ N字节 │ 2字节 │ 1字节 │
│ 0xA5 │ │ │ │ │ │ 0x5A │
│ 0x5A │ │ │ │ │ │ │
└──────┴──────┴──────┴──────┴──────┴──────┴──────┘
2. 转义机制(SLIP 协议)
如果数据中恰好出现了 0xA5 或 0x5A,需要转义:
c
#define FRAME_START 0xA5
#define FRAME_END 0x5A
#define ESCAPE_CHAR 0x7D
void slip_encode(uint8_t *in, int len, uint8_t *out, int *out_len) {
for (int i = 0; i < len; i++) {
if (in[i] == FRAME_START || in[i] == FRAME_END || in[i] == ESCAPE_CHAR) {
out[(*out_len)++] = ESCAPE_CHAR;
}
out[(*out_len)++] = in[i];
}
}
3. CRC16 校验
c
uint16_t crc16_modbus(const uint8_t *data, int len) {
uint16_t crc = 0xFFFF;
for (int i = 0; i < len; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
if (crc & 1) crc = (crc >> 1) ^ 0xA001;
else crc >>= 1;
}
}
return crc;
}
4. 逐字节解析状态机
c
typedef enum { STATE_IDLE, STATE_HEAD1, STATE_HEAD2, STATE_ADDR,
STATE_CMD, STATE_LEN, STATE_DATA, STATE_CRC1,
STATE_CRC2, STATE_TAIL } proto_state_t;
void proto_parse_byte(proto_state_t *s, uint8_t byte) {
switch (*s) {
case STATE_IDLE:
if (byte == 0xA5) *s = STATE_HEAD2; break;
case STATE_HEAD2:
*s = (byte == 0x5A) ? STATE_ADDR : STATE_IDLE; break;
case STATE_ADDR: frame.addr = byte; *s = STATE_CMD; break;
// ... 依此类推
}
}
状态机逐字节解析,不依赖于 DMA 缓冲区的起始位置------即使帧丢失也能自动恢复同步。
下一篇:I2C------主从模式的硬件与软件实现