您好, 登錄| 注冊|
論壇導航
您好, 登錄| 注冊|
子站:
論壇首頁    單片機MCU/嵌入式
  •  發帖
  • 收藏

看環形隊列
閱讀: 290 |  回復: 5 樓層直達

2019/05/27 10:56:21
1
lihui710884923[實習版主]
電源幣:447 | 積分:1 主題帖:147 | 回復帖:486
LV8
師長

QQ截圖20160321155901 【直播】最純粹的技術直播!樂云老師帶你實戰電子負載

QQ截圖20160321155901 【免費送】 美信MAX17220評估板免費試用免費送


    最簡單的串口數據處理機制是數據接收并原樣回發的機制是:成功接收到一個數,觸發進入中斷,在中斷函數中將數據讀取出來,然后立即。這一種數據處理機制是“非緩沖中斷方式”,雖然這種數據處理方式不消耗時間,但是這種數據處理方式嚴重的缺點是:數據無緩沖區,如果先前接收的的數據如果尚未發送完成(處理完成),然后串口又接收到新的數據,新接收的數據就會把尚未處理的數據覆蓋,從而導致“數據丟包”。

標簽 STM32
2019/05/27 10:57:24
2
lihui710884923[實習版主]
電源幣:447 | 積分:1 主題帖:147 | 回復帖:486
LV8
師長

 對于“數據丟包”,最簡單的辦法就是使用一個數組來接收數據:每接收一個數據,數組下標偏移。雖然這樣的做法能起到一定的“緩沖效果”,但是數組的空間得不到很好的利用,已處理的數據仍然會占據原有的數據空間,直到該數組“滿載”(數組的每一個元素都保存了有效的數據),將整個數組的數據處理完成后,重新清零數組,才能開啟新一輪的數據接收。

 

  那么,有什么好的數據接收處理機制既能起到“緩沖”效果,又能有效地利用“數組空間”?答案是:有的,那就是“環形緩沖區”。

 

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

2019/05/27 11:10:11
3
電源網-fqd
電源幣:5059 | 積分:15071 主題帖:449 | 回復帖:5059
LV11
統帥
2019/05/27 14:10:48
4
lihui710884923[實習版主]
電源幣:447 | 積分:1 主題帖:147 | 回復帖:486
LV8
師長

當然,環形緩沖區的“頭指針”和“尾指針”可以用“頭變量”和“尾變量”來代替,因為切換數組的元素空間,除了可以用“指針偏移法”之外,還可以用“素組下標偏移法”。當串口接收到新的數組,則將數組保存到環形緩沖區中,同時將“尾變量”加一,以保存下一個數據;應用程序在讀取數據時,“頭變量”加一,以讀取下一個數據。

 

“環形緩沖區”數據接收處理機制的好處在于:利用了隊列的特點,一頭進,一頭出,互不影響,在數據進去(往里存)的時候,另一邊也可以把數據讀出來,而讀出來的數據,留下的空位,又可以增加多的存儲空間,從而避免一邊接收數據且一邊處理數據會在數據量密集的時候而導致的丟掉數據或者數據產生沖突的問題。

 

  如果僅有一個線程讀取環形緩沖區的數據,只有一個串口往環形緩沖區寫入數據,則不需要添加互斥保護機制就可以保證數據的正確性。

 

  需要注意的是,如果串口每接收x個字節的數據才處理一次,則環形緩沖區的緩沖數組的大小必須是x的N倍,具體N為多少,需要結合具體的數據接收速率以及處理速率,適當調節。這就好比喻,水壺永遠大于水杯,這樣子水壺才能存放很多杯水。

 

如果覺得前文隱晦難懂,那么下面我們來一起討論一下環形隊列的具體狀態以及實現。下文構建的環形隊列采用的是“頭變量”“尾變量”來控制隊列的存儲和讀取。

首先,我們會構造一個結構體,并定義一個結構變量。

 

#define MAX_SIZE  12               //緩沖區大小

 

typedef struct 

{

  unsigned char head;        //緩沖區頭部位置

  unsigned char tail;         //緩沖區尾部位置

  unsigned char ringBuf[MAX_SIZE]; //緩沖區數組

} ringBuffer_t;

 

ringBuffer_t buffer;                 //定義一個結構體

 

定義一個結構頭體則表示新的消息隊列已經創建完成。新建創建的隊列,頭指針head和尾指針tail都是指向數組的元素0。如下圖所示,此時的消息隊列是“空隊列”。


2019/05/27 14:12:28
5
lihui710884923[實習版主]
電源幣:447 | 積分:1 主題帖:147 | 回復帖:486
LV8
師長

當如果l加入隊列,則緩沖隊列處于滿載狀態,如下圖所示:如果此時,接收到新的數據并需要保存,則tail需要歸零,將接收到的數據存到數組的第一個元素空間,如果尚未讀取緩沖數組的一個元素空間的數據,則此數據會被新接收的數據覆蓋。同時head需要增加1,修改頭節點偏移位置丟棄早期數據。


當消息隊列中的所有數據都讀取出來后,此時環形隊列是空的,狀態如下圖所示。從圖可以總結得知,如果tail和head相等,則表示緩沖隊列是空的。

1.3.1 ringBuffer.c

1. 構造環形緩沖區

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

/**********************************************************************************************
描述   :      環形緩沖讀寫
作者   :      Jahol Fan
版本   :      V1.0
修改   :      
完成日期: 
Notice    :本程序只供學習使用,未經作者許可,不得用于其它任何用途。版權所有,盜版必究
***********************************************************************************************/
#include "ringbuffer.h"

#define BUFFER_MAX  36               //緩沖區大小

typedef struct 
{
  unsigned char headPosition;        //緩沖區頭部位置
  unsigned char tailPositon;         //緩沖區尾部位置
  unsigned char ringBuf[BUFFER_MAX]; //緩沖區數組
} ringBuffer_t;

ringBuffer_t buffer; //定義一個結構體

 

首先,需要構建一個結構體ringBuffer_t,如果定義一個結構體變量buffer,則意味著創建一個環形緩沖區。

 

2. 往環形緩沖區存數據

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14

/**
* @brief 寫一個字節到環形緩沖區
* @param data:待寫入的數據
* @return none
*/
void RingBuf_Write(unsigned char data)
{
  buffer.ringBuf[buffer.tailPositon]=data;     //從尾部追加
  if(++buffer.tailPositon>=BUFFER_MAX)         //尾節點偏移
    buffer.tailPositon=0;                      //大于數組最大長度 歸零 形成環形隊列
  if(buffer.tailPositon == buffer.headPosition)//如果尾部節點追到頭部節點,則修改頭節點偏移位置丟棄早期數據
    if(++buffer.headPosition>=BUFFER_MAX)
      buffer.headPosition=0;
}

 

2019/05/27 14:14:18
6
lihui710884923[實習版主]
電源幣:447 | 積分:1 主題帖:147 | 回復帖:486
LV8
師長

8行:將數據存放到tailPosition所指向的元素空間。

9行:tailPosition變量自增1,并且判斷,如果大于最大緩沖,則將tailPosition歸零。

11行:如果tailPositon與headPosition相等,則表示,數據存入速度大于數據取出速度,從到導致“追尾”。此時headPosition需要自增1,以丟棄早期數據,這也就是數據“覆蓋現象”,這種現象是不允許存在的,解決這種現象的辦法是將緩沖隊列的空間再開大點。

13行:如果headPosition也大于最大數組,則需要將headPosition清零。

 

3. 讀取環形緩沖區的數據

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

