Modbus RTU 通信工具设计
Modbus 是一个工业上常用的通讯协议、一种通讯约定。
ModBus 协议是应用层报文传输协议(OSI 模型第7层),它定义了一个与通信层无关的协议数据单元(PDU),即PDU=功能码+数据域。
ModBus 协议能够应用在不同类型的总线或网络。对应不同的总线或网络,Modbus 协议引入一些附加域映射成应用数据单元(ADU),即ADU=附加域+PDU。目前,Modbus 有下列三种通信方式:
1. 以太网,对应的通信模式是Modbus TCP。
2. 异步串行传输(各种介质如有线RS-232-/422/485/;光纤、无线等),对应的通信模式是 Modbus RTU 或 Modbus ASCII。
Modbus 的ASCII、RTU 协议规定了消息、数据的结构、命令和应答的方式,数据通讯采用Maser/Slave方式。
3. 高速令牌传递网络,对应的通信模式是Modbus PLUS。
Modbus 需要对数据进行校验,串行协议中除有奇偶校验外,ASCII 模式采用LRC 校验;RTU 模式采用16位CRC 校验;TCP 模式没有额外规定校验,因为TCP 是一个面向连接的可靠协议。
Modbus 协议的应用中,最常用的是Modbus RTU 传输模式。
RTU 传输模式
当设备使用RTU (Remote Terminal Unit) 模式在 Modbus 串行链路通信, 报文中每个8位字节含有两个4位十六进制字符。这种模式的主要优点是较高的数据密度,在相同的波特率下比ASCII 模式有更高的吞吐率。每个报文必须以连续的字符流传送。
RTU 模式每个字节 ( 11 位 ) 的格式为:
编码系统: 8位二进制。 报文中每个8位的字节含有两个4位十六进制字符(0–9, A–F)
Bits per Byte: 1 起始位
8 数据位, 首先发送最低有效位
1 位作为奇偶校验
1 停止位
偶校验是要求的,其它模式 ( 奇校验, 无校验 ) 也可以使用。为了保证与其它产品的最大兼容性,同时支持无校验模式是建议的。默认校验模式模式 必须为偶校验。注:使用无校验要求2 个停止位。
字符的串行传送方式:
每个字符或字节均由此顺序发送(从左到右):最低有效位 (LSB) . . . 最高有效位 (MSB)
图1:RTU 模式位序列
设备配置为奇校验、偶校验或无校验都可以接受。如果无奇偶校验,将传送一个附加的停止位以填充字符帧:
图2:RTU 模式位序列 (无校验的特殊情况)
帧检验域:循环冗余校验 (CRC)
在RTU 模式包含一个对全部报文内容执行的,基于循环冗余校验 (CRC - Cyclical Redundancy Checking) 算法的错误检验域。
CRC 域检验整个报文的内容。不管报文有无奇偶校验,均执行此检验。
CRC 包含由两个8位字节组成的一个16位值。
CRC 域作为报文的最后的域附加在报文之后。计算后,首先附加低字节,然后是高字节。CRC 高字节为报文发送的最后一个子节。
附加在报文后面的CRC 的值由发送设备计算。接收设备在接收报文时重新计算 CRC 的值,并将计算结果于实际接收到的CRC 值相比较。如果两个值不相等,则为错误。
CRC 的计算,开始对一个16位寄存器预装全1。 然后将报文中的连续的8位子节对其进行后续的计算。只有字符中的8个数据位参与生成CRC 的运算,起始位,停止位和校验位不参与 CRC 计算。
CRC 的生成过程中, 每个 8–位字符与寄存器中的值异或。然后结果向最低有效位(LSB)方向移动(Shift) 1位,而最高有效位(MSB)位置充零。 然后提取并检查 LSB:如果LSB 为1, 则寄存器中的值与一个固定的预置值异或;如果LSB 为 0, 则不进行异或操作。
这个过程将重复直到执行完8次移位。完成最后一次(第8次)移位及相关操作后,下一个8位字节与寄存器的当前值异或,然后又同上面描述过的一样重复8次。当所有报文中子节都运算之后得到的寄存器中的最终值,就是CRC。
当CRC 附加在报文之后时,首先附加低字节,然后是高字节。
CRC 算法如下:
private bool CheckResponse(byte[] response) {//Perform a basic CRC check:byte[] CRC = new byte[2];GetCRC(response, ref CRC);if (CRC[0] == response[response.Length - 2] && CRC[1] == response[response.Length - 1])return true;elsereturn false; }private void GetCRC(byte[] message, ref byte[] CRC) {//Function expects a modbus message of any length as well as a 2 byte CRC array in which to //return the CRC values:ushort CRCFull = 0xFFFF;byte CRCHigh = 0xFF, CRCLow = 0xFF;char CRCLSB;for (int i = 0; i < (message.Length) - 2; i++){CRCFull = (ushort)(CRCFull ^ message[i]);for (int j = 0; j < 8; j++){CRCLSB = (char)(CRCFull & 0x0001);CRCFull = (ushort)((CRCFull >> 1) & 0x7FFF);if (CRCLSB == 1)CRCFull = (ushort)(CRCFull ^ 0xA001);}}CRC[1] = CRCHigh = (byte)((CRCFull >> 8) & 0xFF);CRC[0] = CRCLow = (byte)(CRCFull & 0xFF); }
帧描述 (如下图所示) :
图3:RTU 报文帧
注意:Modbus RTU 帧最大为256字节。
下面是我为公司设计的一个 Modbus RTU 通信测试小工具,界面截图如下:
图4:Modbus RTU 通信工具
我的通用Modbus RTU 动态库,modbus.cs 如下:
using System; using System.Collections.Generic; using System.Text; using System.IO.Ports; using System.Threading;namespace SerialPort_Lib {public class modbus{private SerialPort sp = new SerialPort();public string modbusStatus;#region Constructor / Deconstructorpublic modbus(){}~modbus(){}#endregion#region Open / Close Procedurespublic bool Open(string portName, int baudRate, int databits, Parity parity, StopBits stopBits){//Ensure port isn't already opened:if (!sp.IsOpen){//Assign desired settings to the serial port:sp.PortName = portName;sp.BaudRate = baudRate;sp.DataBits = databits;sp.Parity = parity;sp.StopBits = stopBits;//These timeouts are default and cannot be editted through the class at this point:sp.ReadTimeout = -1;sp.WriteTimeout = 10000;try{sp.Open();}catch (Exception err){modbusStatus = "Error opening " + portName + ": " + err.Message;return false;}modbusStatus = portName + " opened successfully";return true;}else{modbusStatus = portName + " already opened";return false;}}public bool Close(){//Ensure port is opened before attempting to close:if (sp.IsOpen){try{sp.Close();}catch (Exception err){modbusStatus = "Error closing " + sp.PortName + ": " + err.Message;return false;}modbusStatus = sp.PortName + " closed successfully";return true;}else{modbusStatus = sp.PortName + " is not open";return false;}}#endregion#region CRC Computationprivate void GetCRC(byte[] message, ref byte[] CRC){//Function expects a modbus message of any length as well as a 2 byte CRC array in which to //return the CRC values:ushort CRCFull = 0xFFFF;byte CRCHigh = 0xFF, CRCLow = 0xFF;char CRCLSB;for (int i = 0; i < (message.Length) - 2; i++){CRCFull = (ushort)(CRCFull ^ message[i]);for (int j = 0; j < 8; j++){CRCLSB = (char)(CRCFull & 0x0001);CRCFull = (ushort)((CRCFull >> 1) & 0x7FFF);if (CRCLSB == 1)CRCFull = (ushort)(CRCFull ^ 0xA001);}}CRC[1] = CRCHigh = (byte)((CRCFull >> 8) & 0xFF);CRC[0] = CRCLow = (byte)(CRCFull & 0xFF);}#endregion#region Build Messageprivate void BuildMessage(byte address, byte type, ushort start, ushort registers, ref byte[] message){//Array to receive CRC bytes:byte[] CRC = new byte[2];message[0] = address;message[1] = type;message[2] = (byte)(start >> 8);message[3] = (byte)start;message[4] = (byte)(registers >> 8);message[5] = (byte)registers;GetCRC(message, ref CRC);message[message.Length - 2] = CRC[0];message[message.Length - 1] = CRC[1];}#endregion#region Check Responseprivate bool CheckResponse(byte[] response){//Perform a basic CRC check:byte[] CRC = new byte[2];GetCRC(response, ref CRC);if (CRC[0] == response[response.Length - 2] && CRC[1] == response[response.Length - 1])return true;elsereturn false;}#endregion#region Get Responseprivate void GetResponse(ref byte[] response){//There is a bug in .Net 2.0 DataReceived Event that prevents people from using this//event as an interrupt to handle data (it doesn't fire all of the time). Therefore//we have to use the ReadByte command for a fixed length as it's been shown to be reliable.for (int i = 0; i < response.Length; i++){response[i] = (byte)(sp.ReadByte());}}#endregion#region GetModbusData 获得接收数据public bool GetModbusData(ref byte[] values){//Ensure port is open:if (sp.IsOpen){// 等待线程进入 //Monitor.Enter(sp);//Clear in/out buffers://sp.DiscardOutBuffer();//sp.DiscardInBuffer();//Message is 1 addr + 1 type + N Data + 2 CRCtry{//GetResponse(ref readBuffer);//string str = readBuffer.ToString();int count = sp.BytesToRead;if (count > 0){byte[] readBuffer = new byte[count];GetResponse(ref readBuffer);// readData = new byte[29];// Array.Copy(readBuffer, readData, readData.Length);// CRC 验证if (CheckResponse(readBuffer)){//显示输入数据values = readBuffer;modbusStatus = "Write successful";sp.DiscardInBuffer();//values = System.Text.Encoding.ASCII.GetString(readData);return true;}else{modbusStatus = "CRC error";sp.DiscardInBuffer();return false;}}else return false;}catch (Exception err){modbusStatus = "Error in write event: " + err.Message;sp.DiscardInBuffer();return false;}//finally//{// 通知其它对象//Monitor.Pulse(sp);// 释放对象锁 //Monitor.Exit(sp);//}}else{modbusStatus = "Serial port not open";return false;}}#endregion#region SendModbusData 打包发送数据public bool SendModbusData(ref byte[] values){//Ensure port is open:if (sp.IsOpen){//Clear in/out buffers:sp.DiscardOutBuffer();sp.DiscardInBuffer();//Function 3 response buffer:byte[] response = new byte[values.Length + 2];Array.Copy(values, response, values.Length);//BuildMessage(address, (byte)3, start, registers, ref message);//打包带有 CRC 验证的modbus 数据包:byte[] CRC = new byte[2];GetCRC(response, ref CRC);response[response.Length - 2] = CRC[0];response[response.Length - 1] = CRC[1];values = response; //返回带有 CRC 验证的modbus 数据包//Send modbus message to Serial Port:try{sp.Write(response, 0, response.Length);//GetResponse(ref response);return true;}catch (Exception err){modbusStatus = "Error in read event: " + err.Message;return false;}//Evaluate message://if (CheckResponse(response))//{// //Return requested register values:// for (int i = 0; i < (response.Length - 5) / 2; i++)// {// values[i] = response[2 * i + 3];// values[i] <<= 8;// values[i] += response[2 * i + 4];// }// modbusStatus = "Read successful";// return true;//}//else//{// modbusStatus = "CRC error";// return false;//}}else{modbusStatus = "Serial port not open";return false;}}#endregion} }
调用的主要代码如下:
public partial class FormConfig : Form,IModbusData {//业务处理类B_ModbusData ModbusDataBLL = new B_ModbusData();modbus mb = new modbus();//SerialPort sp = new SerialPort();System.Timers.Timer timer = new System.Timers.Timer();public FormConfig(){InitializeComponent();timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);}#region Timer Elapsed 事件处理程序bool runEnd = true;void timer_Elapsed(object sender, ElapsedEventArgs e){if (runEnd == true){runEnd = false;PollFunction();runEnd = true;}}//定时器调用方法private void PollFunction(){byte[] values = null;try{mb.GetModbusData(ref values);//while (!mb.SendFc3(Convert.ToByte(txtSlaveID.Text), pollStart, pollLength, ref values)) ;}catch (Exception err){DoGUIStatus("Error in modbus read: " + err.Message);}if (values != null){//业务处理byte[] sendData = ModbusDataProcess(values);}}#endregion#region IModbusData 接口成员处理public byte[] ModbusDataProcess(byte[] _data){byte[] sendData = ModbusDataBLL.ModbusDataProcess(_data);// CRC验证,并打包发送数据。mb.SendModbusData(ref sendData);return sendData;}#endregion }
其实,三步就能成功调用:
modbus mb = new modbus(); mb.GetModbusData(ref values); // 从串口设备获得数据。 byte[] sendData = ModbusDataBLL.ModbusDataProcess(values); // 你的业务处理,并产生最终返回数据。 mb.SendModbusData(ref sendData); // CRC验证,并打包发送数据。
主要代码已全部提供,由于工作原因暂不提供完整工具源代码,见谅!
(完)
本文转自钢钢博客园博客,原文链接:http://www.cnblogs.com/xugang/archive/2012/12/13/2815800.html,如需转载请自行联系原作者
Modbus RTU 通信工具设计相关推荐
- 倍福EL6021使用Modbus RTU通信例程
EL6021简介 EL6021是倍福推出的RS422/485串口通信模块,一般用作Modbus RTU通信的主站,EL6021是单口RS422/485模块,EL6022是双口(DB9)RS422/48 ...
- Modbus RTU 通信应用案例
如何打开项目归档文件 例程中的TIA博途项目文件与STEP 7项目文件均为归档文件,需要按如下方式打开: TIA博途项目文件 1. 打开TIA博途软件,通过软件左下方"项目视图"按 ...
- java通信rs485_基于VB6.0与485仪表的Modbus RTU通信(含代码)
JZGKCHINA工控技术分享平台 尊重原创 勿抄袭 勿私放其他平台 前言 在写这边文章时,很纠结,到底要不要写呢?原因主要有3个:第一.VB6.0这么古老的语言现在还有人用吗?现在不是很流行C#.P ...
- 欧姆龙 PLC CP1E Modbus RTU通信程序
欧姆龙 PLC CP1E Modbus RTU通信程序,一个多站轮询通信,一个单站通信,稍微更改下Modbus通信地址可以跟其他Modbus设备进行通信!id=635998335748&
- S7-1200 PLC与PAC3200电能表的Modbus RTU通信(详细步骤)
S7-1200 PLC与PAC3200电能表的Modbus RTU通信 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到网站. 本文将主要介绍如何使用Mod ...
- 丰炜vb0和两台变频器的modbus rtu通信程序
丰炜vb0和两台变频器的modbus rtu通信程序 史上最好用的plc modbus rtu通讯程序, 通讯效果如视频,控制,反馈无延迟,真心好用,别的变频器支持modbus rtu一样可以使用. ...
- 丰炜vb0和两台变频器的modbus rtu通信程序 史上最好用的plc modbus rtu通讯程序, 通讯效果如视频,控制,反馈无延迟
丰炜vb0和两台变频器的modbus rtu通信程序 史上最好用的plc modbus rtu通讯程序, 通讯效果如视频,控制,反馈无延迟,真心好用,别的变频器支持modbus rtu一样可以使用id ...
- 欧姆龙 PLC CP1E 与电子称重仪表“柯力XK3101”Modbus RTU通信
欧姆龙 PLC CP1E 与电子称重仪表"柯力XK3101"Modbus RTU通信,稍微更改下Modbus通信地址可以跟其他Modbus设备进行通信! YID:554563599 ...
- 欧姆龙 PLC CP1E 与电子称重仪表“柯力XK3101”Modbus RTU通信,稍微更改下Modbus通信地址可以跟其他Modbus设备进行通信
欧姆龙 PLC CP1E 与电子称重仪表"柯力XK3101"Modbus RTU通信,稍微更改下Modbus通信地址可以跟其他Modbus设备进行通信 YID:8945635998 ...
最新文章
- 源泉书签,助您管理海量收藏。www.yuanquanshuqian.com 今日更新:支持了导入url为js代码的书签...
- python多态的概念_python中的多态
- 机器学习(四)——SVM(2)
- Linux系统管理之用户和组
- CPU负载均衡之loadavg计算
- 关于在ElasticSearch中使用now函数进行时间范围过滤查询的问题
- 大数据告诉你,中国哪里出美女
- php中运算符的作用,在php中,双反位运算符(~~)(也称为“双颚化符”)的作用是什么?...
- Lio_sam运行测试环节遇到的问题以及实测总结
- 实现Photoshop液化工具箱中向前变形工具
- 用 zCloud 解锁智能运维,“以小为美”的苏州银行成就数字化转型“大作为”...
- 2022/06/03密码
- 怎样配置Ruckus AP
- 前端好还是后端好,看看7年前端和后端怎么说
- SAP ABAP 报表新增字段,重新生成维护视图,以及过程中产生的问题处理
- 云端服务器维护,云端服务器维护
- mysql登录密码特殊字符_MySQL用户密码中的特殊字符叹号(!)的妙用
- 26+富有创意且响应式的Drupal 7主题
- 2022 CCF中国开源大会—开放原子开源创新发展论坛顺利举办!
- 同一网络下不同路由器连接打印机(多设备组网)