串口實用的循環緩衝區

小記。

項目臨時需要單片機進行節點控制,主要用來控制模塊的開關,以串口進行通訊。

單片機幾多久沒玩了,選用的是C8051F920,傳說中增強型51,不過看了Datesheet.

還是51而已。。無難度,項目要求主要是功耗的問題,5年內只能更換一次電池。

蛀牙用到模塊是定時器,幾個GPIO,smaRTClock,串口。

主要在通訊協議這部分花的時間較多,串口接收採用循環緩衝區的方式,以FIFO方式進行讀寫

串口的緩衝區需要兩個一個接收,一個發送,通訊協議中規定數據長度在16字節,就把緩衝區

大小定爲64字節

#defineUART0_BUF_SIZE 256

static uchar data UART0_Rx_head; //OUT

static uchar data UART0_Rx_tail; //IN

static uchar data UART0_Tx_head; //IN

static uchar data UART0_Tx_tail; //OUT

串口採用中斷方式:

if(RI0 == 1)

{

RI0= 0; // Clear interrupt flag

tmp_data = SBUF0;

if(((UART0_Rx_In - UART0_Rx_Out) & ~(UART0_RX_BUFF_SIZE - 1)) == 0){

UART0_RxBuffer[UART0_Rx_In & (UART0_RX_BUFF_SIZE - 1)] =tmp_data;

UART0_Rx_In++;

}

}

if(TI0 == 1) // Check if transmit flag is set

{

TI0= 0; // Clear interrupt flag

if (UART0_Tx_In != UART0_Tx_Out) {

SBUF0 = UART0_TxBuffer[UART0_Tx_Out & (UART0_TX_BUFF_SIZE -1)];

UART0_Tx_Out++;

tx_restart_uart0= 0;

}

else tx_restart_uart0 = 1;

}

這裏:

UART0_Rx_In&(UART0_RX_BUFF_SIZE - 1),相當於UART0_Rx_In% UART0_RX_BUFF_SIZE,對IN取模

結果相當於映射到對應的緩衝區位置,比如這裏IN的值是0~255而buffer大小隻有64字節,即

IN的值對應四段緩衝區,064 128 192都對應緩衝區的起始地址。

((UART0_Rx_In- UART0_Rx_Out) & ~(UART0_RX_BUFF_SIZE - 1)) == 0 //這是判斷緩衝區是否滿

因爲IN始終在OUT的前面(當IN=OUT時緩衝區爲空,所以OUT不可能在IN前面),即便是IN=5

OUT=240時,IN<OUT,但IN也在前面,IN- OUT = -235 =21,這裏IN和OUT是無符號字符型,相減也

爲無符號型。故此時緩衝區有21字節數據。UART0_Rx_In++;IN指向的節點內容是空的,當前值應

該在Buffer[IN- 1]

同理:

發送中斷產生時,首先判斷緩衝區是否爲空,爲空可以直接發送,否則,繼續發送緩衝區數據

怎樣才能產生髮送中斷呢,

1.可以先將數據寫入發送緩衝區,然後手動置TI0= 1.產生中斷,使中斷函數自動發送緩衝區數據。

2.發送數據前判斷串口有無數據待發送(包含正在發送和緩衝區數據),沒有則直接發送,有則將

數據繼續加入緩衝區

uint8put_char_uart0(uint8 Data)

{

if(UART0_TX_BUFF_LEN >= UART0_TX_BUFF_SIZE)

return(-1);

if(tx_restart_uart0)

{

tx_restart_uart0= 0;// ==0 ,串口正在發送

SBUF0= Data;

}

else

{

UART0_TxBuffer[UART0_Tx_In& (UART0_TX_BUFF_SIZE - 1)] = Data;//數據放入發送BUF

UART0_Tx_In++;

}

return(0);

}

如果要連續發送一個協議包,只需調用put_char_uart0(),一次寫入一個字節

從緩衝區讀寫數據,這裏有個策略問題,讀數據是有數據就讀,還是隻有當有一個完成的數據包纔讀。

寫數據有足夠空間才寫,還是有空間才寫。

基於串口的特性,採用讀數據時從OUT端點開始向後scan,瀏覽到數據頭纔開始組包。

寫數據,只有當緩衝區大小能夠裝下數據時,才一次寫入。

緩衝區的情況分爲這幾種:

0 size

OUT IN

BUFFER size

空閒大小爲:SIZE – IN + OUT (這裏爲0)

數據大小: IN– OUT

0 OUT IN size

B

A B C

先來看看linux內核怎麼寫循環緩衝區的:

len= min(len, fifo->size - fifo->in + fifo->out);

//len是要發送的數據長度,size爲緩衝區大小,這裏IN/OUT始終在size範圍內,這裏的結果是空閒的數據區大小,結果len爲一次可以寫的大小

/*first put the data starting from fifo->in to buffer end */

l= min(len, fifo->size - (fifo->in & (fifo->size - 1)));

//這裏的結果爲本次循環能寫的大小,如圖C區

memcpy(fifo->buffer+ (fifo->in & (fifo->size - 1)), buffer, l);

//先寫l大小,寫到緩衝區的最後一個字節,

/*then put the rest (if any) at the beginning of the buffer */

memcpy(fifo->buffer,buffer + l, len - l);

//緩衝區滿在寫到緩衝區頭部A區,如果已寫滿,這裏寫的0字節

fifo->in+= len;

//in加上已寫長度,可能溢出計數

//若要使in/out的值始終小於size:

//fifo->in= (fifo->in +len) % fifo->size;這個上面指向的內容是一樣的

returnlen;//這裏返回上層,通知應用程序寫了多少字節。

linux內核讀緩衝區示例:

unsignedint l;

len= min(len, fifo->in - fifo->out);

//可讀數據長度

/*first get the data from fifo->out until the end of the buffer */

l= min(len, fifo->size - (fifo->out & (fifo->size - 1)));

//結果是從OUT到BUF尾部的數據長度,教IN/OUT交換後如C區

memcpy(buffer,fifo->buffer + (fifo->out & (fifo->size - 1)), l);

//首先讀取從OUT到SIZE這部分

/*then get the rest (if any) from the beginning of the buffer */

memcpy(buffer+ l, fifo->buffer, len - l);

//若未讀取完,在讀取前面的一部分A區

fifo->out+= len;

//OUT端點加上讀取長度

returnlen;

數據的檢索:

包頭的搜索:包頭通常位於一個有效數據包的前端,當緩衝區數據大小大於最小包長時,就開始讀取包頭,每讀取一次包頭,緩衝區指針+1,直到不可讀(小於最小包長)。

由於這樣,不是包頭的內容就會丟棄,故包頭的標識應該爲唯一的!這適用與簡單的數據協議,不牽涉文件的傳輸。

我們來看看協議的一般處理流程:

數據處理的起始條件是:緩衝區中的有效數據長度大於或者等於最小包長。

1,搜索包:如果成功,轉到2。失敗則丟棄一個字節,繼續搜索。

2,包長度的檢查:scan出長度域,通過協議中可能出現的最大和最小包長檢查,如果正常,則轉到3,否則丟棄一個字節,轉到1。

3,命令的檢查:scan出幀號,檢查幀號是否爲有效的幀號,有效,則轉到4,否則,丟棄一個字節,轉到1。

4,校驗和的檢查:scan出長度後的數據域和校驗域,檢查校驗和是否正確,錯誤則丟棄一個字節,轉到1。如果正確,則讀取這一完整的命令,取出命令和數據。轉到5。

5,根據命令,執行相應的操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章