欢迎光临
我们一直在努力

Modbus 网络通信学习笔记

本文重点整理 Modbus TCP/IP、Modbus UDP/IP、Modbus RTU Over TCP/IP 与 Modbus RTU Over UDP/IP 的区别、帧结构、C# 常用操作和设备联调排查方法。内容基于当前 Modebus 文件夹中的学习材料、NModbus4 示例代码和设备联调记录整理,并补充实际项目中常见的判断方法。

一、核心概念

术语说明
Modbus 工业现场常用的主从式通信协议,常用于 PLC、传感器、仪表、网关与上位机之间的数据读写。
主站 / 客户端 主动发起请求的一方,例如上位机软件、采集程序、SCADA 系统。
从站 / 服务端 被动响应请求的一方,例如温湿度传感器、PLC、串口服务器、Modbus 模拟器。
从站地址 / Unit ID 用于区分设备或网关后的串口从站。Modbus RTU 中常见范围是 1-247。
功能码 表示本次操作类型,例如读保持寄存器 0x03、读输入寄存器 0x04、写单寄存器 0x06、写多个寄存器 0x10。
寄存器地址 设备内部数据地址。程序中常使用从 0 开始的协议地址,说明书中可能写成 40001、30001 等逻辑地址。
Modbus TCP/IP 原生以太网 Modbus 协议,使用 TCP 传输,通常使用 502 端口,报文中包含 MBAP 头,不使用 RTU CRC。
Modbus UDP/IP 部分设备或软件支持的 Modbus over UDP 实现,是否可用要以设备说明书或网关配置为准。
Modbus RTU 串口场景下常见的 Modbus 格式,常基于 RS-485,帧尾包含 CRC16 校验。
Modbus RTU Over TCP/IP 将完整 RTU 帧通过 TCP 传输,常见于串口服务器或透明传输网关。
Modbus RTU Over UDP/IP 将完整 RTU 帧通过 UDP 数据报传输,常见于部分串口服务器的 UDP 透明传输模式。
MBAP Header Modbus TCP/IP 报文头,包含事务标识、协议标识、长度、单元标识等字段。
CRC16 Modbus RTU 使用的循环冗余校验,低字节在前,高字节在后。

1. Modbus TCP/IP

Modbus TCP/IP 是把 Modbus 应用层数据封装到 TCP 中的通信方式。它是原生以太网 Modbus 协议,不是简单地把 RTU 串口帧放进 TCP。

项目说明
传输层 TCP,面向连接,可靠传输。
常用端口 通常是 502,但串口服务器、网关或模拟器可能配置为其他端口。
报文结构 MBAP Header + PDU。
是否包含 CRC 不包含 RTU CRC,TCP/IP 链路本身已有校验机制。
常见场景 上位机读取 PLC、网关、传感器、模拟器等网络设备。
角色关系 通常设备或模拟器作为服务端监听端口,上位机程序作为客户端主动连接。

通信过程可以理解为:

客户端软件 -> TCP 连接 -> 设备端口 -> 读取或写入寄存器 -> 返回响应

2. Modbus UDP/IP

Modbus UDP/IP 与 Modbus TCP/IP 类似,都是以太网场景下的 Modbus 通信方式,但底层使用 UDP。它不像 Modbus TCP/IP 那样常见,实际项目中必须先确认设备是否支持。

项目说明
传输层 UDP,无连接。
常用端口 可能使用 502,也可能由设备或网关自定义。
报文结构 通常按设备要求封装 Modbus IP 报文。
可靠性 UDP 不保证送达、不保证顺序、不自动重传。
常见场景 设备明确支持 UDP、局域网快速采集、厂家自定义广播或发现机制。

UDP 的优势是开销较小、延迟通常较低;缺点是可靠性需要应用层自行处理,例如超时、重试、丢包判断等。如果设备同时支持 TCP 和 UDP,生产环境通常优先选择 TCP。

3. Modbus RTU Over TCP/IP

Modbus RTU Over TCP/IP 不是 Modbus TCP/IP。它只是把完整的 RTU 帧通过 TCP 传输,帧中仍然包含从站地址和 CRC16。

项目说明
传输层 TCP。
报文结构 RTU 地址 + 功能码 + 数据 + CRC16。
是否包含 CRC 包含 RTU CRC16。
典型设备 RS-485 传感器 + 串口服务器。
常见场景 原有设备只支持 Modbus RTU,但现场希望通过网线或网络访问。

常见链路如下:

上位机程序 -> TCP -> 串口服务器 -> RS-485 -> Modbus RTU 设备

