编辑
2025-11-29
C#
00

目录

什么是 Modbus?
安装 NModbus4.Core
创建 Modbus 客户端接口
连接管理
实现 ModbusTcpClient
字节顺序处理
异常处理
辅助类 ModbusDataHelper
使用案例
总结

我们将详细讨论如何使用 NModbus4.Core 库来实现 Modbus 通信。我们会从基本概念出发,解析如何使用该库构建 Modbus 客户端,处理字节顺序,管理异常,以及读取和写入设备寄存器和线圈。最终我们会完成一个Helper类,对于开发者,这是一个极其重要的工具,可用于工业自动化和设备间的通讯。

什么是 Modbus?

Modbus 是一种在工业电子设备中广泛使用的通信协议,允许设备之间进行信息交换。它的设计初衷是为了支持各类设备之间的互操作性。Modbus 协议主要有两个重载:Modbus RTU 和 Modbus TCP。本文将重点讨论 Modbus TCP 的实现。

安装 NModbus4.Core

首先,我们需要通过 NuGet 包管理器来安装 NModbus4.Core。可以使用以下命令:

Bash
Install-Package NModbus4

创建 Modbus 客户端接口

我们首先定义一个 IModbusClient 接口,作为通用的 Modbus 客户端接口来管理连接和数据操作。

