嵌入式串口通訊處理機(jī)制(附FIFO源碼),大家可以下載作為參考
在嵌入式串口通訊中,串口通訊應(yīng)用幾乎是必須的,非常常用,做好串口的數(shù)據(jù)處理是非常關(guān)鍵的一步,這里再分享一下如何在裸機(jī)中對(duì)串口數(shù)據(jù)的有效處理,
比如一包數(shù)據(jù)0xaa,0x55,0xXX,0xXX,0x0a,0x0d,簡單介紹串口處理的方法
一.直接在接受中斷中判斷數(shù)據(jù)
先定義一個(gè)uint8_t Buff和一個(gè)uint8_t Table[10];
在接收中斷函數(shù)里用HAL_UART_Receive_IT(&UART1_Handler, &Buff, 1)這個(gè)函數(shù),每次在中斷里面都判斷Buff是不是0xaa,如果是則將數(shù)據(jù)存入到Table[0]中且繼續(xù)接收下面的數(shù)據(jù),都存入到Table的數(shù)組中,如果不是則繼續(xù)進(jìn)行判斷。然后對(duì)Table進(jìn)行判斷,首先判斷Table[0]和Table[1]分別為0xaa,0x55后,在進(jìn)行判斷Table[4]和Table[5]分別為0x0a,0x0d。在進(jìn)行判斷校驗(yàn)是否正確,正確后取出Table[2]的數(shù)據(jù)進(jìn)行處理。這種方法在數(shù)據(jù)傳輸慢的情況下,比較簡單方便,還可以,但是在數(shù)據(jù)快的時(shí)候,非常容易造成數(shù)據(jù)的丟失,還有就是要是第一次數(shù)據(jù)接收錯(cuò)誤,回不到初始化狀態(tài),必須復(fù)位操作
二.FIFO方式 超時(shí)接受
接收中斷函數(shù)里用HAL_UART_Receive_IT(&UART1_Handler, &Buff, 1)這個(gè)函數(shù)接收數(shù)據(jù)的時(shí)候不要做數(shù)據(jù)處理,而是忠實(shí)地接收原始字節(jié)流,只管接受入列,就是接受完數(shù)據(jù)0xaa,0x55,0xXX,0xXX,0x0a,0x0d后,超時(shí)時(shí)間RxTimeOut3到以后再對(duì)數(shù)據(jù)處理
/* 數(shù)據(jù)入隊(duì) */chfifo_in(&RxFifo, &Buff);
/* 清零超時(shí) */RxTimeOut3 = 0;
三.DMA+空閑中斷
就是接收到一幀數(shù)據(jù)0xaa,0x55,0xXX,0xXX,0x0a,0x0d后才觸發(fā)中斷接受,處理數(shù)據(jù)非常高效。
下邊具體介紹串口環(huán)形隊(duì)列 FIFO方式
環(huán)形緩沖區(qū)(FIFO)就是一個(gè)帶“頭指針”和“尾指針”的數(shù)組。“頭指針”指向環(huán)形緩沖區(qū)中可讀的數(shù)據(jù),“尾指針”指向環(huán)形緩沖區(qū)中可寫的緩沖空間。通過移動(dòng)“頭指針”和“尾指針”就可以實(shí)現(xiàn)緩沖區(qū)的數(shù)據(jù)讀取和寫入。在通常情況下,應(yīng)用程序讀取環(huán)形緩沖區(qū)的數(shù)據(jù)僅僅會(huì)影響“頭指針”,而串口接收數(shù)據(jù)僅僅會(huì)影響“尾指針”。當(dāng)串口接收到新的數(shù)組,則將數(shù)組保存到環(huán)形緩沖區(qū)中,同時(shí)將“尾指針”加1,以保存下一個(gè)數(shù)據(jù);應(yīng)用程序在讀取數(shù)據(jù)時(shí),“頭指針”加1,以讀取下一個(gè)數(shù)據(jù)。當(dāng)“尾指針”超過數(shù)組大小,則“尾指針”重新指向數(shù)組的首元素,從而形成“環(huán)形緩沖區(qū)”!,有效數(shù)據(jù)區(qū)域在“頭指針”和“尾指針”之間。如下圖所示。
當(dāng)然,環(huán)形緩沖區(qū)的“頭指針”和“尾指針”可以用“頭變量”和“尾變量”來代替,因?yàn)榍袚Q數(shù)組的元素空間,除了可以用“指針偏移法”之外,還可以用“素組下標(biāo)偏移法”。當(dāng)串口接收到新的數(shù)組,則將數(shù)組保存到環(huán)形緩沖區(qū)中,同時(shí)將“尾變量”加一,以保存下一個(gè)數(shù)據(jù);應(yīng)用程序在讀取數(shù)據(jù)時(shí),“頭變量”加一,以讀取下一個(gè)數(shù)據(jù)。
“環(huán)形緩沖區(qū)”數(shù)據(jù)接收處理機(jī)制的好處在于:利用了隊(duì)列的特點(diǎn),一頭進(jìn),一頭出,互不影響,在數(shù)據(jù)進(jìn)去(往里存)的時(shí)候,另一邊也可以把數(shù)據(jù)讀出來,而讀出來的數(shù)據(jù),留下的空位,又可以增加多的存儲(chǔ)空間,從而避免一邊接收數(shù)據(jù)且一邊處理數(shù)據(jù)會(huì)在數(shù)據(jù)量密集的時(shí)候而導(dǎo)致的丟掉數(shù)據(jù)或者數(shù)據(jù)產(chǎn)生沖突的問題。
如果僅有一個(gè)線程讀取環(huán)形緩沖區(qū)的數(shù)據(jù),只有一個(gè)串口往環(huán)形緩沖區(qū)寫入數(shù)據(jù),則不需要添加互斥保護(hù)機(jī)制就可以保證數(shù)據(jù)的正確性。
需要注意的是,如果串口每接收x個(gè)字節(jié)的數(shù)據(jù)才處理一次,則環(huán)形緩沖區(qū)的緩沖數(shù)組的大小必須是x的N倍,具體N為多少,需要結(jié)合具體的數(shù)據(jù)接收速率以及處理速率,適當(dāng)調(diào)節(jié)。這就好比喻,水壺永遠(yuǎn)大于水杯,這樣子水壺才能存放很多杯水。
如果覺得前文隱晦難懂,那么下面我們來一起討論一下環(huán)形隊(duì)列的具體狀態(tài)以及實(shí)現(xiàn)。下文構(gòu)建的環(huán)形隊(duì)列采用的是“頭變量”“尾變量”來控制隊(duì)列的存儲(chǔ)和讀取。
首先,我們會(huì)構(gòu)造一個(gè)結(jié)構(gòu)體,并定義一個(gè)結(jié)構(gòu)變量。
#define MAX_SIZE 12 //緩沖區(qū)大小
typedef struct
{
unsigned char head; //緩沖區(qū)頭部位置
unsigned char tail; //緩沖區(qū)尾部位置
unsigned char ringBuf[MAX_SIZE]; //緩沖區(qū)數(shù)組
} ringBuffer_t;
ringBuffer_t buffer; //定義一個(gè)結(jié)構(gòu)體
定義一個(gè)結(jié)構(gòu)頭體則表示新的消息隊(duì)列已經(jīng)創(chuàng)建完成。新建創(chuàng)建的隊(duì)列,頭指針head和尾指針tail都是指向數(shù)組的元素0。如下圖所示,此時(shí)的消息隊(duì)列是“空隊(duì)列”。
當(dāng)如果l加入隊(duì)列,則緩沖隊(duì)列處于滿載狀態(tài),如下圖所示:如果此時(shí),接收到新的數(shù)據(jù)并需要保存,則tail需要?dú)w零,將接收到的數(shù)據(jù)存到數(shù)組的第一個(gè)元素空間,如果尚未讀取緩沖數(shù)組的一個(gè)元素空間的數(shù)據(jù),則此數(shù)據(jù)會(huì)被新接收的數(shù)據(jù)覆蓋。同時(shí)head需要增加1,修改頭節(jié)點(diǎn)偏移位置丟棄早期數(shù)據(jù)。
當(dāng)消息隊(duì)列中的所有數(shù)據(jù)都讀取出來后,此時(shí)環(huán)形隊(duì)列是空的,狀態(tài)如下圖所示。從圖可以總結(jié)得知,如果tail和head相等,則表示緩沖隊(duì)列是空的。
1.3.1 ringBuffer.c
1. 構(gòu)造環(huán)形緩沖區(qū)
/**********************************************************************************************
描述 : 環(huán)形緩沖讀寫
作者 : Jahol Fan
版本 : V1.0
修改 :
完成日期:
Notice :本程序只供學(xué)習(xí)使用,未經(jīng)作者許可,不得用于其它任何用途。版權(quán)所有,盜版必究
***********************************************************************************************/
#include "ringbuffer.h"
#define BUFFER_MAX 36 //緩沖區(qū)大小
typedef struct
{
unsigned char headPosition; //緩沖區(qū)頭部位置
unsigned char tailPositon; //緩沖區(qū)尾部位置
unsigned char ringBuf[BUFFER_MAX]; //緩沖區(qū)數(shù)組
} ringBuffer_t;
ringBuffer_t buffer; //定義一個(gè)結(jié)構(gòu)體
首先,需要構(gòu)建一個(gè)結(jié)構(gòu)體ringBuffer_t,如果定義一個(gè)結(jié)構(gòu)體變量buffer,則意味著創(chuàng)建一個(gè)環(huán)形緩沖區(qū)。
2. 往環(huán)形緩沖區(qū)存數(shù)據(jù)
/**
* @brief 寫一個(gè)字節(jié)到環(huán)形緩沖區(qū)
* @param data:待寫入的數(shù)據(jù)
* @return none
*/
void RingBuf_Write(unsigned char data)
{
buffer.ringBuf[buffer.tailPositon]=data; //從尾部追加
if(++buffer.tailPositon>=BUFFER_MAX) //尾節(jié)點(diǎn)偏移
buffer.tailPositon=0; //大于數(shù)組最大長度 歸零 形成環(huán)形隊(duì)列
if(buffer.tailPositon == buffer.headPosition)//如果尾部節(jié)點(diǎn)追到頭部節(jié)點(diǎn),則修改頭節(jié)點(diǎn)偏移位置丟棄早期數(shù)據(jù)
if(++buffer.headPosition>=BUFFER_MAX)
buffer.headPosition=0;
}
8行:將數(shù)據(jù)存放到tailPosition所指向的元素空間。
9行:tailPosition變量自增1,并且判斷,如果大于最大緩沖,則將tailPosition歸零。
11行:如果tailPositon與headPosition相等,則表示,數(shù)據(jù)存入速度大于數(shù)據(jù)取出速度,從到導(dǎo)致“追尾”。此時(shí)headPosition需要自增1,以丟棄早期數(shù)據(jù),這也就是數(shù)據(jù)“覆蓋現(xiàn)象”,這種現(xiàn)象是不允許存在的,解決這種現(xiàn)象的辦法是將緩沖隊(duì)列的空間再開大點(diǎn)。
13行:如果headPosition也大于最大數(shù)組,則需要將headPosition清零。
3. 讀取環(huán)形緩沖區(qū)的數(shù)據(jù)
/**
* @brief 讀取環(huán)形緩沖區(qū)的一個(gè)字節(jié)的數(shù)據(jù)
* @param *pData:指針,用于保存讀取到的數(shù)據(jù)
* @return 1表示緩沖區(qū)是空的,0表示讀取數(shù)據(jù)成功
*/
u8 RingBuf_Read(unsigned char* pData)
{
if(buffer.headPosition == buffer.tailPositon) //如果頭尾接觸表示緩沖區(qū)為空
{
return 1; //返回1,環(huán)形緩沖區(qū)是空的
}
else
{
*pData=buffer.ringBuf[buffer.headPosition]; //如果緩沖區(qū)非空則取頭節(jié)點(diǎn)值并偏移頭節(jié)點(diǎn)
if(++buffer.headPosition>=BUFFER_MAX)
buffer.headPosition=0;
return 0; //返回0,表示讀取數(shù)據(jù)成功
}
}
8行:首先判斷headPosition是否等于tailPositon,如果相等,則表明,此時(shí)緩沖區(qū)是空的。
10行:緩沖區(qū)為空,則直接返回,不執(zhí)行后面的程序
12行:如果緩沖區(qū)不為空,則條件成立并執(zhí)行
14行:讀取headPosition所指向的環(huán)形緩沖隊(duì)列的元素空間的數(shù)據(jù)。
15行:headPosition自增1以讀取下一個(gè)數(shù)據(jù)。如果headPosition大于最大值BUFFER_MAX,則將headPosition歸零。
17行:返回0,表示讀取數(shù)據(jù)成功。
看串口中斷函數(shù) ,數(shù)據(jù)接收成功,則將數(shù)據(jù)存入環(huán)形緩沖隊(duì)列
/**
* @brief 串口1中斷函數(shù)
* @param none
* @return none
*/
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //判斷接收標(biāo)志位是否為1
{
USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清楚標(biāo)志位
RingBuf_Write(USART_ReceiveData(USART1));
//阻塞等待直到傳輸完成
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
}
主函數(shù)就是解析數(shù)據(jù)
if(0 == RingBuf_Read(&data))//從環(huán)形緩沖區(qū)中讀取數(shù)據(jù)
{
user code //讀取接收到的數(shù)據(jù)并回發(fā)數(shù)據(jù)
}