Modbus RTU协议各知识点入门 + 实例

1. 起因

最近在做一个项目,我们的设备使用485作为从机与主机PLC通信,用到了modbus,因此整理了一份modbus的资料。
其实modbus还是很简单的,虽然有些名词初看不知道是啥,看懂之后发现就是绕人用的,换个名词就很清晰易懂的(对于我这种不做PLC的人)。

2. 几个重点

2.1 一些难懂的概念

  • 线圈 就是“位变量”
    • 离散输入 就是 只读 的 1位变量
    • 线圈 就是 可读写 的 1位变量
  • 寄存器就是“16位变量”
    • 输入寄存器 就是 只读 的 16位变量
    • 保持寄存器 就是 可读写 的 16位变量

具体可以见5章节,阅读的时候把这几个名词直接替换就好。

2.2 CRC的高低位

Modbus协议的CRC校验码是高位在前还是低位在前的问题。
有些资料说高位在前,有些资料是说低位在前。
其实是因为CRC生成函数,有些在内部已经做了高低位的转换,所以看起来像是高位在前。
实际上,CRC是低位在前发送。详细见第7节。

2.3 其他

  • 不管任何时候,从机都不能主动向主机发送数据
  • 从站的地址是1~247(注意没有0!! 0是广播!都不回复,都会收到!)
  • 数字编码大端序,高字节在前
  • CRC校验位低字节在前
  • modbus的寄存器都是16位的,但是在其他地方(比如MCGS)也见过一些变种,比如32位的,比如float的。有些是把32位按照标准的modbus,拆分成了2个寄存器。有些就是按1个4字节的寄存器来算的。

3. 介绍

3.1 起源

modbus由MODICON公司于1979年开发,是一种工业现场总线协议标准。
1996年施耐德公司推出基于以太网TCP/IP的modbus协议:modbusTCP。

3.2 分类

Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。ASCII是明文的,但是实际效率低,用的比较普遍的就是RTU格式。
这里我也是只用了RTU,就只记录RTU的协议。

4. 格式

4.1 串口协议

  1. 串口波特率
  2. 串口设置

4.2 帧格式

起始位 地址码 功能码 数据区 错误校验码
3.5字符时间 1字节 1字节 0~252字节 CRC16校验码
1~247 见3.5节 CRC低位 CRC高位

注意这里,多字节的数据,是高位在前,低位在后。
但是CRC是低位在前,高位在后。
根据不同的功能码,数据区各不相同,具体可以看第8部分实例来分析。

5. 数据类型

数据类型 简写 读功能码 写功能码 对象 地址范围
离散输入 DI 02 只读的 1位变量 00001~0FFFF
线圈 DO 01 05,15 可读写的 1位变量 10001~1FFFF
输入寄存器 AI 04 只读的 16位变量 30001~3FFFF
保存寄存器 AO 03 06,16 可读写的 16位变量 40001~4FFFF
  • DI: 数字输入,离散输入,一个地址一个数据位,用户只能读取它的状态,不能修改。比如面板上的按键、开关状态,电机的故障状态。
  • DO: 数字输出,线圈输出,一个地址一个数据位,用户可以置位、复位,可以回读状态,比如继电器输出,电机的启停控制信号。
  • AI: 模拟输入,输入寄存器,一个地址16位数据,用户只能读,不能修改,比如一个电压值的读数。
  • AO: 模拟输出,保持寄存器,一个地址16位数据,用户可以写,也可以回读,比如一个控制变频器的电流值。

6. 功能码

功能码很多,用的比较多的就是以下8个:

功能码 功能码16进制 中文名称 操作位 操作寄存器数量
01 0x1 读线圈 1位 单个或多个
02 0x2 读离散输入 1位 单个或多个
03 0x3 读保存寄存器 16位 单个或多个
04 0x4 读输入寄存器 16位 单个或多个
05 0x5 写单个线圈 1位 单个
06 0x6 写单个保存寄存器 16位 单个
15 0xF 写多个线圈 1位 多个
16 0x10 写多个保持寄存器 16位 多个

最主要的就是1、2、3、4、5、6、15、16 这8个功能码。
个人翻译一下
01:读一个或多个可读写的 bit 变量
02:读一个或多个只能读的 bit 变量
03:读一个或多个可读写的 short 变量 (或其他2字节变量)
04:读一个或多个只能读的 short 变量 (或其他2字节变量)
05:写一个可读写的 bit 变量
06:写一个可读写的 short 变量
15:写多个可读写的 bit 变量
16:写多个可读写的 short变量 (可以拼凑成int 、long 、long long、float等变量)

读的时候,都是可以选择读“一个或多个”。
写的时候,按数量分功能码进行操作。

7. CRC16(modbus)

在CRC计算时只用8个数据位,起始位及停止位,如有奇偶校验位也包括奇偶校验位,都不参与CRC计算。
CRC计算方法是:

  1. 加载一值为0XFFFF的16位寄存器,此寄存器为CRC寄存器。
  2. 把第一个8位二进制数据(即通讯信息帧的第一个字节)与16位的CRC寄存器的相异或,异或的结果仍存放于该CRC寄存器中。
  3. 把CRC寄存器的内容右移一位,用0填补最高位,并检测移出位是0还是1。
  4. 如果移出位为零,则重复第三步(再次右移一位);如果移出位为1,CRC寄存器与0XA001进行异或。
  5. 重复步骤3和4,直到右移8次,这样整个8位数据全部进行了处理。
  6. 重复步骤2和5,进行通讯信息帧下一个字节的处理。
  7. 将该通讯信息帧所有字节按上述步骤计算完成后,得到的16位CRC寄存器的高、低字节进行交换
  8. 最后得到的CRC寄存器内容即为:CRC校验码。

注意!这个 高低字节交换 之后的,才是算出来的modbus的CRC校验码。
然后把这个交换后的校验码,按照 先低位,后高位 的 顺序加载到发送队列里。

8. 实例

从网上随便截取了一些别人的例子,部分来自示例

8.1 功能码1

在这里插入图片描述

8.2 功能码2

在这里插入图片描述

8.3 功能码3

在这里插入图片描述

8.4 功能码4

在这里插入图片描述
主机要读取从0x400地址的2个16位寄存器的值。
从机回复了4个字节。

8.5 功能码5

在这里插入图片描述
在这里插入图片描述

8.6 功能码6

在这里插入图片描述

8.7 功能码15

在这里插入图片描述

8.8 功能码16

在这里插入图片描述

9. 实现思路

9.1 我的实现思路

我这边实现思路比较简单,
利用串口中断判断收到的报文,如果满足报头(设备地址),就进行报文连续接收,收满一帧就放入处理队列。另一个线程收到这个数据会取出来解包处理。
寄存器方面就是申请一块连续buf(数组或其结构体),和寄存器地址一一对应上,要改值或者要读值都是根据这个buf的数据来做。

9.2 别人的实现思路(一些开源库)

用的比较广的是FreeModbus和LibModbus。

10. 参考

发布了25 篇原创文章 · 获赞 7 · 访问量 5114

猜你喜欢

转载自blog.csdn.net/tao475824827/article/details/103455403
今日推荐