C#
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Rick.Core.Services.Modbus { /// <summary> /// Modbus客户端接口,定义了Modbus通信的基本操作 /// </summary> public interface IModbusClient : IDisposable { /// <summary> /// 当前连接状态 /// </summary> bool IsConnected { get; } /// <summary> /// 连接到Modbus服务器 /// </summary> void Connect(); /// <summary> /// 异步连接到Modbus服务器 /// </summary> Task ConnectAsync(); /// <summary> /// 断开连接 /// </summary> void Disconnect(); /// <summary> /// 读取线圈状态 /// </summary> /// <param name="slaveAddress">从站地址</param> /// <param name="startAddress">起始地址</param> /// <param name="count">数量</param> /// <returns>线圈状态数组</returns> bool[] ReadCoils(byte slaveAddress, ushort startAddress, ushort count); /// <summary> /// 异步读取线圈状态 /// </summary> Task<bool[]> ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort count); /// <summary> /// 读取离散输入 /// </summary> bool[] ReadDiscreteInputs(byte slaveAddress, ushort startAddress, ushort count); /// <summary> /// 异步读取离散输入 /// </summary> Task<bool[]> ReadDiscreteInputsAsync(byte slaveAddress, ushort startAddress, ushort count); /// <summary> /// 读取保持寄存器 /// </summary> ushort[] ReadHoldingRegisters(byte slaveAddress, ushort startAddress, ushort count); /// <summary> /// 异步读取保持寄存器 /// </summary> Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort count); /// <summary> /// 读取输入寄存器 /// </summary> ushort[] ReadInputRegisters(byte slaveAddress, ushort startAddress, ushort count); /// <summary> /// 异步读取输入寄存器 /// </summary> Task<ushort[]> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort count); /// <summary> /// 写单个线圈 /// </summary> void WriteSingleCoil(byte slaveAddress, ushort coilAddress, bool value); /// <summary> /// 异步写单个线圈 /// </summary> Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value); /// <summary> /// 写单个寄存器 /// </summary> void WriteSingleRegister(byte slaveAddress, ushort registerAddress, ushort value); /// <summary> /// 异步写单个寄存器 /// </summary> Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value); /// <summary> /// 写多个线圈 /// </summary> void WriteMultipleCoils(byte slaveAddress, ushort startAddress, bool[] values); /// <summary> /// 异步写多个线圈 /// </summary> Task WriteMultipleCoilsAsync(byte slaveAddress, ushort startAddress, bool[] values); /// <summary> /// 写多个寄存器 /// </summary> void WriteMultipleRegisters(byte slaveAddress, ushort startAddress, ushort[] values); /// <summary> /// 异步写多个寄存器 /// </summary> Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values); } }

连接管理

Connect() 方法用于建立连接,Disconnect() 方法则用于断开连接,而 IsConnected 属性用于检查当前连接状态。

实现 ModbusTcpClient

我们接下来实现 ModbusTcpClient 类,该类继承 IModbusClient 接口,并使用 NModbus4 来处理 TCP 通信。

C#
using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using Modbus.Device; namespace Rick.Core.Services.Modbus { /// <summary> /// Modbus TCP客户端类,基于NModbus4实现 /// </summary> public class ModbusTcpClient : IModbusClient { private readonly string _ipAddress; private readonly int _port; private readonly int _connectionTimeout; private TcpClient _tcpClient; private ModbusIpMaster _master; /// <summary> /// 是否已连接 /// </summary> public bool IsConnected => _tcpClient?.Connected ?? false; /// <summary> /// 构造函数 /// </summary> /// <param name="ipAddress">IP地址</param> /// <param name="port">端口号,默认为502</param> /// <param name="connectionTimeout">连接超时时间(毫秒),默认为3000</param> public ModbusTcpClient(string ipAddress, int port = 502, int connectionTimeout = 3000) { _ipAddress = ipAddress ?? throw new ArgumentNullException(nameof(ipAddress)); _port = port; _connectionTimeout = connectionTimeout; } /// <summary> /// 连接到Modbus服务器 /// </summary> public void Connect() { if (IsConnected) return; try { _tcpClient = new TcpClient(); var result = _tcpClient.BeginConnect(_ipAddress, _port, null, null); var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(_connectionTimeout)); if (!success) { throw new ModbusException($"连接超时: {_ipAddress}:{_port}"); } _tcpClient.EndConnect(result); // 创建Modbus主站 _master = ModbusIpMaster.CreateIp(_tcpClient); } catch (Exception ex) { _tcpClient?.Dispose(); _tcpClient = null; _master = null; throw new ModbusException($"连接到 {_ipAddress}:{_port} 失败", ex); } } /// <summary> /// 异步连接到Modbus服务器 /// </summary> public async Task ConnectAsync() { if (IsConnected) return; try { _tcpClient = new TcpClient(); var connectTask = _tcpClient.ConnectAsync(_ipAddress, _port); // 添加超时 if (await Task.WhenAny(connectTask, Task.Delay(_connectionTimeout)) != connectTask) { throw new ModbusException($"连接超时: {_ipAddress}:{_port}"); } await connectTask; // 等待连接完成或抛出异常 // 创建Modbus主站 _master = ModbusIpMaster.CreateIp(_tcpClient); } catch (Exception ex) { _tcpClient?.Dispose(); _tcpClient = null; _master = null; throw new ModbusException($"连接到 {_ipAddress}:{_port} 失败", ex); } } /// <summary> /// 断开连接 /// </summary> public void Disconnect() { _master = null; if (_tcpClient != null) { if (_tcpClient.Connected) { _tcpClient.GetStream().Close(); } _tcpClient.Close(); _tcpClient.Dispose(); _tcpClient = null; } } /// <summary> /// 读取线圈状态 /// </summary> public bool[] ReadCoils(byte slaveAddress, ushort startAddress, ushort count) { EnsureConnected(); return _master.ReadCoils(slaveAddress, startAddress, count); } /// <summary> /// 异步读取线圈状态 /// </summary> public async Task<bool[]> ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort count) { EnsureConnected(); return await _master.ReadCoilsAsync(slaveAddress, startAddress, count); } /// <summary> /// 读取离散输入 /// </summary> public bool[] ReadDiscreteInputs(byte slaveAddress, ushort startAddress, ushort count) { EnsureConnected(); return _master.ReadInputs(slaveAddress, startAddress, count); } /// <summary> /// 异步读取离散输入 /// </summary> public async Task<bool[]> ReadDiscreteInputsAsync(byte slaveAddress, ushort startAddress, ushort count) { EnsureConnected(); return await _master.ReadInputsAsync(slaveAddress, startAddress, count); } /// <summary> /// 读取保持寄存器 /// </summary> public ushort[] ReadHoldingRegisters(byte slaveAddress, ushort startAddress, ushort count) { EnsureConnected(); return _master.ReadHoldingRegisters(slaveAddress, startAddress, count); } /// <summary> /// 异步读取保持寄存器 /// </summary> public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort count) { EnsureConnected(); return await _master.ReadHoldingRegistersAsync(slaveAddress, startAddress, count); } /// <summary> /// 读取输入寄存器 /// </summary> public ushort[] ReadInputRegisters(byte slaveAddress, ushort startAddress, ushort count) { EnsureConnected(); return _master.ReadInputRegisters(slaveAddress, startAddress, count); } /// <summary> /// 异步读取输入寄存器 /// </summary> public async Task<ushort[]> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort count) { EnsureConnected(); return await _master.ReadInputRegistersAsync(slaveAddress, startAddress, count); } /// <summary> /// 写单个线圈 /// </summary> public void WriteSingleCoil(byte slaveAddress, ushort coilAddress, bool value) { EnsureConnected(); _master.WriteSingleCoil(slaveAddress, coilAddress, value); } /// <summary> /// 异步写单个线圈 /// </summary> public async Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value) { EnsureConnected(); await _master.WriteSingleCoilAsync(slaveAddress, coilAddress, value); } /// <summary> /// 写单个寄存器 /// </summary> public void WriteSingleRegister(byte slaveAddress, ushort registerAddress, ushort value) { EnsureConnected(); _master.WriteSingleRegister(slaveAddress, registerAddress, value); } /// <summary> /// 异步写单个寄存器 /// </summary> public async Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value) { EnsureConnected(); await _master.WriteSingleRegisterAsync(slaveAddress, registerAddress, value); } /// <summary> /// 写多个线圈 /// </summary> public void WriteMultipleCoils(byte slaveAddress, ushort startAddress, bool[] values) { EnsureConnected(); _master.WriteMultipleCoils(slaveAddress, startAddress, values); } /// <summary> /// 异步写多个线圈 /// </summary> public async Task WriteMultipleCoilsAsync(byte slaveAddress, ushort startAddress, bool[] values) { EnsureConnected(); await _master.WriteMultipleCoilsAsync(slaveAddress, startAddress, values); } /// <summary> /// 写多个寄存器 /// </summary> public void WriteMultipleRegisters(byte slaveAddress, ushort startAddress, ushort[] values) { EnsureConnected(); _master.WriteMultipleRegisters(slaveAddress, startAddress, values); } /// <summary> /// 异步写多个寄存器 /// </summary> public async Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values) { EnsureConnected(); await _master.WriteMultipleRegistersAsync(slaveAddress, startAddress, values); } /// <summary> /// 确保已连接 /// </summary> private void EnsureConnected() { if (!IsConnected) { throw new ModbusException("客户端未连接,请先调用Connect方法"); } } /// <summary> /// 释放资源 /// </summary> public void Dispose() { Disconnect(); } } }

字节顺序处理

在使用 Modbus 协议时,处理数据的字节顺序非常重要。我们定义以下枚举来处理字节顺序:

C#
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Rick.Core.Services.Modbus { /// <summary> /// 字节顺序枚举,用于浮点数和双精度数等多字节数据的读写 /// </summary> public enum ByteOrder { /// <summary> /// 标准顺序 - 1234 /// </summary> ABCD, /// <summary> /// 完全倒序 - 4321 /// </summary> DCBA, /// <summary> /// 交换字顺序 - 2143 /// </summary> BADC, /// <summary> /// 交换字节顺序 - 3412 /// </summary> CDAB } }

异常处理

在 Modbus 通信中,可能会出现一些异常情况。我们可以定义一个 ModbusException 类来捕获和处理这些异常:

C#
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Rick.Core.Services.Modbus { /// <summary> /// Modbus通信异常类 /// </summary> public class ModbusException : Exception { public ModbusException() : base() { } public ModbusException(string message) : base(message) { } public ModbusException(string message, Exception innerException) : base(message, innerException) { } } }

辅助类 ModbusDataHelper

最后,我们可以创建一个辅助类 ModbusDataHelper,用于实现字节转换等实用方法:

C#
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Rick.Core.Services.Modbus { /// <summary> /// Modbus数据处理辅助类 /// 提供各种数据类型与Modbus寄存器之间的转换功能 /// </summary> public static class ModbusDataHelper { /// <summary> /// 将浮点数写入寄存器 /// </summary> /// <param name="client">Modbus客户端</param> /// <param name="slaveAddress">从站地址</param> /// <param name="startAddress">起始地址</param> /// <param name="value">浮点数值</param> /// <param name="byteOrder">字节顺序</param> public static void WriteFloat(IModbusClient client, byte slaveAddress, ushort startAddress, float value, ByteOrder byteOrder = ByteOrder.ABCD) { byte[] floatBytes = BitConverter.GetBytes(value); byte[] orderedBytes = OrderBytes(floatBytes, byteOrder); ushort[] registers = new ushort[2]; registers[0] = BitConverter.ToUInt16(orderedBytes, 0); registers[1] = BitConverter.ToUInt16(orderedBytes, 2); client.WriteMultipleRegisters(slaveAddress, startAddress, registers); } /// <summary> /// 异步将浮点数写入寄存器 /// </summary> public static async Task WriteFloatAsync(IModbusClient client, byte slaveAddress, ushort startAddress, float value, ByteOrder byteOrder = ByteOrder.ABCD) { byte[] floatBytes = BitConverter.GetBytes(value); byte[] orderedBytes = OrderBytes(floatBytes, byteOrder); ushort[] registers = new ushort[2]; registers[0] = BitConverter.ToUInt16(orderedBytes, 0); registers[1] = BitConverter.ToUInt16(orderedBytes, 2); await client.WriteMultipleRegistersAsync(slaveAddress, startAddress, registers); } /// <summary> /// 从寄存器读取浮点数 /// </summary> /// <param name="client">Modbus客户端</param> /// <param name="slaveAddress">从站地址</param> /// <param name="startAddress">起始地址</param> /// <param name="byteOrder">字节顺序</param> /// <returns>浮点数值</returns> public static float ReadFloat(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD) { ushort[] registers = client.ReadHoldingRegisters(slaveAddress, startAddress, 2); return ConvertRegistersToFloat(registers, byteOrder); } /// <summary> /// 异步从寄存器读取浮点数 /// </summary> public static async Task<float> ReadFloatAsync(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD) { ushort[] registers = await client.ReadHoldingRegistersAsync(slaveAddress, startAddress, 2); return ConvertRegistersToFloat(registers, byteOrder); } /// <summary> /// 将双精度浮点数写入寄存器 /// </summary> public static void WriteDouble(IModbusClient client, byte slaveAddress, ushort startAddress, double value, ByteOrder byteOrder = ByteOrder.ABCD) { byte[] doubleBytes = BitConverter.GetBytes(value); byte[] orderedBytes = OrderBytes(doubleBytes, byteOrder); ushort[] registers = new ushort[4]; registers[0] = BitConverter.ToUInt16(orderedBytes, 0); registers[1] = BitConverter.ToUInt16(orderedBytes, 2); registers[2] = BitConverter.ToUInt16(orderedBytes, 4); registers[3] = BitConverter.ToUInt16(orderedBytes, 6); client.WriteMultipleRegisters(slaveAddress, startAddress, registers); } /// <summary> /// 异步将双精度浮点数写入寄存器 /// </summary> public static async Task WriteDoubleAsync(IModbusClient client, byte slaveAddress, ushort startAddress, double value, ByteOrder byteOrder = ByteOrder.ABCD) { byte[] doubleBytes = BitConverter.GetBytes(value); byte[] orderedBytes = OrderBytes(doubleBytes, byteOrder); ushort[] registers = new ushort[4]; registers[0] = BitConverter.ToUInt16(orderedBytes, 0); registers[1] = BitConverter.ToUInt16(orderedBytes, 2); registers[2] = BitConverter.ToUInt16(orderedBytes, 4); registers[3] = BitConverter.ToUInt16(orderedBytes, 6); await client.WriteMultipleRegistersAsync(slaveAddress, startAddress, registers); } /// <summary> /// 从寄存器读取双精度浮点数 /// </summary> public static double ReadDouble(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD) { ushort[] registers = client.ReadHoldingRegisters(slaveAddress, startAddress, 4); return ConvertRegistersToDouble(registers, byteOrder); } /// <summary> /// 异步从寄存器读取双精度浮点数 /// </summary> public static async Task<double> ReadDoubleAsync(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD) { ushort[] registers = await client.ReadHoldingRegistersAsync(slaveAddress, startAddress, 4); return ConvertRegistersToDouble(registers, byteOrder); } /// <summary> /// 将Int32写入寄存器 /// </summary> public static void WriteInt32(IModbusClient client, byte slaveAddress, ushort startAddress, int value, ByteOrder byteOrder = ByteOrder.ABCD) { byte[] intBytes = BitConverter.GetBytes(value); byte[] orderedBytes = OrderBytes(intBytes, byteOrder); ushort[] registers = new ushort[2]; registers[0] = BitConverter.ToUInt16(orderedBytes, 0); registers[1] = BitConverter.ToUInt16(orderedBytes, 2); client.WriteMultipleRegisters(slaveAddress, startAddress, registers); } /// <summary> /// 异步将Int32写入寄存器 /// </summary> public static async Task WriteInt32Async(IModbusClient client, byte slaveAddress, ushort startAddress, int value, ByteOrder byteOrder = ByteOrder.ABCD) { byte[] intBytes = BitConverter.GetBytes(value); byte[] orderedBytes = OrderBytes(intBytes, byteOrder); ushort[] registers = new ushort[2]; registers[0] = BitConverter.ToUInt16(orderedBytes, 0); registers[1] = BitConverter.ToUInt16(orderedBytes, 2); await client.WriteMultipleRegistersAsync(slaveAddress, startAddress, registers); } /// <summary> /// 从寄存器读取Int32 /// </summary> public static int ReadInt32(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD) { ushort[] registers = client.ReadHoldingRegisters(slaveAddress, startAddress, 2); return ConvertRegistersToInt32(registers, byteOrder); } /// <summary> /// 异步从寄存器读取Int32 /// </summary> public static async Task<int> ReadInt32Async(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD) { ushort[] registers = await client.ReadHoldingRegistersAsync(slaveAddress, startAddress, 2); return ConvertRegistersToInt32(registers, byteOrder); } /// <summary> /// 将UInt32写入寄存器 /// </summary> public static void WriteUInt32(IModbusClient client, byte slaveAddress, ushort startAddress, uint value, ByteOrder byteOrder = ByteOrder.ABCD) { byte[] uintBytes = BitConverter.GetBytes(value); byte[] orderedBytes = OrderBytes(uintBytes, byteOrder); ushort[] registers = new ushort[2]; registers[0] = BitConverter.ToUInt16(orderedBytes, 0); registers[1] = BitConverter.ToUInt16(orderedBytes, 2); client.WriteMultipleRegisters(slaveAddress, startAddress, registers); } /// <summary> /// 异步将UInt32写入寄存器 /// </summary> public static async Task WriteUInt32Async(IModbusClient client, byte slaveAddress, ushort startAddress, uint value, ByteOrder byteOrder = ByteOrder.ABCD) { byte[] uintBytes = BitConverter.GetBytes(value); byte[] orderedBytes = OrderBytes(uintBytes, byteOrder); ushort[] registers = new ushort[2]; registers[0] = BitConverter.ToUInt16(orderedBytes, 0); registers[1] = BitConverter.ToUInt16(orderedBytes, 2); await client.WriteMultipleRegistersAsync(slaveAddress, startAddress, registers); } /// <summary> /// 从寄存器读取UInt32 /// </summary> public static uint ReadUInt32(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD) { ushort[] registers = client.ReadHoldingRegisters(slaveAddress, startAddress, 2); return ConvertRegistersToUInt32(registers, byteOrder); } /// <summary> /// 异步从寄存器读取UInt32 /// </summary> public static async Task<uint> ReadUInt32Async(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD) { ushort[] registers = await client.ReadHoldingRegistersAsync(slaveAddress, startAddress, 2); return ConvertRegistersToUInt32(registers, byteOrder); } /// <summary> /// 将Int64写入寄存器 /// </summary> public static void WriteInt64(IModbusClient client, byte slaveAddress, ushort startAddress, long value, ByteOrder byteOrder = ByteOrder.ABCD) { byte[] longBytes = BitConverter.GetBytes(value); byte[] orderedBytes = OrderBytes(longBytes, byteOrder); ushort[] registers = new ushort[4]; registers[0] = BitConverter.ToUInt16(orderedBytes, 0); registers[1] = BitConverter.ToUInt16(orderedBytes, 2); registers[2] = BitConverter.ToUInt16(orderedBytes, 4); registers[3] = BitConverter.ToUInt16(orderedBytes, 6); client.WriteMultipleRegisters(slaveAddress, startAddress, registers); } /// <summary> /// 异步将Int64写入寄存器 /// </summary> public static async Task WriteInt64Async(IModbusClient client, byte slaveAddress, ushort startAddress, long value, ByteOrder byteOrder = ByteOrder.ABCD) { byte[] longBytes = BitConverter.GetBytes(value); byte[] orderedBytes = OrderBytes(longBytes, byteOrder); ushort[] registers = new ushort[4]; registers[0] = BitConverter.ToUInt16(orderedBytes, 0); registers[1] = BitConverter.ToUInt16(orderedBytes, 2); registers[2] = BitConverter.ToUInt16(orderedBytes, 4); registers[3] = BitConverter.ToUInt16(orderedBytes, 6); await client.WriteMultipleRegistersAsync(slaveAddress, startAddress, registers); } /// <summary> /// 从寄存器读取Int64 /// </summary> public static long ReadInt64(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD) { ushort[] registers = client.ReadHoldingRegisters(slaveAddress, startAddress, 4); return ConvertRegistersToInt64(registers, byteOrder); } /// <summary> /// 异步从寄存器读取Int64 /// </summary> public static async Task<long> ReadInt64Async(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD) { ushort[] registers = await client.ReadHoldingRegistersAsync(slaveAddress, startAddress, 4); return ConvertRegistersToInt64(registers, byteOrder); } /// <summary> /// 将UInt64写入寄存器 /// </summary> public static void WriteUInt64(IModbusClient client, byte slaveAddress, ushort startAddress, ulong value, ByteOrder byteOrder = ByteOrder.ABCD) { byte[] ulongBytes = BitConverter.GetBytes(value); byte[] orderedBytes = OrderBytes(ulongBytes, byteOrder); ushort[] registers = new ushort[4]; registers[0] = BitConverter.ToUInt16(orderedBytes, 0); registers[1] = BitConverter.ToUInt16(orderedBytes, 2); registers[2] = BitConverter.ToUInt16(orderedBytes, 4); registers[3] = BitConverter.ToUInt16(orderedBytes, 6); client.WriteMultipleRegisters(slaveAddress, startAddress, registers); } /// <summary> /// 异步将UInt64写入寄存器 /// </summary> public static async Task WriteUInt64Async(IModbusClient client, byte slaveAddress, ushort startAddress, ulong value, ByteOrder byteOrder = ByteOrder.ABCD) { byte[] ulongBytes = BitConverter.GetBytes(value); byte[] orderedBytes = OrderBytes(ulongBytes, byteOrder); ushort[] registers = new ushort[4]; registers[0] = BitConverter.ToUInt16(orderedBytes, 0); registers[1] = BitConverter.ToUInt16(orderedBytes, 2); registers[2] = BitConverter.ToUInt16(orderedBytes, 4); registers[3] = BitConverter.ToUInt16(orderedBytes, 6); await client.WriteMultipleRegistersAsync(slaveAddress, startAddress, registers); } /// <summary> /// 从寄存器读取UInt64 /// </summary> public static ulong ReadUInt64(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD) { ushort[] registers = client.ReadHoldingRegisters(slaveAddress, startAddress, 4); return ConvertRegistersToUInt64(registers, byteOrder); } /// <summary> /// 异步从寄存器读取UInt64 /// </summary> public static async Task<ulong> ReadUInt64Async(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD) { ushort[] registers = await client.ReadHoldingRegistersAsync(slaveAddress, startAddress, 4); return ConvertRegistersToUInt64(registers, byteOrder); } /// <summary> /// 将字符串写入寄存器 /// </summary> public static void WriteString(IModbusClient client, byte slaveAddress, ushort startAddress, string value, int maxLength = 20) { if (string.IsNullOrEmpty(value)) { value = string.Empty; } // 将字符串转换为字节数组 byte[] stringBytes = System.Text.Encoding.ASCII.GetBytes(value); // 计算需要的寄存器数量(每个寄存器2字节) int registerCount = (int)Math.Ceiling((double)maxLength / 2); ushort[] registers = new ushort[registerCount]; // 填充寄存器 for (int i = 0; i < registerCount; i++) { int bytePos = i * 2; byte lowByte = bytePos < stringBytes.Length ? stringBytes[bytePos] : (byte)0; byte highByte = bytePos + 1 < stringBytes.Length ? stringBytes[bytePos + 1] : (byte)0; registers[i] = (ushort)((highByte << 8) | lowByte); } client.WriteMultipleRegisters(slaveAddress, startAddress, registers); } /// <summary> /// 异步将字符串写入寄存器 /// </summary> public static async Task WriteStringAsync(IModbusClient client, byte slaveAddress, ushort startAddress, string value, int maxLength = 20) { if (string.IsNullOrEmpty(value)) { value = string.Empty; } // 将字符串转换为字节数组 byte[] stringBytes = System.Text.Encoding.ASCII.GetBytes(value); // 计算需要的寄存器数量(每个寄存器2字节) int registerCount = (int)Math.Ceiling((double)maxLength / 2); ushort[] registers = new ushort[registerCount]; // 填充寄存器 for (int i = 0; i < registerCount; i++) { int bytePos = i * 2; byte lowByte = bytePos < stringBytes.Length ? stringBytes[bytePos] : (byte)0; byte highByte = bytePos + 1 < stringBytes.Length ? stringBytes[bytePos + 1] : (byte)0; registers[i] = (ushort)((highByte << 8) | lowByte); } await client.WriteMultipleRegistersAsync(slaveAddress, startAddress, registers); } /// <summary> /// 从寄存器读取字符串 /// </summary> public static string ReadString(IModbusClient client, byte slaveAddress, ushort startAddress, int length) { int registerCount = (int)Math.Ceiling((double)length / 2); ushort[] registers = client.ReadHoldingRegisters(slaveAddress, startAddress, (ushort)registerCount); return ConvertRegistersToString(registers, length); } /// <summary> /// 异步从寄存器读取字符串 /// </summary> public static async Task<string> ReadStringAsync(IModbusClient client, byte slaveAddress, ushort startAddress, int length) { int registerCount = (int)Math.Ceiling((double)length / 2); ushort[] registers = await client.ReadHoldingRegistersAsync(slaveAddress, startAddress, (ushort)registerCount); return ConvertRegistersToString(registers, length); } #region 内部辅助方法 /// <summary> /// 根据字节顺序调整字节数组 /// </summary> private static byte[] OrderBytes(byte[] bytes, ByteOrder order) { byte[] result = new byte[bytes.Length]; switch (order) { case ByteOrder.ABCD: // 1234 Array.Copy(bytes, result, bytes.Length); break; case ByteOrder.DCBA: // 4321 for (int i = 0; i < bytes.Length; i++) { result[i] = bytes[bytes.Length - 1 - i]; } break; case ByteOrder.BADC: // 2143 for (int i = 0; i < bytes.Length; i += 2) { if (i + 1 < bytes.Length) { result[i] = bytes[i + 1]; result[i + 1] = bytes[i]; } } break; case ByteOrder.CDAB: // 3412 int halfLength = bytes.Length / 2; for (int i = 0; i < halfLength; i++) { result[i] = bytes[i + halfLength]; result[i + halfLength] = bytes[i]; } break; } return result; } /// <summary> /// 将寄存器转换为浮点数 /// </summary> private static float ConvertRegistersToFloat(ushort[] registers, ByteOrder byteOrder) { if (registers.Length < 2) { throw new ArgumentException("需要至少2个寄存器来转换为浮点数"); } byte[] bytes = new byte[4]; bytes[0] = (byte)(registers[0] & 0xFF); bytes[1] = (byte)(registers[0] >> 8); bytes[2] = (byte)(registers[1] & 0xFF); bytes[3] = (byte)(registers[1] >> 8); byte[] orderedBytes = OrderBytes(bytes, byteOrder); return BitConverter.ToSingle(orderedBytes, 0); } /// <summary> /// 将寄存器转换为双精度浮点数 /// </summary> private static double ConvertRegistersToDouble(ushort[] registers, ByteOrder byteOrder) { if (registers.Length < 4) { throw new ArgumentException("需要至少4个寄存器来转换为双精度浮点数"); } byte[] bytes = new byte[8]; bytes[0] = (byte)(registers[0] & 0xFF); bytes[1] = (byte)(registers[0] >> 8); bytes[2] = (byte)(registers[1] & 0xFF); bytes[3] = (byte)(registers[1] >> 8); bytes[4] = (byte)(registers[2] & 0xFF); bytes[5] = (byte)(registers[2] >> 8); bytes[6] = (byte)(registers[3] & 0xFF); bytes[7] = (byte)(registers[3] >> 8); byte[] orderedBytes = OrderBytes(bytes, byteOrder); return BitConverter.ToDouble(orderedBytes, 0); } /// <summary> /// 将寄存器转换为32位整数 /// </summary> private static int ConvertRegistersToInt32(ushort[] registers, ByteOrder byteOrder) { if (registers.Length < 2) { throw new ArgumentException("需要至少2个寄存器来转换为32位整数"); } byte[] bytes = new byte[4]; bytes[0] = (byte)(registers[0] & 0xFF); bytes[1] = (byte)(registers[0] >> 8); bytes[2] = (byte)(registers[1] & 0xFF); bytes[3] = (byte)(registers[1] >> 8); byte[] orderedBytes = OrderBytes(bytes, byteOrder); return BitConverter.ToInt32(orderedBytes, 0); } /// <summary> /// 将寄存器转换为32位无符号整数 /// </summary> private static uint ConvertRegistersToUInt32(ushort[] registers, ByteOrder byteOrder) { if (registers.Length < 2) { throw new ArgumentException("需要至少2个寄存器来转换为32位无符号整数"); } byte[] bytes = new byte[4]; bytes[0] = (byte)(registers[0] & 0xFF); bytes[1] = (byte)(registers[0] >> 8); bytes[2] = (byte)(registers[1] & 0xFF); bytes[3] = (byte)(registers[1] >> 8); byte[] orderedBytes = OrderBytes(bytes, byteOrder); return BitConverter.ToUInt32(orderedBytes, 0); } /// <summary> /// 将寄存器转换为64位整数 /// </summary> private static long ConvertRegistersToInt64(ushort[] registers, ByteOrder byteOrder) { if (registers.Length < 4) { throw new ArgumentException("需要至少4个寄存器来转换为64位整数"); } byte[] bytes = new byte[8]; bytes[0] = (byte)(registers[0] & 0xFF); bytes[1] = (byte)(registers[0] >> 8); bytes[2] = (byte)(registers[1] & 0xFF); bytes[3] = (byte)(registers[1] >> 8); bytes[4] = (byte)(registers[2] & 0xFF); bytes[5] = (byte)(registers[2] >> 8); bytes[6] = (byte)(registers[3] & 0xFF); bytes[7] = (byte)(registers[3] >> 8); byte[] orderedBytes = OrderBytes(bytes, byteOrder); return BitConverter.ToInt64(orderedBytes, 0); } /// <summary> /// 将寄存器转换为64位无符号整数 /// </summary> private static ulong ConvertRegistersToUInt64(ushort[] registers, ByteOrder byteOrder) { if (registers.Length < 4) { throw new ArgumentException("需要至少4个寄存器来转换为64位无符号整数"); } byte[] bytes = new byte[8]; bytes[0] = (byte)(registers[0] & 0xFF); bytes[1] = (byte)(registers[0] >> 8); bytes[2] = (byte)(registers[1] & 0xFF); bytes[3] = (byte)(registers[1] >> 8); bytes[4] = (byte)(registers[2] & 0xFF); bytes[5] = (byte)(registers[2] >> 8); bytes[6] = (byte)(registers[3] & 0xFF); bytes[7] = (byte)(registers[3] >> 8); byte[] orderedBytes = OrderBytes(bytes, byteOrder); return BitConverter.ToUInt64(orderedBytes, 0); } /// <summary> /// 将寄存器转换为字符串 /// </summary> private static string ConvertRegistersToString(ushort[] registers, int maxLength) { // 计算字节数组长度(每个寄存器2字节) byte[] bytes = new byte[registers.Length * 2]; // 从寄存器提取字节 for (int i = 0; i < registers.Length; i++) { bytes[i * 2] = (byte)(registers[i] & 0xFF); bytes[i * 2 + 1] = (byte)(registers[i] >> 8); } // 截断到指定长度 int length = Math.Min(maxLength, bytes.Length); // 查找第一个0字节(字符串结束符) int zeroIndex = -1; for (int i = 0; i < length; i++) { if (bytes[i] == 0) { zeroIndex = i; break; } } // 如果找到了0字节,则截断到该位置 if (zeroIndex >= 0) { length = zeroIndex; } // 转换为字符串并返回 return System.Text.Encoding.ASCII.GetString(bytes, 0, length); } #endregion } }

使用案例

假设我们有一个控制设备的IP地址为 "192.168.1.10",端口为 502,我们可以使用如下代码实现 Modbus 读写操作:

C#
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Rick.Core.Services.Modbus; namespace Rick.Test { public static class ModbusTest { private static string ServerIp = "127.0.0.1"; // 服务器IP地址 private static int ServerPort = 502; // 服务器端口 private static byte SlaveId = 1; // 从站ID public static async Task Call() { Console.WriteLine("ModbusTcpClient 测试程序"); Console.WriteLine("=====================\n"); // 显示连接配置 Console.WriteLine($"服务器IP: {ServerIp}"); Console.WriteLine($"服务器端口: {ServerPort}"); Console.WriteLine($"从站ID: {SlaveId}"); // 允许用户修改连接配置 AdjustConnectionSettings(); // 声明客户端变量 ModbusTcpClient client = null; try { // 1. 创建Modbus客户端 Console.WriteLine("\n创建Modbus TCP客户端..."); client = new ModbusTcpClient(ServerIp, ServerPort); // 2. 连接到Modbus服务器 Console.WriteLine($"正在连接到服务器 {ServerIp}:{ServerPort}..."); client.Connect(); if (!client.IsConnected) { throw new Exception("无法连接到Modbus服务器,请确认服务器是否已启动"); } Console.WriteLine("✓ 成功连接到服务器"); // 3. 执行测试菜单 bool exitRequested = false; while (!exitRequested && client.IsConnected) { exitRequested = await ShowMenuAndRunTest(client); } } catch (Exception ex) { Console.WriteLine($"\n❌ 错误: {ex.Message}"); Console.WriteLine($"异常类型: {ex.GetType().Name}"); Console.WriteLine($"堆栈跟踪:\n{ex.StackTrace}"); } finally { // 4. 断开连接并释放资源 if (client != null) { if (client.IsConnected) { Console.WriteLine("\n断开连接..."); client.Disconnect(); } client.Dispose(); Console.WriteLine("客户端资源已释放"); } Console.WriteLine("\n按任意键退出..."); Console.ReadKey(); } } // 允许用户调整连接设置 private static void AdjustConnectionSettings() { Console.WriteLine("\n是否需要修改连接设置? (Y/N)"); var key = Console.ReadKey(true); if (key.Key == ConsoleKey.Y) { Console.Write($"\n服务器IP ({ServerIp}): "); string input = Console.ReadLine(); if (!string.IsNullOrWhiteSpace(input)) { ServerIp = input; } Console.Write($"服务器端口 ({ServerPort}): "); input = Console.ReadLine(); if (!string.IsNullOrWhiteSpace(input) && int.TryParse(input, out int port)) { ServerPort = port; } Console.Write($"从站ID ({SlaveId}): "); input = Console.ReadLine(); if (!string.IsNullOrWhiteSpace(input) && byte.TryParse(input, out byte id)) { SlaveId = id; } Console.WriteLine($"\n已更新连接设置: {ServerIp}:{ServerPort}, 从站ID={SlaveId}"); } } // 显示测试菜单并执行选择的测试 private static async Task<bool> ShowMenuAndRunTest(ModbusTcpClient client) { Console.WriteLine("\n== 测试菜单 =="); Console.WriteLine("1. 读写线圈测试"); Console.WriteLine("2. 读取离散输入测试"); Console.WriteLine("3. 读写保持寄存器测试"); Console.WriteLine("4. 读取输入寄存器测试"); Console.WriteLine("5. 浮点数读写测试"); Console.WriteLine("6. 双精度浮点数读写测试"); Console.WriteLine("7. 32位整数读写测试"); Console.WriteLine("8. 字符串读写测试"); Console.WriteLine("9. 批量操作测试"); Console.WriteLine("10. 字节顺序测试"); Console.WriteLine("0. 退出"); Console.Write("\n请选择测试 (0-10): "); var key = Console.ReadKey(true); Console.WriteLine(key.KeyChar); switch (key.KeyChar) { case '1': await TestCoils(client); break; case '2': await TestDiscreteInputs(client); break; case '3': await TestHoldingRegisters(client); break; case '4': await TestInputRegisters(client); break; case '5': await TestFloats(client); break; case '6': await TestDoubles(client); break; case '7': await TestInt32(client); break; case '8': await TestStrings(client); break; case '9': await TestBatchOperations(client); break; case '0': return true; // 退出 default: Console.WriteLine("无效选择,请重试"); break; } return false; // 继续显示菜单 } // 1. 测试线圈读写 private static async Task TestCoils(ModbusTcpClient client) { Console.WriteLine("\n== 线圈读写测试 =="); try { // 询问用户输入线圈地址 Console.Write("请输入线圈起始地址 (0-65535): "); if (!ushort.TryParse(Console.ReadLine(), out ushort coilAddress)) { Console.WriteLine("无效地址,使用默认地址 100"); coilAddress = 100; } // 1.1 读取当前线圈状态 Console.WriteLine($"\n读取线圈 {coilAddress} 的当前状态..."); bool[] initialState = await client.ReadCoilsAsync(SlaveId, coilAddress, 1); Console.WriteLine($"当前状态: {initialState[0]}"); // 1.2 写入新状态(取反) bool newState = !initialState[0]; Console.WriteLine($"\n将线圈 {coilAddress} 写入新状态: {newState}"); await client.WriteSingleCoilAsync(SlaveId, coilAddress, newState); // 1.3 再次读取验证 bool[] updatedState = await client.ReadCoilsAsync(SlaveId, coilAddress, 1); Console.WriteLine($"更新后状态: {updatedState[0]}"); Console.WriteLine($"验证结果: {(updatedState[0] == newState ? "✓ 成功" : "❌ 失败")}"); // 1.4 测试批量线圈 Console.WriteLine("\n测试批量线圈操作:"); ushort batchStartAddress = (ushort)(coilAddress + 10); int batchSize = 5; // 创建测试数据 bool[] coilValues = new bool[batchSize]; for (int i = 0; i < batchSize; i++) { coilValues[i] = i % 2 == 0; // 交替设置true/false } // 写入批量线圈 Console.WriteLine($"写入 {batchSize} 个线圈,起始地址: {batchStartAddress}"); await client.WriteMultipleCoilsAsync(SlaveId, batchStartAddress, coilValues); // 读取验证 bool[] readCoilValues = await client.ReadCoilsAsync(SlaveId, batchStartAddress, (ushort)batchSize); // 验证并显示结果 bool batchSuccess = true; Console.WriteLine("\n读取结果:"); for (int i = 0; i < batchSize; i++) { Console.WriteLine($" 线圈[{batchStartAddress + i}] = {readCoilValues[i]} (期望值: {coilValues[i]})"); if (readCoilValues[i] != coilValues[i]) { batchSuccess = false; } } Console.WriteLine($"\n批量验证结果: {(batchSuccess ? "✓ 成功" : "❌ 失败")}"); } catch (Exception ex) { Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}"); } Console.WriteLine("\n按任意键继续..."); Console.ReadKey(true); } // 2. 测试离散输入读取 private static async Task TestDiscreteInputs(ModbusTcpClient client) { Console.WriteLine("\n== 离散输入读取测试 =="); try { // 询问用户输入离散输入地址 Console.Write("请输入离散输入起始地址 (0-65535): "); if (!ushort.TryParse(Console.ReadLine(), out ushort inputAddress)) { Console.WriteLine("无效地址,使用默认地址 100"); inputAddress = 100; } // 询问用户输入读取数量 Console.Write("请输入要读取的离散输入数量 (1-100): "); ushort count; if (!ushort.TryParse(Console.ReadLine(), out count) || count < 1 || count > 100) { Console.WriteLine("无效数量,使用默认值 10"); count = 10; } // 读取离散输入 Console.WriteLine($"\n读取 {count} 个离散输入,起始地址: {inputAddress}"); bool[] inputs = await client.ReadDiscreteInputsAsync(SlaveId, inputAddress, count); // 显示结果 Console.WriteLine("\n读取结果:"); for (int i = 0; i < inputs.Length; i++) { Console.WriteLine($" 离散输入[{inputAddress + i}] = {inputs[i]}"); } Console.WriteLine($"\n成功读取 {inputs.Length} 个离散输入"); } catch (Exception ex) { Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}"); } Console.WriteLine("\n按任意键继续..."); Console.ReadKey(true); } // 3. 测试保持寄存器读写 private static async Task TestHoldingRegisters(ModbusTcpClient client) { Console.WriteLine("\n== 保持寄存器读写测试 =="); try { // 询问用户输入寄存器地址 Console.Write("请输入保持寄存器起始地址 (0-65535): "); if (!ushort.TryParse(Console.ReadLine(), out ushort registerAddress)) { Console.WriteLine("无效地址,使用默认地址 100"); registerAddress = 100; } // 3.1 读取当前寄存器值 Console.WriteLine($"\n读取寄存器 {registerAddress} 的当前值..."); ushort[] initialValue = await client.ReadHoldingRegistersAsync(SlaveId, registerAddress, 1); Console.WriteLine($"当前值: {initialValue[0]}"); // 3.2 写入新值 ushort newValue = (ushort)(initialValue[0] < 32768 ? initialValue[0] + 1000 : initialValue[0] - 1000); Console.WriteLine($"\n将寄存器 {registerAddress} 写入新值: {newValue}"); await client.WriteSingleRegisterAsync(SlaveId, registerAddress, newValue); // 3.3 再次读取验证 ushort[] updatedValue = await client.ReadHoldingRegistersAsync(SlaveId, registerAddress, 1); Console.WriteLine($"更新后值: {updatedValue[0]}"); Console.WriteLine($"验证结果: {(updatedValue[0] == newValue ? "✓ 成功" : "❌ 失败")}"); // 3.4 测试批量寄存器 Console.WriteLine("\n测试批量寄存器操作:"); ushort batchStartAddress = (ushort)(registerAddress + 10); int batchSize = 5; // 创建测试数据 ushort[] registerValues = new ushort[batchSize]; for (int i = 0; i < batchSize; i++) { registerValues[i] = (ushort)(1000 + i * 100); } // 写入批量寄存器 Console.WriteLine($"写入 {batchSize} 个寄存器,起始地址: {batchStartAddress}"); await client.WriteMultipleRegistersAsync(SlaveId, batchStartAddress, registerValues); // 读取验证 ushort[] readRegisterValues = await client.ReadHoldingRegistersAsync(SlaveId, batchStartAddress, (ushort)batchSize); // 验证并显示结果 bool batchSuccess = true; Console.WriteLine("\n读取结果:"); for (int i = 0; i < batchSize; i++) { Console.WriteLine($" 寄存器[{batchStartAddress + i}] = {readRegisterValues[i]} (期望值: {registerValues[i]})"); if (readRegisterValues[i] != registerValues[i]) { batchSuccess = false; } } Console.WriteLine($"\n批量验证结果: {(batchSuccess ? "✓ 成功" : "❌ 失败")}"); } catch (Exception ex) { Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}"); } Console.WriteLine("\n按任意键继续..."); Console.ReadKey(true); } // 4. 测试输入寄存器读取 private static async Task TestInputRegisters(ModbusTcpClient client) { Console.WriteLine("\n== 输入寄存器读取测试 =="); try { // 询问用户输入寄存器地址 Console.Write("请输入输入寄存器起始地址 (0-65535): "); if (!ushort.TryParse(Console.ReadLine(), out ushort inputRegisterAddress)) { Console.WriteLine("无效地址,使用默认地址 100"); inputRegisterAddress = 100; } // 询问用户输入读取数量 Console.Write("请输入要读取的输入寄存器数量 (1-100): "); ushort count; if (!ushort.TryParse(Console.ReadLine(), out count) || count < 1 || count > 100) { Console.WriteLine("无效数量,使用默认值 10"); count = 10; } // 读取输入寄存器 Console.WriteLine($"\n读取 {count} 个输入寄存器,起始地址: {inputRegisterAddress}"); ushort[] inputRegisters = await client.ReadInputRegistersAsync(SlaveId, inputRegisterAddress, count); // 显示结果 Console.WriteLine("\n读取结果:"); for (int i = 0; i < inputRegisters.Length; i++) { Console.WriteLine($" 输入寄存器[{inputRegisterAddress + i}] = {inputRegisters[i]}"); } Console.WriteLine($"\n成功读取 {inputRegisters.Length} 个输入寄存器"); } catch (Exception ex) { Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}"); } Console.WriteLine("\n按任意键继续..."); Console.ReadKey(true); } // 5. 测试浮点数读写 private static async Task TestFloats(ModbusTcpClient client) { Console.WriteLine("\n== 浮点数读写测试 =="); try { // 询问用户输入起始地址 Console.Write("请输入浮点数起始地址 (0-65534): "); if (!ushort.TryParse(Console.ReadLine(), out ushort floatAddress)) { Console.WriteLine("无效地址,使用默认地址 200"); floatAddress = 200; } // 询问用户输入浮点数值 Console.Write("请输入要写入的浮点数值: "); if (!float.TryParse(Console.ReadLine(), out float floatValue)) { Console.WriteLine("无效值,使用默认值 123.456"); floatValue = 123.456f; } // 显示字节顺序选项 Console.WriteLine("\n字节顺序选项:"); Console.WriteLine("1. ABCD (标准顺序)"); Console.WriteLine("2. DCBA (完全倒序)"); Console.WriteLine("3. BADC (交换字顺序)"); Console.WriteLine("4. CDAB (交换字节顺序)"); // 询问用户选择字节顺序 Console.Write("请选择字节顺序 (1-4): "); ByteOrder byteOrder = ByteOrder.ABCD; if (int.TryParse(Console.ReadLine(), out int orderChoice) && orderChoice >= 1 && orderChoice <= 4) { byteOrder = (ByteOrder)(orderChoice - 1); } else { Console.WriteLine("无效选择,使用默认字节顺序 ABCD"); } // 写入浮点数 Console.WriteLine($"\n写入浮点数: 地址={floatAddress}, 值={floatValue}, 字节顺序={byteOrder}"); await ModbusDataHelper.WriteFloatAsync(client, SlaveId, floatAddress, floatValue, byteOrder); // 读取浮点数 float readValue = await ModbusDataHelper.ReadFloatAsync(client, SlaveId, floatAddress, byteOrder); Console.WriteLine($"读取的浮点数: {readValue}"); // 验证结果 // 由于浮点数精度问题,使用近似比较 bool success = Math.Abs(floatValue - readValue) < 0.0001; Console.WriteLine($"验证结果: {(success ? "✓ 成功" : "❌ 失败")}"); Console.WriteLine($"差值: {Math.Abs(floatValue - readValue)}"); // 显示原始寄存器值 ushort[] registers = await client.ReadHoldingRegistersAsync(SlaveId, floatAddress, 2); Console.WriteLine($"\n原始寄存器值: [{registers[0]}, {registers[1]}]"); } catch (Exception ex) { Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}"); } Console.WriteLine("\n按任意键继续..."); Console.ReadKey(true); } // 6. 测试双精度浮点数读写 private static async Task TestDoubles(ModbusTcpClient client) { Console.WriteLine("\n== 双精度浮点数读写测试 =="); try { // 询问用户输入起始地址 Console.Write("请输入双精度浮点数起始地址 (0-65532): "); if (!ushort.TryParse(Console.ReadLine(), out ushort doubleAddress)) { Console.WriteLine("无效地址,使用默认地址 300"); doubleAddress = 300; } // 询问用户输入双精度浮点数值 Console.Write("请输入要写入的双精度浮点数值: "); if (!double.TryParse(Console.ReadLine(), out double doubleValue)) { Console.WriteLine("无效值,使用默认值 123456.789012"); doubleValue = 123456.789012; } // 显示字节顺序选项 Console.WriteLine("\n字节顺序选项:"); Console.WriteLine("1. ABCD (标准顺序)"); Console.WriteLine("2. DCBA (完全倒序)"); Console.WriteLine("3. BADC (交换字顺序)"); Console.WriteLine("4. CDAB (交换字节顺序)"); // 询问用户选择字节顺序 Console.Write("请选择字节顺序 (1-4): "); ByteOrder byteOrder = ByteOrder.ABCD; if (int.TryParse(Console.ReadLine(), out int orderChoice) && orderChoice >= 1 && orderChoice <= 4) { byteOrder = (ByteOrder)(orderChoice - 1); } else { Console.WriteLine("无效选择,使用默认字节顺序 ABCD"); } // 写入双精度浮点数 Console.WriteLine($"\n写入双精度浮点数: 地址={doubleAddress}, 值={doubleValue}, 字节顺序={byteOrder}"); await ModbusDataHelper.WriteDoubleAsync(client, SlaveId, doubleAddress, doubleValue, byteOrder); // 读取双精度浮点数 double readValue = await ModbusDataHelper.ReadDoubleAsync(client, SlaveId, doubleAddress, byteOrder); Console.WriteLine($"读取的双精度浮点数: {readValue}"); // 验证结果 // 由于浮点数精度问题,使用近似比较 bool success = Math.Abs(doubleValue - readValue) < 0.000001; Console.WriteLine($"验证结果: {(success ? "✓ 成功" : "❌ 失败")}"); Console.WriteLine($"差值: {Math.Abs(doubleValue - readValue)}"); // 显示原始寄存器值 ushort[] registers = await client.ReadHoldingRegistersAsync(SlaveId, doubleAddress, 4); Console.WriteLine($"\n原始寄存器值: [{registers[0]}, {registers[1]}, {registers[2]}, {registers[3]}]"); } catch (Exception ex) { Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}"); } Console.WriteLine("\n按任意键继续..."); Console.ReadKey(true); } // 7. 测试32位整数读写 private static async Task TestInt32(ModbusTcpClient client) { Console.WriteLine("\n== 32位整数读写测试 =="); try { // 询问用户输入起始地址 Console.Write("请输入32位整数起始地址 (0-65534): "); if (!ushort.TryParse(Console.ReadLine(), out ushort int32Address)) { Console.WriteLine("无效地址,使用默认地址 400"); int32Address = 400; } // 询问用户输入整数值 Console.Write("请输入要写入的32位整数值: "); if (!int.TryParse(Console.ReadLine(), out int int32Value)) { Console.WriteLine("无效值,使用默认值 12345678"); int32Value = 12345678; } // 显示字节顺序选项 Console.WriteLine("\n字节顺序选项:"); Console.WriteLine("1. ABCD (标准顺序)"); Console.WriteLine("2. DCBA (完全倒序)"); Console.WriteLine("3. BADC (交换字顺序)"); Console.WriteLine("4. CDAB (交换字节顺序)"); // 询问用户选择字节顺序 Console.Write("请选择字节顺序 (1-4): "); ByteOrder byteOrder = ByteOrder.ABCD; if (int.TryParse(Console.ReadLine(), out int orderChoice) && orderChoice >= 1 && orderChoice <= 4) { byteOrder = (ByteOrder)(orderChoice - 1); } else { Console.WriteLine("无效选择,使用默认字节顺序 ABCD"); } // 写入32位整数 Console.WriteLine($"\n写入32位整数: 地址={int32Address}, 值={int32Value}, 字节顺序={byteOrder}"); await ModbusDataHelper.WriteInt32Async(client, SlaveId, int32Address, int32Value, byteOrder); // 读取32位整数 int readValue = await ModbusDataHelper.ReadInt32Async(client, SlaveId, int32Address, byteOrder); Console.WriteLine($"读取的32位整数: {readValue}"); // 验证结果 bool success = int32Value == readValue; Console.WriteLine($"验证结果: {(success ? "✓ 成功" : "❌ 失败")}"); // 显示原始寄存器值 ushort[] registers = await client.ReadHoldingRegistersAsync(SlaveId, int32Address, 2); Console.WriteLine($"\n原始寄存器值: [{registers[0]}, {registers[1]}]"); // 测试无符号32位整数 Console.WriteLine("\n\n测试无符号32位整数:"); ushort uint32Address = (ushort)(int32Address + 10); uint uint32Value = 3000000000; // 大于有符号整数最大值 // 写入无符号32位整数 Console.WriteLine($"写入无符号32位整数: 地址={uint32Address}, 值={uint32Value}, 字节顺序={byteOrder}"); await ModbusDataHelper.WriteUInt32Async(client, SlaveId, uint32Address, uint32Value, byteOrder); // 读取无符号32位整数 uint readUintValue = await ModbusDataHelper.ReadUInt32Async(client, SlaveId, uint32Address, byteOrder); Console.WriteLine($"读取的无符号32位整数: {readUintValue}"); // 验证结果 bool uintSuccess = uint32Value == readUintValue; Console.WriteLine($"验证结果: {(uintSuccess ? "✓ 成功" : "❌ 失败")}"); // 显示原始寄存器值 ushort[] uintRegisters = await client.ReadHoldingRegistersAsync(SlaveId, uint32Address, 2); Console.WriteLine($"\n原始寄存器值: [{uintRegisters[0]}, {uintRegisters[1]}]"); } catch (Exception ex) { Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}"); } Console.WriteLine("\n按任意键继续..."); Console.ReadKey(true); } // 8. 测试字符串读写 private static async Task TestStrings(ModbusTcpClient client) { Console.WriteLine("\n== 字符串读写测试 =="); try { // 询问用户输入起始地址 Console.Write("请输入字符串起始地址 (0-65534): "); if (!ushort.TryParse(Console.ReadLine(), out ushort stringAddress)) { Console.WriteLine("无效地址,使用默认地址 500"); stringAddress = 500; } // 询问用户输入字符串 Console.Write("请输入要写入的字符串: "); string stringValue = Console.ReadLine(); if (string.IsNullOrEmpty(stringValue)) { Console.WriteLine("无效字符串,使用默认值 'Hello Modbus!'"); stringValue = "Hello Modbus!"; } // 写入字符串 Console.WriteLine($"\n写入字符串: 地址={stringAddress}, 值=\"{stringValue}\""); await ModbusDataHelper.WriteStringAsync(client, SlaveId, stringAddress, stringValue); // 读取字符串 string readValue = await ModbusDataHelper.ReadStringAsync(client, SlaveId, stringAddress, stringValue.Length); Console.WriteLine($"读取的字符串: \"{readValue}\""); // 验证结果 bool success = stringValue == readValue; Console.WriteLine($"验证结果: {(success ? "✓ 成功" : "❌ 失败")}"); // 显示原始寄存器值 int registerCount = (int)Math.Ceiling((double)stringValue.Length / 2); ushort[] registers = await client.ReadHoldingRegistersAsync(SlaveId, stringAddress, (ushort)registerCount); Console.WriteLine("\n原始寄存器值:"); for (int i = 0; i < registers.Length; i++) { Console.WriteLine($" [{i}] = {registers[i]} (十六进制: 0x{registers[i]:X4})"); } // 显示ASCII值 Console.WriteLine("\nASCII字符表示:"); for (int i = 0; i < registers.Length; i++) { char char1 = (char)(registers[i] & 0xFF); char char2 = (char)(registers[i] >> 8); if (char1 < 32 || char1 > 126) char1 = '.'; // 非可打印字符显示为. if (char2 < 32 || char2 > 126) char2 = '.'; // 非可打印字符显示为. Console.WriteLine($" [{i}] = '{char1}' '{char2}'"); } } catch (Exception ex) { Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}"); } Console.WriteLine("\n按任意键继续..."); Console.ReadKey(true); } // 9. 测试批量操作 private static async Task TestBatchOperations(ModbusTcpClient client) { Console.WriteLine("\n== 批量操作测试 =="); try { // 询问用户输入起始地址 Console.Write("请输入批量操作起始地址 (0-65534): "); if (!ushort.TryParse(Console.ReadLine(), out ushort startAddress)) { Console.WriteLine("无效地址,使用默认地址 1000"); startAddress = 1000; } // 询问用户输入批量大小 Console.Write("请输入批量大小 (1-100): "); if (!int.TryParse(Console.ReadLine(), out int batchSize) || batchSize < 1 || batchSize > 100) { Console.WriteLine("无效大小,使用默认值 50"); batchSize = 50; } // 创建批量数据 ushort[] values = new ushort[batchSize]; for (int i = 0; i < batchSize; i++) { values[i] = (ushort)(i * 100 + 1); } // 批量写入 Console.WriteLine($"\n批量写入 {batchSize} 个寄存器,起始地址: {startAddress}"); Console.WriteLine("写入值示例: " + string.Join(", ", values.Take(5)) + (batchSize > 5 ? "..." : "")); // 记录开始时间 var startTime = DateTime.Now; await client.WriteMultipleRegistersAsync(SlaveId, startAddress, values); // 计算写入耗时 var writeTime = DateTime.Now - startTime; Console.WriteLine($"批量写入完成,耗时: {writeTime.TotalMilliseconds:F1} 毫秒"); // 批量读取 Console.WriteLine($"\n批量读取 {batchSize} 个寄存器,起始地址: {startAddress}"); // 记录开始时间 startTime = DateTime.Now; ushort[] readValues = await client.ReadHoldingRegistersAsync(SlaveId, startAddress, (ushort)batchSize); // 计算读取耗时 var readTime = DateTime.Now - startTime; Console.WriteLine($"批量读取完成,耗时: {readTime.TotalMilliseconds:F1} 毫秒"); // 验证结果 bool allMatch = true; int mismatchCount = 0; for (int i = 0; i < batchSize; i++) { if (values[i] != readValues[i]) { allMatch = false; mismatchCount++; // 只显示前5个不匹配项 if (mismatchCount <= 5) { Console.WriteLine($" 不匹配: 索引 {i}, 写入值 {values[i]}, 读取值 {readValues[i]}"); } } } if (mismatchCount > 5) { Console.WriteLine($" ... 以及其他 {mismatchCount - 5} 个不匹配项"); } Console.WriteLine($"\n批量验证结果: {(allMatch ? "✓ 所有数据匹配" : $"❌ 有 {mismatchCount} 个数据不匹配")}"); Console.WriteLine($"读写速率: 写入 {batchSize / writeTime.TotalSeconds:F1} 个/秒,读取 {batchSize / readTime.TotalSeconds:F1} 个/秒"); } catch (Exception ex) { Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}"); } Console.WriteLine("\n按任意键继续..."); Console.ReadKey(true); } // 10. 字节顺序测试 private static async Task TestByteOrder(ModbusTcpClient client) { Console.WriteLine("\n== 字节顺序测试 =="); try { // 询问用户输入起始地址 Console.Write("请输入测试起始地址 (0-65534): "); if (!ushort.TryParse(Console.ReadLine(), out ushort startAddress)) { Console.WriteLine("无效地址,使用默认地址 600"); startAddress = 600; } // 测试值 float testValue = 12345.6789f; Console.WriteLine($"\n使用测试值 {testValue} 测试所有字节顺序"); // 测试所有字节顺序 foreach (ByteOrder order in Enum.GetValues(typeof(ByteOrder))) { ushort address = (ushort)(startAddress + (int)order * 10); Console.WriteLine($"\n测试字节顺序 {order} (地址: {address}):"); // 写入指定字节顺序的浮点数 await ModbusDataHelper.WriteFloatAsync(client, SlaveId, address, testValue, order); // 读取相同字节顺序的浮点数 float readValue = await ModbusDataHelper.ReadFloatAsync(client, SlaveId, address, order); // 读取原始寄存器值 ushort[] registers = await client.ReadHoldingRegistersAsync(SlaveId, address, 2); // 验证结果 bool success = Math.Abs(testValue - readValue) < 0.0001; Console.WriteLine($"写入值: {testValue}"); Console.WriteLine($"读取值: {readValue}"); Console.WriteLine($"原始寄存器: [{registers[0]}, {registers[1]}]"); Console.WriteLine($"验证结果: {(success ? "✓ 成功" : "❌ 失败")}"); // 显示不同字节顺序读取的结果 Console.WriteLine("\n使用不同字节顺序读取同一数据:"); foreach (ByteOrder readOrder in Enum.GetValues(typeof(ByteOrder))) { if (readOrder != order) { float crossValue = await ModbusDataHelper.ReadFloatAsync(client, SlaveId, address, readOrder); Console.WriteLine($" 使用 {readOrder} 读取: {crossValue}"); } } } } catch (Exception ex) { Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}"); } Console.WriteLine("\n按任意键继续..."); Console.ReadKey(true); } } }

image.png

总结

通过以上代码,我们实现了一个简易的 Modbus TCP 客户端,能够与设备进行通信,实现数据的读写。本文从基本概念出发,详细介绍了使用 NModbus4.Core 库处理 Modbus 通信的关键要素。希望本文能够为您在开发 Modbus 应用程序时提供帮助。

如果您有任何问题或进一步的需求,欢迎随时联系。

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!