using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Modbus.Device;
using System.Net.Sockets;
using System.Net;
using Modbus.Data;
using System.Threading;
using Modbus.Utility;
/*
* 注释:
* 当使用Modbus-TCP时,往往使用Slave作为服务端
* 所以NModbus-TCP中主站使用的时Slave对象
*/
namespace NModBus4.Master
{
public partial class Form1 : Form
{
private TcpListener listener;
private ModbusSlave slave;
public Form1()
{
InitializeComponent();
}
///
/// 启动
///
///
///
private void button1_Click(object sender, EventArgs e)
{
try
{
listener = new TcpListener(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text));
listener.Start();
slave = ModbusTcpSlave.CreateTcp(1, listener);
//创建寄存器存储对象
slave.DataStore = DataStoreFactory.CreateDefaultDataStore();
slave.DataStore.DataStoreWrittenTo += DataStore_DataStoreWrittenTo;
slave.ModbusSlaveRequestReceived += Slave_ModbusSlaveRequestReceived;
slave.WriteComplete += Slave_WriteComplete;
slave.Listen();
button1.Enabled = false;
}
catch (Exception)
{
throw;
}
}
private void Slave_WriteComplete(object sender, ModbusSlaveRequestEventArgs e)
{
if (richTextBox1.InvokeRequired)
{
this.BeginInvoke(new Action(delegate
{
richTextBox1.AppendText("[写入数据完成:]\r\n" + Newtonsoft.Json.JsonConvert.SerializeObject(e.Message) + "\r\n ******** \r\n \r\n");
}));
}
}
private void Slave_ModbusSlaveRequestReceived(object sender, ModbusSlaveRequestEventArgs e)
{
if (richTextBox1.InvokeRequired)
{
this.BeginInvoke(new Action(delegate
{
richTextBox1.AppendText("[收到数据:]\r\n" + Newtonsoft.Json.JsonConvert.SerializeObject(e.Message) + "\r\n ******** \r\n \r\n");
}));
}
}
private void DataStore_DataStoreWrittenTo(object sender, DataStoreEventArgs e)
{
switch (e.ModbusDataType)
{
case ModbusDataType.Coil: //code 5
ModbusDataCollection<bool> discretes = slave.DataStore.CoilDiscretes;
if (richTextBox1.InvokeRequired)
{
this.BeginInvoke(new Action(delegate
{
richTextBox1.AppendText("[ModbusDataType.Coil]\r\n" + Newtonsoft.Json.JsonConvert.SerializeObject(discretes) + "\r\n ******** \r\n \r\n");
}));
}
break;
case ModbusDataType.HoldingRegister: //code 15
ModbusDataCollection<ushort> holdingRegisters = slave.DataStore.HoldingRegisters;
if (richTextBox1.InvokeRequired)
{
this.BeginInvoke(new Action(delegate
{
richTextBox1.AppendText("[ModbusDataType.HoldingRegister]\r\n" + Newtonsoft.Json.JsonConvert.SerializeObject(holdingRegisters) + "\r\n ******** \r\n \r\n");
}));
}
break;
default:
if (richTextBox1.InvokeRequired)
{
this.BeginInvoke(new Action(delegate
{
richTextBox1.AppendText($"[{e.ModbusDataType}]\r\n" + Newtonsoft.Json.JsonConvert.SerializeObject(e.Data) + "\r\n ******** \r\n \r\n");
}));
}
break;
}
}
///
/// 断开
///
///
///
private void button2_Click(object sender, EventArgs e)
{
slave.Dispose();
button1.Enabled = true;
}
///
/// 写数据
///
///
///
private void button3_Click(object sender, EventArgs e)
{
//CoilDiscretes表示一个Bit,也就是一个bool类型
slave.DataStore.CoilDiscretes[(int)cliAddress.Value] = cliValue.Value == 1 ? true : false;
//HoldingRegisters表示一个无符号的16位整数(2的16次幂:0-65535)
slave.DataStore.HoldingRegisters[(int)holAddress.Value] = (ushort)holValue.Value;
}
}
}
using Modbus.Device;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Windows.Forms.AxHost;
namespace NModBus4.从站
{
public partial class Form1 : Form
{
private TcpClient client;
private ModbusIpMaster master;
public CancellationTokenSource tokenSource= new CancellationTokenSource();
public Form1()
{
InitializeComponent();
}
///
/// 连接
///
///
///
private void button1_Click(object sender, EventArgs e)
{
client = new TcpClient();
client.Connect(IPAddress.Parse(textBox1.Text.Trim()), int.Parse(textBox2.Text));
master = ModbusIpMaster.CreateIp(client);
}
///
/// 断开连接
///
///
///
private void button2_Click(object sender, EventArgs e)
{
tokenSource.Cancel();
master.Dispose();
}
///
/// 写数据
///
///
///
private void button3_Click(object sender, EventArgs e)
{
//CoilDiscretes表示一个Bit,也就是一个bool类型
//master.WriteSingleCoil((ushort)cliAddress.Value ,cliValue.Value == 1 ? true : false);
//HoldingRegisters表示一个无符号的16位整数(2的16次幂:0-65535)
master.WriteSingleRegister((ushort)holAddress.Value,(ushort)holValue.Value);
}
///
/// 开启线程轮询读数据
///
///
///
private void button4_Click(object sender, EventArgs e)
{
Task.Run(async () => {
while (!tokenSource.IsCancellationRequested)
{
bool[] coils = master.ReadCoils(1, 0, 9);
//ushort[] holding_register = master.ReadHoldingRegisters(1, 0, 9);
this.BeginInvoke(new Action(delegate
{
richTextBox1.AppendText(string.Join(",", coils)+ "\r\n ******** \r\n \r\n");
//richTextBox1.AppendText(string.Join(",", holding_register) + "\r\n ******** \r\n \r\n");
}));
await Task.Delay(TimeSpan.FromSeconds(10));
}
});
}
}
}
Modbus通信协议具有多个变种,支持串口(主要是RS-485总线),以太网多个版本,其中最著名的是Modbus RTU,Modbus ASCII
和Modbus TCP三种。在工业现场一般都是采用Modbus RTU协议,一般大家说的基于串口通信的Modbus通信协议都是指Modbus RTU通信协议。
与Modbus RTU协议相比较,Modbus TCP协议则是在RTU协议上加一个MBAP报文头,并且由于TCP是基于可靠连接的服务,RTU协议中的
CRC校验码就不再需要,所以在Modbus TCP协议中是没有CRC校验码的,所以就常用一句比较通俗的话来说:Modbus TCP协议就是
Modbus RTU协议在前面加上五个0以及一个6,然后去掉两个CRC校验码字节就OK。虽然这句话说得不是特别准确,但是也基本上把RTU与TCP
之间的区别说得比较清楚了。
功能码 | 含义 |
---|---|
0x01 | 读线圈 |
0x02 | 读离散量输入 |
0x03 | 读保持寄存器 |
0x04 | 读输入寄存器 |
0x05 | 写单个线圈 |
0x06 | 写单个保持寄存器 |
0x0F | 写多个线圈 |
0x10 | 写多个保持寄存器 |
MBAP报文头 | 地址码 | 功能码 | 寄存器地址 | 寄存器数量 | CRC校验 | |
---|---|---|---|---|---|---|
Modbus RTU | 无 | 01 | 04 | 00 00 | 00 16 | 71 C4 |
Modbus TCP | 00 00 00 00 00 06 01 | 04 | 00 00 | 00 16 | 无 |
MBAP报文头 | 地址码 | 功能码 | 寄存器地址 | 寄存器数量 | 数据长度 | 正文 | CRC校验 | |
---|---|---|---|---|---|---|---|---|
Modbus RTU | 无 | 00 | 10 | 00 20 | 00 01 | 02 | 00 00 | AC A0 |
Modbus TCP | 00 00 00 00 00 09 00 | 10 | 00 20 | 00 01 | 02 | 00 00 | 无 |
Modbus TCP协议是在RTU协议前面添加MBAP报文头,由于TCP是基于可靠连接的服务,RTU协议中的CRC校验码就不再需要,所以在Modbus TCP协议中是没有CRC校验码。
MBAP报文头:
事务处理标识 | 协议标识 | 长度 | 单元标识符 |
---|---|---|---|
2字节 | 2字节 | 2字节 | 1字节 |
事务处理标识 | 可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文 |
---|---|
协议标识符 | 00 00表示ModbusTCP协议 |
长度 | 表示接下来的数据长度,单位为字节 |
单元标识符 | 可以理解为设备地址 |
1、Modbus主站:Modbus主站可以主动发出指令。
2、Modbus从站:Modbus从站不会主动发出指令。
1、Modbus主站:Modbus主站具有唯一性。
2、Modbus从站:Modbus从站不具有唯一性,可以有多个。
1、Modbus主站:Modbus主站可以对接多个Modbus从站。
2、Modbus从站:Modbus从站职能对接一个Modbus主站。
所以当使用Modbus/TCP时,主站一般作为客户端,从站一般作为服务端
不在赘述关于NModbus4的使用了,有兴趣的看一下这个文章
https://blog.csdn.net/lzl640/article/details/106858263
https://download.csdn.net/download/iml6yu/87060108
http://t.csdn.cn/uyzz3
https://github.com/NModbus4/NModbus4