高效的串口通信設(shè)計(jì):基于 STM32 的環(huán)形緩沖區(qū)收發(fā)機(jī)制
在嵌入式系統(tǒng)開發(fā)中,串口(UART)是最基礎(chǔ)也是最常用的通信方式之一。無論是用于調(diào)試信息的打印、與外設(shè)通信,還是與主控模塊的數(shù)據(jù)交互,一個(gè)穩(wěn)定可靠、結(jié)構(gòu)清晰的串口通信模塊都是不可或缺的。
介紹一個(gè)基于 STM32F4 系列微控制器實(shí)現(xiàn)的串口通信模塊,該模塊采用環(huán)形緩沖區(qū)結(jié)構(gòu),并結(jié)合中斷機(jī)制,實(shí)現(xiàn)了非阻塞、緩存式的數(shù)據(jù)收發(fā)。整體設(shè)計(jì)思路清晰、邏輯模塊化,適合在嵌入式項(xiàng)目中直接復(fù)用。
模塊結(jié)構(gòu)概覽
本模塊主要由兩個(gè)部分組成:
串口驅(qū)動(dòng)模塊(tty.c)
負(fù)責(zé) UART 的初始化、收發(fā)控制與中斷服務(wù)處理。環(huán)形緩沖區(qū)模塊(ringbuffer.c)
提供通用的循環(huán)數(shù)據(jù)緩存接口,實(shí)現(xiàn)數(shù)據(jù)的無損、非阻塞讀寫。
這種設(shè)計(jì)將通信協(xié)議與緩存機(jī)制分離,提升了系統(tǒng)的可維護(hù)性與移植性。
基本數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)
本模塊的核心是一個(gè) ring_buf_t 類型,其內(nèi)部應(yīng)定義如下字段(見 ringbuffer.h):
typedef struct {
unsigned char *buf; // 實(shí)際數(shù)據(jù)緩沖區(qū)
unsigned int size; // 緩沖區(qū)總大?。ū仨殲?的冪)
unsigned int front; // 數(shù)據(jù)讀取指針
unsigned int rear; // 數(shù)據(jù)寫入指針} ring_buf_t;
設(shè)計(jì)約束:緩沖區(qū)大小必須為 2 的整數(shù)次冪。
這是為了優(yōu)化環(huán)形地址 wrap-around 操作,使用按位與(&)替代取模運(yùn)算。
函數(shù)接口說明 初始化與清空
bool ring_buf_init(ring_buf_t *r, unsigned char *buf, unsigned int len);
初始化一個(gè)空的環(huán)形緩沖區(qū),要求 len 是 2 的冪,返回值表示初始化成功與否。
void ring_buf_clr(ring_buf_t *r);
將讀寫指針歸零,清空所有數(shù)據(jù)。
數(shù)據(jù)寫入
unsigned int ring_buf_put(ring_buf_t *r, unsigned char *buf, unsigned int len);
將外部數(shù)據(jù) buf 寫入到環(huán)形緩沖區(qū)中。若緩沖區(qū)剩余空間不足,則只寫入能容納的部分。返回實(shí)際寫入長度。
關(guān)鍵點(diǎn):
支持跨緩沖區(qū)尾部寫入(wrap-around);
寫入操作不會(huì)覆蓋未讀數(shù)據(jù);
使用 rear 指針更新寫入位置。
數(shù)據(jù)讀取
unsigned int ring_buf_get(ring_buf_t *r, unsigned char *buf, unsigned int len);
從環(huán)形緩沖區(qū)讀取數(shù)據(jù)至 buf,若請(qǐng)求數(shù)據(jù)超出已有長度,僅讀取實(shí)際可用部分。返回值為實(shí)際讀取字節(jié)數(shù)。
關(guān)鍵點(diǎn):
支持跨緩沖區(qū)尾部讀??;
讀取數(shù)據(jù)后 front 指針更新;
數(shù)據(jù)一旦讀取即“消費(fèi)”,不可重復(fù)讀取。
獲取當(dāng)前數(shù)據(jù)長度
unsigned int ring_buf_len(ring_buf_t *r);
返回當(dāng)前緩沖區(qū)中已存數(shù)據(jù)長度(rear - front)。注意該實(shí)現(xiàn)默認(rèn)讀寫指針不斷增加,不會(huì)回繞,即 unsigned int 類型下支持最大 4G 字節(jié)空間。
性能優(yōu)化點(diǎn)
位操作替代模運(yùn)算:
緩沖區(qū)大小為 2 的冪時(shí),可用 & (size - 1) 快速計(jì)算 wrap-around 的實(shí)際索引位置,減少 CPU 開銷。
r->rear & (r->size - 1) // 相當(dāng)于 r->rear % r->size
雙段 memcpy 提高吞吐:
為處理尾部 wrap 情況,寫入和讀取都拆分成兩個(gè) memcpy(),分別處理尾部和頭部?jī)啥巍?/span>
一、串口收發(fā)的關(guān)鍵設(shè)計(jì)思想1. 接收與發(fā)送分離
通過 USART1_IRQHandler 中斷服務(wù)函數(shù)分別處理 接收中斷 和 發(fā)送中斷,每次接收到數(shù)據(jù)就放入接收緩沖區(qū)(rxbuf),每次發(fā)送緩沖區(qū)中有數(shù)據(jù)就啟動(dòng)發(fā)送中斷。這樣設(shè)計(jì)的優(yōu)點(diǎn)是:
接收及時(shí)不中斷,防止數(shù)據(jù)丟失;
發(fā)送自動(dòng)控制,避免頻繁輪詢;
系統(tǒng)主循環(huán)更加干凈清晰。
2. 非阻塞緩沖機(jī)制
通過自定義結(jié)構(gòu) ring_buf_t,配合 ring_buf_put 與 ring_buf_get,實(shí)現(xiàn)了一個(gè)靈活的環(huán)形數(shù)據(jù)緩沖區(qū)。相比一次性收發(fā)固定數(shù)據(jù),這種緩存機(jī)制更具魯棒性,適合串口波動(dòng)大、數(shù)據(jù)密集或通信速率不一致的場(chǎng)合。
二、環(huán)形緩沖區(qū)的應(yīng)用價(jià)值
環(huán)形緩沖區(qū)(Ring Buffer)是一種“循環(huán)”的數(shù)據(jù)結(jié)構(gòu),空間開銷小、速度快,非常適合嵌入式實(shí)時(shí)系統(tǒng)中對(duì)性能要求高的通信模塊。
在串口收發(fā)中,它的典型作用包括:
解決串口收發(fā)異步性問題,接收與處理分離;
支持可變長度數(shù)據(jù)幀的緩沖處理;
與中斷或DMA天然契合,避免主線程阻塞;
數(shù)據(jù)臨時(shí)緩存,保障高并發(fā)場(chǎng)景的數(shù)據(jù)完整性。
三、統(tǒng)一串口接口設(shè)計(jì)
為了提高代碼復(fù)用性,模塊中使用了一個(gè)結(jié)構(gòu)體 tty_t 對(duì)串口操作進(jìn)行統(tǒng)一抽象,包括:
串口初始化函數(shù);
發(fā)送數(shù)據(jù)接口;
接收數(shù)據(jù)接口;
緩沖狀態(tài)判斷函數(shù)(是否滿、是否空);
通過將這些函數(shù)指針封裝在結(jié)構(gòu)體中,可以非常方便地實(shí)現(xiàn)“控制臺(tái)接口”或多串口同時(shí)支持,只需更換硬件配置部分即可。
const tty_t tty = {
uart_init,
uart_write,
uart_read,
tx_isfull,
tx_isempty,
rx_isempty
};
這種設(shè)計(jì)方式值得推廣到其他如 SPI、CAN、I2C 等通信模塊上,實(shí)現(xiàn)統(tǒng)一接口調(diào)用,提升代碼一致性。
四、典型應(yīng)用場(chǎng)景
這個(gè)串口收發(fā)模塊適合嵌入式項(xiàng)目中的以下典型場(chǎng)景:
設(shè)備調(diào)試打?。捍谧鳛?printf 的輸出設(shè)備,緩存打印內(nèi)容,防止打印阻塞主循環(huán)。
與上位機(jī)通訊:通過串口接收指令、發(fā)送響應(yīng)數(shù)據(jù),配合協(xié)議幀解析模塊構(gòu)成完整通訊鏈路。
傳感器數(shù)據(jù)采集:將高頻率傳感器的串口數(shù)據(jù)接收后緩存,主線程按需讀取處理。
工業(yè)控制通信:對(duì)實(shí)時(shí)性要求高,使用環(huán)形緩沖區(qū)和中斷機(jī)制可避免數(shù)據(jù)積壓。
五、設(shè)計(jì)優(yōu)點(diǎn)總結(jié)
模塊化清晰:緩存模塊與串口驅(qū)動(dòng)分離,便于獨(dú)立調(diào)試、復(fù)用。
性能穩(wěn)定:中斷驅(qū)動(dòng) + 緩沖機(jī)制,避免數(shù)據(jù)丟失。
擴(kuò)展靈活:支持任意大小的緩存、多個(gè)串口實(shí)例。
移植方便:與具體芯片無強(qiáng)耦合,適合在不同 STM32 系列中復(fù)用。
六、推薦使用方式
建議將此模塊封裝為標(biāo)準(zhǔn)組件,并在上層封裝為串口服務(wù)層,例如:
tty.uart_init(115200);tty.uart_write("Hello World", 11);
上層應(yīng)用只需調(diào)用接口函數(shù),無需關(guān)注底層緩沖邏輯與中斷機(jī)制,提高應(yīng)用開發(fā)效率。
七、后續(xù)可拓展方向
支持 DMA 模式收發(fā),進(jìn)一步提升數(shù)據(jù)吞吐;
加入幀協(xié)議解析支持(如 Modbus、自定義幀);
增加線程/RTOS安全訪問控制;
緩沖區(qū)動(dòng)態(tài)分配與多通道管理。
結(jié)語
一個(gè)好的串口模塊設(shè)計(jì),往往是嵌入式系統(tǒng)穩(wěn)定運(yùn)行的基礎(chǔ)。本文介紹的環(huán)形緩沖機(jī)制與中斷控制結(jié)合的串口收發(fā)架構(gòu),具有良好的通用性、擴(kuò)展性與實(shí)際工程適用性,值得在項(xiàng)目中加以實(shí)踐與改進(jìn)。
如你也在做基于 STM32 的嵌入式項(xiàng)目,這套結(jié)構(gòu)可以幫助你快速搭建一個(gè)健壯、可擴(kuò)展的串口通信模塊。
開源代碼:
#include "ringbuffer.h"#include <string.h>#include <stddef.h>#define min(a,b) ( (a) < (b) )? (a):(b)
/*
*@brief 構(gòu)造一個(gè)空環(huán)形緩沖區(qū)
*@param[in] r - 環(huán)形緩沖區(qū)管理器
*@param[in] buf - 數(shù)據(jù)緩沖區(qū)
*@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 清空環(huán)形緩沖區(qū)
*@param[in] r - 待清空的環(huán)形緩沖區(qū)
*@retval none
*/void ring_buf_clr(ring_buf_t *r){
r->front = r->rear = 0;
}/*
*@brief 獲取環(huán)形緩沖區(qū)數(shù)據(jù)長度
*@retval 環(huán)形緩沖區(qū)中有效字節(jié)數(shù)
*/unsigned int ring_buf_len(ring_buf_t *r){ return r->rear - r->front;
}/*
*@brief 將指定長度的數(shù)據(jù)放到環(huán)形緩沖區(qū)中
*@param[in] buf - 數(shù)據(jù)緩沖區(qū)
* len - 緩沖區(qū)長度
*@retval 實(shí)際放到中的數(shù)據(jù)
*/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 從環(huán)形緩沖區(qū)中讀取指定長度的數(shù)據(jù)
*@param[in] len - 讀取長度
*@param[out] buf - 輸出數(shù)據(jù)緩沖區(qū)
*@retval 實(shí)際讀取長度
*/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;
}
評(píng)論