电子说
在之前的两篇文章中,我们讲解了串口的基础知识和在安卓中使用串口通信的方法,如果还没看过之前文章的同学们,建议先看一遍,不然可能会不理解这篇文章讲的某些内容。
事实上,在实际应用中,我们很少会直接使用串口通信,一般都会使用到 Modbus。
因为正如我上篇文章所说,如果直接使用串口通信的话,需要我们自定义数据层协议,或者干脆就直接发送一个 byte 的数字进行通信,这显然是不方便的,也不安全的。
例如我上篇提到过的一个问题,我所使用的驱动版厂商定义的协议中没有定义数据长度(或者在数据中附上数据长度),也没有定义停止符号,这会导致出现“沾包”或“分包”情况时不好区分数据。
并且自定义协议还需要自己去解析并处理数据,使用起来不是那么方便。
所以,我司在尝试过直接使用串口通信后,最终还是决定放弃直接使用串口通信,而是改用 Modbus 通信。
本篇文章属于系列文章的扩展篇,我们将讲解 Modbus 的基础知识以及如何在安卓中使用 Modbus。
本文中部分图表来自文末标注的参考资料
Modbus 是一种应用层报文传输协议,由 Modicon 公司在 1979 年发布,是为了解决 PLC 通信而研发的协议。
因为 Modbus 是开源的且无著作权要求、易于部署维护、可靠性强的特性,所以 Modbus 已经成为工业领域通信协议事实上的业界标准,并且现在是工业电子设备之间常用的连接方式。
由于 Modbus 定义的只是应用层的报文协议,所以它可以使用串口(RS232、RS485)、以太网作为物理层接口。
Modbus 分为三种传输模式:RTU、ASII、TCP。
在使用 Modbus 时,所有设备的传输模式必须相同。
RTU 使用二进制数据传输、ASCII 使用 ASCII 字符传输。
使用串口连接时支持 RTU 和 ASCII 模式。
使用以太网连接时支持 TCP 模式。
因为本系列文章的重点在于讲解串口通信,所以我们不过多讲解 TCP 模式,同时,由于 ASCII 模式在目前实际应用中比较少,我们一般都是使用的 RTU 模式。故,我们会重点讲解 Modbus RTU。如果对其他传输模式感兴趣的可以阅读参考资料 4 的文档。
额外说明一下,Modbus 和 RS232、RS485 的区别。
RS232、RS485定义的是物理层标准,即接线方式,电平高低,数据传输方式等。
而 Modbus 是应用层协议,即定义了上述物理层传输过来的数据应该以什么样的格式去解析。
使用串口作为物理层协议时,通常采用的是 RS485 。
而我们在第一篇文章就说过,RS485 支持一主多从多个设备同时连接,所以使用 RS485 的 Modbus 同样支持多个设备连接。在标准负载情况下,支持一个主机连接最多32个从机。并且在连接设备时,只能使用菊花链连接,不能使用星型网络:
另外,Modbus 是一种请求/应答协议,即只能通过主站(主机)发送请求给从站后,从站响应数据给主站,而不能从站直接主动发送数据给主站。
在 Modbus 中定义了4种不同的数据模型,具体如下:
名称 | 数据类型 | 访问类型 | 说明 |
---|---|---|---|
离散量输入 | 单个比特(bit) | 只读 | I/O系统提供 |
线圈 | 单个比特(bit) | 读写 | 可通过应用程序改写 |
输入寄存器 | 字(word,16bit) | 只读 | I/O系统提供 |
保持寄存器 | 字(word,16bit) | 读写 | 可通过应用程序改写 |
其中 线圈 和 离散量输入 又可以称为 输出线圈 和 输入线圈。
它们的数据长度都是一个 bit,即只能表示 1 或 0,表现在程序中就是一个 Boolean 类型的数据。对于安卓程序员来说,可能会疑惑啥是线圈,其实这两个模型之所以叫做线圈是因为 Modbus 是为了 PLC 通信而编写的协议,而在 PLC 中一些物理设备(例如继电器)只有两种状态:断开与接通(即 0 或 1 ,或者 Boolean 的 false 与 true ),这些物理设备的状态切换一般都是依赖于线圈的通/断电来实现,所以在 Modbus 中就将这种类型的数据称为 线圈。
而 输入寄存器 和 保持寄存器 又可以称为 输入寄存器 和 输出寄存器。
它们的数据长度是一个 word,即 16 bit,2 byte,表现在程序中可以看成一个 Int 类型。
显然,在同一个设备中不同的数据模型肯定不止一个可用的数据区块,理论上来说,每种数据模型最大可以定义 65536 个数据区块。
因此,每种数据模型的地址定义为如下:
数据模型 | 地址范围 |
---|---|
线圈 | 00001-09999 |
离散输入 | 10001-19999 |
输入寄存器 | 30001-39999 |
保持寄存器 | 40001-49999 |
可以看到,虽然我们上面说每种模型理论上支持 65536 个数据区块,但是实际使用中每种数据模型一般都只会定义最大 10000 个数据区块。
Modbus 允许将四种不同的数据模型存放在不同的数据区块,这样使用不同的功能码(下面会说什么是功能码)读到的是不同的数据:
同时,Modbus 也可以将不同的数据模型映射到同一个数据区块中,这样一来,不同的功能码读取到的可能是相同的数据:
在上一节我们介绍了储存区数据模型,那么我们要如何去读取不同的数据模型数据呢?或者说,在 Modbus 中是怎么区分不同的数据模型?
此时,就要用到 功能码。
在 Modbus 中定义了三种类型的功能码:
公共功能码定义了如下几种:
而我们一般会使用到的有以下几种:
可以看到,我们常用的有 8 个功能码,其实仔细一看就能看出不过是读所有数据模型;以及可写数据模型和写单个/写多个的排列组合。
读取数据时所有数据模型均支持只读取单个和同时读取多个数据,并且使用的都是同一个功能码。
写入数据同样支持只写入单个数据和同时写入多个数据,但是写入单个和写入多个的功能码是分开的。
可能有细心的读者发现了,为什么表中的所有 寄存器地址 都是一样的啊,这是因为上表中的 PLC 地址使用的是绝对地址,一般用于文档中或程序中。
而实际设备的寄存器地址则使用的是相对地址。由于我们已经通过功能码区分开了不同的数据区块,所以为了节约传输时的字节占用,直接使用相对地址即可(如果使用绝对地址,那么现在的字节数不够表示所有地址)。
上文中提到过,使用串口的 Modbus 是主-从协议。即,在同一时刻,只有一个主节点和一个或多个子节点连接在同一个串行总线上。
Modbus 的通信总是由主节点发起,子节点响应。并且子节点之间不会相互通信。
在 Modbus 中,主节点没有地址,每个子节点都有自己唯一的地址(1-247),通常称为从站地址。
主节点有两种方式发出请求:单播模式与广播模式。
在单播模式中,主站(主节点)发送一个带有从站(子节点)地址的请求给当前连接的所有设备,但是只有从站地址符合的从站会响应该请求,并返回数据。其他设备不会响应也不会执行任何操作(读取到地址不符合后直接抛弃这个请求报文)。在这个模式中会产生两个报文:主站的请求报文和从站的响应报文。
在广播模式中所有从站都不会发送响应报文给主站,但是会执行请求的操作,并且主站的请求会发送给所有从站。广播模式一般用于写数据。此时主站发送的请求报文中的从站地址为 0 ,表示广播。
一个 Modbus RTU 的报文帧由 4 个部分组成:
8位从站地址+8位功能码+最大252*8位数据+16位差错校验
在 RTU 中通常使用的错误校验方式是 CRC 校验(眼熟吗?CRC 又出现了)
不知道你们有没有发现,这里的功能码使用了 2 byte ,但是上面介绍功能码时明明最大才到 127 ,那么剩下的一半去哪儿呢?
在 Modbus 定义中,从机如果能够正确处理主机的请求,则返回报文中的功能码将和主机请求的功能码一样,如果出现错误,无法正确的处理请求,则从机返回报文的功能码将是最高位为 1 的功能码,即 128-255 。
数据位在不同的功能码以及主机请求还有从机响应都有不同的数据内容和长度,例如请求读取线圈则数据位的内容为:2字节数据表示读取线圈起始地址+2字节数据表示要读取的线圈数量。
此时从机将会按照请求读取的线圈数量返回数据,数据格式为:1字节表示数据的字节数+N字节表示读取到线圈状态数据。如果读取到的线圈状态数据不是 8 位的整数,则会在后面填充 0 使其满足 8 位的倍数。
数据位在某些情况下,可以为空。
下面举一个数据帧的完整例子(例子来自参考资料 1)。
我们有一个从站是温湿度传感器,从站地址为 1,它会将采集到的湿度写入保持寄存器的 40001 区块中;温度写入保持寄存器的 40002 区块中。此时我们发送读取保持寄存器请求去获取它的温湿度信息。
则,主机的请求报文为:
0103040146013B5A59
分别拆解这个数据帧为:
01 :从站地址
03 :功能码,读保持寄存器
00 00 :读取的起始寄存器地址(对应 40001 的相对地址)
00 02 :读取的寄存器长度(这里表示连续读取两个寄存器)
C4 0B :CRC校验码
从机在接收到请求后,响应报文为:
0103040146013B5A59
拆解数据:
01:从站地址
03: 功能码,读保持寄存器
04 :读取到的数据的字节长度(这里表示4字节)
01 46 01 3B :读取到的数据,前两个字节为湿度(换算成十进制为 326 ,即 32.6% ),后两个字节为温度(十进制为 315,即 31.5 摄氏度)
5A 59 :CRC校验码
这里提一句,别纠结为啥读取到的温湿度的值要除以 10 才是实际值,因为这是温湿度传感器厂家定义的。
全部0条评论
快来发表一下你的评论吧 !