• <li id="00i08"><input id="00i08"></input></li>
  • <sup id="00i08"><tbody id="00i08"></tbody></sup>
    <abbr id="00i08"></abbr>
  • 新聞中心

    EEPW首頁 > 嵌入式系統 > 設計應用 > 高效的串口通信設計:基于 STM32 的環形緩沖區收發機制

    高效的串口通信設計:基于 STM32 的環形緩沖區收發機制

    作者:嵌入式芯視野 時間:2025-07-09 來源:今日頭條 收藏

    在嵌入式系統開發中,串口(UART)是最基礎也是最常用的通信方式之一。無論是用于調試信息的打印、與外設通信,還是與主控模塊的數據交互,一個穩定可靠、結構清晰的模塊都是不可或缺的。

    介紹一個基于 STM32F4 系列微控制器實現的模塊,該模塊采用環形緩沖區結構,并結合中斷機制,實現了非阻塞、緩存式的數據收發。整體設計思路清晰、邏輯模塊化,適合在嵌入式項目中直接復用。

    模塊結構概覽

    本模塊主要由兩個部分組成:

    1. 串口驅動模塊(tty.c)
      負責 UART 的初始化、收發控制與中斷服務處理。

    2. 環形緩沖區模塊(ringbuffer.c)
      提供通用的循環數據緩存接口,實現數據的無損、非阻塞讀寫。

    這種設計將通信協議與緩存機制分離,提升了系統的可維護性與移植性。

    基本數據結構設計

    本模塊的核心是一個 ring_buf_t 類型,其內部應定義如下字段(見 ringbuffer.h):

    typedef struct {
        unsigned char *buf;   // 實際數據緩沖區
        unsigned int size;    // 緩沖區總大小(必須為2的冪)
        unsigned int front;   // 數據讀取指針
        unsigned int rear;    // 數據寫入指針} ring_buf_t;

    設計約束:緩沖區大小必須為 2 的整數次冪。
    這是為了優化環形地址 wrap-around 操作,使用按位與(&)替代取模運算。


    函數接口說明 初始化與清空

    bool ring_buf_init(ring_buf_t *r, unsigned char *buf, unsigned int len);

    初始化一個空的環形緩沖區,要求 len 是 2 的冪,返回值表示初始化成功與否。

    void ring_buf_clr(ring_buf_t *r);

    將讀寫指針歸零,清空所有數據。


    數據寫入

    unsigned int ring_buf_put(ring_buf_t *r, unsigned char *buf, unsigned int len);

    將外部數據 buf 寫入到環形緩沖區中。若緩沖區剩余空間不足,則只寫入能容納的部分。返回實際寫入長度。

    關鍵點:

    • 支持跨緩沖區尾部寫入(wrap-around);

    • 寫入操作不會覆蓋未讀數據;

    • 使用 rear 指針更新寫入位置。


    數據讀取

    unsigned int ring_buf_get(ring_buf_t *r, unsigned char *buf, unsigned int len);

    從環形緩沖區讀取數據至 buf,若請求數據超出已有長度,僅讀取實際可用部分。返回值為實際讀取字節數。

    關鍵點:

    • 支持跨緩沖區尾部讀取;

    • 讀取數據后 front 指針更新;

    • 數據一旦讀取即“消費”,不可重復讀取。


     獲取當前數據長度

    unsigned int ring_buf_len(ring_buf_t *r);

    返回當前緩沖區中已存數據長度(rear - front)。注意該實現默認讀寫指針不斷增加,不會回繞,即 unsigned int 類型下支持最大 4G 字節空間。


    性能優化點

    1. 位操作替代模運算:
      緩沖區大小為 2 的冪時,可用 & (size - 1) 快速計算 wrap-around 的實際索引位置,減少 CPU 開銷。

    r->rear & (r->size - 1)  // 相當于 r->rear % r->size
    1. 雙段 memcpy 提高吞吐:
      為處理尾部 wrap 情況,寫入和讀取都拆分成兩個 memcpy(),分別處理尾部和頭部兩段。


    一、串口收發的關鍵設計思想1. 接收與發送分離

    通過 USART1_IRQHandler 中斷服務函數分別處理 接收中斷 和 發送中斷,每次接收到數據就放入接收緩沖區(rxbuf),每次發送緩沖區中有數據就啟動發送中斷。這樣設計的優點是:

    • 接收及時不中斷,防止數據丟失;

    • 發送自動控制,避免頻繁輪詢;

    • 系統主循環更加干凈清晰。

    2. 非阻塞緩沖機制

    通過自定義結構 ring_buf_t,配合 ring_buf_put 與 ring_buf_get,實現了一個靈活的環形數據緩沖區。相比一次性收發固定數據,這種緩存機制更具魯棒性,適合串口波動大、數據密集或通信速率不一致的場合。


    二、環形緩沖區的應用價值

    環形緩沖區(Ring Buffer)是一種“循環”的數據結構,空間開銷小、速度快,非常適合嵌入式實時系統中對性能要求高的通信模塊。

    在串口收發中,它的典型作用包括:

    • 解決串口收發異步性問題,接收與處理分離;

    • 支持可變長度數據幀的緩沖處理;

    • 與中斷或DMA天然契合,避免主線程阻塞;

    • 數據臨時緩存,保障高并發場景的數據完整性。


    三、統一串口接口設計

    為了提高代碼復用性,模塊中使用了一個結構體 tty_t 對串口操作進行統一抽象,包括:

    • 串口初始化函數;

    • 發送數據接口;

    • 接收數據接口;

    • 緩沖狀態判斷函數(是否滿、是否空);

    通過將這些函數指針封裝在結構體中,可以非常方便地實現“控制臺接口”或多串口同時支持,只需更換硬件配置部分即可。

    const tty_t tty = {
        uart_init,
        uart_write,
        uart_read,
        tx_isfull,
        tx_isempty,
        rx_isempty
    };

    這種設計方式值得推廣到其他如 SPI、CAN、I2C 等通信模塊上,實現統一接口調用,提升代碼一致性。


    四、典型應用場景

    這個串口收發模塊適合嵌入式項目中的以下典型場景:

    • 設備調試打印:串口作為 printf 的輸出設備,緩存打印內容,防止打印阻塞主循環。

    • 與上位機通訊:通過串口接收指令、發送響應數據,配合協議幀解析模塊構成完整通訊鏈路。

    • 傳感器數據采集:將高頻率傳感器的串口數據接收后緩存,主線程按需讀取處理。

    • 工業控制通信:對實時性要求高,使用環形緩沖區和中斷機制可避免數據積壓。


    五、設計優點總結

    • 模塊化清晰:緩存模塊與串口驅動分離,便于獨立調試、復用。

    • 性能穩定:中斷驅動 + 緩沖機制,避免數據丟失。

    • 擴展靈活:支持任意大小的緩存、多個串口實例。

    • 移植方便:與具體芯片無強耦合,適合在不同 STM32 系列中復用。


    六、推薦使用方式

    建議將此模塊封裝為標準組件,并在上層封裝為串口服務層,例如:

    tty.uart_init(115200);tty.uart_write("Hello World", 11);

    上層應用只需調用接口函數,無需關注底層緩沖邏輯與中斷機制,提高應用開發效率。


    七、后續可拓展方向

    • 支持 DMA 模式收發,進一步提升數據吞吐;

    • 加入幀協議解析支持(如 Modbus、自定義幀);

    • 增加線程/RTOS安全訪問控制;

    • 緩沖區動態分配與多通道管理。


    結語

    一個好的串口模塊設計,往往是嵌入式系統穩定運行的基礎。本文介紹的環形緩沖機制與中斷控制結合的串口收發架構,具有良好的通用性、擴展性與實際工程適用性,值得在項目中加以實踐與改進。

    如你也在做基于 STM32 的嵌入式項目,這套結構可以幫助你快速搭建一個健壯、可擴展的模塊。

    開源代碼:

    #include "ringbuffer.h"#include <string.h>#include <stddef.h>#define min(a,b) ( (a) < (b) )? (a):(b)     
         /*
     *@brief      構造一個空環形緩沖區
     *@param[in]  r    - 環形緩沖區管理器
     *@param[in]  buf  - 數據緩沖區
     *@param[in]  len  - buf長度(必須是2的N次冪)
     *@retval     bool
     */bool ring_buf_init(ring_buf_t *r,unsigned char *buf, unsigned int len){
        r->buf    = buf;
        r->size   = len;
        r->front  = r->rear = 0;    return buf != NULL && (len & len -1) == 0;
    }/*
     *@brief      清空環形緩沖區 
     *@param[in]  r - 待清空的環形緩沖區
     *@retval     none
     */void ring_buf_clr(ring_buf_t *r){
        r->front = r->rear = 0;
    }/*
     *@brief      獲取環形緩沖區數據長度
     *@retval     環形緩沖區中有效字節數 
     */unsigned int ring_buf_len(ring_buf_t *r){    return r->rear - r->front;
    }/*
     *@brief       將指定長度的數據放到環形緩沖區中 
     *@param[in]   buf - 數據緩沖區
     *             len - 緩沖區長度 
     *@retval      實際放到中的數據 
     */unsigned int ring_buf_put(ring_buf_t *r,unsigned char *buf,unsigned int len){    unsigned int i;    unsigned int left;
        left = r->size + r->front - r->rear;
        len  = min(len , left);
        i    = min(len, r->size - (r->rear & r->size - 1));   
        memcpy(r->buf + (r->rear & r->size - 1), buf, i); 
        memcpy(r->buf, buf + i, len - i);
        r->rear += len;     
        return len;
        
    }/*
     *@brief       從環形緩沖區中讀取指定長度的數據 
     *@param[in]   len - 讀取長度 
     *@param[out]  buf - 輸出數據緩沖區
     *@retval      實際讀取長度 
     */unsigned int ring_buf_get(ring_buf_t *r,unsigned char *buf,unsigned int len){    unsigned int i;    unsigned int left;    
        left = r->rear - r->front;
        len  = min(len , left);                                
        i    = min(len, r->size - (r->front & r->size - 1));    memcpy(buf, r->buf + (r->front & r->size - 1), i);    
        memcpy(buf + i, r->buf, len - i);   
        r->front += len;    return len;
    }



    關鍵詞: 串口通信

    評論


    相關推薦

    技術專區

    關閉
    主站蜘蛛池模板: 民权县| 沙河市| 中方县| 新邵县| 信宜市| 广汉市| 清新县| 图们市| 天镇县| 通道| 阿合奇县| 福贡县| 龙胜| 卢氏县| 柳州市| 福鼎市| 乌兰察布市| 息烽县| 东宁县| 阳谷县| 洛南县| 密山市| 高淳县| 莲花县| 衢州市| 永安市| 兴宁市| 大连市| 高淳县| 林芝县| 微博| 宜宾县| 长宁县| 彭水| 梁平县| 临海市| 林州市| 兴安县| 瑞金市| 临安市| 司法|