这种方式适合兼容已有串口 RTU 设备。在原生以太网设备之间通信时,一般优先选择 Modbus TCP/IP。

4. Modbus RTU Over UDP/IP

Modbus RTU Over UDP/IP 与 RTU Over TCP 类似,只是底层改为 UDP。它仍然保留 RTU 帧格式和 CRC16,可靠性取决于网络质量以及程序中的超时重试机制。

项目说明
传输层 UDP。
报文结构 RTU 地址 + 功能码 + 数据 + CRC16。
是否包含 CRC 包含 RTU CRC16。
可靠性 UDP 不保证送达,通常需要应用层重试。
常见场景 部分串口服务器的 UDP 透明传输模式。

5. 四种网络方式对比

协议底层传输帧格式是否需要连接是否包含 CRC可靠性常见用途
Modbus TCP/IP TCP MBAP + PDU 需要 原生网口 PLC、网关、模拟器通信。
Modbus UDP/IP UDP 取决于设备实现 不需要 通常否 中低 明确支持 UDP 的设备或厂家扩展场景。
RTU Over TCP/IP TCP RTU 完整帧 需要 较高 串口服务器连接 RS-485 设备。
RTU Over UDP/IP UDP RTU 完整帧 不需要 中低 UDP 透明传输串口服务器。

6. Modbus TCP 与 RTU Over TCP 的关键区别

对比点Modbus TCP/IPModbus RTU Over TCP/IP
本质 原生以太网 Modbus 协议。 串口 RTU 帧的网络透明传输。
报文头 有 MBAP Header。 无 MBAP Header。
CRC 不带 RTU CRC。 带 CRC16。
Unit ID 在 MBAP Header 中。 在 RTU 帧第 1 个字节。
设备类型 网络型 Modbus 设备。 串口设备加串口服务器或网关。
选型依据 设备说明书写 Modbus TCP。 设备说明书写 Modbus RTU,网关配置为 TCP/UDP 透明传输。

二、常用操作

1. 常用功能码

功能码名称常用方法说明
0x01 Read Coils ReadCoils 读取线圈,返回布尔值。
0x02 Read Discrete Inputs ReadInputs 读取离散输入,返回布尔值。
0x03 Read Holding Registers ReadHoldingRegisters 读取保持寄存器,常用于采集设备数据。
0x04 Read Input Registers ReadInputRegisters 读取输入寄存器,常用于只读测量值。
0x05 Write Single Coil WriteSingleCoil 写单个线圈。
0x06 Write Single Register WriteSingleRegister 写单个保持寄存器。
0x0F Write Multiple Coils WriteMultipleCoils 写多个线圈。
0x10 Write Multiple Registers WriteMultipleRegisters 写多个保持寄存器。

2. C# 使用 NModbus4 创建不同通信模式

通信模式关键对象创建方式说明
Modbus TCP/IP TcpClient ModbusIpMaster.CreateIp(tcpClient) 原生 Modbus TCP。
Modbus UDP/IP UdpClient ModbusIpMaster.CreateIp(udpClient) UDP 方式的 Modbus IP,需设备支持。
RTU Over TCP/IP TcpClient ModbusSerialMaster.CreateRtu(tcpClient) TCP 中传输 RTU 帧。
RTU Over UDP/IP UdpClient ModbusSerialMaster.CreateRtu(udpClient) UDP 中传输 RTU 帧。

using Modbus.Device;
using System;
using System.Net;
using System.Net.Sockets;

public enum ModbusNetworkMode
{
   TcpIp,
   UdpIp,
   RtuOverTcp,
   RtuOverUdp
}

public static class ModbusMasterFactory
{
   public static ModbusMaster Create(string ip, int port, ModbusNetworkMode mode)
  {
       IPAddress address = IPAddress.Parse(ip);

       switch (mode)
      {
           case ModbusNetworkMode.TcpIp:
               TcpClient tcpClient = new TcpClient();
               tcpClient.Connect(address, port);
               return ModbusIpMaster.CreateIp(tcpClient);

           case ModbusNetworkMode.UdpIp:
               UdpClient udpClient = new UdpClient();
               udpClient.Connect(address, port);
               return ModbusIpMaster.CreateIp(udpClient);

           case ModbusNetworkMode.RtuOverTcp:
               TcpClient rtuTcpClient = new TcpClient();
               rtuTcpClient.Connect(address, port);
               return ModbusSerialMaster.CreateRtu(rtuTcpClient);

           case ModbusNetworkMode.RtuOverUdp:
               UdpClient rtuUdpClient = new UdpClient();
               rtuUdpClient.Connect(address, port);
               return ModbusSerialMaster.CreateRtu(rtuUdpClient);

           default:
               throw new NotSupportedException("不支持的 Modbus 通信模式");
      }
  }
}

