找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開(kāi)始

搜索
查看: 4452|回復(fù): 2
打印 上一主題 下一主題
收起左側(cè)

單片機(jī)解析協(xié)議基礎(chǔ)教程(基于51單片機(jī))

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



圖1 程序流程圖





在寫(xiě)程序之前,首先對(duì)幾個(gè)變量及標(biāo)志位進(jìn)行說(shuō)明:
    1.幀頭高字節(jié)和幀頭低字節(jié)分別使用宏定義指定,這樣方便修改,如:
            #define FRM_H  0XAA
            #define FRM_L   0XBB
    2.用于指示接收數(shù)據(jù)的位置,定義一個(gè)變量Rx_POS,定義一個(gè)變量Rx_Num用于指示要接收數(shù)據(jù)的長(zhǎng)度。
            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來(lái)標(biāo)識(shí)接收到完整數(shù)據(jù)幀,首先要寫(xiě)的肯定是沒(méi)有接收到完整幀的情形。那么我們可以編寫(xiě)程序如下:
                代碼1:

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

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

                void UART1_ISR() interrupt 4
                {
                        unsigned char Rx_Data;
                        if(RI)
                        {
                                Rx_Data = SBUF;//讀取串口緩沖區(qū)數(shù)據(jù)
                                RI = 0;                //清除串口中斷請(qǐng)求
                        }
                        if(!RXFRMOK)        //如果沒(méi)有接收到完整幀
                        {
                                if(!RXFHOK)       //如果沒(méi)有接收到幀頭
                                {
                                       
//先移位操作再將緩沖區(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ù)長(zhǎng)度存放到數(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基本上對(duì)本文開(kāi)始的那段協(xié)議完成了解析。是不是很簡(jiǎn)單?細(xì)心看代碼3的人可能會(huì)一眼看出來(lái),你這代碼接收到一幀完整數(shù)據(jù)后,RXFRMOK就為1了,后續(xù)該怎么處理呢?應(yīng)該有個(gè)歸零的過(guò)程啊,不然怎么再次接收數(shù)據(jù)呢?這個(gè)問(wèn)題問(wèn)得很好,這里我沒(méi)有把主程序?qū)懗鰜?lái),因?yàn)榻馕龌旧显诖谥袛嗬锿瓿闪,?dāng)接收到一幀完整數(shù)據(jù),其中某一個(gè)數(shù)據(jù)必定有著特定的用途,假設(shè)RX_BUF[0]的數(shù)據(jù)代表接受的數(shù)據(jù)代表的命令,那么,我在主程序中判斷RX_BUF[0]的值即可,處理完相關(guān)任務(wù),我們必須將RXFRMOK清零,那么程序就可以再次接收數(shù)據(jù)幀了。
        其中有一個(gè)特別需要注意的是,有些人沒(méi)有注意分析這些代碼,判斷錯(cuò)誤的數(shù)據(jù),導(dǎo)致不能解析該協(xié)議,這里特別說(shuō)明一下:假設(shè)單片機(jī)接收一幀數(shù)據(jù)為 0xaa 0xbb 0x03 0x40 0x01 0x06,那么RX_BUF中數(shù)據(jù)是什么呢?告訴你,只有后面三個(gè)字節(jié),即0x40 0x01 0x06。解析協(xié)議的時(shí)候已經(jīng)將幀頭和數(shù)據(jù)長(zhǎng)度這三個(gè)字節(jié)去掉了。  
         有了通信協(xié)議,就可以做一個(gè)組網(wǎng)的系統(tǒng),例如醫(yī)院里的醫(yī)護(hù)系統(tǒng),工廠(chǎng)的工業(yè)控制系統(tǒng),應(yīng)用范圍廣,安全高效!值得學(xué)習(xí)!如有興趣,可發(fā)郵件至huzhiqianglz@163.com與我交流。本文轉(zhuǎn)載請(qǐng)注明出處,文中若有錯(cuò)別字,在所難免,請(qǐng)告之本人修正,謝謝!
        其他類(lèi)型的協(xié)議解析暫未整理,如有空再寫(xiě),敬請(qǐng)關(guān)注!
分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享淘帖 頂 踩
回復(fù)

使用道具 舉報(bào)

沙發(fā)
ID:28571 發(fā)表于 2015-4-18 08:07 | 只看該作者
好東西,學(xué)習(xí)一下,謝謝分享!
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表