当前位置: 首页 > news >正文

ModbusRtu读取和写入一个寄存器示例

Modbus RTU协议

Modbus RTU协议是一种允许可编程逻辑控制器(PLC)和计算机之间进行数据交换的通信方式。电子设备可以使用Modbus协议通过串行线路交换信息。
它已被广泛接受并广泛用于 建筑管理系统 的建设(BMS)和工业自动化系统(IAS)。它的易用性,可靠性以及它是开源的,并且可以在任何设备或应用程序中免费使用。
该协议由Modicon@于1979年开发和发布,用于其可编程逻辑控制器。 它使用主/从架构构建,并支持使用RS232/RS485/RS422协议的串行设备。 Modbus通常用于多个仪器和控制设备将信号传输到中央控制器或系统以收集和分析数据的情况。 工业自动化和监督控制和数据采集(SCADA)系统通常采用Modbus协议。

Modbus RTU代表什么?

Modbus RTU(远程终端单元)是原始Modbus规范中定义的两种传输模式之一。这两种模式是ModbuS RTU和ASCII,它们都设计用于支持RS232.RS485和RS422协议的串行设备。 Modbus RTU的一个显着特点是它使用二进制编码和强大的CRC错误检查。Modbus RTU是Modbus协议的实现,最常用于工业应用和自动化生产设施。大多数传感器【比如温度、湿度、压力传感器等】都支持ModbusRtu进行串口通信

Modbus-Rtu说明文档

https://www.modbus.cn/27850.html

现在演示程序只考虑读取单个保存寄存器【命令0x03】和写入单个保存寄存器【命令0x06】

C#新建窗体应用程序ModbusRtuDemo,将默认的Form1重命名为FormModbusRtu。

窗体FormModbusRtu的设计器程序如下:

文件FormModbusRtu.Designer.cs


