首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

串口通信協議設計:完整數據包接收解析與幀同步機制

     編輯:關於C語言

串口通信協議設計:完整數據包接收解析與幀同步機制

在單片機通信系統(多機通信、PC與單片機通信)中,如何可靠地接收一個完整數據包是核心難題。本文系統梳理基於幀頭幀尾、幀長度、超時機制三大主流幀同步方案的原理、代碼實現、優缺點對比及工程避坑指南,適用於UART、SPI、I2C等串行總線通用場景。

核心挑戰:流式數據的幀邊界識別

串口屬於字節流通道,無天然數據包邊界。接收端必須通過協議機制從連續字節流中切分出完整的應用層數據包(幀)。

方案一:幀頭 + 數據 + 校驗 + 幀尾(特征字節法)

協議結構

[幀頭(1B)] [數據長度(可選)] [數據域(NB)] [校驗碼(1-2B)] [幀尾(1B)]

關鍵約束

  • 幀頭/幀尾字節不可出現在數據域中,否則產生誤判
  • 常用幀頭:0xAA、0x55、0xFE、0xEF 等
  • 若數據域可能包含特征字節,必須轉義(如SLIP協議: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;
    }
}

方案二:幀頭 + 長度 + 數據 + 校驗(長度字段法)

變種:幀頭+長度+數據+校驗 或 長度+校驗(無固定幀頭尾)

核心邏輯

  1. 接收幀頭(可選)
  2. 接收長度字節 L
  3. 循環接收 L 個數據字節
  4. 接收校驗字節,驗證通過則幀完成

超時保護機制(關鍵)

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;  // 超時丟棄幀
}

方案三:基於字節間隔超時的幀檢測(Modbus RTU 模式)

核心原理

  • 若連續 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/CCITT:工業標準,檢測突發錯誤能力極強
// 簡易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];
}

高級優化:DMA + 空閒中斷 / FIFO

現代MCU(STM32、ESP32等)支持:

  • DMA接收:零CPU干預搬運數據到內存
  • 空閒中斷:總線空閒超過1幀時間觸發,天然幀邊界
  • FIFO閾值中斷:硬件緩衝區達到水位觸發
// STM32 DMA + IDLE 接收模式
// 1. 配置DMA循環模式接收到環形緩衝區
// 2. 開啟UART IDLE中斷
// 3. IDLE中斷中:計算本幀長度,發送給處理任務(隊列/信號量)
// 4. 主任務從隊列取出幀指針長度,解析處理

總結

可靠的串口數據包接收 = 幀同步機制 + 校驗保護 + 超時容錯 + 緩衝區管理。選型建議:

  1. 簡單主從控制 → 幀頭尾法(易調試)
  2. 高吞吐透明傳輸 → 長度字段法 + CRC16
  3. Modbus兼容/總線監聽 → 超時間隔法
  4. 高性能產品級 → DMA + IDLE中斷 + 雙緩衝/環形緩衝區

建議在協議設計階段即確定最大幀長、轉義規則、校驗算法、超時參數,並編寫自動化測試覆蓋殘幀、粘包、亂序、干擾等異常場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved