小記。
項目臨時需要單片機進行節點控制,主要用來控制模塊的開關,以串口進行通訊。
單片機幾多久沒玩了,選用的是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,根據命令,執行相應的操作。