MODBUS是使用广泛的协议,通讯测试时进行有使用。Modbus通讯分为主站和从站,使用RS485通讯时同一个网络内只能有一个主站,多个从站。使用TCP通讯时没有这方面的限制,可以同时支持多个主站的通讯读写。
开发测试时有各种复杂的需求,现有的仪器仪表实物搭建费时费力,可以用C#使用NMODBUS组件快速编写自己的从站仿真器,从而实现各种复杂场景的模拟。
编程思路:
创建通讯侦听端口、添加Modbus从站设备、定时改变数据。
主要实现代码如下:
cs
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using NModbus;
using System.Net.Sockets;
using System.Net;
using System.Linq;
namespace EasyModbusSlaveSimulator
{
public partial class Form1 : Form
{
private TcpListener modbusListener;
private byte CountSlave = 10;
private ushort CountRegisterMax = 1000;
//数据仿真
ushort uValue = 0;
ushort uStep = 1;
private bool bEnableDataSimulation = true;
private List<IModbusSlave> listSlave = new List<IModbusSlave>();
IModbusSlaveNetwork modbusSlaveNetwork = null;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
comboBox1.Items.Add("0.0.0.0");
var localIPs = getAllIpAddress();
// 输出本地IP地址
foreach (IPAddress ipAddress in localIPs)
{
comboBox1.Items.Add(ipAddress.ToString());
//Console.WriteLine("IP地址: " + ipAddress.ToString());
}
comboBox1.SelectedIndex = 0;
for (int i = 1; i <= 250; i++)
{
comboBox2.Items.Add(i);
}
comboBox2.SelectedIndex = 9;
}
private void button1_Click(object sender, EventArgs e)
{
string Ip = comboBox1.Text;
int port = int.Parse(textBox2.Text);
CountSlave = byte.Parse(comboBox2.Text);
CountRegisterMax = ushort.Parse(textBox4.Text);
//创建通讯绑定端口
modbusListener = new TcpListener(IPAddress.Parse(Ip), port);
modbusListener.Start();
IModbusFactory factory = new ModbusFactory();
modbusSlaveNetwork = factory.CreateSlaveNetwork(modbusListener);
//创建两个Slave设备,Id分别为1和2
for (byte i = 0; i < CountSlave; i++)
{
IModbusSlave slave = factory.CreateSlave((byte)(i + 1));
listSlave.Add(slave);
modbusSlaveNetwork.AddSlave(slave);
}
//接受连接
modbusSlaveNetwork.ListenAsync();
button1.Enabled = false;
button2.Enabled = true;
}
private void button2_Click(object sender, EventArgs e)
{
modbusListener.Stop();
modbusSlaveNetwork.Dispose();
listSlave.Clear();
modbusListener = null;
modbusSlaveNetwork = null;
button1.Enabled = true;
button2.Enabled = false;
}
private IEnumerable<IPAddress> getAllIpAddress()
{
// 获取本地主机的相关信息
IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
// 使用LINQ查询语句筛选出符合条件的IP地址
var localIPs = from ipAddress in host.AddressList
where ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork
&& !IPAddress.IsLoopback(ipAddress)
select ipAddress;
return localIPs;
}
private void timer1_Tick(object sender, EventArgs e)
{
if (bEnableDataSimulation)
{
bool[] vb = new bool[CountRegisterMax];
ushort[] vu = new ushort[CountRegisterMax];
for (int i = 0; i < CountRegisterMax; i ++)
{
vu[i] = uValue;
vb[i] = (uValue % 2 == 1) ? true:false ;
}
foreach(var slave in listSlave) {
slave.DataStore.CoilDiscretes.WritePoints(0, vb);
slave.DataStore.CoilInputs.WritePoints(0, vb);
slave.DataStore.InputRegisters.WritePoints(0,vu);
slave.DataStore.HoldingRegisters.WritePoints(0,vu);
}
uValue += uStep;
}
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
if (checkBox1.Checked)
{
timer1.Interval = int.Parse(textBox5.Text)*1000;
timer1.Enabled = true;
uStep = ushort.Parse(textBox3.Text);
}
else
{
timer1.Enabled=false;
}
}
}
}
NModbus的WritePoints方法,可以批量写入数据,不需要考虑同步造成的问题。各种ABCD、BADC、CDAB等格式调试,可以调整数据数组实现。浮点、32位整数、字符串等都可以通过调整slave.DataStore.InputRegisters.WritePoints和slave.DataStore.HoldingRegisters.WritePoints所对应的数组来实现。
参考: