编辑
2025-09-17
C#
00

目录

什么是Modbus?
下载地址
合并版本
Modbus功能表格
NModbus4的安装
Modbus TCP客户端示例
连接到Modbus TCP服务器
读取保持寄存器
写入单个保持寄存器
读取输入寄存器
读取线圈状态
写入多个线圈
Modbus RTU客户端示例
连接到Modbus RTU设备
读取离散输入
异常处理
高级应用:异步操作
高级应用示例
批量读取不同类型的数据
实现定期轮询
实现Modbus TCP服务器
读取浮点数
写入浮点数
结论

什么是Modbus?

Modbus是一种广泛使用的工业通信协议,而NModbus4是一个用于.NET平台的开源Modbus库。本文将介绍如何在C#中使用NModbus4来实现Modbus通信。

它是一个Bus,即总线协议。

它被工业领域所接受的原因是它具备一下三个优点

  • 公开发表并且无版权要求
  • 易于部署和维护
  • 对供应商来说,修改移动本地的比特或字节没有很多限制

Modbus是主从方式通信,也就是说,不能同步进行通信,总线上每次只有一个数据进行传输,即主机发送,从机应答,主机不发送,总线上就没有数据通信。

下载地址

HTML
[https://github.com/NModbus4/NModbus4/](https://github.com/NModbus4/NModbus4/)

NModbus is a C# implementation of the Modbus protocol. Provides connectivity to Modbus slave compatible devices and applications. Supports serial ASCII, serial RTU, TCP, and UDP protocols. NModbus4 it's a fork of NModbus

“MIT LicenseMIT是和BSD一样宽松的许可协议,作者只想保留版权,而无任何其他了限制.也就是说,你必须在你的发行版里包含原许可协议的声明,无论你是以二进制发布的还是以源代码发布的。* 你可以使用,复制和修改软件* 你可以免费使用软件或出售* 唯一的限制是,它是必须附有MIT授权协议

合并版本

dotnet --list-runtimes

dotnet --list-sdks 查看本机安装的.net版本

dotnet new globaljson --sdk-version 2.1.602

dotnet migrate 先安装.net 2.1版本

Modbus功能表格

功能名称描述适用对象
ReadCoils读取DO的状态数字输出(DO)
ReadInputs读取DI的状态数字输入(DI)
ReadHoldingRegisters读取AO的值模拟输出(AO)
ReadInputRegisters读取AI的值模拟输入(AI)
WriteSingleCoil写入值到DO数字输出(DO)
WriteSingleRegister写入值到AO模拟输出(AO)
WriteMultipleCoils写多线圈寄存器数字输出(DO)
WriteMultipleRegisters写多个保持寄存器模拟输出(AO)
ReadWriteMultipleRegisters读写多个保持寄存器模拟输出(AO)

NModbus4的安装

首先,我们需要在项目中安装NModbus4。可以通过NuGet包管理器安装:

C#
Install-Package NModbus4

Modbus TCP客户端示例

连接到Modbus TCP服务器

C#
static void Main(string[] args) { string ipAddress = "127.0.0.1"; int port = 502; using (TcpClient client = new TcpClient(ipAddress, port)) { ModbusIpMaster master = ModbusIpMaster.CreateIp(client); Console.WriteLine("Connected to Modbus slave"); } }

image.png

读取保持寄存器

C#
static void Main(string[] args) { string ipAddress = "127.0.0.1"; int port = 502; using (TcpClient client = new TcpClient(ipAddress, port)) { ModbusIpMaster master = ModbusIpMaster.CreateIp(client); byte slaveId = 1; ushort startAddress = 0; ushort numRegisters = 10; ushort[] holdingRegisters = master.ReadHoldingRegisters(slaveId, startAddress, numRegisters); Console.WriteLine("保持寄存器的值:"); for (int i = 0; i < holdingRegisters.Length; i++) { Console.WriteLine($"寄存器 {startAddress + i}: {holdingRegisters[i]}"); } } }

image.png

写入单个保持寄存器

C#
static void Main(string[] args) { string ipAddress = "127.0.0.1"; int port = 502; using (TcpClient client = new TcpClient(ipAddress, port)) { // 设置连接和操作超时 client.ReceiveTimeout = 1000; // 1秒接收超时 client.SendTimeout = 1000; // 1秒发送超时 ModbusIpMaster master = ModbusIpMaster.CreateIp(client); byte slaveId = 1; ushort registerAddress = 1; ushort value = 42; master.WriteSingleRegister(slaveId, registerAddress, value); Console.WriteLine($"已写入值 {value} 到寄存器 {registerAddress}"); } }

读取输入寄存器

C#
ushort startAddress = 0; ushort numRegisters = 5; ushort[] inputRegisters = master.ReadInputRegisters(slaveId, startAddress, numRegisters); Console.WriteLine("输入寄存器的值:"); for (int i = 0; i < inputRegisters.Length; i++) { Console.WriteLine($"寄存器 {startAddress + i}: {inputRegisters[i]}"); }

读取线圈状态

C#
static void Main(string[] args) { string ipAddress = "127.0.0.1"; int port = 502; using (TcpClient client = new TcpClient(ipAddress, port)) { ModbusIpMaster master = ModbusIpMaster.CreateIp(client); byte slaveId = 1; ushort startAddress = 100; ushort numCoils = 16; bool[] coilStatus = master.ReadCoils(slaveId, startAddress, numCoils); Console.WriteLine("线圈状态:"); for (int i = 0; i < coilStatus.Length; i++) { Console.WriteLine($"线圈 {startAddress + i}: {coilStatus[i]}"); } } }

image.png

写入多个线圈

C#
ushort startAddress = 0; bool[] coilValues = new bool[] { true, false, true, true, false }; master.WriteMultipleCoils(slaveId, startAddress, coilValues); Console.WriteLine("已写入多个线圈状态");

image.png

Modbus RTU客户端示例

连接到Modbus RTU设备

C#
static void Main() { string portName = "COM1"; int baudRate = 9600; Parity parity = Parity.None; int dataBits = 8; StopBits stopBits = StopBits.One; using (SerialPort port = new SerialPort(portName, baudRate, parity, dataBits, stopBits)) { port.Open(); ModbusSerialMaster master = ModbusSerialMaster.CreateRtu(port); byte slaveId = 1; ushort startAddress = 100; ushort numInputs = 8; bool[] discreteInputs = master.ReadInputs(slaveId, startAddress, numInputs); Console.WriteLine("离散输入状态:"); for (int i = 0; i < discreteInputs.Length; i++) { Console.WriteLine($"输入 {startAddress + i}: {discreteInputs[i]}"); } } }

image.png

读取离散输入

C#
ushort startAddress = 0; ushort numInputs = 8; bool[] discreteInputs = master.ReadInputs(slaveId, startAddress, numInputs); Console.WriteLine("离散输入状态:"); for (int i = 0; i < discreteInputs.Length; i++) { Console.WriteLine($"输入 {startAddress + i}: {discreteInputs[i]}"); }

异常处理

在使用NModbus4时,适当的异常处理非常重要。以下是一个包含异常处理的示例:

C#
try { ushort[] holdingRegisters = master.ReadHoldingRegisters(slaveId, startAddress, numRegisters); // 处理读取的数据 } catch (IOException ex) { Console.WriteLine($"Modbus IO异常: {ex.Message}"); } catch (Modbus.SlaveException ex) { Console.WriteLine($"从站异常: {ex.Message}"); } catch (Exception ex) { Console.WriteLine($"发生未知异常: {ex.Message}"); }

高级应用:异步操作

NModbus4还支持异步操作,这在处理多个设备或需要非阻塞操作时非常有用。

C#
async Task ReadHoldingRegistersAsync() { try { ushort[] holdingRegisters = await master.ReadHoldingRegistersAsync(slaveId, startAddress, numRegisters); Console.WriteLine("异步读取保持寄存器完成"); // 处理读取的数据 } catch (Exception ex) { Console.WriteLine($"异步操作异常: {ex.Message}"); } }

高级应用示例

批量读取不同类型的数据

在实际应用中,我们经常需要同时读取不同类型的数据。以下是一个批量读取保持寄存器、输入寄存器和线圈的例子:

C#
static void Main() { string ipAddress = "127.0.0.1"; int port = 502; byte slaveId = 1; using (TcpClient client = new TcpClient(ipAddress, port)) { ModbusIpMaster master = ModbusIpMaster.CreateIp(client); // 批量读取 ushort[] holdingRegisters = master.ReadHoldingRegisters(slaveId, 100, 10); ushort[] inputRegisters = master.ReadInputRegisters(slaveId, 100, 10); bool[] coils = master.ReadCoils(slaveId, 100, 16); // 输出结果 Console.WriteLine("保持寄存器:"); for (int i = 0; i < holdingRegisters.Length; i++) { Console.WriteLine($"寄存器 {i}: {holdingRegisters[i]}"); } Console.WriteLine("\n输入寄存器:"); for (int i = 0; i < inputRegisters.Length; i++) { Console.WriteLine($"寄存器 {i}: {inputRegisters[i]}"); } Console.WriteLine("\n线圈状态:"); for (int i = 0; i < coils.Length; i++) { Console.WriteLine($"线圈 {i}: {coils[i]}"); } } }

image.png

实现定期轮询

在许多工业应用中,我们需要定期轮询设备以获取最新数据。以下是一个使用Timer实现定期轮询的例子:

C#
using System; using System.Net.Sockets; using System.Timers; using Modbus.Device; class PollingExample { private static ModbusIpMaster master; private static Timer pollingTimer; static void Main() { string ipAddress = "127.0.0.1"; int port = 502; byte slaveId = 1; using (TcpClient client = new TcpClient(ipAddress, port)) { master = ModbusIpMaster.CreateIp(client); // 设置定时器,每5秒轮询一次 pollingTimer = new Timer(5000); pollingTimer.Elapsed += OnTimedEvent; pollingTimer.AutoReset = true; pollingTimer.Enabled = true; Console.WriteLine("按任意键停止轮询..."); Console.ReadKey(); } } private static void OnTimedEvent(Object source, ElapsedEventArgs e) { try { ushort[] holdingRegisters = master.ReadHoldingRegisters(1, 0, 5); Console.WriteLine($"轮询时间: {DateTime.Now}"); for (int i = 0; i < holdingRegisters.Length; i++) { Console.WriteLine($"寄存器 {i}: {holdingRegisters[i]}"); } Console.WriteLine(); } catch (Exception ex) { Console.WriteLine($"轮询错误: {ex.Message}"); } } }

image.png

实现Modbus TCP服务器

NModbus4不仅可以用作客户端,还可以用来实现Modbus TCP服务器。以下是一个简单的Modbus TCP服务器示例:

C#
static void Main() { byte slaveId = 1; int port = 502; IPAddress address = IPAddress.Parse("127.0.0.1"); // 创建并启动服务器 TcpListener slaveTcpListener = new TcpListener(address, port); slaveTcpListener.Start(); ModbusSlave slave = ModbusTcpSlave.CreateTcp(slaveId, slaveTcpListener); // 创建数据存储 slave.DataStore = DataStoreFactory.CreateDefaultDataStore(); // 设置初始数据 - 注意地址从1开始 // 保持寄存器 (Holding Registers) slave.DataStore.HoldingRegisters[1] = 100; // 地址 40001 slave.DataStore.HoldingRegisters[2] = 200; // 地址 40002 // 线圈 (Coils) slave.DataStore.CoilDiscretes[1] = true; // 地址 00001 slave.DataStore.CoilDiscretes[2] = false; // 地址 00002 // 输入寄存器 (Input Registers) slave.DataStore.InputRegisters[1] = 1000; // 地址 30001 slave.DataStore.InputRegisters[2] = 2000; // 地址 30002 // 离散输入 (Discrete Inputs) slave.DataStore.InputDiscretes[1] = true; // 地址 10001 slave.DataStore.InputDiscretes[2] = false; // 地址 10002 // 启动服务器 slave.ListenAsync(); Console.WriteLine("Modbus TCP 服务器已启动..."); Console.WriteLine($"IP地址: {address}, 端口: {port}"); Console.WriteLine("已设置的数据:"); Console.WriteLine($"保持寄存器 40001: {slave.DataStore.HoldingRegisters[1]}"); Console.WriteLine($"保持寄存器 40002: {slave.DataStore.HoldingRegisters[2]}"); Console.WriteLine($"线圈 00001: {slave.DataStore.CoilDiscretes[1]}"); Console.WriteLine($"线圈 00002: {slave.DataStore.CoilDiscretes[2]}"); Console.WriteLine($"输入寄存器 30001: {slave.DataStore.InputRegisters[1]}"); Console.WriteLine($"输入寄存器 30002: {slave.DataStore.InputRegisters[2]}"); Console.WriteLine($"离散输入 10001: {slave.DataStore.InputDiscretes[1]}"); Console.WriteLine($"离散输入 10002: {slave.DataStore.InputDiscretes[2]}"); Console.WriteLine("\n按任意键停止服务器..."); Console.ReadKey(); // 清理资源 slave.Dispose(); slaveTcpListener.Stop(); }

读取浮点数

Modbus协议本身不直接支持浮点数,但我们可以使用两个连续的寄存器来表示一个浮点数。以下是读取和写入浮点数的例子:

C#
using System; using System.Net.Sockets; using Modbus.Device; class Program { static void Main() { string ipAddress = "127.0.0.1"; int port = 502; byte slaveId = 1; using (TcpClient client = new TcpClient(ipAddress, port)) { ModbusIpMaster master = ModbusIpMaster.CreateIp(client); // 读取两个连续的寄存器 ushort[] readRegisters = master.ReadHoldingRegisters(slaveId, 100, 2); // 方法1:使用位操作 uint combined = ((uint)readRegisters[0] << 16) | readRegisters[1]; float value1 = BitConverter.ToSingle(BitConverter.GetBytes(combined), 0); // 方法2:使用byte数组 byte[] bytes = new byte[4]; bytes[0] = (byte)(readRegisters[1] & 0xFF); // 低字节的低字节 bytes[1] = (byte)(readRegisters[1] >> 8); // 低字节的高字节 bytes[2] = (byte)(readRegisters[0] & 0xFF); // 高字节的低字节 bytes[3] = (byte)(readRegisters[0] >> 8); // 高字节的高字节 float value2 = BitConverter.ToSingle(bytes, 0); // 打印结果 Console.WriteLine($"寄存器值: {readRegisters[0]}, {readRegisters[1]}"); Console.WriteLine($"转换后的浮点数 (方法1): {value1}"); Console.WriteLine($"转换后的浮点数 (方法2): {value2}"); // 如果字节顺序相反,可以尝试这种方式 byte[] reversedBytes = new byte[4]; reversedBytes[3] = (byte)(readRegisters[1] & 0xFF); reversedBytes[2] = (byte)(readRegisters[1] >> 8); reversedBytes[1] = (byte)(readRegisters[0] & 0xFF); reversedBytes[0] = (byte)(readRegisters[0] >> 8); float value3 = BitConverter.ToSingle(reversedBytes, 0); Console.WriteLine($"转换后的浮点数 (字节序相反): {value3}"); } } // 辅助方法:将两个ushort转换为float (大端序) static float ConvertToFloat_BigEndian(ushort register1, ushort register2) { byte[] bytes = new byte[4]; bytes[0] = (byte)(register1 >> 8); // 高字节的高字节 bytes[1] = (byte)(register1 & 0xFF); // 高字节的低字节 bytes[2] = (byte)(register2 >> 8); // 低字节的高字节 bytes[3] = (byte)(register2 & 0xFF); // 低字节的低字节 return BitConverter.ToSingle(bytes, 0); } // 辅助方法:将两个ushort转换为float (小端序) static float ConvertToFloat_LittleEndian(ushort register1, ushort register2) { byte[] bytes = new byte[4]; bytes[3] = (byte)(register1 >> 8); bytes[2] = (byte)(register1 & 0xFF); bytes[1] = (byte)(register2 >> 8); bytes[0] = (byte)(register2 & 0xFF); return BitConverter.ToSingle(bytes, 0); } }

image.png

写入浮点数

C#
static void Main(string[] args) { string ipAddress = "127.0.0.1"; int port = 502; byte slaveId = 1; ushort startAddress = 100; try { using (TcpClient client = new TcpClient(ipAddress, port)) { ModbusIpMaster master = ModbusIpMaster.CreateIp(client); // 写入浮点数 float valueToWrite = 1.234567F; WriteFloatWithOrder(master, slaveId, startAddress, valueToWrite, ByteOrder.CDAB); Console.WriteLine($"写入的值: {valueToWrite}"); } } catch (Exception ex) { Console.WriteLine($"错误: {ex.Message}"); } Console.WriteLine("按任意键退出..."); Console.ReadKey(); } public enum ByteOrder { ABCD, // 1234 DCBA, // 4321 BADC, // 2143 CDAB // 3412 } public static void WriteFloatWithOrder(ModbusIpMaster master, byte slaveId, ushort startAddress, float value, ByteOrder order) { byte[] floatBytes = BitConverter.GetBytes(value); byte[] orderedBytes = new byte[4]; switch (order) { case ByteOrder.ABCD: orderedBytes = floatBytes; break; case ByteOrder.DCBA: orderedBytes[0] = floatBytes[3]; orderedBytes[1] = floatBytes[2]; orderedBytes[2] = floatBytes[1]; orderedBytes[3] = floatBytes[0]; break; case ByteOrder.BADC: orderedBytes[0] = floatBytes[1]; orderedBytes[1] = floatBytes[0]; orderedBytes[2] = floatBytes[3]; orderedBytes[3] = floatBytes[2]; break; case ByteOrder.CDAB: orderedBytes[0] = floatBytes[2]; orderedBytes[1] = floatBytes[3]; orderedBytes[2] = floatBytes[0]; orderedBytes[3] = floatBytes[1]; break; } ushort[] registers = new ushort[2]; registers[0] = BitConverter.ToUInt16(orderedBytes, 0); registers[1] = BitConverter.ToUInt16(orderedBytes, 2); master.WriteMultipleRegisters(slaveId, startAddress, registers); Console.WriteLine($"写入寄存器: [0x{registers[0]:X4}, 0x{registers[1]:X4}]"); }

结论

NModbus4为C#开发者提供了一个强大而灵活的工具,用于实现Modbus通信。通过本文的示例,您应该能够开始使用NModbus4进行基本的Modbus操作。记住要适当处理异常,并考虑使用异步方法来提高应用程序的响应性。

随着您对NModbus4的深入了解,您将能够处理更复杂的Modbus通信场景,如实现Modbus服务器、处理不同的数据类型、以及优化性能等。

本文作者:技术老小子

本文链接:

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