STM32串口開發(fā)之環(huán)形緩沖區(qū)
01簡介
在之前的文章《stm32 串口詳解》中,我們講解了串口的基本應(yīng)用,使用串口中斷接收數(shù)據(jù),串口中斷發(fā)送回包(一般可以使用非中斷形式發(fā)送回包,在數(shù)據(jù)接收不頻繁的應(yīng)用中。串口接收中斷保證串口數(shù)據(jù)及時響應(yīng),使用非中斷方式發(fā)送回包即可)。
后面的文章《STM32使用DMA接收串口數(shù)據(jù)》和《STM32使用DMA發(fā)送串口數(shù)據(jù)》講解了如何使用DMA輔助串口收發(fā)數(shù)據(jù),使用DMA的好處在于不用CPU即可完成串口收發(fā)數(shù)據(jù),減輕CPU負(fù)擔(dān),在串口通信頻繁且不想頻繁中斷的應(yīng)用中非常有用。
除了上述兩種場景,還有一種應(yīng)用場景:串口接收數(shù)據(jù)長度位置,頻率未知,不要求實時處理的場景。如果采用上述方案,接收一幀數(shù)據(jù)立即處理,那么在處理的時候來的數(shù)據(jù)包就“丟失”了。這個時候就需要緩沖隊列來解決這個問題。
02緩沖區(qū)
緩沖區(qū)看名字就知道,是緩沖數(shù)據(jù)用的。實現(xiàn)緩沖區(qū)最簡單的辦法時,定義多個數(shù)組,接收一包數(shù)據(jù)到數(shù)組A,就把接收數(shù)據(jù)的地址換成數(shù)組B,每個數(shù)據(jù)有個標(biāo)記字節(jié)用于表示這個數(shù)組是否收到數(shù)據(jù),收到數(shù)據(jù)是否處理完成。
上述方案是完全可行的,但有缺點:
①緩沖數(shù)據(jù)組數(shù)一定,且有多變量,代碼結(jié)構(gòu)不太清晰。
②接收數(shù)據(jù)長度可能大于數(shù)組大小,也可能小于數(shù)組大小。不靈活,需要接收數(shù)據(jù)很長時容易出錯,且內(nèi)存利用率低。
解決這個問題的好辦法是:環(huán)形緩沖區(qū)。
環(huán)形緩沖區(qū)就是一個帶“頭指針”和“尾指針”的數(shù)組。“頭指針”指向環(huán)形緩沖區(qū)中可讀的數(shù)據(jù),“尾指針”指向環(huán)形緩沖區(qū)中可寫的緩沖空間。通過移動“頭指針”和“尾指針”就可以實現(xiàn)緩沖區(qū)的數(shù)據(jù)讀取和寫入。在通常情況下,應(yīng)用程序讀取環(huán)形緩沖區(qū)的數(shù)據(jù)僅僅會影響“頭指針”,而串口接收數(shù)據(jù)僅僅會影響“尾指針”。當(dāng)串口接收到新的數(shù)組,則將數(shù)組保存到環(huán)形緩沖區(qū)中,同時將“尾指針”加1,以保存下一個數(shù)據(jù);應(yīng)用程序在讀取數(shù)據(jù)時,“頭指針”加1,以讀取下一個數(shù)據(jù)。當(dāng)“尾指針”超過數(shù)組大小,則“尾指針”重新指向數(shù)組的首元素,從而形成“環(huán)形緩沖區(qū)”!,有效數(shù)據(jù)區(qū)域在“頭指針”和“尾指針”之間。如下圖
如上面說的,環(huán)形緩沖區(qū)其實就是一個數(shù)組,將其“剪開”,然后“拉直”后如下圖
環(huán)形緩沖區(qū)的特性
1、先進(jìn)新出。
2、當(dāng)緩沖區(qū)被使用完,且又有新的數(shù)據(jù)需要存儲時,丟掉歷史最久的數(shù)據(jù),保存最新數(shù)據(jù)。
03代碼實現(xiàn)
環(huán)形緩沖區(qū)的實現(xiàn)很簡單,只需要簡單的幾個接口即可。
首先需要創(chuàng)建一個環(huán)形緩沖區(qū)
- #define RINGBUFF_LEN (500) //定義最大接收字節(jié)數(shù) 500
- #define RINGBUFF_OK 1
- #define RINGBUFF_ERR 0
- typedef struct
- {
- uint16_t Head;
- uint16_t Tail;
- uint16_t Lenght;
- uint8_t Ring_data[RINGBUFF_LEN];
- }RingBuff_t;
- RingBuff_t ringBuff;//創(chuàng)建一個ringBuff的緩沖區(qū)
當(dāng)我們發(fā)現(xiàn)環(huán)形緩沖區(qū)被“沖爆”時,也就是緩沖區(qū)滿了,但是還有待緩沖的數(shù)據(jù)時,只需要修改RINGBUFF_LEN的宏定義,增大緩沖區(qū)間即可。
環(huán)形緩沖區(qū)的初始化
- /**
- * @brief RingBuff_Init
- * @param void
- * @return void
- * @note 初始化環(huán)形緩沖區(qū)
- */
- void RingBuff_Init(void)
- {
- //初始化相關(guān)信息
- ringBuff.Head = 0;
- ringBuff.Tail = 0;
- ringBuff.Lenght = 0;
- }
主要是將環(huán)形緩沖區(qū)的頭,尾和長度清零,表示沒有任何數(shù)據(jù)存入。
環(huán)形緩沖區(qū)的寫入
- /**
- * @brief Write_RingBuff
- * @param uint8_t data
- * @return FLASE:環(huán)形緩沖區(qū)已滿,寫入失敗;TRUE:寫入成功
- * @note 往環(huán)形緩沖區(qū)寫入uint8_t類型的數(shù)據(jù)
- */
- uint8_t Write_RingBuff(uint8_t data)
- {
- if(ringBuff.Lenght >= RINGBUFF_LEN) //判斷緩沖區(qū)是否已滿
- {
- return RINGBUFF_ERR;
- }
- ringBuff.Ring_data[ringBuff.Tail]=data;
- ringBuff.Tail = (ringBuff.Tail+1)%RINGBUFF_LEN;//防止越界非法訪問
- ringBuff.Lenght++;
- return RINGBUFF_OK;
- }
這個接口是寫入一個字節(jié)到環(huán)形緩沖區(qū)。這里注意:大家可以根據(jù)自己的實際應(yīng)用修改為一次緩沖多個字節(jié)。并且這個做了緩沖區(qū)滿時報錯且防止非法越界的處理,大家可以自行修改為緩沖區(qū)滿時覆蓋最早的數(shù)據(jù)。
環(huán)形緩沖區(qū)的讀取
- /**
- * @brief Read_RingBuff
- * @param uint8_t *rData,用于保存讀取的數(shù)據(jù)
- * @return FLASE:環(huán)形緩沖區(qū)沒有數(shù)據(jù),讀取失敗;TRUE:讀取成功
- * @note 從環(huán)形緩沖區(qū)讀取一個u8類型的數(shù)據(jù)
- */
- uint8_t Read_RingBuff(uint8_t *rData)
- {
- if(ringBuff.Lenght == 0)//判斷非空
- {
- return RINGBUFF_ERR;
- }
- *rData = ringBuff.Ring_data[ringBuff.Head];//先進(jìn)先出FIFO,從緩沖區(qū)頭出
- ringBuff.Head = (ringBuff.Head+1)%RINGBUFF_LEN;//防止越界非法訪問
- ringBuff.Lenght--;
- return RINGBUFF_OK;
- }
讀取的話也很簡單,同樣是讀取一個字節(jié),大家可以自行修改為讀取多個字節(jié)。
04驗證
光說不練假把式,下面我們就來驗證上面的代碼可行性。
串口中斷函數(shù)中緩沖數(shù)據(jù)
- void USART1_IRQHandler(void)
- {
- if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
- {
- Write_RingBuff(USART_ReceiveData(USART1));
- USART_ClearFlag(USART1, USART_FLAG_RXNE);
- }
- }
在主循環(huán)中,讀取緩沖區(qū)的數(shù)據(jù),然后發(fā)送出去,因為是簡單的demo,添加了延時模擬CPU處理其他任務(wù)。
- while (1)
- {
- if(Read_RingBuff(&data)) //從環(huán)形緩沖區(qū)中讀取數(shù)據(jù)
- {
- USART_SendData(USART1, data);
- }
- SysCtlDelay(1*(SystemCoreClock/3000));
- }
驗證,間隔100ms發(fā)送數(shù)據(jù)。
結(jié)果顯示沒有出現(xiàn)丟包問題。如果你的應(yīng)用場景串口通信速率快,數(shù)據(jù)量大或處理速度慢導(dǎo)致丟包,建議增大RINGBUFF_LEN的宏定義,增大緩沖區(qū)間即可。
本文轉(zhuǎn)載自微信公眾號「 知曉編程」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系 知曉編程公眾號。