STM32串口開發之環形緩衝區

0一、簡介

在以前的文章《stm32 串口詳解》中,咱們講解了串口的基本應用,使用串口中斷接收數據,串口中斷髮送回包(通常可使用非中斷形式發送回包,在數據接收不頻繁的應用中。串口接收中斷保證串口數據及時響應,使用非中斷方式發送回包便可)。git

後面的文章《STM32使用DMA接收串口數據》和《STM32使用DMA發送串口數據》講解了如何使用DMA輔助串口收發數據,使用DMA的好處在於不用CPU便可完成串口收發數據,減輕CPU負擔,在串口通訊頻繁且不想頻繁中斷的應用中很是有用。github

除了上述兩種場景,還有一種應用場景:串口接收數據長度位置,頻率未知,不要求實時處理的場景。若是採用上述方案,接收一幀數據當即處理,那麼在處理的時候來的數據包就「丟失」了。這個時候就須要緩衝隊列來解決這個問題。數組

0二、緩衝區

緩衝區看名字就知道,是緩衝數據用的。實現緩衝區最簡單的辦法時,定義多個數組,接收一包數據到數組A,就把接收數據的地址換成數組B,每一個數據有個標記字節用於表示這個數組是否收到數據,收到數據是否處理完成。app

上述方案是徹底可行的,但有缺點:函數

①緩衝數據組數必定,且有多變量,代碼結構不太清晰。ui

②接收數據長度可能大於數組大小,也可能小於數組大小。不靈活,須要接收數據很長時容易出錯,且內存利用率低。spa

解決這個問題的好辦法是:環形緩衝區。3d

環形緩衝區就是一個帶「頭指針」和「尾指針」的數組。「頭指針」指向環形緩衝區中可讀的數據,「尾指針」指向環形緩衝區中可寫的緩衝空間。經過移動「頭指針」和「尾指針」就能夠實現緩衝區的數據讀取和寫入。在一般狀況下,應用程序讀取環形緩衝區的數據僅僅會影響「頭指針」,而串口接收數據僅僅會影響「尾指針」。當串口接收到新的數組,則將數組保存到環形緩衝區中,同時將「尾指針」加1,以保存下一個數據;應用程序在讀取數據時,「頭指針」加1,以讀取下一個數據。當「尾指針」超過數組大小,則「尾指針」從新指向數組的首元素,從而造成「環形緩衝區」!,有效數據區域在「頭指針」和「尾指針」之間。以下圖指針

 

 

 如上面說的,環形緩衝區其實就是一個數組,將其「剪開」,而後「拉直」後以下圖code

 

 

 環形緩衝區的特性

一、先進新出。

二、當緩衝區被使用完,且又有新的數據須要存儲時,丟掉歷史最久的數據,保存最新數據。

0三、代碼實現

環形緩衝區的實現很簡單,只須要簡單的幾個接口便可。

首先須要建立一個環形緩衝區

#define  RINGBUFF_LEN          (500)     //定義最大接收字節數 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;//建立一個ringBuff的緩衝區

當咱們發現環形緩衝區被「衝爆」時,也就是緩衝區滿了,可是還有待緩衝的數據時,只須要修改RINGBUFF_LEN的宏定義,增大緩衝區間便可。

環形緩衝區的初始化

 
/**
* @brief  RingBuff_Init
* @param  void
* @return void
* @note   初始化環形緩衝區
*/
void RingBuff_Init(void)
{
  //初始化相關信息
  ringBuff.Head = 0;
  ringBuff.Tail = 0;
  ringBuff.Lenght = 0;
}

主要是將環形緩衝區的頭,尾和長度清零,表示沒有任何數據存入。

環形緩衝區的寫入

/**
* @brief  Write_RingBuff
* @param  uint8_t data
* @return FLASE:環形緩衝區已滿,寫入失敗;TRUE:寫入成功
* @note   往環形緩衝區寫入uint8_t類型的數據
*/
uint8_t Write_RingBuff(uint8_t data)
{
  if(ringBuff.Lenght >= RINGBUFF_LEN) //判斷緩衝區是否已滿
  {
    return RINGBUFF_ERR;
  }
  ringBuff.Ring_data[ringBuff.Tail]=data;
  ringBuff.Tail = (ringBuff.Tail+1)%RINGBUFF_LEN;//防止越界非法訪問
  ringBuff.Lenght++;
  return RINGBUFF_OK;
}

這個接口是寫入一個字節到環形緩衝區。這裏注意:你們能夠根據本身的實際應用修改成一次緩衝多個字節。而且這個作了緩衝區滿時報錯且防止非法越界的處理,你們能夠自行修改成緩衝區滿時覆蓋最先的數據。

環形緩衝區的讀取

/**
* @brief  Read_RingBuff
* @param  uint8_t *rData,用於保存讀取的數據
* @return FLASE:環形緩衝區沒有數據,讀取失敗;TRUE:讀取成功
* @note   從環形緩衝區讀取一個u8類型的數據
*/
uint8_t Read_RingBuff(uint8_t *rData)
{
  if(ringBuff.Lenght == 0)//判斷非空
  {
    return RINGBUFF_ERR;
  }
  *rData = ringBuff.Ring_data[ringBuff.Head];//先進先出FIFO,從緩衝區頭出
  ringBuff.Head = (ringBuff.Head+1)%RINGBUFF_LEN;//防止越界非法訪問
  ringBuff.Lenght--;
  return RINGBUFF_OK;
}

讀取的話也很簡單,一樣是讀取一個字節,你們能夠自行修改成讀取多個字節。

0四、驗證

光說不練假把式,下面咱們就來驗證上面的代碼可行性。

串口中斷函數中緩衝數據

void USART1_IRQHandler(void)
{
  if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
  {
    Write_RingBuff(USART_ReceiveData(USART1));
    USART_ClearFlag(USART1, USART_FLAG_RXNE);
  }
}

在主循環中,讀取緩衝區的數據,而後發送出去,由於是簡單的demo,添加了延時模擬CPU處理其餘任務。

 
while (1)
  {
    if(Read_RingBuff(&data))            //從環形緩衝區中讀取數據
    {
      USART_SendData(USART1, data);
    }
    SysCtlDelay(1*(SystemCoreClock/3000));
  }

驗證,間隔100ms發送數據。

 

 結果顯示沒有出現丟包問題。若是你的應用場景串口通訊速率快,數據量大或處理速度慢致使丟包,建議增大RINGBUFF_LEN的宏定義,增大緩衝區間便可。

Keil和IAR的工程文件下載地址:

https://github.com/strongercjd/STM32F207VCT6

點擊查看本文所在的專輯,STM32F207教程

相關文章
相關標籤/搜索