Modbus-RTU详解

目录

Modbus-RTU协议

帧结构示例

CRC16校验算法

CRC16算法的过程

modbus-rtu的使用

发送数据

接收数据

tcp网口完整实现modbus-rtu协议

使用NModbus4实现modbus-rtu协议

安装NModbus4库。

串口实现NModbus4


Modbus-RTU协议

Modbus RTU 协议是一种开放的串行协议,广泛应用于当今的工业监控设备中。该协议使用 RS-232 或 RS-485 串行接口进行通信,并得到市场上几乎所有商业 SCADA、HMI、OPC 服务器和数据采集软件程序的支持。因此,很容易将 Modbus 兼容设备集成到新的或现有的监控应用程序中,并具有即时的软件支持。

帧结构:设备地址、功能码、数据和CRC校验字段。

常用功能码:Modbus-RTU协议定义了一系列常用的功能码,用于执行不同的操作,如读取保持寄存器、写入单个寄存器、写入多个寄存器等。

(1). 功能码-0x03读保持寄存器:该功能码用于从设备中读取一个或多个保持寄存器的值。
(2). 功能码-0x06写单个寄存器:该功能码用于向设备中写入一个保持寄存器的值。
(3). 功能码-0x10写多个寄存器:该功能码用于向设备中写入多个连续的保持寄存器的值。

帧结构示例

Modbus RTU 功能 01 用于从 Modbus 从站数据采集设备读取线圈状态或数字输出状态。请参阅下面的典型命令和响应以及使用说明。

    主机发送:  01 03 00 00 00 01 84 0A
    从机响应:  01 03 02 19 98 B2 7E

该例子中,主机发送的数据为`地址 + 功能码 + 数据 + 校验`​,CRC校验码是根据前面的数据计算得出的

回复的数据格式

CRC16校验算法

CRC全称循环冗余校验(Cyclic Redundancy Check, CRC),是通信领域数据传输技术中常用的检错方法,用于保证数据传输的可靠性。

CRC校验的基本思路是数据发送方发送数据之前,先生成一个CRC校验码,可以是单bit也可以是多bit,并附在有效数据末尾,以串行方式发送到接收方。接收方接收到数据后,进行CRC校验,根据校验结果就可以知道数据是否有误。

CRC校验码的生成:将有效数据**扩展后**作为被除数,使用一个指定的**多项式**作为除数,进行模二除法,得到的**余数**就是校验码。

数据接收方的CRC校验:将接受的数据(**有效数据+CRC校验码**)扩展后作为被除数,用指定的多项式作为除数,进行模二除法,得到**余数为0**,则表示校验正确。

我们使用代码向设备发送命令帧时需要使用CRC算法计算校验值,当设备响应数据时使用CRC算法校验该数据是否正确,

CRC16算法的过程

1 初始化一个16位的寄存器地址 用作初始值
2 遍历数据字节,从最高位到最低位, 
3 将数据字节与寄存器异或
4 对寄存器进行8次迭代,每一次迭代将寄存器右移一位
5 如果最低位位1,将寄存器与生成多项式0x8005异或,否则只进行右移操作
6 重复上述步骤直到遍历完所有的字节
7 最终寄存器的值就是crc16校验码
8 crc计算之后高低位进行互换

