1.协议简介
Modbus由MODICON公司(现施耐德公司)于1979年开发,是一种工业现场总线协议标准。主要分为了RTU,ASCII,TCP三种协议类型。
本文我们只探讨C# modbusTCP的应用。 modbusTcp协议采用master/slave模型。在modbus总线中是以"一主多从"关系存在的。通讯方式是主站发出请求(广播或者单播),从站收到请求后应答。
2.Modbus Tcp 主从站关系
什么时候需要编写主站程序,什么时候需要编写从站程序,对于初学者总有些疑惑。
2.1 Modbus 主站(master):Modbus主站具有唯一性,可以主动发出读取、修改指令,对接多个Modbus从站。主站(master)一般作为工控机上位机程序来读取传感器设备的数据,编程时作为网络客户端(TCP Client),IP地址不需要固定。
2.2 Modbus从站(slave):Modbus从站可以有多个,不会主动发出读取指令,只能对接一个Modbus主站。从站(slave)一般是传感器部分的程序,需要一个固定的IP地址,从站编程时一般作为网络服务端(TCP Server),监听回应主站发来的请求。
3.Modbus的功能码
功能码 | 含义 |
0x01 | 读线圈 |
0x02 | 读离散量输入 |
0x03 | 读保持寄存器 |
0x04 | 读输入寄存器 |
0x05 | 写单个线圈 |
0x06 | 写单个保持寄存器 |
0x0F | 写多个线圈 |
0x10 | 写多个保持寄存器 |
例程:0x03:读保持寄存器,从远程设备中读保持寄存器连续块的内容
请求 :19 15 00 00 00 06 01 03 05 4A 00 01
应答:19 15 00 00 00 05 01 03 02 11 22
请求解析:
19 15 为报文标识符(用户自定义),一般每次通信加1来表示区别不同报文
00 00 表示modbusTCP协议
00 06 表示后面的数据长度
01 从站号
03 功能码。
05 4A 读取数据的起始地址
00 01 读几位地址(寄存器数量)
应答解析:
19 15 为报文标识符
00 00 表示modbusTCP协议
00 05 表示后面的数据长度
01 从站号
03 功能码
02 表示读到的数据长度
11 22 表示读到的数据
4. 基于NModbus4的Modbus TCP主从站例程
下面程序参考了https://blog.csdn.net/qq_34699535/article/details/111658342网站。
4.1 Modbus客户端:Slave(一般为设备端)
// Modbus TCP
using Modbus.Device;
using System.Net.Sockets;
using System.Net;
using Modbus.Data;
using System.Threading;
using Modbus.Utility;
private TcpListener listener;
private ModbusSlave slave;
//客户端监听端口
private void btn_SlaveListen_Click(object sender, EventArgs e)
{
listener = new TcpListener(IPAddress.Parse(txt_SlaveIP.Text), (int)nud_SlavePort.Value);
listener.Start();
slave = ModbusTcpSlave.CreateTcp(1, listener);
//创建寄存器存储对象
slave.DataStore = DataStoreFactory.CreateDefaultDataStore();
//订阅数据到达事件,可以在此事件中读取寄存器
slave.DataStore.DataStoreWrittenTo += new EventHandler<DataStoreEventArgs>((obj, o) =>
{
switch (o.ModbusDataType)
{
case ModbusDataType.Coil: //code 5
ModbusDataCollection<bool> discretes = slave.DataStore.CoilDiscretes;
if (ckb_CD_1.InvokeRequired)
{
this.BeginInvoke(new Action(delegate
{
ckb_CD_1.Checked = discretes[1];
}));
}
break;
case ModbusDataType.HoldingRegister: //code 15
ModbusDataCollection<ushort> holdingRegisters = slave.DataStore.HoldingRegisters;
if (txt_HR_1.InvokeRequired)
{
this.BeginInvoke(new Action(delegate
{
txt_HR_1.Text = holdingRegisters[1].ToString();
}));
}
break;
}
});
//此事件,待补充
slave.ModbusSlaveRequestReceived += new EventHandler<ModbusSlaveRequestEventArgs>((obj, o) =>
{
});
//此事件,待补充
slave.WriteComplete += new EventHandler<ModbusSlaveRequestEventArgs>((obj, o) =>
{
});
slave.Listen();
}
//写入寄存器
private void btn_SlaveSend_Click(object sender, EventArgs e)
{
//CoilDiscretes表示一个Bit,也就是一个bool类型
slave.DataStore.CoilDiscretes[(int)nud_SlaveCoilAds.Value] = nud_SlaveCoilVal.Value == 1 ? true : false;
//HoldingRegisters表示一个无符号的16位整数(2的16次幂:0-65535)
slave.DataStore.HoldingRegisters[(int)nud_SlaveHRAds.Value] = (ushort)nud_SlaveHRVal.Value;
}
//停止监听
private void btn_SlaveStop_Click(object sender, EventArgs e)
{
slave.Dispose();
}
4.2 Modbus主机端:Master(一般为上位机)
private TcpClient client;
private ModbusIpMaster master;
//连接
private void btn_MasterConnect_Click(object sender, EventArgs e)
{
client = new TcpClient();
client.Connect(IPAddress.Parse(txt_SlaveIP.Text.Trim()), (int)nud_SlavePort.Value);
master = ModbusIpMaster.CreateIp(client);
}
//写入寄存器
private void btn_MasterSend_Click(object sender, EventArgs e)
{
master.WriteSingleCoil((ushort)nud_MasterCoilAds.Value, nud_MasterCoilVal.Value == 1 ? true : false);
master.WriteSingleRegister((ushort)nud_MasterHRAds.Value, (ushort)nud_MasterHRVal.Value);
}
//定时器中循环读取线圈和寄存器的值,当然,你也可以使用异步的方式
private void timer1_Tick(object sender, EventArgs e)
{
bool[] coils = master.ReadCoils(1, 0, 9);
ushort[] holding_register = master.ReadHoldingRegisters(1, 0, 9);
}
4.3 程序源码
参考网站:
https://www.cnblogs.com/ioufev/articles/10830028.html