3. Modbus协议基础
Modbus协议自1979年由Modicon(现施耐德电气)推出以来,凭借其开放、简单、可靠的特点,已成为工业自动化领域应用最广泛的通信协议之一。在ThingsBoard物联网平台接入Modbus设备时,理解RTU与TCP两种传输模式的差异、四种寄存器类型的物理含义、以及功能码与数据模型的映射关系,是完成设备接入、数据采集和控制指令下发的核心前提。本章将从协议分层、报文结构、寄存器寻址、功能码应用四个维度展开,并结合ThingsBoard网关配置的实际场景,帮助学员建立完整的Modbus协议知识框架。
上图展示了Modbus协议的核心知识框架。在后续内容中,我们将逐一拆解RTU与TCP的报文差异、四种寄存器的物理意义、功能码与数据模型的对应关系,并通过一个实际的温湿度传感器接入案例,演示如何在ThingsBoard中完成Modbus设备的配置与数据采集。
3.1 Modbus RTU与TCP的区别
3.1.1 核心原理:两种传输模式的本质差异
Modbus RTU(Remote Terminal Unit)和Modbus TCP(Transmission Control Protocol)是Modbus协议在两种不同物理层和传输层上的实现。RTU模式运行在串行通信链路(RS-232/RS-485)上,采用主从架构,一个主站最多可挂载247个从站设备(地址1-247,0为广播地址)。每个报文帧由地址码、功能码、数据区和CRC16校验码组成,帧间空闲时间至少为3.5个字符时间,用于帧同步。
Modbus TCP则运行在以太网TCP/IP协议栈之上,默认端口502。它去掉了CRC校验(由TCP的可靠传输保证),增加了MBAP报文头(7字节),包含事务处理标识符、协议标识符、长度和单元标识符。TCP模式支持多主站并发访问,且不受串行链路距离限制,可跨网段通信。
| 对比维度 | Modbus RTU | Modbus TCP |
|---|---|---|
| 物理层 | RS-232/RS-485串行总线 | 以太网(RJ45/光纤) |
| 传输层 | 无(直接帧传输) | TCP/IP(端口502) |
| 帧结构 | 地址(1B)+功能码(1B)+数据(NB)+CRC16(2B) | MBAP头(7B)+功能码(1B)+数据(NB) |
| 校验方式 | CRC16循环冗余校验 | TCP校验和(无需应用层CRC) |
| 最大从站数 | 247个(地址1-247) | 理论无限制(IP地址决定) |
| 通信方式 | 半双工,主从轮询 | 全双工,支持并发请求 |
| 典型应用 | PLC、电表、传感器(短距离) | 工业网关、SCADA、跨区域采集 |
| ThingsBoard配置 | 需配置串口参数(波特率、数据位等) | 需配置IP地址和端口 |
3.1.2 判断方法:如何选择RTU还是TCP
在实际项目中,选择RTU还是TCP主要取决于设备接口类型和现场网络条件。判断步骤如下:
- 查看设备硬件接口:如果设备提供RS-485/RS-232端子,则使用RTU;如果提供RJ45网口,则使用TCP。
- 评估通信距离与速率:RS-485在9600bps下最大传输距离约1200米,而以太网通过交换机可轻松扩展至数百米甚至跨区域。
- 考虑网络拓扑:RTU需要主站轮询,从站不能主动上报;TCP支持设备主动连接网关(ThingsBoard网关可配置为TCP客户端或服务端)。
- 检查ThingsBoard网关能力:ThingsBoard IoT Gateway的Modbus Connector同时支持RTU和TCP,但RTU需要网关具备串口资源,TCP则只需网络可达。
3.1.3 案例拆解:RTU与TCP报文对比
假设需要读取从站地址为1的设备保持寄存器地址0x0000(对应PLC地址40001)的当前值:
// Modbus RTU请求帧(读取保持寄存器,功能码03)
01 03 00 00 00 01 84 0A
│ │ │ │ │ │ └── CRC16低字节
│ │ │ │ │ └───── CRC16高字节
│ │ │ │ └──────── 读取数量(1个寄存器)
│ │ │ └─────────── 起始地址高字节(0x00)
│ │ └────────────── 起始地址低字节(0x00)
│ └───────────────── 功能码(03=读保持寄存器)
└──────────────────── 从站地址(0x01)
// Modbus TCP请求帧(相同功能)
00 01 00 00 00 06 01 03 00 00 00 01
│ │ │ │ │ │ │ │ │ │ │ └── 读取数量
│ │ │ │ │ │ │ │ │ │ └───── 起始地址低字节
│ │ │ │ │ │ │ │ │ └──────── 起始地址高字节
│ │ │ │ │ │ │ │ └─────────── 功能码
│ │ │ │ │ │ │ └────────────── 单元标识符(从站地址)
│ │ │ │ │ │ └───────────────── 长度(后续字节数=6)
│ │ │ │ │ └──────────────────── 协议标识符(0=Modbus)
│ │ │ │ └─────────────────────── 事务处理标识符高字节
│ │ │ └────────────────────────── 事务处理标识符低字节
│ │ └───────────────────────────── 协议标识符高字节(0)
│ └──────────────────────────────── 协议标识符低字节(0)
└─────────────────────────────────── 事务处理标识符(0x0001)
从上述报文可以看出,RTU帧总长为8字节,TCP帧总长为12字节。TCP的MBAP头增加了事务标识符,用于匹配请求与响应,这在并发通信中至关重要。在ThingsBoard网关的Modbus Connector配置中,RTU模式需要指定串口设备路径(如/dev/ttyS0),而TCP模式只需填写设备IP和端口。
3.1.4 常见误区与总结
总结:RTU适合串行链路、设备数量少、距离短的场景;TCP适合以太网环境、需要高速率或跨区域采集的场景。在ThingsBoard中,两种模式均通过Modbus Connector配置,但RTU需要额外的串口资源,TCP则依赖网络连通性。
3.2 寄存器类型与数据模型
3.2.1 核心原理:四种寄存器的物理意义
Modbus协议定义了四种寄存器类型,分别对应不同的数据存储区域和访问权限。理解这四种类型是正确配置ThingsBoard数据映射的基础。
- 线圈(Coil):可读写的位(bit)数据,用于控制输出,如继电器开关、电机启停。地址范围在PLC中通常为00001-09999,协议地址从0x0000开始。
- 离散输入(Discrete Input):只读的位数据,用于读取开关状态,如限位开关、按钮状态。地址范围10001-19999,协议地址从0x0000开始。
- 输入寄存器(Input Register):只读的16位(字)数据,用于读取模拟量输入,如温度传感器、压力变送器的测量值。地址范围30001-39999,协议地址从0x0000开始。
- 保持寄存器(Holding Register):可读写的16位数据,用于读取或设置设备参数,如设定温度、PID参数、累计量等。地址范围40001-49999,协议地址从0x0000开始。
| 寄存器类型 | 数据宽度 | 读写属性 | PLC地址范围 | 协议地址偏移 | 典型功能码 | ThingsBoard用途 |
|---|---|---|---|---|---|---|
| 线圈(Coil) | 1 bit | 读/写 | 00001-09999 | 0x0000-0x270F | 01(读)、05(写单)、15(写多) | RPC控制(开关) |
| 离散输入(Discrete Input) | 1 bit | 只读 | 10001-19999 | 0x0000-0x270F | 02(读) | 遥信采集(状态) |
| 输入寄存器(Input Register) | 16 bit | 只读 | 30001-39999 | 0x0000-0x270F | 04(读) | 遥测采集(模拟量) |
| 保持寄存器(Holding Register) | 16 bit | 读/写 | 40001-49999 | 0x0000-0x270F | 03(读)、06(写单)、16(写多) | 遥测+遥控(参数) |
3.2.2 判断方法:地址映射与偏移计算
在ThingsBoard中配置Modbus设备时,需要将PLC地址转换为协议地址。转换规则为:协议地址 = PLC地址 - 地址偏移量。例如,PLC地址40001对应的协议地址为0x0000(40001-40001=0),PLC地址30001对应的协议地址也为0x0000(30001-30001=0)。但需要注意,不同厂商的设备可能采用不同的地址映射方式,有些设备手册直接给出协议地址(如0x0000),有些则给出PLC地址(如40001)。
- 查阅设备手册:找到寄存器映射表,确认每个参数对应的寄存器类型和地址。
- 确定地址格式:如果手册给出的是40001格式,则协议地址=40001-40001=0;如果给出的是0x0000格式,则直接使用。
- 在ThingsBoard中配置:在Modbus Connector的JSON配置中,
address字段填写协议地址(十六进制或十进制均可),registerType字段填写寄存器类型(COIL、DISCRETE、INPUT、HOLDING)。
3.2.3 案例拆解:多寄存器数据解析
假设一个流量计使用两个连续的保持寄存器存储累计流量(32位浮点数),地址为40003和40004。在ThingsBoard中需要配置为:
{
"deviceName": "FlowMeter",
"attributes": [
{"tag": "total_flow", "registerType": "HOLDING", "address": 2, "count": 2, "type": "FLOAT", "byteOrder": "BIG_BIG"}
]
}
这里address为2(对应PLC地址40003),count为2(读取两个寄存器),type为FLOAT(32位浮点数),byteOrder为BIG_BIG(大端字节序,即高字节在前)。如果设备采用小端字节序,则需配置为LITTLE_LITTLE。错误的字节序会导致解析出的数值完全错误,这是最常见的配置错误之一。
3.2.4 常见误区与总结
总结:四种寄存器类型构成了Modbus的数据模型基础。在ThingsBoard中,正确识别寄存器类型和地址偏移是数据采集的前提。建议在配置前先使用Modbus调试工具(如Modbus Poll)验证设备地址和数据类型,再迁移到ThingsBoard网关配置中。
3.3 功能码与数据模型映射
3.3.1 核心原理:功能码定义操作类型
功能码是Modbus报文中的第二个字节,用于指示主站希望从站执行的操作。常用的功能码包括:
- 01 (0x01):读线圈状态,返回线圈的当前值(0或1)。
- 02 (0x02):读离散输入状态。
- 03 (0x03):读保持寄存器,可一次读取多个寄存器。
- 04 (0x04):读输入寄存器。
- 05 (0x05):写单个线圈,强制线圈为ON或OFF。
- 06 (0x06):写单个保持寄存器。
- 15 (0x0F):写多个线圈。
- 16 (0x10):写多个保持寄存器。
在ThingsBoard的Modbus Connector中,功能码通常由registerType和操作类型(读/写)自动决定。例如,读取保持寄存器时自动使用功能码03,写入保持寄存器时使用功能码06或16。
3.3.2 判断方法:根据数据模型选择功能码
在配置ThingsBoard设备时,需要根据数据模型确定功能码:
- 读取位数据:如果是线圈,使用功能码01;如果是离散输入,使用功能码02。
- 读取字数据:如果是保持寄存器,使用功能码03;如果是输入寄存器,使用功能码04。
- 写入位数据:写单个线圈用05,写多个线圈用15。
- 写入字数据:写单个保持寄存器用06,写多个保持寄存器用16。
ThingsBoard的RPC功能允许通过服务器下发命令,例如控制继电器(写线圈)或修改设定值(写保持寄存器)。在配置RPC时,需要指定functionCode字段(如05或06),并确保设备支持相应的功能码。
3.3.3 案例拆解:ThingsBoard中配置读写操作
以下是一个完整的ThingsBoard Modbus Connector配置示例,包含读取和写入操作:
{
"server": {
"type": "tcp",
"host": "192.168.1.100",
"port": 502,
"timeout": 5000
},
"devices": [
{
"deviceName": "PLC_1",
"unitId": 1,
"attributes": [
{"tag": "temperature", "registerType": "HOLDING", "address": 0, "count": 1, "type": "INT16"},
{"tag": "humidity", "registerType": "HOLDING", "address": 1, "count": 1, "type": "UINT16"}
],
"timeseries": [
{"tag": "pressure", "registerType": "INPUT", "address": 0, "count": 1, "type": "INT16"}
],
"rpc": [
{"tag": "setRelay", "registerType": "COIL", "address": 0, "type": "BOOL", "functionCode": 5},
{"tag": "setSetpoint", "registerType": "HOLDING", "address": 2, "type": "INT16", "functionCode": 6}
]
}
]
}
在这个配置中:
attributes:读取保持寄存器0和1,分别映射为温度和湿度,作为设备属性。timeseries:读取输入寄存器0,映射为压力,作为遥测数据。rpc:定义了两个RPC命令,setRelay使用功能码05写线圈,setSetpoint使用功能码06写保持寄存器。
当ThingsBoard服务器下发RPC命令时,网关会构造相应的Modbus请求帧并发送给设备。例如,调用setRelay且参数为true时,网关发送:01 05 00 00 FF 00 8C 3A(RTU模式),其中FF00表示线圈ON。
3.3.4 常见误区与总结
unitId时务必使用实际从站地址。
总结:功能码是Modbus协议的操作指令,与寄存器类型紧密关联。在ThingsBoard中,通过registerType和functionCode的组合,可以灵活实现数据采集和控制下发。建议在配置前使用Modbus调试工具验证功能码的正确性,避免因功能码不匹配导致通信失败。
3.4 综合实战:温湿度传感器接入ThingsBoard
3.4.1 场景描述与目标
某工厂需要将一台RS-485接口的温湿度传感器接入ThingsBoard平台,实现实时监控和超限报警。传感器采用Modbus RTU协议,从站地址为1,波特率9600,8数据位,无校验,1停止位。传感器寄存器映射如下:
| 参数 | 寄存器类型 | PLC地址 | 协议地址 | 数据类型 | 精度 |
|---|---|---|---|---|---|
| 温度 | 保持寄存器 | 40001 | 0x0000 | INT16 | 0.1°C |
| 湿度 | 保持寄存器 | 40002 | 0x0001 | UINT16 | 0.1%RH |
| 设备状态 | 离散输入 | 10001 | 0x0000 | BOOL | - |
3.4.2 实施步骤
- 硬件连接:将传感器RS-485 A/B线连接到ThingsBoard网关的RS-485接口,确认网关串口设备路径(如/dev/ttyUSB0)。
- 配置Modbus Connector:在ThingsBoard IoT Gateway的配置文件中添加Modbus连接器,设置串口参数和轮询周期。
- 定义设备映射:按照上述寄存器映射表,在JSON配置中定义attributes(温度和湿度)和timeseries(设备状态)。
- 测试通信:使用Modbus Poll工具模拟读取,确认数据正确。
- 部署上线:重启网关,在ThingsBoard界面查看设备数据。
// Modbus Connector配置片段
{
"name": "modbus",
"type": "modbus",
"configuration": {
"server": {
"type": "rtu",
"port": "/dev/ttyUSB0",
"baudRate": 9600,
"dataBits": 8,
"stopBits": 1,
"parity": "NONE",
"timeout": 3000
},
"devices": [
{
"deviceName": "TempHumiditySensor",
"unitId": 1,
"pollPeriod": 5000,
"attributes": [
{"tag": "temperature", "registerType": "HOLDING", "address": 0, "count": 1, "type": "INT16", "multiplier": 0.1},
{"tag": "humidity", "registerType": "HOLDING", "address": 1, "count": 1, "type": "UINT16", "multiplier": 0.1}
],
"timeseries": [
{"tag": "device_status", "registerType": "DISCRETE", "address": 0, "count": 1, "type": "BOOL"}
]
}
]
}
}
3.4.3 结果与复盘
配置完成后,ThingsBoard每5秒轮询一次传感器数据。温度值通过multiplier字段自动乘以0.1,得到实际的摄氏温度。设备状态作为遥测数据上报,可用于监控传感器在线状态。在实施过程中,需要注意以下几点:
- 串口权限:确保网关用户对串口设备有读写权限(如将用户加入dialout组)。
- 轮询周期:过短的轮询周期会增加总线负载,建议根据设备响应时间调整(一般500ms-5s)。
- 数据精度:如果传感器返回的是无符号整数,使用UINT16类型;如果可能为负值(如温度),使用INT16类型。
multiplier字段可以用于数据缩放,但要注意数据类型转换的顺序。例如,INT16原始值乘以0.1后,结果可能为浮点数,ThingsBoard会自动处理。如果设备返回的是BCD编码,则需要使用type: "BCD"进行解码。
通过本章的学习,学员应能够区分Modbus RTU与TCP的适用场景,掌握四种寄存器类型的物理含义和地址映射方法,理解功能码与数据模型的关系,并能在ThingsBoard中完成Modbus设备的配置与数据采集。在实际项目中,建议先使用调试工具验证通信,再迁移到ThingsBoard网关,以降低排障成本。