以下是封装的CRC16效验算法类:

    public static class CRC16{/// <summary>/// CRC校验,参数data为byte数组/// </summary>/// <param name="data">校验数据,字节数组</param>/// <returns>字节0是高8位,字节1是低8位</returns>public static byte[] CRCCalc(byte[] data){//crc计算赋初始值int crc = 0xffff;for (int i = 0; i < data.Length; i++){crc = crc ^ data[i];for (int j = 0; j < 8; j++){int temp;temp = crc & 1;crc = crc >> 1;crc = crc & 0x7fff;if (temp == 1){crc = crc ^ 0xa001;}crc = crc & 0xffff;}}//CRC寄存器的高低位进行互换byte[] crc16 = new byte[2];//CRC寄存器的高8位变成低8位,crc16[1] = (byte)((crc >> 8) & 0xff);//CRC寄存器的低8位变成高8位crc16[0] = (byte)(crc & 0xff);return crc16;}/// <summary>/// CRC校验,参数为空格或逗号间隔的字符串/// </summary>/// <param name="data">校验数据,逗号或空格间隔的16进制字符串(带有0x或0X也可以),逗号与空格不能混用</param>/// <returns>字节0是高8位,字节1是低8位</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();//crc计算赋初始值return CRCCalc(crcbuf);}/// <summary>///  CRC校验,截取data中的一段进行CRC16校验/// </summary>/// <param name="data">校验数据,字节数组</param>/// <param name="offset">从头开始偏移几个byte</param>/// <param name="length">偏移后取几个字节byte</param>/// <returns>字节0是高8位,字节1是低8位</returns>public static byte[] CRCCalc(byte[] data, int offset, int length){byte[] Tdata = data.Skip(offset).Take(length).ToArray();return CRCCalc(Tdata);}}

modbus-rtu的使用

发送数据

现要读取变送器设备(地址 0x01)的风速值,文档如图所示

我们发送的命令帧应为

根据命令帧计算校验码(3种方式)

    byte[] buffer = new byte[] { 0x01, 0x03, 0x00, 0x00, 0x00, 0x01 };byte[] crc16 = CRC16.CRCCalc(buffer);  // 根据字节数组计算Console.WriteLine($"{crc16[0]:X2} {crc16[1]:X2}");string data1 = "0x01,0x03,0x00,0x00,0x00,0x02";string data1 = "0x01 0x03 0x00 0x00 0x00 0x02";byte[] crc16 = CRC16.CRCCalc(data1);    // 根据字符串计算Console.WriteLine($"{crc16[0]:X2} {crc16[1]:X2}");byte[] buffer = new byte[] { 0x01, 0x03, 0x00, 0x00, 0x00, 0x02, 0xC4, 0x0B };byte[] crc16 = CRC16Calc(buffer,0,6);  // 从字节数组中截取某部分计算Console.WriteLine($"{crc16[0]:X2} {crc16[1]:X2}");

将数据和校验码数组进行合并然后发送

    byte[] buffer = new byte[] { 0x01, 0x03, 0x00, 0x00, 0x00, 0x01 };byte[] crc16 = CRC16.CRCCalc(buffer);byte[] data = buffer.Concat(crc16).ToArray();serialPort.Write(data, 0, data.Length);

接收数据

我们将命令帧(请求帧、问询码)发送后,如果没有错误,从设备会返回对应的数据,如下读取变送器设备(地址 0x01)的实时风力等级值,将会返回如图所示的数据,我们需要将数据读取、校验、计算、展示

    // 假设这是从设备响应的数据// 0x01:设备地址码// 0x03:功能码// 0x02:读取到的数据字节// 0x00, 0x01:当前风力等级// 0x79, 0x84:校验码byte[] value = new byte[] { 0x01, 0x03, 0x02, 0x00, 0x01, 0x79, 0x84 };// 1、验证校验码是否正确byte[] crc = CRC16.CRCCalc(value, 0, value.Length - 2);if (crc[0] != value[value.Length - 2] || crc[1] != value[value.Length-1] ) {MessageBox.Show("数据校验错误,应忽略");return;}// 2、验证设备地址if (value[0] !=  0x01){MessageBox.Show("设备地址不正确");return;}// 3、计算数据// int v = value[3] * 256 + value[4];  // 因为这个数据占两个字节,每个字节最大255,相当于256进制,转换为10进制int v = (value[3] << 8) + value[4];       // 也可以使用左移运算符,高位左移8位,相当于乘2的8次方// 4、数据展示MessageBox.Show("风力等级:" + v);

tcp网口完整实现modbus-rtu协议

public partial class Form1 : Form
{/// <summary>/// 套接字/// </summary>Socket socket;/// <summary>/// IP地址/// </summary>string Ip = "192.168.107.5";/// <summary>/// 端口/// </summary>string Dk = "8016";/// <summary>/// 命令帧/// </summary>string Icommand = "01 03 00 00 00 02";public Form1(){InitializeComponent();button1.Enabled = false;checkBox1.Enabled = false;}/// <summary>/// 打开连接/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void button2_Click(object sender, EventArgs e){if (button2.Text == "连接网口"){try{socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);socket.Connect(Ip, int.Parse(Dk));button1.Enabled = true;checkBox1.Enabled = true;button2.Text = "断开";this.timer1.Start();}catch (Exception ex){MessageBox.Show(ex.Message);}}else{if (socket == null) return;socket.Close();button1.Enabled = false;checkBox1.Enabled = false;checkBox1.Checked = false;button2.Text = "连接网口";this.timer1.Stop();}}/// <summary>/// 刷新风速风向数据/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private async void button1_Click(object sender, EventArgs e){byte[] bs = new byte[1024];bs = StringToByte(Icommand);byte[] bb = CRCCalc(bs);bs = bs.Concat(bb).ToArray();await Task.Run(() =>{socket.Send(bs); // 发送请求帧byte[] body = new byte[1024];int length = socket.Receive(body); // 获取响应帧double value = (body[3] * 256+ body[4]) *0.01;double value2 = (body[5] * 256 + body[6]);this.Invoke(new Action(() =>{this.textBox1.Text = value.ToString() + "m/s";this.textBox2.Text = value2.ToString();}));});}/// <summary>/// 字符串转字节/// </summary>/// <param name="s"></param>/// <returns></returns>byte[] StringToByte(string s){string[] strings = s.Split(' ') ;byte[] bs = new byte[strings.Length];for (int i = 0; i < strings.Length; i++){bs[i] = Convert.ToByte(strings[i],16);}return bs;}/// <summary>/// CRC效验/// </summary>/// <param name="data"></param>/// <returns></returns>public static byte[] CRCCalc(byte[] data){//crc计算赋初始值int crc = 0xffff;for (int i = 0; i < data.Length; i++){//XOR//(1) 0^0=0,0^1=1  0异或任何数=任何数//(2) 1 ^ 0 = 1,1 ^ 1 = 0  1异或任何数-任何数取反//(3) 1 ^ 1 = 0,0 ^ 0 = 0  任何数异或自己=把自己置0//异或操作符是^。异或的特点是相同为false,不同为true。crc = crc ^ data[i]; //和^表示按位异或运算。//0x0fff ^ 0x01 Console.WriteLine(result.ToString("X")); // 输出结果为4094,即十六进制数1001for (int j = 0; j < 8; j++){int temp;temp = crc & 1; // & 运算符(与) 1 & 0 为 0  ;0 & 0 为0;1 & 1 为1//右移 (>>) 将第一个操作数向右移动第二个操作数所指定的位数,空出的位置补0。右移相当于整除. 右移一位相当于除以2;右移两位相当于除以4;右移三位相当于除以8。//int i = 7;//int j = 2;//Console.WriteLine(i >> j);   //输出结果为1crc = crc >> 1;crc = crc & 0x7fff;if (temp == 1){crc = crc ^ 0xa001;}crc = crc & 0xffff;}}//CRC寄存器的高低位进行互换byte[] crc16 = new byte[2];//CRC寄存器的高8位变成低8位,crc16[1] = (byte)((crc >> 8) & 0xff);//CRC寄存器的低8位变成高8位crc16[0] = (byte)(crc & 0xff);return crc16;}
}

使用NModbus4实现modbus-rtu协议

NModbus4是一个C#实现的Modbus库,它允许开发者以Modbus RTU的方式与工业设备进行通信。

安装NModbus4库。

通过NuGet安装NModbus4

串口实现NModbus4

public partial class Form1 : Form
{// 创建对象ModbusSerialMaster master;public Form1(){InitializeComponent();this.serialPort1.PortName = "COM2"; // 串口名this.serialPort1.BaudRate = 9600;this.serialPort1.DataBits = 8;this.serialPort1.Parity =System.IO.Ports.Parity.None;this.serialPort1.StopBits = System.IO.Ports.StopBits.One;serialPort1.Open();master = ModbusSerialMaster.CreateRtu(serialPort1);}/// <summary>///  读取数据/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private async void button1_Click(object sender, EventArgs e){// ReadHoldingRegistersAsync 异步读取数据// await 等待异步任务执行完之后 再往下执行// 参数1 从站地址, 参数2 起始地址  参数3:寄存器个数// values 元素个数和寄存器个数有关ushort[] values = await master.ReadHoldingRegistersAsync(1,0x00,3);comboBox1.DataSource = values;}/// <summary>/// 写入数据/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private async void button2_Click(object sender, EventArgs e){// 写入 单个的寄存器// 参数1 从站地址// 参数2 写入的地址// 参数3 写入的数据 // short 短整型// ushort 无符号的短整型await master.WriteSingleRegisterAsync(1,0x04,14);}
}

本文部分来源网络,如有侵权请联系作者删除!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/392555.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GroupMamba实战:使用GroupMamba实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整策略设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试完整的代码 在上…

26集 ESP32 AIchat启动代码分析-《MCU嵌入式AI开发笔记》

26集 ESP32 AIchat启动代码分析-《MCU嵌入式AI开发笔记》 这集我们分析代码如何组织起来&#xff0c;如何编译 先用sourceinsight把代码加进工程。 新建一个sourceinsight工程&#xff0c;把AI-CHAT代码加进来&#xff0c;之后把ESP IDF代码加进来&#xff0c;之后把ESP-ADF加…

大语言模型(LLM)文本预处理实战

大语言模型&#xff08;LLM&#xff09;文本预处理实战 文章目录 大语言模型&#xff08;LLM&#xff09;文本预处理实战2.1 理解词嵌入2.2 文本分词2.3 将 token 转换为 token ID2.4 添加特殊上下文 token2.5 字节对编码 (BytePair Encoding, BPE)2.6 使用滑动窗口进行数据采样…

sql注入部分总结和复现

一个端口对应一个服务 联合查询注入 所有的程序中&#xff0c;单双引号必须成对出现 需要从这个引号里面逃出来 在后面查询内容 ?id1 要查库名&#xff0c;表名&#xff0c;列名。但是联合查询要知道有多少列&#xff0c;所以通过order by 去查询 order by # 通过二分法…

Cartopy简介和安装

Cartopy 是一个开源免费的第三方 Python 扩展包&#xff0c;由英国气象办公室的科学家们开发&#xff0c;支持 Python 2.7 和 Python 3&#xff0c;致力于使用最简单直观的方式生成地图&#xff0c;并提供对 matplotlib 友好的协作接口。初学Cartopy&#xff0c;欢迎指正&#…

算法回忆录(3)

11. 假设有7个物品&#xff0c;它们的重量和价值如下表所示。若这些物品均不能被分割&#xff0c;且背包容量M&#xff1d;150&#xff0c;设计算法求解怎么装才能使得获取的价值最大&#xff1f;请写出伪代码。 #include <stdio.h>#define MAX_ITEMS 100 #define …

新手小白嵌入式单片机教程,ESP32

1.什么是ESP32。 ESP32是一款由乐鑫信息科技&#xff08;Espressif Systems&#xff09;推出的高度集成的低功耗系统级芯片&#xff08;SoC&#xff09;&#xff0c;它结合了双核处理器、无线通信、低功耗特性和丰富的外设&#xff0c;特别适用于各种物联网&#xff08;IoT&am…

Robot Operating System——深度解析单线程执行器(SingleThreadedExecutor)执行逻辑

大纲 创建SingleThreadedExecutor新增Nodeadd_nodetrigger_entity_recollectcollect_entities 自旋等待get_next_executablewait_for_workget_next_ready_executableTimerSubscriptionServiceClientWaitableAnyExecutable execute_any_executable 参考资料 在ROS2中&#xff0c…

python 爬虫入门实战——爬取维基百科“百科全书”词条页面内链

1. 简述 本次爬取维基百科“百科全书”词条页面内链&#xff0c;仅发送一次请求&#xff0c;获取一个 html 页面&#xff0c;同时不包含应对反爬虫的知识&#xff0c;仅包含最基础的网页爬取、数据清洗、存储为 csv 文件。 爬取网址 url 为 “https://zh.wikipedia.org/wiki/…

数据结构:基于顺序表实现通讯录系统(含源码)

目录 一、前言 二、各个功能的实现 2.1 初始化通讯录 2.2 添加通讯录数据 2.3 查找通讯录数据 2.4 删除通讯录数据 2.5 修改通讯录数据 2.6 展示通讯录数据​编辑 2.7 销毁通讯录数据 三、添加菜单和测试 四、完整源码 sxb.h sxb.c contact.h contact.c test.c 一、前…

【隐私计算篇】混淆电路之深入浅出

入门隐私计算的阶段&#xff0c;一般都会涉及对于混淆电路的学习&#xff0c;这是因为混淆电路是多方安全计算中的基础密码原语&#xff0c;也是隐私保护中重要的技术。为了帮助更好地理解混淆电路的原理&#xff0c;今天对其进行原理以及相关优化手段进行解析和分享。 1. 混淆…

不同角色路由权限配置(六)

一、启用方式 配置开启config/config.ts。同时需要 src/access.ts 提供权限配置 export default {access: {},// access 插件依赖 initial State 所以需要同时开启initialState: {}, };这里以扩展的路由配置为例&#xff0c;配置只有admin权限才能查看的页面 1、在src/acces…

前端web开发HTML+CSS3+移动web(0基础,超详细)——第3天

目录 一&#xff0c;列表-无序和有序的定义列表 二&#xff0c;表格-基本使用与表格结构标签 三&#xff0c;合并单元格 四&#xff0c;表单-input标签 五&#xff0c;表单-下拉菜单 六&#xff0c;表单-文本域 七&#xff0c;表单-label标签 八&#xff0c;表单-按钮 …

【已解决】页面操作系统功能,诡异报错500nginx错误

【已解决】页面操作系统功能&#xff0c;诡异报错500nginx错误&#xff0c;后台没有任何报错信息 不知道啥原因 清理了浏览器缓存 也没有效果 还有一个表现情况&#xff0c;同样的操作&#xff0c;有时可以又是不行 因为报错ng的代理问题&#xff0c;检查了ng配置 后续经过同…

【C/C++】C语言和C++实现Stack(栈)对比

我们初步了解了C&#xff0c;也用C语言实现过栈&#xff0c;就我们当前所更新过的有关C学习内容以栈为例子&#xff0c;来简单对比一下C语言和C。 1.C中栈的实现 栈的C语言实现在【数据结构】栈的概念、结构和实现详解-CSDN博客 &#xff0c;下面是C实现的栈&#xff0c; 在St…

OD C卷 - 多线段数据压缩

多段 线 数据压缩 &#xff08;200&#xff09; 如图中每个方格为一个像素&#xff08;i&#xff0c;j&#xff09;&#xff0c;线的走向只能水平、垂直、倾斜45度&#xff1b;图中线段表示为(2, 8)、&#xff08;3,7&#xff09;、&#xff08;3, 6&#xff09;、&#xff08…

学习STM32(2)--STM32单片机GPIO应用

目录 1 引 言 2 实验目的 3 实验内容 3.1掌握STM32F103的GPIO控制 3.1.1 GPIO的分组 3.1.2 GPIO的常用功能 3.1.3 STM32单片机GPIO位结构 3.1.4 STM32单片机GPIO工作模式 3.1.5 STM32的GPIO 输出-点亮LED编程要点 使用GPIO时&#xff0c;按下面步骤进行&#xff1…

部署服务器项目及发布

当技术总监直接丢给我一个服务器账号密码时&#xff0c;我该怎么完成映射本机&#xff1b;配置网关&#xff1b;配置代理和发布项目呢&#xff1f; 我使用的是putty远程登录到服务器 输入ip后&#xff0c;点open 输入账号密码 登录的账号如果不是root&#xff1b;使用sudo su…

sqllab靶场练习第1~15关

1、第一关 代码解析 if(isset($_GET[id]))//判断获取的id字段是否为空 { $id$_GET[id]; //logging the connection parameters to a file for analysis. $fpfopen(result.txt,a);//打开这个文件&#xff0c;记录操作的日志 fwrite($fp,ID:.$id."\n"); fclose($fp);…

【C++高阶】深入理解C++异常处理机制:从try到catch的全面解析

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;Lambda表达式 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀C异常 &#x1f4d2;1. C异常概念…