namespace ModbusRtuDemo
{partial class FormModbusRtu{/// <summary>/// 必需的设计器变量。/// </summary>private System.ComponentModel.IContainer components = null;/// <summary>/// 清理所有正在使用的资源。/// </summary>/// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>protected override void Dispose(bool disposing){if (disposing && (components != null)){components.Dispose();}base.Dispose(disposing);}#region Windows 窗体设计器生成的代码/// <summary>/// 设计器支持所需的方法 - 不要修改/// 使用代码编辑器修改此方法的内容。/// </summary>private void InitializeComponent(){this.btnOpen = new System.Windows.Forms.Button();this.label1 = new System.Windows.Forms.Label();this.txtPortName = new System.Windows.Forms.TextBox();this.txtBaudRate = new System.Windows.Forms.TextBox();this.label2 = new System.Windows.Forms.Label();this.txtStationNo = new System.Windows.Forms.TextBox();this.label3 = new System.Windows.Forms.Label();this.btnClose = new System.Windows.Forms.Button();this.txtAddress = new System.Windows.Forms.TextBox();this.label4 = new System.Windows.Forms.Label();this.txtValue = new System.Windows.Forms.TextBox();this.label5 = new System.Windows.Forms.Label();this.btnRead = new System.Windows.Forms.Button();this.btnWrite = new System.Windows.Forms.Button();this.rtxtMessage = new System.Windows.Forms.RichTextBox();this.SuspendLayout();// // btnOpen// this.btnOpen.Location = new System.Drawing.Point(243, 19);this.btnOpen.Name = "btnOpen";this.btnOpen.Size = new System.Drawing.Size(75, 23);this.btnOpen.TabIndex = 0;this.btnOpen.Text = "打开串口";this.btnOpen.UseVisualStyleBackColor = true;this.btnOpen.Click += new System.EventHandler(this.btnOpen_Click);// // label1// this.label1.AutoSize = true;this.label1.Location = new System.Drawing.Point(27, 24);this.label1.Name = "label1";this.label1.Size = new System.Drawing.Size(41, 12);this.label1.TabIndex = 1;this.label1.Text = "串口名";// // txtPortName// this.txtPortName.Location = new System.Drawing.Point(87, 21);this.txtPortName.Name = "txtPortName";this.txtPortName.Size = new System.Drawing.Size(100, 21);this.txtPortName.TabIndex = 2;this.txtPortName.Text = "COM1";// // txtBaudRate// this.txtBaudRate.Location = new System.Drawing.Point(87, 53);this.txtBaudRate.Name = "txtBaudRate";this.txtBaudRate.Size = new System.Drawing.Size(100, 21);this.txtBaudRate.TabIndex = 4;this.txtBaudRate.Text = "9600";// // label2// this.label2.AutoSize = true;this.label2.Location = new System.Drawing.Point(27, 56);this.label2.Name = "label2";this.label2.Size = new System.Drawing.Size(41, 12);this.label2.TabIndex = 3;this.label2.Text = "波特率";// // txtStationNo// this.txtStationNo.Location = new System.Drawing.Point(87, 80);this.txtStationNo.Name = "txtStationNo";this.txtStationNo.Size = new System.Drawing.Size(100, 21);this.txtStationNo.TabIndex = 6;this.txtStationNo.Text = "1";// // label3// this.label3.AutoSize = true;this.label3.Location = new System.Drawing.Point(27, 83);this.label3.Name = "label3";this.label3.Size = new System.Drawing.Size(29, 12);this.label3.TabIndex = 5;this.label3.Text = "站号";// // btnClose// this.btnClose.Location = new System.Drawing.Point(243, 53);this.btnClose.Name = "btnClose";this.btnClose.Size = new System.Drawing.Size(75, 23);this.btnClose.TabIndex = 7;this.btnClose.Text = "关闭串口";this.btnClose.UseVisualStyleBackColor = true;this.btnClose.Click += new System.EventHandler(this.btnClose_Click);// // txtAddress// this.txtAddress.Location = new System.Drawing.Point(123, 116);this.txtAddress.Name = "txtAddress";this.txtAddress.Size = new System.Drawing.Size(100, 21);this.txtAddress.TabIndex = 9;this.txtAddress.Text = "11";// // label4// this.label4.AutoSize = true;this.label4.Location = new System.Drawing.Point(-2, 119);this.label4.Name = "label4";this.label4.Size = new System.Drawing.Size(119, 12);this.label4.TabIndex = 8;this.label4.Text = "偏移地址【0~65535】";// // txtValue// this.txtValue.Location = new System.Drawing.Point(123, 151);this.txtValue.Name = "txtValue";this.txtValue.Size = new System.Drawing.Size(100, 21);this.txtValue.TabIndex = 11;this.txtValue.Text = "0";// // label5// this.label5.AutoSize = true;this.label5.Location = new System.Drawing.Point(100, 154);this.label5.Name = "label5";this.label5.Size = new System.Drawing.Size(17, 12);this.label5.TabIndex = 10;this.label5.Text = "值";// // btnRead// this.btnRead.Location = new System.Drawing.Point(243, 108);this.btnRead.Name = "btnRead";this.btnRead.Size = new System.Drawing.Size(75, 23);this.btnRead.TabIndex = 12;this.btnRead.Text = "读取03";this.btnRead.UseVisualStyleBackColor = true;this.btnRead.Click += new System.EventHandler(this.btnRead_Click);// // btnWrite// this.btnWrite.Location = new System.Drawing.Point(243, 151);this.btnWrite.Name = "btnWrite";this.btnWrite.Size = new System.Drawing.Size(75, 23);this.btnWrite.TabIndex = 13;this.btnWrite.Text = "写入06";this.btnWrite.UseVisualStyleBackColor = true;this.btnWrite.Click += new System.EventHandler(this.btnWrite_Click);// // rtxtMessage// this.rtxtMessage.Location = new System.Drawing.Point(379, 12);this.rtxtMessage.Name = "rtxtMessage";this.rtxtMessage.ReadOnly = true;this.rtxtMessage.Size = new System.Drawing.Size(818, 547);this.rtxtMessage.TabIndex = 14;this.rtxtMessage.Text = "";// // FormModbusRtu// this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;this.ClientSize = new System.Drawing.Size(1209, 571);this.Controls.Add(this.rtxtMessage);this.Controls.Add(this.btnWrite);this.Controls.Add(this.btnRead);this.Controls.Add(this.txtValue);this.Controls.Add(this.label5);this.Controls.Add(this.txtAddress);this.Controls.Add(this.label4);this.Controls.Add(this.btnClose);this.Controls.Add(this.txtStationNo);this.Controls.Add(this.label3);this.Controls.Add(this.txtBaudRate);this.Controls.Add(this.label2);this.Controls.Add(this.txtPortName);this.Controls.Add(this.label1);this.Controls.Add(this.btnOpen);this.Name = "FormModbusRtu";this.Text = "ModbusRtu串口调试工具";this.ResumeLayout(false);this.PerformLayout();}#endregionprivate System.Windows.Forms.Button btnOpen;private System.Windows.Forms.Label label1;private System.Windows.Forms.TextBox txtPortName;private System.Windows.Forms.TextBox txtBaudRate;private System.Windows.Forms.Label label2;private System.Windows.Forms.TextBox txtStationNo;private System.Windows.Forms.Label label3;private System.Windows.Forms.Button btnClose;private System.Windows.Forms.TextBox txtAddress;private System.Windows.Forms.Label label4;private System.Windows.Forms.TextBox txtValue;private System.Windows.Forms.Label label5;private System.Windows.Forms.Button btnRead;private System.Windows.Forms.Button btnWrite;private System.Windows.Forms.RichTextBox rtxtMessage;}
}

窗体FormModbusRtu程序如下

文件FormModbusRtu.cs

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 System.IO.Ports;
using System.Threading;
/*Modbus-RTU说明* https://www.modbus.cn/27850.html* 一个寄存器地址占用两个字节,Modbus中是按照字【Word】为单位,一个字【Word】使用两个字节【Byte】* ---------发送命令-【读取】:* 01 - 地址,也就是你传感器的地址
03 - 功能码,03代表查询功能,查询传感器的数据
00 00 - 代表查询的起始寄存器地址.说明从0x0000开始查询。这里需要说明以下,Modbus把数据存放在寄存器中,通过查询寄存器来得到不同变量的值,一个寄存器地址对应2字节数据
00 01 - 代表查询了一个寄存器.结合前面的00 00,意思就是查询从0开始的1个寄存器值,一个寄存器是一个字Word=UInt16,对应两个字节,比如读取一个Int32或者Real【Float】就需要2个寄存器
84 0A - 循环冗余校验,是modbus的校验公式,从首个字节开始到84前面为止;【也就是对前6个字节进行CRC校验】
---------接收报文-【读取】:
01 - 地址,也就是你传感器的地址
03 - 功功能码,03代表查询功能,查询传感器的数据。这里要注意的是注意发给从机的功能码是啥,从机就要回复同样的功能码,如果不一样说明这一帧数据有错误
02 - 代表后面数据的字节数,因为上面说到,一个寄存器有2个字节,所以后面的字节数肯定是2*查询的寄存器个数;
19 98 - 寄存器的值是19 98,结合发送的数据看出,01这个寄存器的值为19 98
B2 7E - 循环冗余校验
------------------------------------------------------------------------------------------------------------------------
---------发送命令-【写入】:
01-主机要查询的从机地址
06-功能码,06代表修改单个寄存器功能,修改有些不同,有修改一个寄存器和修改多个寄存器;
00 00-代表修改的起始寄存器地址.说明从0x0000开始.
00 01-代表修改的值为00 01.结合前面的00 00,意思就是修改0号寄存器值为00 01;
48 0A-循环冗余校验,是modbus的校验公式,从首个字节开始到48前面为止;
---------接收报文-【写入】:
01-从机返回给主机自己的地址,说明这就是主机查的从机
06-功能码,代表修改单个寄存器功能,主机发啥功能码,从机就必须回什么功能码;
00 00-代表修改的起始寄存器地址.说明是0x0000.
00 01-代表修改的值为00 01.结合前面的00 00,意思就是修改0号寄存器值为00 01;
48 0A-循环冗余校验,是modbus的校验公式,从首个字节开始到48前面为止;
*/namespace ModbusRtuDemo
{public partial class FormModbusRtu : Form{SerialPort serialPort = new SerialPort(); private List<byte> buffer = new List<byte>();/// <summary>/// 处理接收的数据事件【考虑到有些通信串口的传输速率较慢,触发多次DataReceived事件,报文会拆包和粘包】/// </summary>private event Action<List<byte>> HandleReceivedDataEvent;public FormModbusRtu(){InitializeComponent();serialPort.DataReceived -= SerialPort_DataReceived;HandleReceivedDataEvent -= FormModbusRtu_HandleReceivedDataEvent;serialPort.DataReceived += SerialPort_DataReceived;HandleReceivedDataEvent += FormModbusRtu_HandleReceivedDataEvent;}/// <summary>/// 处理接收的数据事件【考虑到有些通信串口的传输速率较慢,触发多次DataReceived事件,报文会拆包和粘包】/// 这里整理出有效的报文后进行解析,并清除掉已处理的有效数据/// </summary>/// <param name="recvBuffer"></param>private void FormModbusRtu_HandleReceivedDataEvent(List<byte> recvBuffer){if (recvBuffer == null || recvBuffer.Count < 7) {return;}if (recvBuffer[0] == 0x01 && recvBuffer[1] == 0x03){//假设从机地址【站号】是1 并且是 读取保持寄存器【命令0x03】的反馈int readByteCount = recvBuffer[2];//读取多少个字节//01 03 readByteCount 占用3个字节, 然后 数据占用readByteCount个字节,最后是CRC校验占用2个字节if (3 + readByteCount + 2 <= recvBuffer.Count){byte[] validData = recvBuffer.Take(3 + readByteCount + 2).ToArray();DisplayMessage($"这里对有效的数据进行解析【{string.Join(" ", validData.Select(x => x.ToString("X2")))}】");buffer.RemoveRange(0, 3 + readByteCount + 2);//移除前面的元素【已处理的有效数据删除掉】}}else if (recvBuffer[0] == 0x01 && recvBuffer[1] == 0x06){//假设从机地址【站号】是1 并且是 写入保持寄存器【命令0x06】的反馈if (recvBuffer.Count >= 8){DisplayMessage($"写入保持寄存器【命令0x06】的操作成功,接收到的一条完整报文【{string.Join(" ", recvBuffer.Take(8).Select(x => x.ToString("X2")))}】");buffer.RemoveRange(0, 8);//移除前面的元素【已处理的有效数据删除掉】}}else if (recvBuffer[0] == 0x01 && recvBuffer[1] == 0x10){//假设从机地址【站号】是1 并且是 写入多个连续保持寄存器【命令0x10】的反馈//接收报文01 10 地址高字节 地址低字节 寄存器数量高字节,寄存器数量低字节 CRC校验,CRC校验if (recvBuffer.Count >= 8){DisplayMessage($"写入多个保持寄存器【命令0x10】的操作成功,接收到的一条完整报文【{string.Join(" ", recvBuffer.Take(8).Select(x => x.ToString("X2")))}】");buffer.RemoveRange(0, 8);//移除前面的元素【已处理的有效数据删除掉】}}else {//这里进行其他Modbus命令处理}}private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e){byte[] buf = new byte[serialPort.BytesToRead];serialPort.Read(buf, 0, buf.Length);buffer.AddRange(buf);DisplayMessage($"接收到的报文为【{string.Join(" ", buf.Select(x => x.ToString("X2")))}】");if (buffer.Count >= 7) {HandleReceivedDataEvent?.Invoke(buffer);}}/// <summary>/// 异步显示文本内容/// </summary>/// <param name="message"></param>private void DisplayMessage(string message){if (!IsHandleCreated){return;}this.BeginInvoke(new Action(() =>{if (rtxtMessage.TextLength >= 40960){rtxtMessage.Clear();}rtxtMessage.AppendText($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}->{message}\n");rtxtMessage.ScrollToCaret();}));}private void btnOpen_Click(object sender, EventArgs e){serialPort.PortName = txtPortName.Text.Trim();int baudrate;if (!int.TryParse(txtBaudRate.Text, out baudrate)) {baudrate = 9600;}serialPort.BaudRate = baudrate;try{serialPort.Open();DisplayMessage($"打开串口成功【{serialPort.PortName }】-【{serialPort.BaudRate}】");}catch (Exception ex){DisplayMessage($"打开串口失败【{serialPort.PortName }】-【{serialPort.BaudRate}】,原因【{ex.Message}】"); ;}}private void btnClose_Click(object sender, EventArgs e){serialPort.Close();}private void btnRead_Click(object sender, EventArgs e){if (!serialPort.IsOpen) {DisplayMessage($"请先打开串口");return;}buffer.Clear();//先清空报文byte stationNo = byte.Parse(txtStationNo.Text);byte[] bufferAddr = BitConverter.GetBytes(ushort.Parse(txtAddress.Text));//读取两个寄存器的值,将反馈4个字节byte[] srcBytes = new byte[] { stationNo, 0x03, bufferAddr[1], bufferAddr[0], 0x00, 0x02 };byte[] bufferCRC = CRC16Modbus.CRCCalc(srcBytes);byte[] sendBytes = new byte[srcBytes.Length + 2];Array.Copy(srcBytes, 0, sendBytes, 0, srcBytes.Length);sendBytes[sendBytes.Length - 2] = bufferCRC[0];sendBytes[sendBytes.Length - 1] = bufferCRC[1];serialPort.DiscardOutBuffer();serialPort.Write(sendBytes,0, sendBytes.Length);DisplayMessage($"【读取】发送命令【{string.Join(" ", sendBytes.Select(x => x.ToString("X2")))}】");if (SpinWait.SpinUntil(() => this.buffer.Count > 0, 5000)){byte[] recvBytes = buffer.ToArray();DisplayMessage($"【读取】读取到的反馈报文为【{string.Join(" ", recvBytes.Select(x => x.ToString("X2")))}】");if (recvBytes.Length >= 5) {ushort angle = BitConverter.ToUInt16(new byte[] { recvBytes[4], recvBytes[3] }, 0);txtValue.Text = angle.ToString();}}else{DisplayMessage($"接收数据超时【5S】");}}private void btnWrite_Click(object sender, EventArgs e){if (!serialPort.IsOpen){DisplayMessage($"请先打开串口");return;}buffer.Clear();//先清空报文byte stationNo = byte.Parse(txtStationNo.Text);byte[] bufferAddr = BitConverter.GetBytes(ushort.Parse(txtAddress.Text));byte[] writeBuffer = BitConverter.GetBytes(ushort.Parse(txtValue.Text));byte[] srcBytes = new byte[] { 0x03, stationNo, 0x06, bufferAddr[1], bufferAddr[0], writeBuffer[1], writeBuffer[0] };byte[] bufferCRC = CRC16Modbus.CRCCalc(srcBytes);byte[] sendBytes = new byte[srcBytes.Length + 2];Array.Copy(srcBytes, 0, sendBytes, 0, srcBytes.Length);sendBytes[sendBytes.Length - 2] = bufferCRC[0];sendBytes[sendBytes.Length - 1] = bufferCRC[1];serialPort.DiscardOutBuffer();serialPort.Write(sendBytes, 0, sendBytes.Length);DisplayMessage($"【写入】发送命令【{string.Join(" ", sendBytes.Select(x => x.ToString("X2")))}】");if (SpinWait.SpinUntil(() => this.buffer.Count > 0, 5000)){byte[] recvBytes = buffer.ToArray();DisplayMessage($"【写入】读取到的反馈报文为【{string.Join(" ", recvBytes.Select(x => x.ToString("X2")))}】");if (recvBytes.Length >= 5){DisplayMessage($"写入成功");}}else{DisplayMessage($"接收数据超时【5S】");}}}
}

CRC校验程序如下:

文件CRC16Modbus.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ModbusRtuDemo
{public class CRC16Modbus{/// <summary>/// 计算字节数组的 CRC16 校验码/// 返回一个两个字节组成的数组,低字节在前/// </summary>/// <param name="data"></param>/// <returns></returns>public static byte[] CRCCalc(byte[] data){int crc = 0xffff;for (int i = 0; i < data.Length; i++){crc ^= data[i];for (int j = 0; j < 8; j++){int temp = crc & 1;crc >>= 1;if (temp == 1){crc ^= 0xa001;}}}byte[] crc16 = new byte[2];crc16[1] = (byte)((crc >> 8) & 0xff);crc16[0] = (byte)(crc & 0xff);return crc16;}/// <summary>/// 计算字符串的 CRC16 校验码/// </summary>/// <param name="data"></param>/// <returns></returns>public static byte[] CRCCalc(string data){IEnumerable<string> datac = data.Contains(",") ?data.Replace(" ", "").Replace("0x", "").Replace("0X", "").Trim().Split(',') :data.Replace("0x", "").Replace("0X", "").Split(' ').ToList().Where(u => u != "");List<byte> bytedata = new List<byte>();foreach (string str in datac){bytedata.Add(byte.Parse(str, System.Globalization.NumberStyles.AllowHexSpecifier));}byte[] crcbuf = bytedata.ToArray();return CRCCalc(crcbuf);}// 计算指定偏移量的 CRC16 校验码public static byte[] CRCCalc(byte[] data, int offset, int length){byte[] Tdata = data.Skip(offset).Take(length).ToArray();return CRCCalc(Tdata);}}
}

测试运行如图:

http://www.dtcms.com/a/614715.html

相关文章:

  • 电商网站商品表设计方案如何找网站做推广
  • Linux 34TCP服务器多进程并发
  • 网站建设找谁好深圳聘请做网站人员
  • C语言编译器Visual Studio | 高效开发与调试工具
  • 滨海新区建设和交通局网站一个人建设小型网站
  • Java 8 Lambda表达式详解
  • vip视频解析网站怎么做离石古楼角网站建设
  • DVL数据协议深度解析:PD0、PD4、PD6格式详解与实践应用
  • Web自动化测试详细流程和步骤
  • P1909 [NOIP 2016 普及组] 买铅笔
  • 萍乡网站开发公司k8s wordpress mysql
  • C++条件判断与循环(二)(算法竞赛)
  • 浏阳建设局网站广告电商怎么做
  • 微信朋友圈做网站推广赚钱吗网站建设费专票会计分录
  • 友元的作用与边界
  • 如何提高英语口语?
  • (6)框架搭建:Qt实战项目之主窗体快捷工具条
  • 做阿里云网站空间建设工程施工合同实例
  • web中间件——Tomcat
  • Linux中管理员和一般用户的用法小结
  • html手机网站模板html5网页设计教程
  • 【Mac】开发环境使用/维护
  • 网站代码设计惠州网站建设排名
  • 精美网站建设wordpress gae
  • 【STM32MP157 异核通信框架学习篇】(10)Linux下Remoteproc相关API (下)
  • 企业建站服务退役军人215专业品牌网站建设
  • 杭州模板网站建站做国外夏令营的网站
  • 基于SpringBoot的房屋租赁管理系统【协同过滤推荐算法+可视化统计+合同签署】
  • 【MySQL | 基础】函数
  • Java Set