微波EDA网,见证研发工程师的成长!
首页 > 研发问答 > 嵌入式设计讨论 > MCU和单片机设计讨论 > 单片机上位机和下位机多字节通信的应用实例

单片机上位机和下位机多字节通信的应用实例

时间:10-02 整理:3721RD 点击:

       这次我们来说一下串口多字节通信,简单的串口通信一般都是单字节通信,不需要数据头、校验码、数据尾的,比如郭天祥的视频教程里面有一节是学习利用串口助手和单片机通信,从而控制开发板上面的LED,只需要收发一个字节的十六进制数据就可以了。而工业上的通信则都是使用的多字节通信,原因是为了避免通信时和其他设备发生冲突和偶然性,每个设备都有自己数据格式(即数据头+数据+检测码+数据尾)。要想学会并熟练使用多字节通信的方法,必然要先知道单字节通信的原理和框架的,等学会单字节通信后再学习多字节通信只不过也就是多检测几个数据罢了,下面先简单介绍一下单字节串口通信的主要步骤吧!
       首先串口是基于中断的,而且每次只能收或发一个字节。在中断内部每接收到一个字节数据就要产生一次中断,并在中断中写出接收标志位,用于告诉main函数中断已经接收到发送来的数据了,从而可以准备下一次的收发数据。当main函数检测到接收标志位置位后,就开始下一次的数据发送,同理,在数据发送结束后也要有一个发送标志位,用于告诉中断这边已经将数据发送完了,你那边可以接收啦!正是有了这种“对话的”机制,收发数据才可以正常无误的进行下去。下面贴一段代码一看就清楚了:

/*------------------------本代码通过串口发送数据给单片机并返回发送的值--------------------*/
#include<reg52.h>
unsigned char uart_flag,a;
/*******串口初始化函数********/
void initial()
{
        TMOD=0x20;                //设置定时器1为工作方式2
        TH1=0xfd;                //9600波特率
        TL1=0xfd;
        EA=1;                        //打开总中断
        ES=1;                    //打开串口中断
        TR1=1;                        //启动定时器
        REN=1;                        //串口允许接收控制位
        SM0=0;                        //串口工作方式1: 10位异步收发(8位数据),波特率可变(由定时器1的溢出率控制)
        SM1=1;
}
void main()
{
        initial();
        while(1)       
        {
                if(uart_flag==1)        //uart_flag就是中断收到数据后写的一个标志位,main函数里面检测到置位后方可                 {                               开始进行数据的发送或处理其他功能
                        ES=0;                //发送数据时关闭串口开关,发送完成后再打开
                        uart_flag=0;      //接收标志位要及时清零
                        SBUF=a;           //将上次接收到的数据显示出来便于观察(其实就属于发送数据了)
                        while(!TI);          //发送结束标志位(硬件自动置1的,不用管他,但是需手动清零)
                        TI=0;                 //标志位清零
                        ES=1;                //打开串口开关
                }       
        }
}
void ser() interrupt 4                        //串口中断服务程序
{
        if(RI==1)                          //串口收到数据后RI自动置1,这里需要检测
        {
                 RI=0;                     //RI清零                
                 P1=SBUF;                //把收到的数据赋值给其他变量,实现某些功能
                 a=SBUF;                   //同时再赋值给一个变量,用于主函数中可以显示发送的数据
                 uart_flag=1;              //这个就是串口接收数据完毕后写的一个标志位
        }
}
       以上就是一个简单的单字节的串口通信,用串口助手发送0xfe,单片机开发板上第一个led就会点亮,发送0xfd,第二个led就会亮了。(具体将SBUF里面的值赋给那个口就看你自己的硬件接线了)大家可以看到,里面并没有涉及到数据头、校验位、数据尾的检测对比,直接就赋值来回通信了,因为它就只有一个字节,没有必要去检查数据,更何况检查也就只能检查这一个字节,没有意义的。好了,这个原理知道后下面开始教大家多字节通信的方法。不过我的是基于使用蓝牙APP用手机通过蓝牙模块和单片机通信的,没关系有图解的,至于蓝牙模块大家可以不用纠结,只要你配置好了就和相当于一个无线的串口,如果不会配置的话就请查找相关资料,这里就不多说了。
        妖孽!看图。哈哈....






      这个就是我的蓝牙助手的APP喽,上面有好几种模式共选择的,功能算是比较强大吧,不管那个软件都不会影响这篇文章的讲解的。图上有12个按键,这个APP按键功能是当你按下按键并释放后,按键会被触发并且发送几个字节的16进制数据:如下图







        大家可以看到按键的属性,而且自己可以自行修改按键的name和要发送的数据,第一张图片就是我做蓝牙小车修改的。下面注意了!上图第一个的“点击发送”框里就是按键触发后发送给下位机的数据,是6个字节的十六进制数据,那么问题来了:串口不是只能接收或发送一个字节的数据吗?这六个字节的数据它怎么处理?   这就是多字节通信的重点!虽说是六个字节,但是串口还是一个字节一个字节接收处理的。手机也是一个字节一个字节发送出去的,只是双发处理比较快感觉不到延时而已。“A55A040206AA”这6个字节到底代表什么意思呢?
