標(biāo)題: 單片機(jī)解析協(xié)議基礎(chǔ)教程(基于51單片機(jī)) [打印本頁]

作者: heicad    時間: 2014-10-21 01:12
標(biāo)題: 單片機(jī)解析協(xié)議基礎(chǔ)教程(基于51單片機(jī))
        以前喜歡寫一些技術(shù)性的文章放到空間讓大家學(xué)習(xí)一下,這本是好意,不料這段時間翻看一下自己空間的日志,關(guān)于技術(shù)方面的文章卻是屈指可數(shù),仔細(xì)一看內(nèi)容,卻有一種誤人子弟的感覺,實在慚愧至極。工作快半年了,多多少少也積累了一些經(jīng)驗,只不過很多東西都是放在腦子里,沒有用文字等記錄下來。最近不停地被坑,心中郁悶至極,今天干活也沒有什么積極性了,放下手下的事情,寫一些文字,就當(dāng)放松了,轉(zhuǎn)入正題,下面講解的知識適合于單片機(jī)初學(xué)者,當(dāng)然我也算是初學(xué)者。
        其實,使用單片機(jī)解析協(xié)議很簡單,我們需要分析協(xié)議的格式,將其分開處理即可。例如某一串口指令模塊的指令格式為:幀頭(2字節(jié))+數(shù)據(jù)長度(1字節(jié))+數(shù)據(jù)(N字節(jié),其中N<256),那么我們就按照幀頭、數(shù)據(jù)長度、數(shù)據(jù)分別處理。寫單片機(jī)程序要有一種思路就是從整體到局部。那么我們?nèi)绾闻袛嘁粋數(shù)據(jù)幀被完整接收呢?這里就要用到一些標(biāo)志位來處理了。先給出程序流程圖吧



圖1 程序流程圖





在寫程序之前,首先對幾個變量及標(biāo)志位進(jìn)行說明:
    1.幀頭高字節(jié)和幀頭低字節(jié)分別使用宏定義指定,這樣方便修改,如:
            #define FRM_H  0XAA
            #define FRM_L   0XBB
    2.用于指示接收數(shù)據(jù)的位置,定義一個變量Rx_POS,定義一個變量Rx_Num用于指示要接收數(shù)據(jù)的長度。
            unsigned char Rx_POS;
            unsigned char Rx_Num;
    3.接收到完整幀標(biāo)志位、接收到幀頭標(biāo)志位、
            bit RXFRMOK;     //接收到完整幀標(biāo)志位
            bit RXFHOK;        //接收到完整幀頭標(biāo)志位
    4.接收的數(shù)據(jù)緩沖區(qū)
            unsinged char RXFH[3];
            unsigned char
RX_BUF[32];
         