具体是否能正常通信,取决于设备或网关支持的协议模式,不能只看代码创建方式。

3. 读取保持寄存器

参数示例说明
slaveAddress 1 从站地址或 Unit ID。
startAddress 0 起始寄存器地址,通常从 0 开始。
numberOfPoints 4 读取寄存器数量。
返回值 ushort[] 每个寄存器是 16 位无符号整数。

using Modbus.Device;

public static ushort[] ReadFourHoldingRegisters(ModbusMaster master)
{
   byte slaveAddress = 1;
   ushort startAddress = 0;
   ushort registerCount = 4;

   return master.ReadHoldingRegisters(slaveAddress, startAddress, registerCount);
}

4. 定时读取设备数据

操作说明
建立连接 根据设备说明选择 TCP、UDP、RTU Over TCP 或 RTU Over UDP。
周期读取 使用定时器或后台任务定时读取寄存器。
数据校验 判断返回数量是否符合预期。
UI 更新 WinForms 中需要通过 Invoke 回到 UI 线程。
停止读取 使用 CancellationTokenSource 取消后台任务。

using Modbus.Device;
using System;
using System.Threading;
using System.Threading.Tasks;

public class ModbusPollingService
{
   private readonly ModbusMaster _master;
   private CancellationTokenSource _cts;

   public ModbusPollingService(ModbusMaster master)
  {
       _master = master;
  }

   public void Start(Action<ushort[]> onDataReceived)
  {
       _cts = new CancellationTokenSource();

       Task.Run(async () =>
      {
           while (!_cts.IsCancellationRequested)
          {
               ushort[] data = _master.ReadHoldingRegisters(1, 0, 4);

               if (data.Length == 4)
              {
                   onDataReceived(data);
              }

               await Task.Delay(1000, _cts.Token);
          }
      }, _cts.Token);
  }

   public void Stop()
  {
       _cts?.Cancel();
  }
}

5. 写单个保持寄存器

参数示例说明
从站地址 1 目标设备地址。
寄存器地址 0 要写入的寄存器地址。
写入值 12 写入的 16 位数值。

using Modbus.Device;

public static void WriteSingleRegister(ModbusMaster master)
{
   byte slaveAddress = 1;
   ushort registerAddress = 0;
   ushort value = 12;

   master.WriteSingleRegister(slaveAddress, registerAddress, value);
}

6. RTU 帧结构与 CRC16

字段长度说明
从站地址 1 字节 设备地址,常见范围 1-247。
功能码 1 字节 表示读写类型。
数据区 N 字节 起始地址、数量、写入值等。
CRC16 2 字节 低字节在前,高字节在后。

读取保持寄存器 01 03 00 00 00 0A 的 CRC 结果常见为 CD C5,完整 RTU 请求帧为:

01 03 00 00 00 0A CD C5
public static byte[] CalculateModbusCrc16(byte[] data)
{
   ushort crc = 0xFFFF;

   foreach (byte item in data)
  {
       crc ^= item;

       for (int i = 0; i < 8; i++)
      {
           bool lsb = (crc & 0x0001) != 0;
           crc >>= 1;

           if (lsb)
          {
               crc ^= 0xA001;
          }
      }
  }

   return new[] { (byte)(crc & 0xFF), (byte)(crc >> 8) };
}

7. 设备联调流程

步骤说明
确认设备型号 根据设备名称和型号找到官网资料。
下载说明书 重点查看通信协议、寄存器表、接线方式、默认地址、波特率、端口。
判断协议类型 区分 Modbus TCP、Modbus RTU、RTU Over TCP、RTU Over UDP。
准备工具 使用 Modbus Poll、Modbus Slave、网络调试助手、串口调试助手或厂家工具。
先工具验证 先用工具读通设备,再写代码。
编写程序 按说明书读取指定寄存器,并处理比例系数、单位和数据类型。
记录结果 记录 IP、端口、从站地址、寄存器地址、数据含义和异常现象。

三、问题排查

