jjzjj

C# NModbus4 TCP 主从站通信样例

iml6yu 2023-04-11 原文

文章目录

实现效果

实现代码

主站

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();
        }

        /// <summary>
        /// 启动
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        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;

            }
        }

        /// <summary>
        /// 断开
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            slave.Dispose();
            button1.Enabled = true;
        }

        /// <summary>
        /// 写数据
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        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();
        }
        /// <summary>
        /// 连接
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        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); 
        }
        /// <summary>
        /// 断开连接
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            tokenSource.Cancel();
            master.Dispose();
        }
        /// <summary>
        /// 写数据
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        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);
        }
        /// <summary>
        /// 开启线程轮询读数据
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        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 协议简介

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

之间的区别说得比较清楚了。

Modbus的功能码

功能码含义
0x01读线圈
0x02读离散量输入
0x03读保持寄存器
0x04读输入寄存器
0x05写单个线圈
0x06写单个保持寄存器
0x0F写多个线圈
0x10写多个保持寄存器

读指令对比(0x04)

MBAP报文头地址码功能码寄存器地址寄存器数量CRC校验
Modbus RTU010400 0000 1671 C4
Modbus TCP00 00 00 00 00 06 010400 0000 16

写指令对比(0x10)

MBAP报文头地址码功能码寄存器地址寄存器数量数据长度正文CRC校验
Modbus RTU001000 2000 010200 00AC A0
Modbus TCP00 00 00 00 00 09 001000 2000 010200 00

Modbus TCP MBAP

Modbus TCP协议是在RTU协议前面添加MBAP报文头,由于TCP是基于可靠连接的服务,RTU协议中的CRC校验码就不再需要,所以在Modbus TCP协议中是没有CRC校验码。

MBAP报文头:

事务处理标识协议标识长度单元标识符
2字节2字节2字节1字节
事务处理标识可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文
协议标识符00 00表示ModbusTCP协议
长度表示接下来的数据长度,单位为字节
单元标识符可以理解为设备地址

Modbus 主从站关系

Modbus主站和从站区别为:发出指令不同、唯一性不同、对接不同。

一、发出指令不同

1、Modbus主站:Modbus主站可以主动发出指令。

2、Modbus从站:Modbus从站不会主动发出指令。

二、唯一性不同

1、Modbus主站:Modbus主站具有唯一性。

2、Modbus从站:Modbus从站不具有唯一性,可以有多个。

三、对接不同

1、Modbus主站:Modbus主站可以对接多个Modbus从站。

2、Modbus从站:Modbus从站职能对接一个Modbus主站。

所以当使用Modbus/TCP时,主站一般作为客户端,从站一般作为服务端

NModbus4

  • 在Nuget上能找到的最新包2.x版本,不支持.netcore

不在赘述关于NModbus4的使用了,有兴趣的看一下这个文章
https://blog.csdn.net/lzl640/article/details/106858263

demo下载

https://download.csdn.net/download/iml6yu/87060108

介绍NModbus的使用,适用于Core的环境

http://t.csdn.cn/uyzz3

https://github.com/NModbus4/NModbus4