現(xiàn)在我們使用RXFRMOK來標(biāo)識接收到完整數(shù)據(jù)幀,首先要寫的肯定是沒有接收到完整幀的情形。那么我們可以編寫程序如下:
                代碼1:

                void UART1_ISR() interrupt 4
                {
                        unsigned char Rx_Data;
                        if(RI)
                        {
                                Rx_Data = SBUF;//讀取串口緩沖區(qū)數(shù)據(jù)
                                RI = 0;                //清除串口中斷請求
                        }
                        if(!RXFRMOK)//如果沒有接收到完整幀
                        {
                                
                        }
                        if(TI)
                        {
                               TI = 0;
                        }
                 }
        代碼寫到這里,可能就有人急了,怎么才寫這么一點?別急,這是為了讓新手更容易程序是怎么一步步的寫出來的,我將代碼一點點地寫完整,讓新手更加輕松地入門。
        根據(jù)從整體到局部的思想,我們把整體框架已完成,那么現(xiàn)在開始處理局部的問題了,仔細(xì)一看數(shù)據(jù)幀格式,開頭是兩個字節(jié)的幀頭,那么,開辟兩個數(shù)組,其中一個用于緩存幀頭和數(shù)據(jù)長度,另一個用于緩存串口接收到的數(shù)據(jù),緩存幀頭和數(shù)據(jù)長度的數(shù)組定義為RXFH[3],我們假設(shè)一次接收的數(shù)據(jù)不超過32字節(jié),則開辟的數(shù)組為RX_BUF[32],設(shè)置一個unsigned char型的變量Rx_POS用以指示接收數(shù)據(jù)的位置,這個位置僅用于指示接收到數(shù)據(jù)的位置,注意:不包含幀頭和數(shù)據(jù)。
         準(zhǔn)備工作做好后,此刻當(dāng)然是先處理沒有接收到幀頭的程序了,每接收到一個字節(jié)的數(shù)據(jù),我們都把緩沖區(qū)的數(shù)據(jù)依次往前挪一個位置,然后判斷第一和第二字節(jié)數(shù)據(jù)是否為幀頭,如果是幀頭,那么第三字節(jié)就表示數(shù)據(jù)長度了,我們把上面的代碼1拷貝過來,繼續(xù)添加代碼(添加部分用淺藍(lán)色標(biāo)識):
           代碼2:
                void UART1_ISR() interrupt 4
                {
                        unsigned char Rx_Data;
                        if(RI)
                        {
                                Rx_Data = SBUF;//讀取串口緩沖區(qū)數(shù)據(jù)
                                RI = 0;                //清除串口中斷請求
                        }
                        if(!RXFRMOK)        //如果沒有接收到完整幀
                        {
                                if(!RXFHOK)       //如果沒有接收到幀頭
                                {
                                       
//先移位操作再將緩沖區(qū)內(nèi)容與幀頭進(jìn)行比較
                                        RXFH[0] = RXFH[1];
                                        RXFH[1] = RXFH[2];
                                        RXFH[2] = Rx_Data;
                                        if((RXFH[0]==FRM_H)&&(RXFH[1]==FRM_L))
                                        {
                                                RXFHOK = 1;            //正常接收到幀頭標(biāo)志位置1
                                                Rx_Num = RXFH[2];
                                        }        
                                }                           
                        }
                        if(TI)
                        {
                               TI = 0;
                        }
                 }

        看到這里你是不是感覺這種做法有點巧妙呢?判斷幀頭就像把接收到的數(shù)據(jù)放到一個三字節(jié)的窗口中,自己盯著窗口找?guī)^,如果發(fā)現(xiàn)幀頭匹配,那么我就要處理接收到幀頭的程序了。接收到幀頭處理方法為:每接收到一個字節(jié),則接收到的數(shù)據(jù)位置Rx_POS加1,如果接收的數(shù)據(jù)長度等于Rx_Num,則對表示一幀數(shù)據(jù)接收完成,則應(yīng)給接收一幀完整數(shù)據(jù)標(biāo)志位置位,同時將接收數(shù)據(jù)位置Rx_P清零,并將接收到幀頭標(biāo)識為清零。詳見下面的代碼(增加的部分用淺紅色標(biāo)識):
                 代碼3:

                void UART1_ISR() interrupt 4
                {
                        unsigned char Rx_Data;
                        if(RI)
                        {
                                Rx_Data = SBUF;//讀取串口緩沖區(qū)數(shù)據(jù)
                                RI = 0;                //清除串口中斷請求
                        }
                        if(!RXFRMOK)        //如果沒有接收到完整幀
                        {
                                if(!RXFHOK)       //如果沒有接收到幀頭
                                {
                                       
//先移位操作再將緩沖區(qū)內(nèi)容與幀頭進(jìn)行比較
                                        RXFH[0] = RXFH[1];
                                        RXFH[1] = RXFH[2];
                                        RXFH[2] = Rx_Data;
                                        if((RXFH[0]==FRM_H)&&(RXFH[1]==FRM_L))
                                        {
                                                RXFHOK = 1;            //正常接收到幀頭標(biāo)志位置1
                                                Rx_Num = RXFH[2];
                                                goto    TX:                 //接收完數(shù)據(jù)當(dāng)然要跳出去了,不能把數(shù)據(jù)長度存放到數(shù)據(jù)中了

                                        }        
                                }        
                              
  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基本上對本文開始的那段協(xié)議完成了解析。是不是很簡單?細(xì)心看代碼3的人可能會一眼看出來,你這代碼接收到一幀完整數(shù)據(jù)后,RXFRMOK就為1了,后續(xù)該怎么處理呢?應(yīng)該有個歸零的過程啊,不然怎么再次接收數(shù)據(jù)呢?這個問題問得很好,這里我沒有把主程序?qū)懗鰜,因為解析基本上在串口中斷里完成了,?dāng)接收到一幀完整數(shù)據(jù),其中某一個數(shù)據(jù)必定有著特定的用途,假設(shè)RX_BUF[0]的數(shù)據(jù)代表接受的數(shù)據(jù)代表的命令,那么,我在主程序中判斷RX_BUF[0]的值即可,處理完相關(guān)任務(wù),我們必須將RXFRMOK清零,那么程序就可以再次接收數(shù)據(jù)幀了。
        其中有一個特別需要注意的是,有些人沒有注意分析這些代碼,判斷錯誤的數(shù)據(jù),導(dǎo)致不能解析該協(xié)議,這里特別說明一下:假設(shè)單片機(jī)接收一幀數(shù)據(jù)為 0xaa 0xbb 0x03 0x40 0x01 0x06,那么RX_BUF中數(shù)據(jù)是什么呢?告訴你,只有后面三個字節(jié),即0x40 0x01 0x06。解析協(xié)議的時候已經(jīng)將幀頭和數(shù)據(jù)長度這三個字節(jié)去掉了。  
         有了通信協(xié)議,就可以做一個組網(wǎng)的系統(tǒng),例如醫(yī)院里的醫(yī)護(hù)系統(tǒng),工廠的工業(yè)控制系統(tǒng),應(yīng)用范圍廣,安全高效!值得學(xué)習(xí)!如有興趣,可發(fā)郵件至huzhiqianglz@163.com與我交流。本文轉(zhuǎn)載請注明出處,文中若有錯別字,在所難免,請告之本人修正,謝謝!
        其他類型的協(xié)議解析暫未整理,如有空再寫,敬請關(guān)注!

作者: yyxtj    時間: 2015-4-18 08:07
好東西,學(xué)習(xí)一下,謝謝分享!




歡迎光臨 (http://www.torrancerestoration.com/bbs/) Powered by Discuz! X3.1