工作需要实现HMI作为Modbus主站,工控机作为modbus从站。在网上找了一些资料,但多数都是使用工控机作为主站的方案,于是自己根据一些网上的说明,再加上一些摸索,实现了工控机作为从站的方法,在这里给大家分享一下。
首先,工控机作为从站的话,是没有办法去读写主站的,也就是说,工控机端只能读取HMI写进来的数据,或者把数据写如到存储区,等待HMI来读取。这里使用的工具有虚拟串口,modbuspoll,modbusslave,打包下载在这里:
https://download.csdn.net/download/gaooolianggg/12147467
虚拟串口中添加好端口后,开启modbus poll,因为此时还没有启动从站程序,所以会报time out 的错误,这个先无所谓。
接下来进入到正题,使用C#建立从站。我们使用的NModbus。NModbus的安装很简单,直接在NuGet包管理中搜索NModbus即可,也可以进入到NModbus的github主页,下载源码自己编译。这里建议从Github下载源码,在NModbus项目中的samples工程里,有很多使用示例,本文所列举的使用方法也是从示例中找到的。如果你想直接使用的话,那么从NuGet安装了NModbus后,直接调用以下方法即可。
我的测试工程可以从这里下载,其中主要是开源samples,我自己测试用的工程为myTest,开发工具为vs2019社区版。希望可以帮助到有需要的人,如果有问题可以联系我[email protected]
代码下载链接
private const string PrimarySerialPortName = "COM1";
private const string SecondarySerialPortName = "COM2";
public async Task StartModbusSerialRtuSlaveWithCustomStore()
{
using (SerialPort slavePort = new SerialPort(PrimarySerialPortName))
{
// configure serial port
slavePort.BaudRate = 9600;
slavePort.DataBits = 8;
slavePort.Parity = Parity.None;
slavePort.StopBits = StopBits.One;
slavePort.Open();
var factory = new ModbusFactory();
var slaveNetwork = factory.CreateRtuSlaveNetwork(slavePort);
var dataStore = new SlaveStorage();
dataStore.HoldingRegisters.ReadPoints(0, 10);
ushort[] d = new ushort[3]{ 3, 4, 5 };
dataStore.HoldingRegisters.WritePoints(0, d);
dataStore.CoilDiscretes.StorageOperationOccurred += (sender, args) => textBox1.Invoke(new Action(()=> textBox1.AppendText($"Coil discretes: {args.Operation} starting at {args.StartingAddress},num {args.Points.Length} + from task "+Task.CurrentId+Environment.NewLine)));
dataStore.CoilInputs.StorageOperationOccurred += (sender, args) => textBox1.Invoke(new Action(() => textBox1.AppendText($"Coil inputs: {args.Operation} starting at {args.StartingAddress},num {args.Points.Length}" + Environment.NewLine)));
dataStore.InputRegisters.StorageOperationOccurred += (sender, args) => textBox1.Invoke(new Action(() => textBox1.AppendText($"Input registers: {args.Operation} starting at {args.StartingAddress},num {args.Points.Length}" + Environment.NewLine)));
dataStore.HoldingRegisters.StorageOperationOccurred += (sender, args) => textBox1.Invoke(new Action(() => textBox1.AppendText($"Holding registers: {args.Operation} starting at {args.StartingAddress},num {args.Points.Length} + from task " + Task.CurrentId + Environment.NewLine)));
IModbusSlave slave1 = factory.CreateSlave(1, dataStore);
slaveNetwork.AddSlave(slave1);
await slaveNetwork.ListenAsync();
}
}
using NModbus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace myTest
{
public class SlaveStorage : ISlaveDataStore
{
private readonly SparsePointSource _coilDiscretes;
private readonly SparsePointSource _coilInputs;
private readonly SparsePointSource _holdingRegisters;
private readonly SparsePointSource _inputRegisters;
public SlaveStorage()
{
_coilDiscretes = new SparsePointSource();
_coilInputs = new SparsePointSource();
_holdingRegisters = new SparsePointSource();
_inputRegisters = new SparsePointSource();
}
public SparsePointSource CoilDiscretes
{
get { return _coilDiscretes; }
}
public SparsePointSource CoilInputs
{
get { return _coilInputs; }
}
public SparsePointSource HoldingRegisters
{
get { return _holdingRegisters; }
}
public SparsePointSource InputRegisters
{
get { return _inputRegisters; }
}
IPointSource ISlaveDataStore.CoilDiscretes
{
get { return _coilDiscretes; }
}
IPointSource ISlaveDataStore.CoilInputs
{
get { return _coilInputs; }
}
IPointSource ISlaveDataStore.HoldingRegisters
{
get { return _holdingRegisters; }
}
IPointSource ISlaveDataStore.InputRegisters
{
get { return _inputRegisters; }
}
}
///
/// Sparse storage for points.
///
public class SparsePointSource : IPointSource
{
private readonly Dictionary _values = new Dictionary();
public event EventHandler> StorageOperationOccurred;
///
/// Gets or sets the value of an individual point wih tout
///
///
///
public TPoint this[ushort registerIndex]
{
get
{
TPoint value;
if (_values.TryGetValue(registerIndex, out value))
return value;
return default(TPoint);
}
set { _values[registerIndex] = value; }
}
public TPoint[] ReadPoints(ushort startAddress, ushort numberOfPoints)
{
var points = new TPoint[numberOfPoints];
for (ushort index = 0; index < numberOfPoints; index++)
{
points[index] = this[(ushort)(index + startAddress)];
}
StorageOperationOccurred?.Invoke(this,
new StorageEventArgs(PointOperation.Read, startAddress, points));
return points;
}
public void WritePoints(ushort startAddress, TPoint[] points)
{
for (ushort index = 0; index < points.Length; index++)
{
this[(ushort)(index + startAddress)] = points[index];
}
StorageOperationOccurred?.Invoke(this,
new StorageEventArgs(PointOperation.Write, startAddress, points));
}
}
public class StorageEventArgs : EventArgs
{
private readonly PointOperation _pointOperation;
private readonly ushort _startingAddress;
private readonly TPoint[] _points;
public StorageEventArgs(PointOperation pointOperation, ushort startingAddress, TPoint[] points)
{
_pointOperation = pointOperation;
_startingAddress = startingAddress;
_points = points;
}
public ushort StartingAddress
{
get { return _startingAddress; }
}
public TPoint[] Points
{
get { return _points; }
}
public PointOperation Operation
{
get { return _pointOperation; }
}
}
public enum PointOperation
{
Read,
Write
}
}