〇 写在前面
本人本科硕士皆是天坑专业,后自学转行去做了嵌入式软件开发,曾在华为和大疆等公司任职。
在转行自学的过程中,困难艰辛不必多说,幸运的是得到了网上很多大佬的帮助。现在不敢说小有所成,也算是偶有心得,回顾一路以来的困顿苦恼,决定把自己的一些经验拙见写成博客带给大家,希望能够帮助大家。也欢迎大家积极留言互动问问题,看到消息都会回复的。
最后,如果觉得文章有用或者能够帮助到你,麻烦点个赞给个关注支持一下,你的点赞关注是对我的最大肯定和支持,也是我不断创作的动力,同时也让我满足一下虚荣心,哈哈~
文章目录
一 啥是modbus
modbus 是一家名为Modicon开发的总线协议,于是取公司名缩写(mod)加上总线协议(bus),组成了名字modbus。
modbus区别于底层硬件,是一种协议。
举个例子,如果将modbus比作汉语的话,那么通过打电话,发邮件,或者用嘴巴说,都可以使用汉语,打电话,发邮件,用嘴巴说就是底层实现的硬件,汉语就是上层的协议,无论用什么硬件实现,只要协议是相同的,就可以正常沟通。
二 分类
按照传输方式来分的话,modbus分为以下几类:
1.串行通信:
- Modbus RTU:
运行在串口上的协议,采用二进制表现形式以及紧凑型数据结构,通信效率高,应用广泛。它适用于长距离、低速率的通信场合,如使用RS-485、RS-232等串行接口进行通信。 - Modbus ASCII:
也是运行在串口上的协议,但采用ASCII码传输数据,并利用特殊字符作为其字节的开始与结束标识。由于每个数据字节都被转换为两个ASCII字符,因此其传输效率远低于Modbus RTU,通常只在通信数据量较小的情况下使用。
2.以太网通信:
- Modbus TCP/IP:
运行在以太网上的协议,采用TCP/IP协议进行通信。它支持全双工通信,即两个方向上的数据传输可以同时进行,适用于需要远程控制和监控的场合。Modbus TCP/IP的端口号默认为502。
简单来说,用串口的是RTU和ASCII,用网线的是TCP/IP。RTU最常用,其次是TCP/IP,ASCII不常用。
根据Modbus协议的规定,如果一个设备支持Modbus协议,它通常会默认支持Modbus RTU模式,故Modbus RTU是使用最广泛的Modbus协议,本文以Modbus RTU为例进行讲解,以下如无特别说明,均为Modbus RTU模式。
三 协议详解
1.通信架构
Modbus RTU采用主从式通讯架构,即网络中只能有一个主站(Master)存在,负责发起通信请求,而从站(Slave)则响应这些请求。
主站在Modbus网络上没有地址,而每个从站必须有唯一的地址,地址范围通常为1到247(其中0为广播地址,用于向所有从站发送消息)。
Modbus RTU可以是全双工也可以半双工,具体取决于其使用的物理接口,常用的有RS485(半双工),RS422(全双工),RS232(全双工)等。
2. 数据传输方式
Modbus RTU协议主要位于应用层。它定义了主站和从站之间交换数据的格式和规则,包括设备地址、功能码、数据域等关键元素。
Modbus RTU协议中数据字节的传输顺序大端模式,即先发送最高位。
3.帧格式:
帧格式如下图:
解析:
- 设备地址:
占用1个字节(8位),用于识别网络上的特定设备。
合法的设备地址范围是1~247(0通常用作广播地址)。 - 功能码:
占用1个字节,用于指示要执行的操作或响应的类型。
功能码决定了后续数据域的结构和内容。 - 数据域:
其长度和内容由功能码决定。
包含了操作所需的具体数据,如寄存器地址、读取/写入的数据量等。 - CRC校验(错误校验码):
用于检测数据传输过程中的错误。
在Modbus RTU中,通常使用CRC-16校验方法,占用2个字节。 - 结束符:
Modbus RTU协议要求每帧报文前后至少需要间隔3.5个字符的传输时间(以波特率计算的时间间隔),以此来区分不同的报文帧。
举个例子:
如果采用波特率9600的串口通信,且底层帧格式为1个起始位、8个数据位和1个停止位,则每一帧数据包含10个位,这个一帧数据也对应上文中的一个字符。
则一帧数据(一个字符)的传输时间为:10 / 9600 ≈ 1.04ms。
帧间间隔 ≈ 3.5 × 1.04ms ≈ 3.64ms。所以在这种情况下,每帧报文间隔至少为3.64ms。
四 数据类型
modbus 共有四种基本数据类型:
-
线圈(Coils/Discrete Outputs)
定义:
位数据表示单个开关量的状态,如开/关、真/假等。在Modbus中,位数据通常用于控制设备的输出,如继电器、指示灯等。
特点:
只能读取和写入单个位(bit)。 使用功能码01(读线圈状态)或05(写单个线圈)进行读写操作。
应用场景:
常用于控制设备的开关状态,如启动/停止按钮、报警指示灯等。 -
离散量输入(Discrete Inputs)
定义:
离散量输入表示输入的状态,如传感器信号、按钮状态等。这些输入状态是离散的,即它们只能取有限个值(如开/关)。
特点:
只能读取,不能写入。使用功能码02(读离散输入状态)进行读操作。
应用场景:
用于读取外部设备的状态信息,如按钮是否被按下、传感器是否触发等。 -
输入寄存器(Input Registers)
定义:
输入寄存器表示无符号的16位整数(Word),用于存储来自输入设备的数据。这些数据可以是传感器读数、计数器值等。
特点:
只能读取,不能写入。使用功能码04(读输入寄存器)进行读操作。
应用场景:
用于读取外部设备的数据,如温度传感器的读数、流量计的计数值等。 -
保持寄存器(Holding Registers)
定义:
保持寄存器表示有符号或无符号的16位整数(Word),用于存储内部处理的数据或控制参数。这些数据可以是设备的设置参数、运行状态等。
特点:
可以读取和写入。使用功能码03(读保持寄存器)、06(写单个保持寄存器)或16(写多个保持寄存器)等进行读写操作。
应用场景:
用于存储和处理内部数据,如设置设备的控制参数、读取设备的运行状态等。
五 功能码
以下是一些常见的功能码:
功能码 | 名称 | 描述 |
---|---|---|
01 | 读取线圈状态 | 用于读取从站设备中一个或多个线圈(数字量输出)的状态 |
02 | 读取离散输入状态 | 用于读取从站设备中一个或多个离散输入(数字量输入)的状态 |
03 | 读取保持寄存器 | 用于读取从站设备中一个或多个保持寄存器的值 |
04 | 读取输入寄存器 | 用于读取从站设备中一个或多个输入寄存器的值 |
05 | 写单个线圈 | 用于将从站设备中的一个线圈设置为指定状态(ON或OFF) |
06 | 写单个保持寄存器 | 用于将从站设备中的一个保持寄存器设置为指定的值 |
15 | 写多个线圈 | 用于同时将从站设备中的多个线圈设置为指定的状态(ON或OFF) |
16 | 写多个寄存器 | 用于将从站设备中的多个保持寄存器设置为指定的值 |
六.实例
功能码01:读取线圈状态
- 从地址123的设备上,读取离散量输出20~38(离散量从1开始)。
请求帧
从站地址(1字节) | 功能码(1字节) | 起始地址(2字节) | 线圈数量(2字节) | CRC校验码(2字节) |
---|---|---|---|---|
7B | 01 | 00 13 | 00 13 | 98 87 |
响应帧
从站地址(1字节) | 功能码(1字节) | 字节数(1字节) | 数据区(3字节) | CRC校验码(2字节) |
---|---|---|---|---|
7B | 01 | 03 | CD 6B 05 | 18 49 |
数据解释:
在返回的数据中共三个字节,从左往右数,第一个字节代表27~20的离散量状态,27在MSB,20在LSB,以此类推,第三个字节只有低位的三位有效,代表38 ~ 36 三个离散量状态,剩余的五个位用0填充。
0xCD = 0b11001101 即:
离散量编号 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 |
---|---|---|---|---|---|---|---|---|
状态 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 1 |
0x6b=0b01101011 即:
离散量编号 | 35 | 34 | 33 | 32 | 31 | 30 | 29 | 28 |
---|---|---|---|---|---|---|---|---|
状态 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 1 |
0x05 = 0b00000101
离散量编号 | - | - | - | - | - | 38 | 37 | 36 |
---|---|---|---|---|---|---|---|---|
状态 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
最后用0填充高位
功能码02:读取离散输入状态
- 从地址123的设备上,读取离散量输出197~218(离散量从1开始)。
请求帧数据
从站地址(1字节) | 功能码(1字节) | 起始地址(2字节) | 输入数量(2字节) | CRC校验码(2字节) |
---|---|---|---|---|
7B | 02 | 00 C4 | 00 16 | A3 B3 |
响应帧数据
从站地址(1字节) | 功能码(1字节) | 字节数(1字节) | 数据区(3字节) | CRC校验码(2字节) |
---|---|---|---|---|
7B | 02 | 03 | AC DB 35 | XX |
数据解释:
0xAC = 0b10101100 即:
离散量编号 | 204 | 203 | 202 | 201 | 200 | 199 | 198 | 197 |
---|---|---|---|---|---|---|---|---|
状态 | 1 | 0 | 1 | 0 | 1 | 1 | 0 | 0 |
0xDB = 11011011 即:
离散量编号 | 212 | 211 | 210 | 209 | 208 | 207 | 206 | 205 |
---|---|---|---|---|---|---|---|---|
状态 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 |
0x35 = 0b10101100 即:
离散量编号 | - | - | 218 | 217 | 216 | 215 | 214 | 213 |
---|---|---|---|---|---|---|---|---|
状态 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1 |
功能码03(读取保持寄存器)
- 从地址123的设备上,读保持寄存器 108~110
请求帧数据
从站地址(1字节) | 功能码(1字节) | 起始地址(2字节) | 寄存器数量(2字节) | CRC校验码(2字节) |
---|---|---|---|---|
7B | 03 | 00 6B | 00 03 | XX |
响应帧数据
从站地址(1字节) | 功能码(1字节) | 字节数(1字节) | 数据区(3字节) | CRC校验码(2字节) |
---|---|---|---|---|
7B | 03 | 06 | 02 2B 00 00 00 64 | XX |
数据解释:
寄存器108值为 0x022b = 555
寄存器109值为 0x00 = 0
寄存器110值为 0x64= 100
功能码04(读取输入寄存器)
- 从地址123的设备上,读取输入寄存器9
请求帧数据
从站地址(1字节) | 功能码(1字节) | 起始地址(2字节) | 寄存器数量(2字节) | CRC校验码(2字节) |
---|---|---|---|---|
7B | 04 | 00 08 | 00 01 | XX |
响应帧数据
从站地址(1字节) | 功能码(1字节) | 字节数(1字节) | 数据区(3字节) | CRC校验码(2字节) |
---|---|---|---|---|
7B | 04 | 02 | 00 0A | XX |
数据解释:
寄存器9的值为 0x000a = 10
功能码05(写单个线圈)
- 从地址123的设备上,写线圈173为ON
请求帧数据
从站地址(1字节) | 功能码(1字节) | 线圈地址(2字节) | 线圈值(2字节) | CRC校验码(2字节) |
---|---|---|---|---|
7B | 05 | 00 AC | FF 00 | XX |
线圈值: 0xFF00请求线圈为ON,0x0000请求为OFF
响应帧数据
从站地址(1字节) | 功能码(1字节) | 线圈地址(2字节) | 线圈值(2字节) | CRC校验码(2字节) |
---|---|---|---|---|
7B | 05 | 00 AC | FF 00 | XX |
功能码06(写单个寄存器)
- 从地址123的设备上,将0x03写入寄存器2
请求帧数据
从站地址(1字节) | 功能码(1字节) | 寄存器地址(2字节) | 寄存器值(2字节) | CRC校验码(2字节) |
---|---|---|---|---|
7B | 06 | 00 01 | 00 03 | XX |
响应帧数据
从站地址(1字节) | 功能码(1字节) | 线圈地址(2字节) | 线圈值(2字节) | CRC校验码(2字节) |
---|---|---|---|---|
7B | 06 | 00 01 | 00 03 | XX |
功能码15(写多个线圈)
- 从地址123的设备上,从线圈20开始写入10个线圈,写入内容为 0xcd01
请求帧数据
从站地址(1字节) | 功能码(1字节) | 起始地址(2字节) | 线圈数量(2字节) | 字节数(1字节) | 线圈值(n字节) | CRC校验码(2字节) |
---|---|---|---|---|---|---|
7B | 0F | 00 13 | 00 0A | 02 | CD 01 | XX |
响应帧数据
从站地址(1字节) | 功能码(1字节) | 线圈地址(2字节) | 线圈值(2字节) | CRC校验码(2字节) |
---|---|---|---|---|
7B | 0F | 00 13 | 00 0A | XX |
功能码16(写多个寄存器)
- 从地址123的设备上,将0x000a和0x0102写入从第2个寄存器开始的两个寄存器(03 04号寄存器)
请求帧数据
从站地址(1字节) | 功能码(1字节) | 起始地址(2字节) | 寄存器数量(2字节) | 字节数(1字节) | 寄存器值(2n字节) | CRC校验码(2字节) |
---|---|---|---|---|---|---|
7B | 10 | 00 01 | 00 02 | 04 | 00 0a 01 02 | XX |
响应帧数据
从站地址(1字节) | 功能码(1字节) | 线圈地址(2字节) | 线圈值(2字节) | CRC校验码(2字节) |
---|---|---|---|---|
7B | 10 | 00 01 | 00 02 | XX |
错误响应
从站地址(1字节) | 异常功能码(1字节) | 异常码(1字节) | CRC校验码(2字节) |
---|---|---|---|
7B | 0x80+功能码 | 01/02/03/04 | XX |
七 modbus 国标下载
我用夸克网盘分享了「GB_T 19582.1-2008 基于Modbus协议的工业自动化网络规范 第1部分:Modbus应用协议.pdf」,点击链接即可保存。打开「夸克APP」在线查看,支持多种文档格式转换。
链接:https://pan.quark.cn/s/0155530b636f
提取码:GK1V
如果帮助到你,麻烦点个赞给个关注,你的支持和关注是我持续更新的动力!谢谢~