一、前言
在嵌入式系統(tǒng)開發(fā)中,與上位機(jī)進(jìn)行串口通信是非常常見的場景。上位機(jī)可以通過串口發(fā)送指令或者數(shù)據(jù)給嵌入式設(shè)備,而嵌入式設(shè)備需要可靠地接收并解析這些數(shù)據(jù),以執(zhí)行相應(yīng)的操作。然而,在串口通信過程中,上位機(jī)發(fā)送數(shù)據(jù)的速率往往與嵌入式設(shè)備接收和處理數(shù)據(jù)的速率不一致,這就可能導(dǎo)致數(shù)據(jù)的丟失或者誤解析。
為了解決這個問題,決定設(shè)計并實現(xiàn)一個環(huán)形緩沖區(qū)來進(jìn)行數(shù)據(jù)接收管理。環(huán)形緩沖區(qū)是一種高效的數(shù)據(jù)結(jié)構(gòu),適用于數(shù)據(jù)產(chǎn)生速率快于消費(fèi)速率的場景。它具有固定大小的緩沖區(qū),并且可以循環(huán)利用空間,保證數(shù)據(jù)的連續(xù)存儲和有效利用。
在本項目中,選擇使用STM32微控制器來實現(xiàn)串口數(shù)據(jù)接收功能。STM32具有豐富的外設(shè)資源和強(qiáng)大的性能,非常適合用于串口通信和數(shù)據(jù)處理。通過在STM32上實現(xiàn)環(huán)形緩沖區(qū),可以實現(xiàn)以下目標(biāo):
(1)數(shù)據(jù)穩(wěn)定接收:通過使用環(huán)形緩沖區(qū),確保即使在接收數(shù)據(jù)速率慢于發(fā)送速率的情況下,數(shù)據(jù)也能夠得到穩(wěn)定的接收,避免數(shù)據(jù)丟失。
(2)數(shù)據(jù)緩存和管理:環(huán)形緩沖區(qū)可以作為一個數(shù)據(jù)緩存區(qū),將接收到的數(shù)據(jù)暫時存儲起來,以便后續(xù)處理。這樣可以降低數(shù)據(jù)處理的延遲和復(fù)雜性。
(3)數(shù)據(jù)解析和應(yīng)用:通過從環(huán)形緩沖區(qū)中讀取數(shù)據(jù),并進(jìn)行解析和處理,嵌入式設(shè)備可以根據(jù)接收到的數(shù)據(jù)執(zhí)行相應(yīng)的操作,如控制外部設(shè)備或響應(yīng)上位機(jī)指令。
通過使用環(huán)形緩沖區(qū)管理串口接收的數(shù)據(jù),可以實現(xiàn)可靠的數(shù)據(jù)接收和處理,并提高系統(tǒng)的穩(wěn)定性和可靠性。同時,該方案也適用于其他嵌入式系統(tǒng)和通信場景。
二、實現(xiàn)思路
(1)定義環(huán)形緩沖區(qū)的結(jié)構(gòu)體:首先,需要定義一個表示環(huán)形緩沖區(qū)的結(jié)構(gòu)體,其中包含以下成員變量:
- 緩沖區(qū)的大?。╟apacity):表示環(huán)形緩沖區(qū)的容量,即可以存儲的最大元素數(shù)量。
- 寫指針(write_ptr):表示當(dāng)前可寫入數(shù)據(jù)的位置。
- 讀指針(read_ptr):表示當(dāng)前可讀取數(shù)據(jù)的位置。
- 數(shù)據(jù)數(shù)組(buffer):用于存儲實際的數(shù)據(jù)。
(2)初始化環(huán)形緩沖區(qū):在使用環(huán)形緩沖區(qū)之前,需要進(jìn)行初始化。初始化時,將緩沖區(qū)的大小、寫指針和讀指針都設(shè)置為初始位置,通常都是0。
(3)寫入數(shù)據(jù):當(dāng)有新的數(shù)據(jù)要寫入緩沖區(qū)時,需要執(zhí)行以下操作:
- 檢查緩沖區(qū)是否已滿,如果已滿則無法寫入新的數(shù)據(jù)。
- 將數(shù)據(jù)寫入當(dāng)前寫指針?biāo)赶虻奈恢谩?/li>
- 更新寫指針的位置,通常是將其加1,并考慮到環(huán)形特性,需要進(jìn)行取模運(yùn)算。
(4)讀取數(shù)據(jù):當(dāng)需要從緩沖區(qū)中讀取數(shù)據(jù)時,需要執(zhí)行以下操作:
- 檢查緩沖區(qū)是否為空,如果為空則無數(shù)據(jù)可讀取。
- 讀取當(dāng)前讀指針?biāo)赶虻臄?shù)據(jù)。
- 更新讀指針的位置,通常是將其加1,并考慮到環(huán)形特性,需要進(jìn)行取模運(yùn)算。
(5)判斷緩沖區(qū)狀態(tài):為了方便使用和管理緩沖區(qū),可以實現(xiàn)一些用于判斷緩沖區(qū)狀態(tài)的函數(shù),例如:
- is_full():判斷緩沖區(qū)是否已滿。
- is_empty():判斷緩沖區(qū)是否為空。
實現(xiàn)環(huán)形緩沖區(qū)時,需要注意:
- 寫指針和讀指針的位置計算要考慮到環(huán)形特性,即超過緩沖區(qū)容量時需要進(jìn)行取模運(yùn)算。
- 緩沖區(qū)大小要合理選擇,根據(jù)實際需求確定,以充分利用內(nèi)存資源并避免數(shù)據(jù)丟失。
- 多線程或中斷環(huán)境下的并發(fā)訪問要考慮數(shù)據(jù)同步和互斥操作,以避免競爭條件和數(shù)據(jù)不一致的問題。
通過以上思路,可以在C語言中實現(xiàn)一個簡單高效的環(huán)形緩沖區(qū),用于存儲和管理數(shù)據(jù),在數(shù)據(jù)收發(fā)過程中提高系統(tǒng)的穩(wěn)定性和可靠性。
三、 C語言實現(xiàn)驗證思路
#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE 10
typedef struct {
int* buffer; // 緩沖區(qū)數(shù)組指針
int size; // 緩沖區(qū)大小
int head; // 頭部索引
int tail; // 尾部索引
} CircularBuffer;
// 創(chuàng)建環(huán)形緩沖區(qū)
CircularBuffer* createCircularBuffer(int size) {
CircularBuffer* cb = (CircularBuffer*)malloc(sizeof(CircularBuffer)); // 分配內(nèi)存空間
cb->buffer = (int*)malloc(sizeof(int) * size); // 分配緩沖區(qū)數(shù)據(jù)的內(nèi)存空間
cb->size = size; // 設(shè)置緩沖區(qū)大小
cb->head = 0; // 初始化頭部索引為0
cb->tail = 0; // 初始化尾部索引為0
return cb;
}
// 銷毀環(huán)形緩沖區(qū)
void destroyCircularBuffer(CircularBuffer* cb) {
free(cb->buffer); // 釋放緩沖區(qū)數(shù)據(jù)的內(nèi)存空間
free(cb); // 釋放緩沖區(qū)結(jié)構(gòu)體的內(nèi)存空間
}
// 判斷環(huán)形緩沖區(qū)是否已滿
int isCircularBufferFull(CircularBuffer* cb) {
return ((cb->tail + 1) % cb->size == cb->head);
}
// 判斷環(huán)形緩沖區(qū)是否為空
int isCircularBufferEmpty(CircularBuffer* cb) {
return (cb->head == cb->tail);
}
// 寫入數(shù)據(jù)到環(huán)形緩沖區(qū)
void writeData(CircularBuffer* cb, int data) {
if (isCircularBufferFull(cb)) { // 如果緩沖區(qū)已滿,則無法寫入數(shù)據(jù)
printf("Circular buffer is full. Data cannot be written.n");
return;
}
cb->buffer[cb->tail] = data; // 將數(shù)據(jù)寫入緩沖區(qū)的尾部
cb->tail = (cb->tail + 1) % cb->size; // 更新尾部索引,循環(huán)利用緩沖區(qū)空間
}
// 從環(huán)形緩沖區(qū)讀取數(shù)據(jù)
int readData(CircularBuffer* cb) {
if (isCircularBufferEmpty(cb)) { // 如果緩沖區(qū)為空,則無數(shù)據(jù)可讀取
printf("Circular buffer is empty. No data to read.n");
return -1; // 返回一個默認(rèn)值表示讀取失敗
}
int data = cb->buffer[cb->head]; // 從緩沖區(qū)的頭部讀取數(shù)據(jù)
cb->head = (cb->head + 1) % cb->size; // 更新頭部索引,循環(huán)利用緩沖區(qū)空間
return data;
}
int main() {
CircularBuffer* cb = createCircularBuffer(BUFFER_SIZE); // 創(chuàng)建大小為BUFFER_SIZE的環(huán)形緩沖區(qū)
writeData(cb, 1); // 寫入數(shù)據(jù)1
writeData(cb, 2); // 寫入數(shù)據(jù)2
writeData(cb, 3); // 寫入數(shù)據(jù)3
printf("Read data: %dn", readData(cb)); // 讀取數(shù)據(jù)并打印
printf("Read data: %dn", readData(cb));
writeData(cb, 4);
writeData(cb, 5);
printf("Read data: %dn", readData(cb));
printf("Read data: %dn", readData(cb));
printf("Read data: %dn", readData(cb));
destroyCircularBuffer(cb); // 銷毀環(huán)形緩沖區(qū)
return 0;
}
四、STM32串口接收
#define BUFFER_SIZE 256
typedef struct {
uint8_t buffer[BUFFER_SIZE];
uint16_t head;
uint16_t tail;
} CircularBuffer;
// 初始化環(huán)形緩沖區(qū)
void CircularBuffer_Init(CircularBuffer* cb) {
cb->head = 0;
cb->tail = 0;
}
// 判斷環(huán)形緩沖區(qū)是否已滿
bool CircularBuffer_IsFull(const CircularBuffer* cb) {
return (cb->head + 1) % BUFFER_SIZE == cb->tail;
}
// 判斷環(huán)形緩沖區(qū)是否為空
bool CircularBuffer_IsEmpty(const CircularBuffer* cb) {
return cb->head == cb->tail;
}
// 向環(huán)形緩沖區(qū)寫入數(shù)據(jù)
bool CircularBuffer_Write(CircularBuffer* cb, uint8_t data) {
if (CircularBuffer_IsFull(cb)) { // 緩沖區(qū)已滿,無法寫入
return false;
}
cb->buffer[cb->head] = data;
cb->head = (cb->head + 1) % BUFFER_SIZE;
return true;
}
// 從環(huán)形緩沖區(qū)讀取數(shù)據(jù)
bool CircularBuffer_Read(CircularBuffer* cb, uint8_t* data) {
if (CircularBuffer_IsEmpty(cb)) { // 緩沖區(qū)為空,無數(shù)據(jù)可讀取
return false;
}
*data = cb->buffer[cb->tail];
cb->tail = (cb->tail + 1) % BUFFER_SIZE;
return true;
}
// 獲取環(huán)形緩沖區(qū)剩余大小
uint16_t CircularBuffer_GetRemainingSize(const CircularBuffer* cb) {
if (cb->head >= cb->tail) {
return BUFFER_SIZE - (cb->head - cb->tail);
} else {
return cb->tail - cb->head - 1;
}
}
// 獲取環(huán)形緩沖區(qū)已寫入大小
uint16_t CircularBuffer_GetWrittenSize(const CircularBuffer* cb) {
if (cb->head >= cb->tail) {
return cb->head - cb->tail;
} else {
return BUFFER_SIZE - (cb->tail - cb->head - 1);
}
}
// 從環(huán)形緩沖區(qū)讀取指定長度的數(shù)據(jù)
bool CircularBuffer_ReadData(CircularBuffer* cb, uint8_t* data, uint16_t length) {
if (CircularBuffer_GetWrittenSize(cb) < length) {
return false; // 緩沖區(qū)中的數(shù)據(jù)不足
}
for (uint16_t i = 0; i < length; ++i) {
if (!CircularBuffer_Read(cb, &data[i])) {
return false; // 讀取數(shù)據(jù)出錯
}
}
return true;
}
// 向環(huán)形緩沖區(qū)寫入指定長度的數(shù)據(jù)
bool CircularBuffer_WriteData(CircularBuffer* cb, const uint8_t* data, uint16_t length) {
if (CircularBuffer_GetRemainingSize(cb) < length) {
return false; // 緩沖區(qū)剩余空間不足
}
for (uint16_t i = 0; i < length; ++i) {
if (!CircularBuffer_Write(cb, data[i])) {
return false; // 寫入數(shù)據(jù)出錯
}
}
return true;
}
// 示例:STM32串口接收中斷處理函數(shù)
void USART_Receive_IRQHandler(void) {
uint8_t data = USART_ReceiveData(USART1); // 獲取接收到的數(shù)據(jù)
if (!CircularBuffer_Write(&rxBuffer, data)) {
// 緩沖區(qū)已滿,處理錯誤
}
}
在代碼中,定義了一個名為CircularBuffer
的結(jié)構(gòu)體來表示環(huán)形緩沖區(qū)。包含了一個具有固定大小的數(shù)組buffer
用于存儲數(shù)據(jù),以及頭部指針head
和尾部指針tail
用于管理數(shù)據(jù)的讀寫位置。
接下來,實現(xiàn)了一些函數(shù)來對環(huán)形緩沖區(qū)進(jìn)行操作。CircularBuffer_Init
函數(shù)用于初始化環(huán)形緩沖區(qū);CircularBuffer_IsFull
和CircularBuffer_IsEmpty
函數(shù)分別判斷緩沖區(qū)是否已滿和是否為空;CircularBuffer_Write
函數(shù)用于向緩沖區(qū)寫入數(shù)據(jù);CircularBuffer_Read
函數(shù)用于從緩沖區(qū)讀取數(shù)據(jù)。
CircularBuffer_GetRemainingSize
函數(shù)用于獲取環(huán)形緩沖區(qū)的剩余大小,即還能寫入多少個字節(jié)的數(shù)據(jù);CircularBuffer_GetWrittenSize
函數(shù)用于獲取已經(jīng)寫入到緩沖區(qū)的字節(jié)數(shù);CircularBuffer_ReadData
函數(shù)用于從環(huán)形緩沖區(qū)讀取指定長度的數(shù)據(jù),將其存儲到提供的數(shù)據(jù)數(shù)組中;CircularBuffer_WriteData
函數(shù)用于向環(huán)形緩沖區(qū)寫入指定長度的數(shù)據(jù),從提供的數(shù)據(jù)數(shù)組中復(fù)制相應(yīng)的字節(jié)。
使用這些方便函數(shù),可以更方便地管理環(huán)形緩沖區(qū),實現(xiàn)數(shù)據(jù)的讀取和寫入。
最后,給出了一個示例,展示在STM32串口接收中斷處理函數(shù)中將接收到的數(shù)據(jù)寫入環(huán)形緩沖區(qū)。在中斷處理函數(shù)中,通過USART_ReceiveData
函數(shù)獲取接收到的數(shù)據(jù),調(diào)用CircularBuffer_Write
函數(shù)將數(shù)據(jù)寫入緩沖區(qū)。