大家一定要记住每个字节都分别代表一个意义的:第一个字节A5,第二个字节5A,第三个字节04,都是上面提到的数据头(每种上位机通信的协议不一样发送的数据头字节数和内容是不一样的),单片机接收到A5后就必须要先判断接收到的是不是A5,如果是就检测第二个字节是不是5A,如果是继续第三个,这样才能保证通信的正常;第四个字节02,这个就是你这个按键的数据,main函数中也要检测这个这个字节判断是那个按键被触发了,同样在中断里收到后也需要检测;第五个字节06,这个就是校验码,这个上位机规定的校验码格式是最后一位数据头的值04和发送的那个数据02的值两者之和(04+02=06);最后一个字节AA,就是这一串字节的数据尾了,中断里面检测到它后就说明数据发送完毕了。
A55A040105AA       04+01=05
A55A040206AA       04+02=06
A55A040307AA       04+03=07                看到了吧,变化的只有2位字节,其他的都是不变的。
A55A040408AA       04+04=08
A55A040509AA       04+05=09

(再说一遍每个上位机和下位机的通信协议是不一样的,数据头、检测位、数据尾都需要根据自己的修改判断)
下面我把检测这6个字节的代码发出来:
一些头文件,变量神马的没有贴了,不影响通信的介绍。
uchar receive_SBUF[6]={0,0,0,0,0,0};                  //接收缓存区
char uart_flag,back;
/*****************************串口初始化*********************************/
void Uart_init(void)
{
        TMOD=0x20;                //定时器1,方式2(8位自动重装载)
        TH1=0xfd;                //波特率在9600下,9600=(1/32)*11059200/[256-x]*12     x=253(0xfd)
        TL1=0xfd;
        EA=1;
        ES=1;                        //串口中断允许位
        REN=1;                        //串口接收位打开
        SM0=0;                        //串行口工作方式1(10位异步收发8位数据,波特率可变(由定时器1的溢出率控制))
        SM1=1;
        TR1=1;
}
/**************************************中断处理函数***************************************/
void serve() interrupt 4        //串口中断服务程序
{
        static char count=0;                //中断次数计数
        if(RI)
        {
                RI=0;                                        //接收标志位清0
                receive_SBUF[count]=SBUF;       
                //        back=SBUF;                                //把数据复制back,便于观察发回的数据
                                   
       
                  /***************************同时判断count和收到的数据*****************************/
                  //下面校验数据的方法还是比较严谨的,每接收一个字节数据就检测一下,
                  //整个过程中只要有一个数据出错就会清零重新开始,基本不会发生错误,
                  //除非数据的起始位和校验位、结束位都一样,但是这样的几率太太太小了。
                  if(count==0&&receive_SBUF[count]==0xa5)                    count++;           //数据头
                  else if(count==1&&receive_SBUF[count]==0x5a)          count++;           //数据头
                  else if(count==2&&receive_SBUF[count]==0x04)          count++;           //数据头
                  else if(count==3&&((receive_SBUF[count]==0x01)||(receive_SBUF[count]==0x02)||(receive_SBUF[count]==0x03)||(receive_SBUF[count]==0x04)||(receive_SBUF[count]==0x05)||(receive_SBUF[count]==0x06)||(receive_SBUF[count]==0x07)||(receive_SBUF[count]==0x08)) )          count++;
                  else if(count==4&&(receive_SBUF[count]==(receive_SBUF[2]+receive_SBUF[3])) )                 count++;         //判断校验和,其他的校验方法也可能是固定的帧尾
                  else if(count==5&&receive_SBUF[count]==0xaa)
                  {
                     count=0;
                     uart_flag =1;        //串口接收成功标志,为1时在主程序中回复,然后清零
                  }
                  else
                  {
                     count=0;//判断不满足条件就将计数值清零
                  }
        }
}

/*******************************主函数**************************************/
void main()
{
        Uart_init();
        while(1)
        {       
                if(uart_flag==1)       
                {
                        ES=0;
//                        SBUF=back;                //将刚才发送的数据显示出来
                        uart_flag=0;
                        switch(receive_SBUF[3])
                        {
                                case 0x01:         P1=0xfe;break;         //这里判断的就是每个按键特有的数据位了,
                                case 0x02:         P1=0xfd;break;            也就是6个字节中的第4字节,检测到是哪
                                case 0x03:         P1=0xfb;break;            一个按键后就让MCU执行相应的功能;
                                case 0x04:         P1=0xf7;break;
                                case 0x05:         P1=0xef;break;
                                case 0x06:         P1=0xdf;break;
                                case 0x07:         P1=0xbf;break;
                                case 0x08:         P1=0x7f;break;
                                default:    break;
                        }
                                                               
      //        while(!TI);                   //因为上面需要发送的数据注释掉了,没有要发送的数据,就不要一直等待发送结束了
     //                TI=0;           //否则程序会一直卡在发送结束标志位的,死的很惨很惨的,不要不要的,调到大半夜也没看出来
                ES=1;
                }
                       
        }
}
       在中断服务程序里面可以看到,每收到一个字节的数据都要和你要发送的数据匹配检测一下是不是一样的,如果内容和顺序都一样就继续收发下一个字节,只要有一个字节发送错误,整个通信过程就会全部重新开始,直至正确匹配!这样做才能保证多字节通信不会发生错误。多字节和单字节通信的区别也就在这里,多字节的就需要每个字节一个一个的匹配检测。

       好哒,这篇贴就写到这里了,如果还有不懂的地方自己一定要多看看别人的程序和资料了,学习嘛,不下功夫哪行!


个别部分排版没有弄好,嘿嘿,有点小难看!

真的是太感谢了,明白了许多。

谢谢了,有很好的借鉴作用

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top