ModBus通讯-封装
Modbus 协议分类与详解
Modbus 是工业自动化领域最常用的应用层通信协议,由施耐德电气于 1979 年推出,旨在实现控制器与现场设备(如传感器、执行器、PLC)之间的标准化数据交互。其核心优势是结构简单、开源免费、兼容性强,已成为工业通信的 “通用语言”。根据传输层介质和实现方式的不同,Modbus 主要分为以下几类,各类别在物理层、传输方式、应用场景上存在显著差异。
一、按传输介质与底层协议分类(核心分类)
这是 Modbus 最主流的分类方式,根据数据传输的 “物理载体” 和 “底层通信协议”,可分为 Modbus RTU、Modbus ASCII、Modbus TCP、Modbus UDP 四大类,其中前两者基于串行通信,后两者基于以太网。
1. Modbus RTU(Remote Terminal Unit)
核心特点
- 底层协议:基于串行通信(RS-232/RS-485/RS-422),是工业现场最常用的 Modbus 类型。
- 数据格式:采用二进制编码(16 进制),数据紧凑、传输效率高(无冗余字符)。
- 帧结构:每帧数据包含 “地址码→功能码→数据段→CRC 校验码”,帧长度不固定(取决于数据长度)。
plaintext
┌──────────┬──────────┬──────────┬──────────┐│ 地址码 │ 功能码 │ 数据段 │ CRC校验 ││ 1字节 │ 1字节 │ N字节 │ 2字节 │└──────────┴──────────┴──────────┴──────────┘
- 校验方式:CRC-16 循环冗余校验(强校验,适合工业复杂电磁环境)。
- 传输速率:波特率可配置(常见 9600/19200/38400/115200 bps),受串行通信限制,传输距离取决于物理层(RS-485 最大 1200 米)。
优势与局限
- ✅ 优势:传输效率高(二进制无冗余)、抗干扰能力强(CRC 校验)、硬件成本低(仅需 RS-485 芯片)。
- ❌ 局限:仅支持串行总线拓扑(1 对多,最多 32 个从站)、不支持跨网段通信、需手动配置串口参数(波特率、数据位等)。
典型应用
- 工业现场近距离设备通信(如 PLC 与传感器、变频器的连接);
- 嵌入式设备(如 STM32、Arduino)的低成本数据交互。
2. Modbus ASCII
核心特点
- 底层协议:同样基于串行通信(RS-232/RS-485),是 Modbus RTU 的 “人类可读版本”。
- 数据格式:采用ASCII 编码(文本字符),每个字节的二进制数据转换为 2 个 ASCII 字符(如二进制
0x12
转为\"12\"
)。 - 帧结构:每帧以起始符
:
(冒号,ASCII 0x3A)开头,以结束符\\r\\n
(回车 + 换行)结尾,包含 “地址码→功能码→数据段→LRC 校验码”。plaintext
┌──────┬──────────┬──────────┬──────────┬───────┐│ 起始符: │ 地址码 │ 功能码 │ 数据段 │ LRC校验 │ 结束符\\r\\n ││ 1字节 │ 2字符 │ 2字符 │ 2N字符 │ 2字符 │ 2字节 │└──────┴──────────┴──────────┴──────────┴───────┘
- 校验方式:LRC(纵向冗余校验),计算简单但校验强度弱于 CRC-16。
- 传输速率:因每个字节需 2 个 ASCII 字符,传输效率仅为 Modbus RTU 的 50%,波特率支持范围与 RTU 一致。
优势与局限
- ✅ 优势:数据可直接通过串口助手读取(如
\"12 03 00 01 00 02\"
),便于调试;校验计算简单(适合资源受限的低端设备)。 - ❌ 局限:传输效率低(冗余字符多)、抗干扰能力弱(LRC 校验)、仅适用于调试或低速场景。
典型应用
- 早期工业设备的调试与诊断(如手动发送 ASCII 指令测试设备响应);
- 对传输效率无要求、需人工干预的简单场景。
3. Modbus TCP(Transmission Control Protocol)
核心特点
- 底层协议:基于以太网(TCP/IP 协议栈),是 Modbus 协议在局域网 / 互联网的扩展,也是当前工业以太网的主流选择。
- 数据格式:保留 Modbus RTU 的二进制数据结构,但在传输层封装为 TCP 数据包,增加 “MBAP 报文头”(Modbus Application Protocol Header)。
- 帧结构:TCP 数据包 = MBAP 头(7 字节) + Modbus RTU 帧(地址码 + 功能码 + 数据 + CRC),其中 CRC 校验可省略(TCP 协议自带校验)。
plaintext
┌────────────────────────────────┬───────────────────────────────┐│ MBAP 报文头(7字节) │ Modbus RTU 帧(N字节) ││ 事务ID(2)+协议ID(2)+长度(2)+从站地址(1) │ 功能码(1)+数据段(N-2) │└────────────────────────────────┴───────────────────────────────┘
- 通信方式:采用 “客户端 - 服务器” 模式(主站 = 客户端,从站 = 服务器),主站通过 IP 地址 + 端口(默认 502)访问从站,支持跨网段、广域网通信。
- 传输速率:取决于以太网带宽(100Mbps/1Gbps),无距离限制(可通过路由器、交换机扩展)。
优势与局限
- ✅ 优势:传输速率高(以太网带宽)、支持跨网段 / 广域网、拓扑灵活(星型、树型)、可同时连接大量从站(理论无上限,取决于网络设备)。
- ❌ 局限:硬件成本高(需以太网芯片 / 模块)、依赖 TCP/IP 协议栈(嵌入式设备需额外适配)、在强电磁干扰环境下需做好网络防护。
典型应用
- 工业以太网控制系统(如车间级 PLC 与上位机的通信);
- 远程监控系统(如通过互联网访问异地设备数据);
- 智慧城市、智能家居中的设备联网(如智能电表、网关)。
4. Modbus UDP(User Datagram Protocol)
核心特点
- 底层协议:基于以太网(UDP/IP 协议栈),是 Modbus TCP 的 “无连接版本”。
- 数据格式:与 Modbus TCP 类似,同样包含 MBAP 报文头,但基于 UDP 数据包传输(无 TCP 的三次握手、重传机制)。
- 通信方式:无连接模式,主站发送请求后不等待确认,从站收到请求后直接回复;支持广播 / 组播(可同时向多个从站发送指令)。
- 传输速率:因无 TCP 的连接开销,理论传输效率高于 TCP,但可靠性依赖应用层处理(需手动实现重传、超时机制)。
优势与局限
- ✅ 优势:低延迟(无连接开销)、支持广播 / 组播(适合批量控制)、资源占用少(嵌入式设备适配更简单)。
- ❌ 局限:不可靠(UDP 数据包可能丢失、乱序)、需应用层实现可靠性机制(如超时重传)、适用场景有限。
典型应用
- 对实时性要求高、可接受少量数据丢失的场景(如实时监控、批量状态查询);
- 物联网(IoT)中的低功耗设备(如传感器周期性上报数据)。
二、按功能角色分类
根据设备在 Modbus 通信中的角色,可分为 Modbus 主站(Master) 和 Modbus 从站(Slave),二者职责明确,遵循 “主从问答” 机制。
1. Modbus 主站(Master)
- 核心职责:主动发起通信请求(如读取从站数据、向从站写入指令),控制通信时序;
- 特点:一个 Modbus 网络中通常只有 1 个主站(避免冲突),可同时与多个从站通信;
- 典型设备:上位机(如电脑、触摸屏)、PLC(作为主站控制其他从站设备)、工业网关。
2. Modbus 从站(Slave)
- 核心职责:被动响应主站请求(如返回数据、执行写入指令),不能主动发起通信;
- 特点:每个从站有唯一的 “从站地址”(1~247,0 为广播地址),主站通过地址区分不同从站;
- 典型设备:传感器、变频器、执行器、智能仪表(如流量计、温控器)。
三、按数据存储类型分类
Modbus 协议定义了不同类型的 “寄存器 / 线圈” 用于存储设备数据,主站通过 “功能码” 指定操作的数据类型,常见分类如下:
- 示例:主站发送功能码
0x03
,表示 “读取从站的保持寄存器数据”;发送功能码0x06
,表示 “向从站的单个保持寄存器写入数据”。
四、其他衍生分类
1. Modbus Plus(MB+)
- 施耐德推出的专有 Modbus 扩展,基于令牌环拓扑的串行总线,支持更高的传输速率(1Mbps)和更多从站(最多 64 个);
- 主要用于施耐德 PLC 之间的高速通信,兼容性差(仅支持施耐德设备),目前已逐步被 Modbus TCP 替代。
2. Modbus RTU Over TCP
- 将 Modbus RTU 帧直接封装在 TCP 数据包中(不添加 MBAP 头),本质是 “通过 TCP 传输 RTU 数据”;
- 适用于需兼容旧 RTU 设备,但需通过以太网扩展传输距离的场景,需主从站均支持该模式。
3. Modbus Security
- 针对 Modbus TCP 的安全扩展,添加 TLS/SSL 加密、身份认证机制,解决传统 Modbus 无安全防护的问题;
- 适用于工业互联网场景(如跨企业设备通信),防止数据被窃听或篡改。
五、各类 Modbus 协议对比总结
六、选择建议
- 现场近距离、低成本场景:优先选择 Modbus RTU(效率高、抗干扰强);
- 调试或人工干预场景:选择 Modbus ASCII(数据可读,便于排查问题);
- 跨网段、高速通信场景:选择 Modbus TCP(兼容性强、支持广域网);
- 实时性要求高、批量控制场景:选择 Modbus UDP(低延迟、支持广播);
- 安全敏感场景:选择 Modbus Security(加密传输、身份认证)。
通过理解 Modbus 协议的分类与差异,可根据实际项目的 “传输距离、速率、成本、拓扑” 需求,选择最适配的通信方案。
TouchSocket.3.0.25
public interface IModbusMaster{void OpenConnection();void CloseConnection(); /// /// 读取保持寄存器 /// /// 从设备地址 /// 起始地址 /// 读取数据 /// 读取数量 void ReadHoldingRegisters(int slaveAddress, int startAddress, short[] data, int count); /// /// 写入单个保持寄存器 /// /// 从设备地址 /// 寄存器地址 /// 寄存器值 void WriteSingleRegister(int slaveAddress, int registerAddress, short data); /// /// 写入多个保持寄存器 /// /// 从设备地址 /// 起始地址 /// 寄存器值数组 /// 写入数量 void WriteMultipleRegisters(int slaveAddress, int startAddress, short[] data, int count); #region 暂时不用的接口接口 void ReadDigitalInputs(int slaveAddress, int startAddress, bool[] data, int numofInputs); void ReadDigitalOutputs(int slaveAddress, int startAddress, bool[] data, int numofOutputs); void WriteDigitalOutput(int slaveAddress, int startAddress, bool data); void ReadFloatInputs(int slaveAddress, int startAddress, float[] data, int numofInputs);void ReadFloatOutputs(int slaveAddress, int startAddress, float[] data, int numofOutputs);void WriteFloatOutputs(int slaveAddress, int startAddress, float[] data, int numofInputs); #endregion}/// /// Modbus Tcp Master Communication/// public class ModbusTcpMasterComm : LifecycleBase, Skyverse.Cerasus.Kernel.Hardware.DeviceComm.Interface.IModbusMaster{ private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private string _ipAddress; private int _port; private Measure _timeout; private int retries = 3; private ModbusTcpMaster _modbusTcpMaster; /// /// Initializes a new instance of the class. /// /// The name of the component. /// The key of the component. public ModbusTcpMasterComm(string name, ComponentKey key) : base(name, key) { loggerName = $\"Hardware#{name}\"; } public void OpenConnection() { try { if (_modbusTcpMaster == null) { _modbusTcpMaster = new ModbusTcpMaster(); _modbusTcpMaster.Setup(new TouchSocketConfig() .SetRemoteIPHost(new IPHost($\"{_ipAddress}:{_port}\")) .ConfigureContainer(a => { a.AddEasyLogger(new Action(str => { LogTrace($\"Touchsocket info:{str}\"); })); })); } if (_modbusTcpMaster.Online) { LogInfo(\"Modbus Connection to controller is already Established\"); } else { Result result = _modbusTcpMaster.TryConnect(); if (result.IsSuccess) { LogInfo($\"Modbus Connection to controller Established Successfully.IpAddress:{_ipAddress},Timeout:{_timeout},Retries:{retries}\", InfoLevel.Important); } else { throw new Exception($\"Modbus Connection to controller failed:{result.Message}\"); } } } catch (Exception ex) { LogError(\"Attempt to establish Modbus Connection failed\", ex); throw; } } public void CloseConnection() { if (_modbusTcpMaster.Online) { _modbusTcpMaster.SafeClose(); LogInfo(\"ModbusTcpMaster closed\", InfoLevel.Important); } _modbusTcpMaster?.Dispose(); LogInfo(\"ModbusTcpMaster disposed\", InfoLevel.Important); } public void ReadHoldingRegisters(int slaveAddress, int startAddress, short[] data, int count) { ThrowIfNotInitialized(); if (data == null) { throw new ArgumentNullException(nameof(data), \"Data array cannot be null\"); } if (data.Length < count) { throw new ArgumentException(\"Data array length is insufficient for requested count\", nameof(data)); } _semaphore.Wait(); try { ReconnectIfOffline(); //var response = _modbusTcpMaster.ReadHoldingRegisters((ushort)startAddress, (ushort)count); ModbusRequest modbusRequest = new ModbusRequest(FunctionCode.ReadHoldingRegisters); modbusRequest.StartingAddress = (ushort)startAddress; modbusRequest.Quantity = (ushort)count; var response = _modbusTcpMaster.SendModbusRequest(modbusRequest, (int)_timeout.GetScalar(Unit.Millisecond), CancellationToken.None); if (response.ErrorCode == ModbusErrorCode.Success) { var reader = response.CreateReader(); var values = reader.ToInt16s(EndianType.Big); Array.Copy(values.ToArray(), data, count); } else { throw new Exception($\"ReadHoldingRegisters failed: {response.ErrorCode.GetDescription()}\"); } } catch (Exception exception) { LogError($\"Encountered error while reading holding registers from slave:{slaveAddress} at address:{startAddress}\", exception); throw; } finally { _semaphore.Release(); } } public void WriteSingleRegister(int slaveAddress, int registerAddress, short data) { ThrowIfNotInitialized(); _semaphore.Wait(); try { ReconnectIfOffline(); ModbusRequest modbusRequest = new ModbusRequest(FunctionCode.WriteSingleRegister); modbusRequest.StartingAddress = (ushort)registerAddress; modbusRequest.SetValue(TouchSocketBitConverter.BigEndian.GetBytes(data)); var response = _modbusTcpMaster.SendModbusRequest(modbusRequest, (int)_timeout.GetScalar(Unit.Millisecond), CancellationToken.None); if (response.ErrorCode != ModbusErrorCode.Success) { throw new Exception($\"WriteSingleRegister failed: {response.ErrorCode.GetDescription()}\"); }} catch (Exception exception) { LogError($\"Encountered error while writing sinagle register to slave:{slaveAddress} at address:{registerAddress}.{exception.Message}\"); throw; } finally { _semaphore.Release(); } } public void WriteMultipleRegisters(int slaveAddress, int startAddress, short[] data, int count) { ThrowIfNotInitialized(); if (data == null) { throw new ArgumentNullException(nameof(data), \"Data array cannot be null\"); } if (data.Length < count) { throw new ArgumentException(\"Data array length is insufficient for requested count\", nameof(data)); }_semaphore.Wait(); try { ReconnectIfOffline(); byte[] bytes = new byte[count * 2]; // 每个short占2字节 for (int i = 0; i < count; i++) { TouchSocketBitConverter.BigEndian.GetBytes(data[i]).CopyTo(bytes, i * 2); } ModbusRequest modbusRequest = new ModbusRequest(FunctionCode.WriteMultipleRegisters); modbusRequest.StartingAddress = (ushort)startAddress; modbusRequest.SetValue(bytes.ToArray()); var response = _modbusTcpMaster.SendModbusRequest(modbusRequest, (int)_timeout.GetScalar(Unit.Millisecond), CancellationToken.None); if (response.ErrorCode != ModbusErrorCode.Success) { throw new Exception($\"WriteMultipleRegisters failed: {response.ErrorCode.GetDescription()}\"); } } catch (Exception exception) { LogError($\"Encountered Error while writing sinagle register to slave:{slaveAddress} at address:{startAddress}.{exception.Message}\"); throw; } finally { _semaphore.Release(); } } #region NotImplemented public void ReadDigitalInputs(int slaveAddress, int startAddress, bool[] data, int numofInputs) { throw new NotImplementedException(); } public void ReadDigitalOutputs(int slaveAddress, int startAddress, bool[] data, int numofOutputs) { throw new NotImplementedException(); } public void WriteDigitalOutput(int slaveAddress, int startAddress, bool data) { throw new NotImplementedException(); } public void ReadFloatInputs(int slaveAddress, int startAddress, float[] data, int numofInputs) { throw new NotImplementedException(); } public void ReadFloatOutputs(int slaveAddress, int startAddress, float[] data, int numofOutputs) { throw new NotImplementedException(); } public void WriteFloatOutputs(int slaveAddress, int startAddress, float[] data, int numofInputs) { throw new NotImplementedException(); } #endregion /// /// Builds the subcomponents of the component. /// protected override void buildSubcomponents() { TraceEnter(); IComponentService instance = ComponentService.Instance; try { TCPClientConfig tcpConfig = instance.GetComponent(\"SystemConfigService\").Load(GetComponentName()); _ipAddress = tcpConfig.IPAddress; _timeout = Util.StringToTimeout(tcpConfig.Timeout, 5000, GetComponentName()).ConvertTo(Unit.Millisecond); _port = tcpConfig.Port; LogInfo($\"TCP Configuration loaded successfully,ipAddress={_ipAddress} port ={_port}\", InfoLevel.Important); } catch (Exception exception) { LogError(\"There was a problem loading the configuration for from the database. Database key = \" + GetComponentName(), exception); throw; } finally { TraceExit(); } } /// /// Initializes the component. /// protected override void initializeThis() { TraceEnter(); try { OpenConnection(); } catch (Exception ex) { LogError(\"Exception occured in initializeThis()\", ex); throw; } finally { TraceExit(); } } /// /// Uninitializes the component. /// protected override void uninitializeThis() { TraceEnter(); try { CloseConnection(); } catch (Exception ex) { LogError(\"Exception occured in uninitializeThis()\", ex); throw; } finally { TraceExit(); } } private void ReconnectIfOffline() { if (!_modbusTcpMaster.Online) { Result result = _modbusTcpMaster.TryConnect(); if (result.ResultCode != ResultCode.Success) { throw new Exception($\"Reconnect failed:{result.Message}\"); } else { LogInfo(\"Modbus Connection Reestablished Successfully\", InfoLevel.Important); } } }}[ConfigurationDescription(\"This is a Description for TCPClientConfig\")]public class TCPClientConfig{public string IPAddress { get; set; }public int Port { get; set; }public string Timeout { get; set; }public bool ReconnectOnFailures { get; set; }}
ModbusTcpMasterComm 类解析:Modbus TCP 主站通信组件
一、类的核心定位与功能
ModbusTcpMasterComm
是一个基于 Modbus TCP 协议的通信组件,实现了 IModbusMaster
接口,作为 Modbus 主站(Master)与从站(Slave)设备进行数据交互。该类继承自 LifecycleBase
,遵循组件化生命周期管理,主要用于工业自动化场景中与支持 Modbus TCP 协议的设备(如 PLC、传感器、执行器等)进行通信。
核心功能包括:
- 建立和关闭与 Modbus 从站的 TCP 连接
- 读取保持寄存器(Holding Registers)
- 写入单个寄存器和多个寄存器
- 连接状态监测与自动重连
- 线程安全的数据操作
二、技术依赖与关键库
该组件主要依赖以下技术和库:
- Modbus 协议:基于 Modbus TCP 规范,使用标准功能码进行数据交互
- TouchSocket:第三方网络通信库,提供 Modbus TCP 协议的实现(
ModbusTcpMaster
类) - Skyverse 框架:提供生命周期管理、配置服务、日志服务等基础能力
- System.Net.Sockets:基础网络通信支持
三、核心字段与属性解析
_semaphore
SemaphoreSlim
_ipAddress
string
_port
int
_timeout
Measure
retries
int
_modbusTcpMaster
ModbusTcpMaster
四、核心方法解析
1. 生命周期相关方法
(1)buildSubcomponents()
:构建子组件,加载配置
csharp
protected override void buildSubcomponents(){ // 从配置服务加载TCP客户端配置 TCPClientConfig tcpConfig = instance.GetComponent(\"SystemConfigService\") .Load(GetComponentName()); // 解析配置参数 _ipAddress = tcpConfig.IPAddress; _timeout = Util.StringToTimeout(tcpConfig.Timeout, 5000, GetComponentName()) .ConvertTo(Unit.Millisecond); _port = tcpConfig.Port;}
- 作用:从系统配置服务加载 Modbus 通信所需的配置参数(IP 地址、端口、超时时间等)
- 关键:使用框架的配置服务实现配置与代码解耦,便于后期维护和配置变更
(2)initializeThis()
:初始化组件
csharp
protected override void initializeThis(){ OpenConnection(); // 初始化时建立连接}
(3)uninitializeThis()
:销毁组件
csharp
protected override void uninitializeThis(){ CloseConnection(); // 销毁时关闭连接}
2. 连接管理方法
(1)OpenConnection()
:建立连接
csharp
public void OpenConnection(){ if (_modbusTcpMaster == null) { // 初始化Modbus TCP主站并配置连接参数 _modbusTcpMaster = new ModbusTcpMaster(); _modbusTcpMaster.Setup(new TouchSocketConfig() .SetRemoteIPHost(new IPHost($\"{_ipAddress}:{_port}\")) .ConfigureContainer(a => { // 配置日志输出 a.AddEasyLogger(new Action(str => { LogTrace($\"Touchsocket info:{str}\"); })); })); } if (!_modbusTcpMaster.Online) { // 尝试连接并处理结果 Result result = _modbusTcpMaster.TryConnect(); if (!result.IsSuccess) { throw new Exception($\"Modbus Connection failed:{result.Message}\"); } } }
- 作用:初始化
ModbusTcpMaster
实例并建立与从站的连接 - 关键:使用 TouchSocket 的配置模式进行灵活配置,包括远程主机地址和日志输出
(2)CloseConnection()
:关闭连接
csharp
public void CloseConnection(){ if (_modbusTcpMaster.Online) { _modbusTcpMaster.SafeClose(); // 安全关闭连接 _modbusTcpMaster.Dispose(); // 释放资源 }}
(3)ReconnectIfOffline()
:离线自动重连
csharp
private void ReconnectIfOffline(){ if (!_modbusTcpMaster.Online) { Result result = _modbusTcpMaster.TryConnect(); if (result.ResultCode != ResultCode.Success) { throw new Exception($\"Reconnect failed:{result.Message}\"); } } }
- 作用:在执行通信操作前检查连接状态,如已断开则自动重连
- 关键:提高通信可靠性,自动处理临时网络故障
3. Modbus 数据操作方法
(1)ReadHoldingRegisters()
:读取保持寄存器
csharp
public void ReadHoldingRegisters(int slaveAddress, int startAddress, short[] data, int count){ ThrowIfNotInitialized(); // 参数校验 if (data == null) throw new ArgumentNullException(nameof(data)); if (data.Length < count) throw new ArgumentException(\"Data array too small\"); _semaphore.Wait(); // 确保线程安全 try { ReconnectIfOffline(); // 检查并确保连接 // 构建读取保持寄存器的请求 ModbusRequest modbusRequest = new ModbusRequest(FunctionCode.ReadHoldingRegisters); modbusRequest.StartingAddress = (ushort)startAddress; modbusRequest.Quantity = (ushort)count; // 发送请求并获取响应 var response = _modbusTcpMaster.SendModbusRequest( modbusRequest, (int)_timeout.GetScalar(Unit.Millisecond), CancellationToken.None); if (response.ErrorCode == ModbusErrorCode.Success) { // 解析响应数据 var reader = response.CreateReader(); var values = reader.ToInt16s(EndianType.Big); Array.Copy(values.ToArray(), data, count); } else { throw new Exception($\"Read failed: {response.ErrorCode.GetDescription()}\"); } } finally { _semaphore.Release(); // 释放信号量 }}
- 作用:读取从站的保持寄存器数据(功能码 0x03)
- 关键:
- 使用信号量确保线程安全
- 发送前检查连接状态
- 处理响应并转换为合适的数据格式(大端模式)
(2)WriteSingleRegister()
:写入单个寄存器
csharp
public void WriteSingleRegister(int slaveAddress, int registerAddress, short data){ ThrowIfNotInitialized(); _semaphore.Wait(); try { ReconnectIfOffline(); // 构建写入单个寄存器的请求(功能码0x06) ModbusRequest modbusRequest = new ModbusRequest(FunctionCode.WriteSingleRegister); modbusRequest.StartingAddress = (ushort)registerAddress; modbusRequest.SetValue(TouchSocketBitConverter.BigEndian.GetBytes(data)); var response = _modbusTcpMaster.SendModbusRequest( modbusRequest, (int)_timeout.GetScalar(Unit.Millisecond), CancellationToken.None); if (response.ErrorCode != ModbusErrorCode.Success) { throw new Exception($\"Write failed: {response.ErrorCode.GetDescription()}\"); } } finally { _semaphore.Release(); }}
- 作用:向从站的指定寄存器写入一个 16 位值(功能码 0x06)
- 关键:使用大端模式(BigEndian)进行数据编码,符合 Modbus 标准
(3)WriteMultipleRegisters()
:写入多个寄存器
csharp
public void WriteMultipleRegisters(int slaveAddress, int startAddress, short[] data, int count){ // 参数校验... _semaphore.Wait(); try { ReconnectIfOffline(); // 将short数组转换为字节数组(大端模式) byte[] bytes = new byte[count * 2]; for (int i = 0; i < count; i++) { TouchSocketBitConverter.BigEndian.GetBytes(data[i]).CopyTo(bytes, i * 2); } // 构建写入多个寄存器的请求(功能码0x10) ModbusRequest modbusRequest = new ModbusRequest(FunctionCode.WriteMultipleRegisters); modbusRequest.StartingAddress = (ushort)startAddress; modbusRequest.SetValue(bytes.ToArray()); var response = _modbusTcpMaster.SendModbusRequest( modbusRequest, (int)_timeout.GetScalar(Unit.Millisecond), CancellationToken.None); if (response.ErrorCode != ModbusErrorCode.Success) { throw new Exception($\"Write failed: {response.ErrorCode.GetDescription()}\"); } } finally { _semaphore.Release(); }}
- 作用:向从站的连续多个寄存器写入数据(功能码 0x10)
- 关键:正确处理多字节数据的编码和长度计算
五、类的设计亮点
-
线程安全设计:使用
SemaphoreSlim
确保在多线程环境下,Modbus 操作的原子性和一致性,避免数据混乱。 -
连接可靠性保障:
- 提供自动重连机制(
ReconnectIfOffline
) - 操作前检查连接状态
- 完善的异常处理和日志记录
- 提供自动重连机制(
-
配置驱动设计:通过配置服务加载通信参数,实现了代码与配置的分离,便于不同环境下的部署和修改。
-
生命周期管理:继承
LifecycleBase
实现了标准化的初始化、销毁流程,符合框架规范,便于集成和管理。 -
异常处理:每个方法都有完善的异常捕获和日志记录,同时将异常向上抛出,由调用者决定具体处理策略。
六、未实现的方法
类中标记了 #region NotImplemented
的方法尚未实现,包括:
- 读取数字输入(
ReadDigitalInputs
) - 读取数字输出(
ReadDigitalOutputs
) - 写入数字输出(
WriteDigitalOutput
) - 读取和写入浮点型数据的相关方法
这些方法对应 Modbus 协议的其他功能码,如读取离散输入(0x02)、读取线圈状态(0x01)、写入单个线圈(0x05)、写入多个线圈(0x0F)等,可以根据实际需求逐步实现。
七、使用场景与注意事项
典型使用场景
- 工业自动化系统中与 PLC 通信
- 读取传感器数据(通过 Modbus TCP 接口)
- 控制执行器(如阀门、电机等)
- 工业监控系统的数据采集
注意事项
- 参数匹配:确保与从站设备的 IP 地址、端口号、超时时间等参数匹配
- 数据格式:Modbus 协议通常使用大端模式,需注意数据字节序转换
- 线程安全:该类已实现线程安全处理,可在多线程环境下直接使用
- 异常处理:调用者需处理可能的通信异常(如网络故障、超时等)
- 从站地址:当前实现中未实际使用 slaveAddress 参数,如需支持多从站需进一步完善
该组件为工业环境下的 Modbus TCP 通信提供了可靠的基础实现,可根据具体项目需求在此基础上扩展更多功能。