在單片機通信系統(多機通信、PC與單片機通信)中,如何可靠地接收一個完整數據包是核心難題。本文系統梳理基於幀頭幀尾、幀長度、超時機制三大主流幀同步方案的原理、代碼實現、優缺點對比及工程避坑指南,適用於UART、SPI、I2C等串行總線通用場景。
串口屬於字節流通道,無天然數據包邊界。接收端必須通過協議機制從連續字節流中切分出完整的應用層數據包(幀)。
[幀頭(1B)] [數據長度(可選)] [數據域(NB)] [校驗碼(1-2B)] [幀尾(1B)]
0xAA、0x55、0xFE、0xEF 等0xDB 0xDC 代表 0xC0)typedef struct {
uint8_t state; // 0:待幀頭 1:接收中 2:待幀尾
uint8_t buffer[256];
uint16_t index;
uint8_t complete_flag;
} UartRxContext;
void UartRxISR(uint8_t byte) {
switch (ctx.state) {
case 0: // 待幀頭
if (byte == FRAME_HEAD) {
ctx.state = 1;
ctx.index = 0;
ctx.complete_flag = 0;
}
break;
case 1: // 接收數據
if (byte == FRAME_TAIL) {
ctx.state = 0;
ctx.complete_flag = 1; // 一幀完成
} else if (ctx.index < sizeof(ctx.buffer)) {
ctx.buffer[ctx.index++] = byte;
} else {
ctx.state = 0; // 溢出重置
}
break;
}
}
變種:幀頭+長度+數據+校驗 或 長度+校驗(無固定幀頭尾)
typedef struct {
uint8_t data[256];
uint16_t expected_len;
uint16_t received_len;
uint32_t timeout_counter; // 由SysTick或定時器遞減
} FrameReceiver;
void SysTick_Handler() {
if (rx.timeout_counter > 0) rx.timeout_counter--;
}
void UartRxISR(uint8_t byte) {
if (rx.received_len == 0) {
rx.expected_len = byte; // 假設第一字節為長度
rx.timeout_counter = FRAME_TIMEOUT_MS; // 啟動超時
} else {
rx.data[rx.received_len++] = byte;
rx.timeout_counter = FRAME_TIMEOUT_MS; // 重載超時
if (rx.received_len >= rx.expected_len + 1) { // +校驗
// 校驗並標記完成
}
}
}
// 主循環檢查超時
if (rx.timeout_counter == 0 && rx.received_len > 0) {
rx.received_len = 0; // 超時丟棄幀
}
3.5 個字符傳輸時間 無數據到達,判定上一幀結束
接收到字節 → 重載定時器(3.5T) → 定時器溢出 → 標記幀完成 → 主循環處理
↑_____________________________________________|
每收到字節重新計時
// 定時器中斷(如1ms或更高精度)
void TimerISR() {
if (rx.gap_timer > 0) {
rx.gap_timer--;
if (rx.gap_timer == 0 && rx.len > 0) {
rx.frame_ready = 1; // 間隔超時,幀結束
}
}
}
void UartRxISR(uint8_t byte) {
rx.buffer[rx.len++] = byte;
rx.gap_timer = INTER_CHAR_TIMEOUT_TICKS; // 重載字符間超時
}
| 維度 | 特征字節法 | 長度字段法 | 超時間隔法 |
|---|---|---|---|
| 可靠性 | 高(雙邊界驗證) | 高(長度確定) | 中(依賴發送連續性) |
| 數據透明度 | 需轉義 | 無需轉義 | 無需轉義 |
| 實現複雜度 | 中 | 低 | 極低 |
| 適用場景 | 主從請求響應、調試協議 | 高速流數據、文件傳輸 | Modbus RTU、總線探針 |
| 抗干擾恢復 | 強(雙邊界重同步) | 中(需長度合法性檢查) | 弱(需超時重置) |
無論採用哪種幀同步方案,校驗碼不可或缺:
CRC-8 實現簡單// 簡易CRC-16 表驅動實現
uint16_t crc16_update(uint16_t crc, uint8_t data) {
static const uint16_t table[256] = { ... };
return (crc >> 8) ^ table[(crc ^ data) & 0xFF];
}
現代MCU(STM32、ESP32等)支持:
// STM32 DMA + IDLE 接收模式 // 1. 配置DMA循環模式接收到環形緩衝區 // 2. 開啟UART IDLE中斷 // 3. IDLE中斷中:計算本幀長度,發送給處理任務(隊列/信號量) // 4. 主任務從隊列取出幀指針長度,解析處理
可靠的串口數據包接收 = 幀同步機制 + 校驗保護 + 超時容錯 + 緩衝區管理。選型建議:
建議在協議設計階段即確定最大幀長、轉義規則、校驗算法、超時參數,並編寫自動化測試覆蓋殘幀、粘包、亂序、干擾等異常場景。