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版本
功能名称 | 描述 | 适用对象 |
---|---|---|
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。可以通过NuGet包管理器安装:
C#Install-Package NModbus4
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");
}
}
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]}");
}
}
}
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]}");
}
}
}
C#ushort startAddress = 0;
bool[] coilValues = new bool[] { true, false, true, true, false };
master.WriteMultipleCoils(slaveId, startAddress, coilValues);
Console.WriteLine("已写入多个线圈状态");
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]}");
}
}
}
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]}");
}
}
}
在许多工业应用中,我们需要定期轮询设备以获取最新数据。以下是一个使用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}");
}
}
}
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);
}
}
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 许可协议。转载请注明出处!