有关C# NModbus4 TCP 主从站通信样例的更多相关文章

  1. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  2. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  3. 1个串口用1根线实现多机半双工通信+开机控制电路 - 2

    功能需求:主机使用一个串口,与两个从机进行双向通信,主机向从机发送数据,从机能够返回数据,由于结构限制,主机与从机之间只有3根线(电源、地、数据线),并且从机上没有设物理的电源开关,需要通过与主机连接的数据线来控制开机,总结如下:1、数据线只有1根2、能够双向通信3、主机能够控制从机开机4、主机可以单独向1个从机发数据,也可以同时向两个从机发送数据根据需求,设计出如下电路:工作原理分析:VCC_24V_IN、GND、LINE_L(LINE_R)三根线接线连接到从机,电源开启电路是从机内部的电源控制。开机的逻辑:*主机先上电,LINE_L因为主机的R1上拉而有高电平,使Q6导通,Q5的G极电压被

  4. 计算机网络笔记:TCP三次握手和四次挥手过程 - 2

    TCP是面向连接的协议,连接的建立和释放是每一次面向连接的通信中必不可少的过程。TCP连接的管理就是使连接的建立和释放都能正常地进行。三次握手TCP连接的建立—三次握手建立TCP连接①若主机A中运行了一个客户进程,当它需要主机B的服务时,就发起TCP连接请求,并在所发送的分段中用SYN=1表示连接请求,并产生一个随机发送序号x,如果连接成功,A将以x作为其发送序号的初始值:seq=x。主机B收到A的连接请求报文,就完成了第一次握手。客户端发送SYN=1表示连接请求客户端发送一个随机发送序号x,如果连接成功,A将以x作为其发送序号的初始值:seq=x②主机B如果同意建立连接,则向主机A发送确认报

  5. ruby - 如何创建与帧缓冲区通信的 Ruby 应用程序? - 2

    我有一个RaspberryPiTFT7"触摸屏显示器,我想创建一个简单的应用程序来显示和输出系统数据(即CPU使用率、温度等)。我注意到目前常见的实现方法是使用pygame库输出到显示器连接到的帧缓冲区/dev/fb1。我想执行相同的操作,但使用Ruby,因为我更熟悉这门语言。有人可以为我指明正确的方向,让我知道如何开始吗?我查看了ruby​​game和gosu库,它们似乎能够做我想做的事情,即绘制屏幕,​​但我找不到任何关于如何将输出定向到的信息帧缓冲区本身。 最佳答案 rubycorelib有一个IO您应该能够使用该类将输出定向

  6. [蓝桥杯单片机]学习笔记——串口通信的基本原理与应用 - 2

    目录一、原理部分1、什么是串行通信(1)并行通信与串行通信(2)串行通信的制式(3)串行通信的主要方式  2、配置串口(1)SCON和PCON:串行口1的控制寄存器(2)SBUF:串行口数据缓冲寄存器 (3)AUXR:辅助寄存器​编辑(4)ES、PS:与串行口1中断相关的寄存器(5)波特率设置  3、串口框架编写二、程序案例一、原理部分1、什么是串行通信(1)并行通信与串行通信微控制器与外部设备的数据通信,根据连线结构和传送方式的不同,可以分为两种:并行通信和串行通信。并行通信:数据的各位同时发送与接收,每个数据位使用一条导线,这种方式传输快,但是需要多条导线进行信号传输。串行通信:数据一位一

  7. ruby - 后台工作人员在 Rails 应用程序中查看通信 - 2

    假设我有一个包含帖子的博客应用程序。创建帖子后,将创建一个工作人员来处理一些后台操作。我的情况是,在提交表单后我想显示某种加载消息(gif加载器等),当工作人员完成时我想隐藏加载消息并显示工作人员提供的一些数据。我的问题是,传达工作人员已完成工作并将其显示在用户前端的最佳方式是什么。worker回调看起来像这样defworker_finish#messagetheuserend 最佳答案 我认为您可能忽略了拥有后台工作人员的意义,基本上,您试图做的是弄巧成拙。--如果用户提交表单并且你在你的Controller中排队作业,只是为了让

  8. ruby - 为什么在使用 savon 进行 ruby​​ soap 通信时将 "wsdl"命名空间插入到操作名称中? - 2

    我正在尝试访问我无法控制的SOAP服务。其中一个操作称为ProcessMessage。我按照这个例子生成了一个SOAP请求,但我收到一条错误消息,指出该操作不存在。我将问题追溯到生成信封正文的方式。USER658e702d5feff1777a6c741847239eb5d6d86e482010-02-18T02:05:25Zpassword......ProcessMessage标签应该是:这就是示例Java应用程序生成它时的样子,并且可以正常工作。该标记是我的Ruby应用程序生成的内容与示例Java应用程序之间的唯一区别。有什么方法可以去掉那个标签前面的"wsdl:"命名空间并添加这

  9. ruby - yarn 未初始化常量 Socket::SOL_TCP - 2

    我在这里尝试使用yarn,遇到了一个可能与ruby​​相关的问题。在执行任何yarn命令,我收到错误.../.rvm/gems/ruby-2.3.0/gems/yarn-0.1.1/lib/yarn/server.rb:14:in':uninitializedconstantSocket::SOL_TCP(NameError)错误堆栈:$yarn.../.rvm/gems/ruby-2.3.0/gems/yarn-0.1.1/lib/yarn/server.rb:14:in':uninitializedconstantSocket::SOL_TCP(NameError)Didyoume

  10. QT 设计一个串口调试工具,用一个工程就能轻松解决,外加虚拟串口工具模拟调试,在日常工作中可类比模块间通信,非常详细建议收藏 - 2

    QT串口调试工具第一节虚拟串口工具安装第二节QT创建一个基于QWidget的项目第三节UI界面设计第三节项目头文件widget.h第四节项目实现文件widget.cpp第五节main函数第六节编译结果重点第七节使用QT打包程序,不安装QT的电脑可使用第一节虚拟串口工具安装-----------------------------------------下载所需工具---------------------------------------------------------------------链接:https://pan.baidu.com/s/1QkT36S4EnH2HEAhZ1TZ8

随机推荐