Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。
所以这也是我们工控领域软件开发的所必懂的通讯协议,我也是初次学习,先贴上我的学习笔记
一 .协议概述
(1)Modbus协议是应用于控制器上的一种通用语言,实现控制器之间,控制器通过网络和其他设备之间的通信,支持传统RS232/RS422/RS485和以太网设备,它已经成为一种通用的工业标准,有了它不同厂商生产的控制设备可以连成工业网络,进行集中控制,此协议定义了一个控制器能认识使用的消息结构
(2) 如果按照国际 ISO/OSI 的 7 层网络模型来说,标准 MODBUS 协议定义了通信物理层、链路层及应用层;
物理层:定义了基于 RS232 和 RS485 的异步串行通信规范;
链路层:规定了基于站号识别、主 / 从方式的介质访问控制;
应用层:规定了信息规范(或报文格式)及通信服务功能;
二. 协议要点
(1) MODBUS 是主 / 从通信协议。主站主动发送报文 , 只有与主站发送报文中呼叫地址相同的从站才向主站发送回答报文。
(2) 报文以 0 地址发送时为广播模式,无需从站应答,可作为广播报文发送,包括:
①修改线圈状态;
②修改寄存器内容;
③强置多线圈;
④预置多寄存器;
⑤询问诊断;
(3) MODBUS 规定了 2 种字符传输模式: ASCII 模式、 RTU (二进制)模式;两种传输模式不能混用;
(4) 传输错误校验
传输错误校验有奇偶校验、冗余校验检验。
当校验出错时,报文处理停止,从机不再继续通信,不对此报文产生应答;
通信错误一旦发生,报文便被视为不可靠; MODBUS 主机在一定时间过后仍未收到从站应答,即作出“通信错误已发生”的判断。
(5) 报文级(字符级)采用 CRC-16 (循环冗余错误校验)
(6) MODBUS 报文 RTU 格式
三. 异常应答
(1) 从机接收到的主机报文,没有传输错误,但从机无法正确执行主机命令或无法作出正确应答,从机将以“异常应答”回答之。
(2) 异常应答报文格式
例:主机发请求报文,功能码 01 :读 1 个 04A1 线圈值
由于从机最高线圈地址为 0400 ,则 04A1 超地址上限,从机作出异常应答如下(注意:功能码最高位置 1 ):
(3)异常应答码
四. 寄存器和功能码
modbus的功能码很多,且不同功能码对应的报文也不一致,后续博客我会借用开源库实现一个modbus master 测试功能码 解析报文
下边我用表格总结一下寄存器,功能码,报文格式
注:
(1)报文中的所有字节均为16进制
(2)由上图我们总结出不同的功能码的报文(无论询问报文还是响应报文)前8个字节都是一致的 都是2字节消息号+2字节ModBus标识+2字节长度+1字节站号+1字节功能码 后边根据功能码不同而不同
(3)报文中,指定线圈通断标志 FF00 置线圈为ON 0000置线圈为OFF
五.具体实现
接下来我们使用开源库NModbus库,来实现一个Modbus master
创建工程,从NuGet管理器安装NModbusu
先简单介绍一下NModbus中的几个重要方法
接下来做具体实现
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 using System.Windows.Forms; 10 using NModbus; 11 using System.Net.Sockets; 12 using System.Threading; 13 14 namespace ModbusTcp 15 { 16 public partial class Form1 : Form 17 { 18 19 private static ModbusFactory modbusFactory; 20 private static IModbusMaster master; 21 //写线圈或写寄存器数组 22 bool[] coilsBuffer; 23 ushort[] registerBuffer; 24 //功能码 25 string functionCode; 26 //参数(分别为站号,起始地址,长度) 27 byte slaveAddress; 28 ushort startAddress; 29 ushort numberOfPoints; 30 31 public Form1() 32 { 33 InitializeComponent(); 34 35 } 36 private void Form1_Load(object sender, EventArgs e) 37 { 38 //初始化modbusmaster 39 modbusFactory = new ModbusFactory(); 40 //在本地测试 所以使用回环地址,modbus协议规定端口号 502 41 master = modbusFactory.CreateMaster(new TcpClient("127.0.0.1", 502)); 42 //设置读取超时时间 43 master.Transport.ReadTimeout = 2000; 44 master.Transport.Retries = 2000; 45 groupBox1.Enabled = false; 46 groupBox2.Enabled = false; 47 } 48 /// <summary> 49 /// 读/写 50 /// </summary> 51 /// <param name="sender"></param> 52 /// <param name="e"></param> 53 private void button1_Click(object sender, EventArgs e) 54 { 55 ExecuteFunction(); 56 } 57 58 private async void ExecuteFunction() 59 { 60 try 61 { 62 //重新实例化是为了 modbus slave更换连接时不报错 63 master = modbusFactory.CreateMaster(new TcpClient("127.0.0.1", 502)); 64 if (functionCode != null) 65 { 66 switch (functionCode) 67 { 68 case "01 Read Coils"://读取单个线圈 69 SetReadParameters(); 70 coilsBuffer = master.ReadC