错误1:SocketException 或连接失败

  • 现象:TCP 连接失败,程序无法连接设备 IP 和端口。

  • 原因:IP 不通、端口错误、防火墙拦截、设备未开启 Modbus TCP 服务、串口服务器模式配置错误。

  • 解决:

    • 检查网络:确认电脑和设备在同一网段,先 ping 设备 IP。

    • 检查端口:Modbus TCP 通常使用 502,串口服务器可能使用自定义端口。

    • 检查模式:设备如果是 RTU 设备接串口服务器,应确认网关是 TCP Server、TCP Client 还是 UDP 模式。

    • 检查占用:同一设备可能只允许一个客户端连接,关闭其他调试工具后重试。

错误2:读取寄存器返回超时

  • 现象:连接成功,但 ReadHoldingRegisters 长时间无响应或抛出超时异常。

  • 原因:从站地址错误、寄存器地址错误、功能码不支持、设备没有接好、串口参数不一致。

  • 解决:

    • 确认从站地址:RTU 设备尤其要确认地址,常见默认值为 1。

    • 确认寄存器地址:说明书写 40001 时,程序里常用地址 0。

    • 确认功能码:保持寄存器用 0x03,输入寄存器用 0x04。

    • 确认串口参数:RTU 设备需要一致的波特率、数据位、校验位、停止位。

错误3:CRC 校验失败

  • 现象:RTU 或 RTU Over TCP/UDP 返回数据后 CRC 校验不通过。

  • 原因:通信噪声、帧截断、协议模式选错、把 Modbus TCP 当成 RTU Over TCP 使用。

  • 解决:

    • 区分协议:Modbus TCP 不带 RTU CRC;RTU Over TCP/UDP 必须带 CRC。

    • 检查网关模式:串口服务器需要配置为透明传输或对应的 Modbus 网关模式。

    • 检查接线:RS-485 的 A/B 线接反、屏蔽接地不良都可能导致数据异常。

    • 降低频率:轮询太快可能导致设备响应不完整,适当增加读取间隔。

错误4:数据能读到但数值不正确

  • 现象:寄存器有返回值,但温度、湿度、压力等数据明显不合理。

  • 原因:寄存器地址偏移、字节序错误、数据类型解析错误、未处理比例系数。

  • 解决:

    • 查看说明书:确认数据类型是 ushort、short、int32、float 还是 BCD。

    • 处理比例系数:很多传感器需要除以 10、100 或 1000。

    • 处理有符号数:温度等可能使用 short 表示负数。

    • 检查高低字顺序:32 位数据可能存在 AB CD、CD AB、BA DC、DC BA 等字节序差异。

错误5:UDP 模式偶发丢包

  • 现象:UDP 通信有时读取成功,有时无响应。

  • 原因:UDP 本身不保证可靠传输,网络拥塞或设备处理能力不足都会导致丢包。

  • 解决:

    • 增加重试:请求失败后重发 1 到 3 次。

    • 设置超时:避免程序一直等待。

    • 降低频率:读取周期不要过短。

    • 优先 TCP:如果设备支持 TCP,生产环境优先使用 Modbus TCP 或 RTU Over TCP。

错误6:WinForms 实时读取界面卡顿或跨线程异常

  • 现象:实时读取时界面卡死,或提示不能从非 UI 线程访问控件。

  • 原因:在 UI 线程执行耗时通信,或后台线程直接修改控件。

  • 解决:

    • 后台读取:把 Modbus 读写放到 Task.Run 或定时任务中。

    • UI 线程更新:WinForms 使用 Invoke 或 BeginInvoke 更新控件。

    • 支持取消:使用 CancellationTokenSource 停止实时读取。

四、相关资源

  • 官方文档:Modbus Organization

  • 协议规范:Modbus Application Protocol Specification

  • C# 库:NModbus4 NuGet

  • 常用工具:Modbus Poll

  • 常用工具:Modbus Slave

五、学习总结

  • 协议选择:设备说明书写 Modbus TCP 时,用原生 Modbus TCP/IP;设备是 RS-485 RTU 再通过串口服务器联网时,通常考虑 RTU Over TCP/IP 或 RTU Over UDP/IP。

  • 帧格式判断:带 MBAP、不带 CRC 的是 Modbus TCP/IP;带从站地址和 CRC 的是 Modbus RTU 或 RTU Over 网络。

  • UDP 使用原则:UDP 不是首选通用方案,只有设备明确支持或业务确实需要时才使用,并且要做好超时和重试。

  • 联调顺序:先读说明书,再用工具验证,最后写代码。不要一开始就直接写程序猜地址和功能码。

  • 常见坑点:端口不对、从站地址不对、寄存器偏移、功能码选错、比例系数没处理、TCP 与 RTU Over TCP 混淆。

赞(0)
未经允许不得转载:171主机测评 » Modbus 网络通信学习笔记
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址