向Modbus协议说"So easy!"
1、先聊聊“数据通信模型”
玩嵌入式的小伙伴们肯定是玩过各种通信方式和通信协议,因为我们所做的项目不是一个独立的个体,它需要跟外界交互,交互就需要有一套大家都遵守的东西那就是我们的协议,一般的与计算机有点关系的信息类专业,大学应该或多或少学过《计算机网络》这门课程,可以说计算机网络系统是一套非常完备的通信系统。我们今天的modbus协议的介绍肯定比他简单不知道多少倍,所以大家不要惊慌。不过我觉得说起通信一定不能缺少下面这个模型:
可以说几乎所有的通信方式都会基于该数据通信模型进行分层制定和开发。我们在前面的文章中的编程技巧也提到了这个模型,个人觉得它最大的特点就是分层,并且下层仅仅为上层提供服务,但是却只一个层面上的交互。(形象点说,就好打我们电话我们只在乎你说的话的内容,我们并不在乎我们所说的话是怎样通过电话和通信传递给对方的)
好了,我们平时在嵌入式中经常遇到的串行数据通信,比如串口通信,SPI,IIC,CAN等等,他们都属于上面模型中的数据链路层和物理层,可能很多小伙伴们自己制定了简单的通信协议,那么可以说直接从用户层-->数据链路层-->物理层进行传输,中间层基本上就没有了。所以说我们可抽取其中的几个层面来进行我们通信协议的设计。
2、通信的本质(纯粹个人理解)
通信的本质我觉得就是沟通嘛。那么在我们编程中我们可以叫做"交换数据",比如说我把数据传给你,你接受到处理以后把我想要的数据传给我,就这么简单。我们也知道大部分信号都是电信号,然后我们去看电信号来对应数据吗?电信号数据无非就是0和1,可以对应着我们程序的二进制文件,有点难懂,既然传输的是数据我们如果能够用8bit-byte的形式来表示那就最好了。那我们在程序中接受到数据直接就可以拿来使用了。
对于SPI,IIC等这类通讯方式,我们就认为在传byte就行了,我们在以后的项目开发中我们经常会遇到通信FIFO来进行缓冲(所以我之前写了一篇《无数据类型的队列实现》是非常有用的),最终解析数据的话基本上都是去FIFO中取数据进行解析,所以用如下图模型(大家好像叫字节流-那么就叫"字节流模型")再好不过了。对于CAN这种一次性发送多个byte的通信方式,虽然有特殊的处理的通信协议,不过我们完全可以转化为如下图所示的方式进行数据解析和处理。(只是说我们可能会考虑到传输效率和应用等会开发出新的通信协议)
3、超级简单的Modbus协议
根据我们第二小节所说的字节流模型,我们知道数据都是1个bye一个byte传输的,我们只要规定好每个byte代表什么意思,然后把byte组合起来是不是发送方也接受方都懂了?那么规定这些byte意思的就是我们的协议-协议完全可以自己定,不过我们这里今天选用了一种应用非常广泛的modbus协议来供大家学习和参考。一方面可以参考协议如何制定,以后可以直接模仿创造自己新的协议,另一方面可以积累一些设计上的经验。好了,下面开始我们的modbus协议的讲解:(这里我不可能把modbus协议全面的为大家讲解,也没有必要,因为很多东西都是重复的,只要大家理解查查modbus协议手册分分钟的事情)
1)介绍modbus协议核心
modbus协议是一种请求/应答的协议。说白了就是一问一答。可能小伙伴们会问,主机向从机写数据也是问吗?是的,比说说:我问你123456789收到没?你回答说接受到了123456789.下面为大家展示一下modbus一些的核心:
modbus的核心就是由这样四个区域的byte组成的数据包来进行相互传输相互交互的。
地址:(占用1byte)modbus通信总线上有非常多的设备,并且其总线上只有一个主机,就好像一间教室里面一个老师正在发作业本一样,老师会根据学生的名称(也就是该地址)发送对应的作业本给学生,而学生写完作业以后也会填上字节的名字进行上交。并且这些地址肯定是唯一的标识,因为我们modbus协议还不能像老师一样能够根据外表来同名区分。(总结:如果该数据包是主机发送,那么地址就是对应从机的地址,如果该数据包是从机的上报,那么该地址就是从机的地址)
功能码:(占用1byte)字面上的意思就是区分不同的数据帧的类型,其实modbus协议就是通过不同的功能码定义了数据部分的不同格式,从而实现了多种传输功能。所以modbus的功能码特别多,不够我总结了一下基本上就是读数据和写数据,只是说形式上分得更多了点,我们只要根据modbus协议上规定的方式进行填充即可。后面我会举例说明。
数据:(根据功能码占用多个byte)根据功能码的不同,不同的byte代表不同的意思。
校验:(占用2byte)为了保持数据的稳定性,尽量避免在通讯过程中的数据错乱,就好比把前面的数据进行求和然后放在该区域发送出去。只是说modbus协议使用更加优秀的CRC校验(前面我也发表过CRC校验的文章,CRC校验分很多类型,在数据校验中经常使用)
说了很简单吧,下面我以一个功能码为大家进行具体的讲解:(其他功能码和一些注意事项都可以在modbus的详细协议文档中找到答案)
2)modbus协议功能码实例
我们以功能码:0x04为例,下面完全满足我们上面的格式(其中数据区位于功能码和校验之间)
发送方 | 地址 | 功能码 | 起始地址 | 输入寄存器数量 | 校验 |
1byte | 0x04 | 2byte | 2byte | 2byte | |
接受方 (Success) | 地址 | 功能码 | 字节数 | 输入寄存器值 | 校验 |
1byte | 0x04 | 1byte | N*2byte | 2byte | |
接受方 (Fault) | 地址 | 差错码 | 异常类型 | 校验 | |
1byte | 0x84 | 1byte | 2byte |
注意:1)modbus中的地址和数据都是大端模式,也是就是说高字节先传,比如地址2byte分别是0x1122,那么发送字节流的时候先发0x11然后放0x22。
2)上面接受方接受到数据,会判断功能码,字节数输入寄存器个数等等,是否在正确的范围,如果都满足要求,就以接受方(success)的数据格式正确的返回数据,如果检查有问题就会以接收方(Fault)的格式把0x04+0x80作为功能码,然后确定好异常的类型(在modbus标准协议中有说明)然后进行发送。
3)其他的功能码都是类似的,并且Modbus协议是一种不依赖物理通讯的通信协议!大家可以根据自己实际的项目进行改造。
好了今天的Modbus协议就讲到这里吧!挺晚了该休息了。这里是公众号:"最后一个bug",欢迎大家的关注!也欢迎大家分享转发,往期内容也非常精彩。
推荐阅读