4、Modbus协议基础:Modbus历史与版本、RTU与TCP区别、寄存器类型详解

好,咱们今天聊聊Modbus协议。说实话,在工业物联网这个圈子里混,Modbus是绕不开的坎儿。我做了十几年项目,从PLC到DCS,从串口到以太网,几乎每个项目都会跟它打交道。你想想看,一个1980年代诞生的协议,到现在还在大量使用,这本身就说明问题。

4.1 Modbus的历史与版本

Modbus是1979年由Modicon公司(就是现在的施耐德电气)发明的。当时是为了让PLC之间能互相通信。嗯,那个年代还没有TCP/IP,串口通信是主流。

我记得第一次接触Modbus是在一个水处理项目上。甲方要求用上位机监控几十个水泵和阀门。我那时候刚入行,看到一堆寄存器地址和功能码,头都大了。后来慢慢摸透了,才发现这东西其实很简洁。

Modbus主要经历了几个版本:

  • Modbus RTU:二进制传输,紧凑高效。串口通信的标配。
  • Modbus ASCII:用ASCII字符传输,可读性好但效率低。现在用得少了。
  • Modbus TCP:基于以太网,封装在TCP/IP协议栈里。是目前最流行的版本。
  • Modbus Plus:Modicon的私有高速网络,不开放。咱们做物联网的很少碰到。

我个人习惯,新项目首选Modbus TCP。为什么?布线方便,调试简单,而且现在工业以太网交换机便宜得很。但如果是老旧设备改造,或者现场环境恶劣(比如强电磁干扰),那Modbus RTU还是更靠谱。

核心要点:Modbus RTU和Modbus TCP是当前最主流的两个版本。RTU用于串口,TCP用于以太网。其他版本基本可以忽略。

4.2 RTU与TCP的区别

很多初学者搞不清RTU和TCP到底差在哪。我直接给你说透:

对比项 Modbus RTU Modbus TCP
物理层 RS-232 / RS-485 以太网(RJ45)
传输方式 二进制,字节流 TCP/IP数据包
地址 设备地址(1-247) IP地址 + 端口(默认502)
校验 CRC16校验 TCP协议自带校验
最大距离 约1200米(RS-485) 100米(网线),可扩展
通信方式 半双工,主从模式 全双工,客户端/服务器
典型场景 工厂车间、户外设备 控制室、数据中心

这里有个坑,我必须要提醒你。Modbus RTU的报文里没有报文长度字段,它是靠时间间隔来区分帧的。也就是说,如果两个字节之间的间隔超过3.5个字符时间,接收方就认为这是一帧的结束。我曾经在一个项目中,因为串口波特率设置不一致,导致设备一直收不到完整报文,排查了整整两天。后来用示波器一看,才发现是时序问题。

避坑指南:Modbus RTU的帧间隔时间非常关键。波特率越高,间隔时间越短。如果使用USB转串口工具,一定要确认驱动是否稳定。我曾经遇到过劣质USB转串口线,丢包率高达30%,换了工业级转换器才解决。

再说说Modbus TCP。它去掉了CRC校验和从站地址,因为TCP/IP协议栈已经处理了这些。取而代之的是MBAP报文头,包含了事务处理标识符、协议标识符、长度和单元标识符。说白了,就是把RTU的地址和校验功能交给了底层网络协议。

4.3 寄存器类型详解

Modbus协议定义了四种基本的数据对象,也就是寄存器类型。很多新手搞混,我当年也犯过这个错。咱们一个一个说清楚。

4.3.1 线圈(Coil)

线圈是Modbus里最基础的数据类型。它只有两个状态:ON(1)或OFF(0)。说白了就是开关量输出。比如控制一个继电器的通断、启动一个电机、点亮一个指示灯。

  • 功能码:01(读线圈)、05(写单个线圈)、15(写多个线圈)
  • 地址范围:00001 - 09999(PLC习惯的5位地址)
  • 特点:可读可写,用于输出控制

我在一个智能照明项目里,用Modbus RTU控制200多个线圈,每个线圈对应一个灯控继电器。调试的时候发现,批量写线圈(功能码15)比单个写(功能码05)效率高得多。你想想看,如果逐个写200个线圈,那得发200条报文。用批量写,一条报文就搞定了。

4.3.2 离散输入(Discrete Input)

离散输入和线圈很像,也是只有0和1。但区别在于:离散输入是只读的。它用来读取开关量输入信号,比如按钮状态、限位开关、光电传感器等。

  • 功能码:02(读离散输入)
  • 地址范围:10001 - 19999
  • 特点:只读,用于采集输入信号

嗯,这里要注意。离散输入和线圈虽然都是位数据,但用途完全不同。线圈是你去控制别人,离散输入是别人告诉你状态。千万别搞反了。

4.3.3 保持寄存器(Holding Register)

保持寄存器是Modbus里最常用的数据类型。它是16位的,可以存储数值。比如设置PID参数、设定温度值、读取累计流量等。可读可写。

  • 功能码:03(读保持寄存器)、06(写单个寄存器)、16(写多个寄存器)
  • 地址范围:40001 - 49999
  • 特点:可读可写,用于参数设置和数据存储

我个人习惯,凡是需要保存的参数,都用保持寄存器。比如变频器的频率设定、温控器的目标温度。因为保持寄存器断电后数据不丢失(取决于设备实现),而输入寄存器断电就丢了。

实战技巧:32位数据(比如浮点数)需要占用两个连续的保持寄存器。Modbus协议本身不规定字节序,所以不同厂家的设备可能有大端和小端的区别。我建议你在对接设备时,先读一个已知数值的寄存器,确认字节序后再开发。否则数据解析出来全是乱的。

4.3.4 输入寄存器(Input Register)

输入寄存器和保持寄存器一样,也是16位的。但它是只读的。用来读取模拟量输入,比如温度传感器、压力变送器、电流互感器的数值。

  • 功能码:04(读输入寄存器)
  • 地址范围:30001 - 39999
  • 特点:只读,用于采集模拟量数据

举个例子。一个4-20mA的压力变送器,输出信号经过模拟量输入模块转换成数字量,这个数字量就存储在输入寄存器里。你通过功能码04读取它,然后根据量程换算成实际压力值。

4.4 寄存器地址的坑

说到地址,我必须提一个容易踩的坑。Modbus协议里,寄存器地址是从0开始的。但PLC的地址习惯是从1开始的。比如保持寄存器40001,对应的协议地址是0x0000。40002对应0x0001,以此类推。

我在ThingsBoard里配置Modbus连接时,就遇到过这个问题。设备手册上写的是40001,但ThingsBoard的Modbus插件里,地址要填0。如果你填40001,那读到的数据就是错的。嗯,这个细节很多人不注意,调试半天找不到原因。

总结一下四种寄存器:

  • 线圈(0xxxx):可读可写,控制开关量输出
  • 离散输入(1xxxx):只读,采集开关量输入
  • 输入寄存器(3xxxx):只读,采集模拟量输入
  • 保持寄存器(4xxxx):可读可写,存储参数和数据

记住这个口诀:线圈控制、离散采集、输入只读、保持读写。

好了,Modbus协议的基础就讲到这里。下一节咱们聊聊如何在ThingsBoard里配置Modbus连接器,把真实设备的数据采集上来。到时候我会拿一个实际项目案例来演示,包括接线、配置、调试的全过程。你准备好了吗?