以前喜歡寫一些技術性的文章放到空間讓大家學習一下,這本是好意,不料這段時間翻看一下自己空間的日志,關于技術方面的文章卻是屈指可數,仔細一看內容,卻有一種誤人子弟的感覺,實在慚愧至極。工作快半年了,多多少少也積累了一些經驗,只不過很多東西都是放在腦子里,沒有用文字等記錄下來。最近不停地被坑,心中郁悶至極,今天干活也沒有什么積極性了,放下手下的事情,寫一些文字,就當放松了,轉入正題,下面講解的知識適合于單片機初學者,當然我也算是初學者。 其實,使用單片機解析協議很簡單,我們需要分析協議的格式,將其分開處理即可。例如某一串口指令模塊的指令格式為:幀頭(2字節)+數據長度(1字節)+數據(N字節,其中N<256),那么我們就按照幀頭、數據長度、數據分別處理。寫單片機程序要有一種思路就是從整體到局部。那么我們如何判斷一個數據幀被完整接收呢?這里就要用到一些標志位來處理了。先給出程序流程圖吧
圖1 程序流程圖
在寫程序之前,首先對幾個變量及標志位進行說明:
1.幀頭高字節和幀頭低字節分別使用宏定義指定,這樣方便修改,如:
#define FRM_H 0XAA
#define FRM_L 0XBB
2.用于指示接收數據的位置,定義一個變量Rx_POS,定義一個變量Rx_Num用于指示要接收數據的長度。
unsigned char Rx_POS;
unsigned char Rx_Num;
3.接收到完整幀標志位、接收到幀頭標志位、
bit RXFRMOK; //接收到完整幀標志位
bit RXFHOK; //接收到完整幀頭標志位
4.接收的數據緩沖區
unsinged char RXFH[3];
unsigned char RX_BUF[32];
現在我們使用RXFRMOK來標識接收到完整數據幀,首先要寫的肯定是沒有接收到完整幀的情形。那么我們可以編寫程序如下:
代碼1:
void UART1_ISR() interrupt 4
{
unsigned char Rx_Data;
if(RI)
{
Rx_Data = SBUF;//讀取串口緩沖區數據
RI = 0; //清除串口中斷請求
}
if(!RXFRMOK)//如果沒有接收到完整幀
{
}
if(TI)
{
TI = 0;
}
}
代碼寫到這里,可能就有人急了,怎么才寫這么一點?別急,這是為了讓新手更容易程序是怎么一步步的寫出來的,我將代碼一點點地寫完整,讓新手更加輕松地入門。
根據從整體到局部的思想,我們把整體框架已完成,那么現在開始處理局部的問題了,仔細一看數據幀格式,開頭是兩個字節的幀頭,那么,開辟兩個數組,其中一個用于緩存幀頭和數據長度,另一個用于緩存串口接收到的數據,緩存幀頭和數據長度的數組定義為RXFH[3],我們假設一次接收的數據不超過32字節,則開辟的數組為RX_BUF[32],設置一個unsigned char型的變量Rx_POS用以指示接收數據的位置,這個位置僅用于指示接收到數據的位置,注意:不包含幀頭和數據。
準備工作做好后,此刻當然是先處理沒有接收到幀頭的程序了,每接收到一個字節的數據,我們都把緩沖區的數據依次往前挪一個位置,然后判斷第一和第二字節數據是否為幀頭,如果是幀頭,那么第三字節就表示數據長度了,我們把上面的代碼1拷貝過來,繼續添加代碼(添加部分用淺藍色標識):
代碼2:
void UART1_ISR() interrupt 4
{
unsigned char Rx_Data;
if(RI)
{
Rx_Data = SBUF;//讀取串口緩沖區數據
RI = 0; //清除串口中斷請求
}
if(!RXFRMOK) //如果沒有接收到完整幀
{
if(!RXFHOK) //如果沒有接收到幀頭
{
//先移位操作再將緩沖區內容與幀頭進行比較
RXFH[0] = RXFH[1];
RXFH[1] = RXFH[2];
RXFH[2] = Rx_Data;
if((RXFH[0]==FRM_H)&&(RXFH[1]==FRM_L))
{
RXFHOK = 1; //正常接收到幀頭標志位置1
Rx_Num = RXFH[2];
}
}
}
if(TI)
{
TI = 0;
}
}
看到這里你是不是感覺這種做法有點巧妙呢?判斷幀頭就像把接收到的數據放到一個三字節的窗口中,自己盯著窗口找幀頭,如果發現幀頭匹配,那么我就要處理接收到幀頭的程序了。接收到幀頭處理方法為:每接收到一個字節,則接收到的數據位置Rx_POS加1,如果接收的數據長度等于Rx_Num,則對表示一幀數據接收完成,則應給接收一幀完整數據標志位置位,同時將接收數據位置Rx_P清零,并將接收到幀頭標識為清零。詳見下面的代碼(增加的部分用淺紅色標識):
代碼3:
void UART1_ISR() interrupt 4
{
unsigned char Rx_Data;
if(RI)
{
Rx_Data = SBUF;//讀取串口緩沖區數據
RI = 0; //清除串口中斷請求
}
if(!RXFRMOK) //如果沒有接收到完整幀
{
if(!RXFHOK) //如果沒有接收到幀頭
{
//先移位操作再將緩沖區內容與幀頭進行比較
RXFH[0] = RXFH[1];
RXFH[1] = RXFH[2];
RXFH[2] = Rx_Data;
if((RXFH[0]==FRM_H)&&(RXFH[1]==FRM_L))
{
RXFHOK = 1; //正常接收到幀頭標志位置1
Rx_Num = RXFH[2];
goto TX: //接收完數據當然要跳出去了,不能把數據長度存放到數據中了
}
}
if(RXFHOK)
{
RXBUF[Rx_POS] = Rx_Data; Rx_POS ++;
if(Rx_POS > Rx_Num-1)
{
RXFRMOK = 1;
RXFHOK = 0;
Rx_POS = 0;
}
}
}
TX: if(TI)
{
TI = 0;
}
}
以上代碼3基本上對本文開始的那段協議完成了解析。是不是很簡單?細心看代碼3的人可能會一眼看出來,你這代碼接收到一幀完整數據后,RXFRMOK就為1了,后續該怎么處理呢?應該有個歸零的過程啊,不然怎么再次接收數據呢?這個問題問得很好,這里我沒有把主程序寫出來,因為解析基本上在串口中斷里完成了,當接收到一幀完整數據,其中某一個數據必定有著特定的用途,假設RX_BUF[0]的數據代表接受的數據代表的命令,那么,我在主程序中判斷RX_BUF[0]的值即可,處理完相關任務,我們必須將RXFRMOK清零,那么程序就可以再次接收數據幀了。
其中有一個特別需要注意的是,有些人沒有注意分析這些代碼,判斷錯誤的數據,導致不能解析該協議,這里特別說明一下:假設單片機接收一幀數據為 0xaa 0xbb 0x03 0x40 0x01 0x06,那么RX_BUF中數據是什么呢?告訴你,只有后面三個字節,即0x40 0x01 0x06。解析協議的時候已經將幀頭和數據長度這三個字節去掉了。
有了通信協議,就可以做一個組網的系統,例如醫院里的醫護系統,工廠的工業控制系統,應用范圍廣,安全高效!值得學習!如有興趣,可發郵件至huzhiqianglz@163.com與我交流。本文轉載請注明出處,文中若有錯別字,在所難免,請告之本人修正,謝謝!
其他類型的協議解析暫未整理,如有空再寫,敬請關注!
|