/**
* @brief 讀取環形緩沖區的一個字節的數據
* @param *pData:指針,用于保存讀取到的數據
* @return 1表示緩沖區是空的,0表示讀取數據成功
*/
u8 RingBuf_Read(unsigned char* pData)
{
  if(buffer.headPosition == buffer.tailPositon)    //如果頭尾接觸表示緩沖區為空
    {
            return 1;   //返回1,環形緩沖區是空的
    }
  else
  {
    *pData=buffer.ringBuf[buffer.headPosition];    //如果緩沖區非空則取頭節點值并偏移頭節點
    if(++buffer.headPosition>=BUFFER_MAX)
      buffer.headPosition=0;
    return 0;     //返回0,表示讀取數據成功
  }
}

 

8行:首先判斷headPosition是否等于tailPositon,如果相等,則表明,此時緩沖區是空的。

10行:緩沖區為空,則直接返回,不執行后面的程序

12行:如果緩沖區不為空,則條件成立并執行

14行:讀取headPosition所指向的環形緩沖隊列的元素空間的數據。

15行:headPosition自增1以讀取下一個數據。如果headPosition大于最大值BUFFER_MAX,則將headPosition歸零。

17行:返回0,表示讀取數據成功。

 

1.3.2 Hal_uart.c

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

/**
* @brief 串口1中斷函數
* @param none
* @return none
*/
void USART1_IRQHandler(void)
{
  if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //判斷接收標志位是否為1
  {         
        USART_ClearITPendingBit(USART1,USART_IT_RXNE);       //清楚標志位
        RingBuf_Write(USART_ReceiveData(USART1));
        //阻塞等待直到傳輸完成
        while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    
    }
}

11行:數據接收成功,則將數據存入環形緩沖隊列。

 

1.3.3 Main.c

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

/**
************************************************************
* @file         main.c
* @brief        MCU entry file
* @author       JaholFan
* @date         2017-11-20
* @version      V010100
* @note         
***********************************************************/
#include "Hal_Led/Hal_Led.h"
#include "Hal_delay/delay.h"
#include "Hal_Key/Hal_Key.h"
#include "Hal_Relay/Hal_Relay.h"
#include "Hal_Usart/hal_uart.h"
#include "ringbuffer.h"
/**
* @brief 程序入口
* @param none
* @return none
*/
int main(void)
{
    u8 data = 0;
  SystemInit();  //系統時鐘初始化
  delayInit(72); //滴答定時器初始化
    uartxInit();
    while(1)
    {
        if(0 == RingBuf_Read(&data))            //從環形緩沖區中讀取數據
        {
            USART_SendData(USART1,data);          //讀取接收到的數據并回發數據
        }
        delayMs(1);  //延時1ms:使得處理數據的速度小于接收數據的速度,用于驗證接收緩沖區的“緩沖”特性
        
    }
}

30行:讀取環形緩沖的數據,如果環形緩沖隊列有數據,則條件成立

32行:將數據原樣回發

34行:延時1ms的目的是:使得處理數據的速度小于接收數據的速度,用于驗證接收緩沖區的“緩沖”特性。實際上,在項目工程中,項目代碼的執行是消耗一定的CPU時間的,本例程序用延時1毫秒來等效替代項目代碼執行小號的CPU時間。

 

客服熱線
服務時間:周一至周五9:00-18:00
微信關注
免費技術研討會
獲取一手干貨分享

互聯網違法不良信息舉報

Reporting Internet Illegal and Bad Information
editor@netbroad.com
022-58392381
有豪模式的彩票平台 长丰县| 罗源县| 庆阳市| 安龙县| 依兰县| 广安市| 长宁县| 右玉县| 镶黄旗| 务川| 邵阳县| 万山特区| 内江市| 平舆县| 辽阳市| 玛多县| 桦甸市| 怀化市| 威远县| 松滋市| 政和县| 瓦房店市| 辰溪县| 收藏| 兰西县| 台前县| 通州区| 平利县| 靖远县| 桐梓县| 雅江县| 涞源县| 正宁县| 精河县| 平定县| 施甸县| 江油市| 姚安县|