找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 3639|回复: 2
收起左侧

单片机解析协议基础教程(基于51单片机)

[复制链接]
ID:262 发表于 2014-10-21 01:12 | 显示全部楼层 |阅读模式
        以前喜欢写一些技术性的文章放到空间让大家学习一下,这本是好意,不料这段时间翻看一下自己空间的日志,关于技术方面的文章却是屈指可数,仔细一看内容,却有一种误人子弟的感觉,实在惭愧至极。工作快半年了,多多少少也积累了一些经验,只不过很多东西都是放在脑子里,没有用文字等记录下来。最近不停地被坑,心中郁闷至极,今天干活也没有什么积极性了,放下手下的事情,写一些文字,就当放松了,转入正题,下面讲解的知识适合于单片机初学者,当然我也算是初学者。
        其实,使用单片机解析协议很简单,我们需要分析协议的格式,将其分开处理即可。例如某一串口指令模块的指令格式为:帧头(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与我交流。本文转载请注明出处,文中若有错别字,在所难免,请告之本人修正,谢谢!
        其他类型的协议解析暂未整理,如有空再写,敬请关注!
回复

使用道具 举报

ID:28571 发表于 2015-4-18 08:07 | 显示全部楼层
好东西,学习一下,谢谢分享!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表