现学现卖:基于51单片机与PC的自定义协议无线通信详解【自定义协议】

  • SingleYork
  • LV5工程师
  • |      2016-03-27 14:27:29
  • 浏览量 1459
  • 回复:9

        最近在研究单片机跟PC端无线通信的问题,一开始由于从来没接触过,感觉很难,不知道该从哪里下手,后面找了很多资料,找到了有关射频通信的一些资料,然后从网上购买了两个RF模块,自己做了个小板子编程实现单片机与PC端的无线通信,功能是这样的:PC端通过串口助手向单片机发送查询指令,单片机接收到指令后,将自己的IO口状态(8输入8输出)回传给PC,话不多说,先来看下效果吧:


        


        本例程用到了两个FR模块,是从网上买的:

        


        前一个FR模块使用的是TTL电平接口,主要是为了方便设计板子时容易跟单片机连接,第二个FR模块使用的是USB接口,插在电脑上装下驱动就可以直接用了。总之,选这两个模块的原因就是四个字--使用方便!因为我不紧紧只是拿来做实验,还要根据实际情况用来做项目的!


        至于两个模块的介绍我就不多说了,毕竟我不是来卖产品的本帖主要是介绍单片机如何利用串口自定义协议跟PC通信,这才是本帖的重点!下面我就以我所使用的单片机STC12C5A60S2利用串口2跟TTL的FR模块通信,并将数据转发至PC端为例,简单的讲讲自定义协议吧。


        首先,什么叫自定义协议呢?楼主你是在说笑嘛,这还不简单,就是自己根据实际使用情况定义好的协议呗!差不多可以这么理解吧。现在市面上有很多的标准模块,协议都是厂家定义好的,使用起来也很方便,但是我们在做项目的过程中会发现,很多时候那些标准的模块所具备的功能或许并不是我们所需要的,而且既然人家是标准的模块,那么价格方便也不会很便宜,本菜之前也从网上找了很多现成的产品,带光电隔离输入输出的可编程FR模块都要一千多!而我手上的一个项目需要用到40多个这样的模块,尼玛,这算起来成本都去了好几万了,老板肯定不干啦!所以本菜只好寻找一种更经济的办法--只能自己想办法设计板子啦!谁叫咱是干这个的呢!


        由于应用现场需要较远距离通信--最远有几百米吧,所以WIFI通信就不考虑了,在论坛群里也咨询过,可以用SIM芯片来做产品,可是咱不会呀!要重新去学得花多少时间啊!几经周折,最终选择了FR无线通信,通信距离既能保证,价格也不算太贵!当然FR模块咱是不会设计的,就算抄袭别人的,也并不能保证做的稳定,所以保险起见,咱还是选择了标准的FR模块,然后用单片机的串口跟FR模块通信。别问我为什么,因为我只会串口通信呀,哈哈!做项目肯定是优先选择自己会的啦,然后再考虑如何降低成本


        说了这么多,其实都是在说设计这款板子的背景啦!现在开始,咱来说说技术上的问题吧。由于设计到一对多通信,那么各个模块还是得设计一个地址啦,这样PC端才能分辨收到的数据到底是哪个模块发来的嘛,于是咱选择了用8位的拨码开关来设置地址,这样地址范围就可以从0x00~0xFF,总共256个啦!这样做的目的主要是方便以后客户需要扩展更多的模块,虽然说现在只需要用到四十多个,但谁能保证以后不会增加呢,是吧?设计的时候留点余量还是有必要的!来看看最终设计效果吧:


        


        本板子特意设计了一个接口跟TTL的FR模块对接,然后就可以直接将FR模块焊在板子上,或者用杜邦座跟杜邦针对插的方式,总之一切为了方便,呵呵!硬件设计好了,那么接下来我们讲讲软件如何实现吧!


        软件设计上,本菜是思路是这样的:

        首先,PC端做主机,所有单片机模块都为从机,主机采用轮训的方式,逐一发命令给每个从机,用不同地址区分,每个从机都会接收到主机发来的数据,但是只有收到跟自己地址相符的数据帧的时候,才会处理该数据帧并做出相应。


        PC端发送的指令格式如下:

        37 73 + 长度(4) + 命令0x30(查询)+(硬件)地址0XFF + 编号0XFF + 和校验


        其中 :

        “37”、“73”是帧头,各占一个字节;

        “长度”是指从“长度位”开始后的数据的中长度,总共是四个字节;

        “命令”是PC端告诉单片机端,你现在该干嘛,占一个字节;

        “地址”是单片机模块的地址,占一个字节;

        “编号”是PC端给单片机模块设定的一个软编号,占一个字节;

        “校验和”是指从“长度位”开始的数据一直相加,直到加到“编号”位,占一个字节,数据会自动溢出;


        PC端定义好了,那么接下来就是单片机端如何接收PC端发来的命令了!


        本菜在单片机做接收处理的时候,用的是串口2的中断接收:

     

        


        首先是判断帧头,由于PC端发送数据是以37 73作为帧头,所以只有我们在连续收到“37”、“73”的时候,我们才认为我们收到了数据,否则收到其他任何数据单片机都认为是无效的数据,不做任何理会!


        当收到帧头的时候,我们就知道PC在对单片机模块发送指令了,那么接下来单片机就来判断,这条指令到底是不是发给我的,因为PC端发送数据的时候是在轮询的,所以也有可能这条命令不是发给我这个模块的,于是,我们就要来判断这个数据帧的目标对象是谁啦!于是地址位就起作用啦!单片机 模块一上电的时候就会读取自己模块的地址:

        

        

        

        为了方便起见,本菜直接将单片机的P0口用做地址口了,这样读地址的时候就不用转换了,呵呵!实际应用中,大家可以根据自己单片机的IO口情况自行设置。


        当然,其实在接收地址位的之前,单片机还是会接收到两个数据,即“长度”跟“命令”:

        

        


        这个长度主要是用来判断PC端发来的这帧数据什么时候结束,“命令”主要是用来识别PC要我这个模块做什么。但是在没有校验地址位之前,即使收到了前面这些数据,单片机也不会做太多处理的,只是用一个数组简单的暂存一下,如果地址位不是自己的,最终还是会把收到的数据给丢弃!


          


        在接收到地址位的时候,单片机就会将该地址位跟自己的地址做比较,即:if(dattmp==Addr),如果地址正好是自己的地址,就继续接收下一个字节,如果不是自己的地址,就不再接收后面的数据,将数据的计数清零,等待重新接收下一帧数据。

        

        若地址位是本模块的,那么单片机会继续后面数据的接收,直到接收完成,最终通过长度位来判断数据是否接收完毕:


        即:if(RX_Len==Rec) //数据是否接收完成


        RX_Len是在接收长度位之后开始计数的。在数据接收完成之后,还需要来校验一下数据的准确性。说到校验,相信大家会有很多中方法,本菜选择的是和校验,即:将收到的数据累加,最终得到的和来跟PC端发来的最后一个字节(和校验位)做比较,如果正确则认为数据接收完成,不正确则放弃刚才接收的这帧数据:


        


        如果仔细观察本菜写的接收流程不难发现,本菜在接收的每个阶段都设置了一个Y0X = 0;没错,本菜这样做的目的是用来判断单片机接收的时候,哪个阶段收不到数据,这样的话只要看单片机模块的输出指示灯就知道哪个阶段的数据没收到了!这种方法是不是比仿真还简单呢?哈哈……山人自有妙计!


        好了,既然单片机端收到了PC端发来的命令,那么就该根据接收到的命令做具体的事情啦!在本例中,本菜是这么规定的:当单片机接收到PC发来的查询命令时,单片机端会按照指定格式返回相应的数据!格式如下:


        5A A5 + 长度(6)+(硬件)地址0XFF + 编号0XFF(软件设置)+ 输入状态(0XFF)+ 输出状态(0XFF)+ 和校验


        其中:

        “5A”、“A5”是帧头,各占一个字节;

        “长度”即从该位开始,后面所有数据的总长度,本例是6个字节;

        “地址”是指单片机模块的地址,占一个字节;

        “编号”是指单片机模块的软编号,占一个字节,可有PC端自由设置;

        “输入状态”是指单片机模块输入口的状态,本例为8个输入口,占一个字节;

        “输出状态”是指单片机模块输出口的状态,本例为8个输出口,占一个字节;

        “校验和”跟PC发送时候的校验是同样的道理,在此不再做解释;


        数据的返回,本菜是这样设计的:

        在接收完成之后,程序会让一个自定义的标志位置1,即:RXFRMOK = 1;在程序的主函数里会判断RXFRMOK的值,当查询到RXFRMOK = 1时,重新将该标志位置0,即:RXFRMOK = 0;同时会判断接收到的“命令位”,根据不同的命令,处理不同的事情。本例中设计的“命令位”的值是0X30,即当单片机模块收到0X30命令时,会按指定格式将数据返回给PC端:


        

        这样,整个通信的流程就结束了,那么我们来看看实际的效果吧,可能视频里面不是很清楚,本菜来一步一步分析吧:


        首先,PC端发送:37 73 04 30 FF 05 38 命令,即:查询模块地址为0XFF,模块编号位0X05的单片机模块IO口的状态;


        那么我们先来看下当前单片机IO的状态信息吧:


        


        输入口状态从高到低依次为:1111 1101,即:0XFD;

        输出口状态从高到低依次位:1000 0000,即:0X80;

        模块地址从高到低依次为:    1111 1111,即:0XFF;


        


        现在我们来验证一下,实际通信过程中,PC端接收到的数据是不是这样子的呢?


        

        

        从上图不难看出,PC端向单片机模块发送“37 73 04 30 FF 05 38”命令时,单片机模块会返回“5A A5 06 FF 00  FD 80 82”,实时证明,PC跟单片机之间就是按照本菜所定义的格式进行通信的!


        当然,本例中还有个功能没有完善,大家从上面的图片中应该可以看出,实际的“模块编号”还没有起作用,因为本实例中还有一个设置的命令,即:0X31,这个命令是用来设置模块编号的,用户可以根据自己的实际需求,先设置好模块编号后,再开始查询,另外模块的编号还可以存储到单片机的EEPROM中,单片机每次上电初始化的时候读取EEPROM中的模块编号,即可实现编号的掉电保持,下一帖子本菜会继续在现有硬件基础上介绍STC12C5A60S2单片机的EEPROM存储功能,欢迎大家围观!


        到此,本帖介绍完毕,谢谢!如有讲解不到或介绍有误之处,欢迎吐槽!




  • 0
  • 收藏
  • 举报
  • 分享
我来回复

登录后可评论,请 登录注册

所有回答 数量:6
执念 2017-07-16
点个赞
0   回复
举报
发布
SingleYork 回复 2017-07-17
说好的赞呢?你点了吗?:lol
0   回复
举报
new world 回复 2017-07-20
不错,继续加油!!!
0   回复
举报
geek_michael 回复 2017-07-21
厉害!!!!!!!!!!
0   回复
举报
yangjiaxu 2017-07-16
666
0   回复
举报
发布
lygo 2017-07-12
厉害!!!!!!!!!!
0   回复
举报
发布
SingleYork 2016-04-01
感谢夸奖,呵呵……
0   回复
举报
发布
兵临城下 2016-03-31
动手能力很强。
0   回复
举报
发布
zigzagroad 2016-03-28
no bad
0   回复
举报
发布
x
收藏成功!点击 我的收藏 查看收藏的全部帖子