‌Modbus RTU 编程:从串口通信到多从机组网(一)

一、Modbus RTU 协议基础
**

Modbus 协议是工业领域应用极为广泛的一种通信协议,由 Modicon 公司(现施耐德电气 Schneider Electric)于 1979 年为可编程逻辑控制器(PLC)通信而开发 。它已成为工业领域通信协议的事实标准,也是工业电子设备之间常用的连接方式。Modbus 协议有多种变体,其中 Modbus RTU(Remote Terminal Unit)是最常见的一种,主要应用于串行通信。

Modbus RTU 使用二进制编码在串行通信线路上传输数据,数据帧结构紧凑,传输效率高。其数据帧由地址、功能码、数据和 CRC 校验码组成,这种设计使得数据在传输过程中能有效保证完整性和准确性 。比如在一个简单的工业控制系统中,主设备通过 Modbus RTU 协议向从设备发送读取温度数据的请求,从设备响应请求并返回温度数据,整个过程通过标准的数据帧格式进行,确保了通信的可靠性。

与 Modbus TCP 相比,Modbus RTU 在传输介质和网络结构上有明显区别。Modbus TCP 基于以太网,使用 TCP/IP 作为传输层协议,适用于局域网或广域网中的设备通信 ,可以通过路由器、交换机等网络设备进行连接;而 Modbus RTU 通常使用 RS-232 或 RS-485 作为物理层标准,适用于点对点或多点的串行通信网络,一般用于较短距离的通信 。例如,在一个大型工厂中,Modbus TCP 常用于连接分布在不同区域的大型设备,而 Modbus RTU 则常用于连接同一控制柜内的小型设备。此外,Modbus TCP 的数据单元包含 MBAP 头用于网络数据封装和识别 ,而 Modbus RTU 的数据单元直接在串行线上传输 ;Modbus TCP 的通信速度受网络带宽和设备性能限制但可覆盖更远距离 ,Modbus RTU 的通信速度受波特率限制通常波特率较低但在短距离内通信稳定 ;Modbus TCP 协议本身无内置安全机制需借助 VPN、防火墙等增强安全性 ,Modbus RTU 由于网络环境相对封闭通常被认为较安全也可通过加密技术进一步提高安全性 。

二、串口通信基础
(一)串口通信原理
串口通信,即串行通信,是指外设和计算机间,通过数据信号线、地线、控制线等,按位进行传输数据的一种通讯方式 。它与并行通信相对,数据一位一位地在单条传输线上传输,如同在一条窄巷中依次通行的车辆。

在串口通信里,数据传输以数据帧为单位,每一帧包含起始位、数据位、校验位和停止位。起始位是一个逻辑 “0” 电平,它就像起跑线上的发令枪响,标志着数据传输的开始 。当接收设备检测到从高电平到低电平的跳变时,便知道一个新的数据帧即将到来。数据位是真正传输的数据内容,其位数可以是 5 位、6 位、7 位或 8 位,具体取决于通信协议和应用需求,比如标准 ASCII 码使用 7 位数据位,而扩展 ASCII 码则使用 8 位数据位 。校验位用于数据的错误检测,是一种简单的检错方式,常见的校验方式有奇校验、偶校验、高校验和低校验 。以偶校验为例,如果数据位中 “1” 的个数为奇数,校验位就设为 “1”,使包括校验位在内的 “1” 的总数为偶数;若数据位中 “1” 的个数为偶数,校验位则设为 “0”。停止位表示单个数据帧的结束,典型的值为 1 位、1.5 位或 2 位,它是高电平,用于给接收设备提供一个数据接收结束的标志,同时也为设备之间的时钟同步提供了校正机会 。例如,在一个简单的温度传感器与微控制器的串口通信中,传感器将温度数据编码成数据帧,通过串口发送给微控制器,微控制器根据约定的格式解析数据帧,获取温度值。

(二)串口通信硬件
常用的串口通信硬件接口标准有 RS - 232 和 RS - 485,它们在工业控制、仪器仪表等领域广泛应用 。

RS - 232 是个人计算机上的通讯接口之一,由电子工业协会(EIA)制定的异步传输标准接口 。它通常使用 9 针 D 型连接器(DB - 9)或 25 针 D 型连接器(DB - 25),其中 DB - 9 更为常见。RS - 232 采用单端信号传输,其信号电平较高,逻辑 “1” 为 - 3V ~ - 15V,逻辑 “0” 为 + 3V ~ + 15V,这使得它容易损坏接口电路的芯片,并且与 TTL 电平不兼容,需要使用电平转换电路才能与 TTL 电路连接 。它的传输速率较低,异步传输时波特率一般为 20Kbps,传输距离有限,最大传输距离标准值为 50 英尺(约 15 米) 。在早期的计算机与调制解调器连接中,常使用 RS - 232 接口,通过串口线将计算机的串口与调制解调器相连,实现数据的传输。

RS - 485 是在 RS - 232 的基础上发展而来的,它采用差分信号传输,使用一对双绞线,将其中一线定义为 A,另一线定义为 B 。逻辑 “1” 以两线间的电压差为 + (2 - 6) V 表示,逻辑 “0” 以两线间的电压差为 - (2 - 6) V 表示,接口信号电平比 RS - 232 降低了,不易损坏接口电路的芯片,且该电平与 TTL 电平兼容,可方便与 TTL 电路连接 。RS - 485 的数据最高传输速率为 10Mbps,最大传输距离标准值为 4000 英尺(约 1219 米) 。它允许多个设备连接到同一总线上,在同一总线上最多可以挂接 32 个节点(理论上可挂接更多节点,通过增加中继器可扩展节点数量),这种特性使得它非常适合多点通信的应用场景,如工业自动化生产线中的多个传感器与控制器之间的通信 。在一个大型工厂的车间里,分布着众多的温度、压力、流量等传感器,这些传感器通过 RS - 485 总线连接到中央控制器,实现数据的集中采集和处理。

在接线方式上,RS - 232 一般使用三线制,即发送线(TXD)、接收线(RXD)和地线(GND),设备之间通过直连线或交叉线进行连接,直连线用于连接不同类型的设备(如计算机与调制解调器),交叉线用于连接相同类型的设备(如计算机与计算机) 。RS - 485 通常采用两线制,即 A 线和 B 线,加上地线(GND),所有设备的 A 线连接在一起,B 线连接在一起,形成总线式拓扑结构 。在实际应用中,为了提高通信的可靠性,还需要在总线的两端连接匹配电阻,电阻值一般与传输电缆的特性阻抗相当,通常为 120Ω 。

(三)串口通信软件设置
在软件层面进行串口通信设置时,需要配置一系列关键参数,以确保通信的准确性和稳定性 。

波特率是串口通信中一个重要的参数,它表示每秒钟传输的信号单元数量,即数据传输的速度,单位是波特(Baud) 。常见的波特率有 9600、19200、38400、57600、115200 等 。例如,9600 波特意味着每秒可以传输 9600 个二进制位。通信双方必须设置相同的波特率,否则数据传输将出现错误 。在一个简单的单片机与上位机的串口通信项目中,如果单片机设置的波特率为 9600,而上位机设置的波特率为 115200,那么上位机将无法正确解析单片机发送的数据。

数据位是指每个数据字节中实际用于传输数据的位数,通常可以设置为 5 位、6 位、7 位或 8 位 。8 位数据位是最常用的设置,它可以表示一个完整的字节数据,能够满足大多数应用场景的需求 。如传输 ASCII 码字符时,若使用标准 ASCII 码(0 - 127),可以选择 7 位数据位;若需要传输扩展 ASCII 码(0 - 255),则需要使用 8 位数据位 。

校验位用于检测数据传输过程中是否出现错误,常见的校验方式有无校验(None)、奇校验(Odd)、偶校验(Even)、标记校验(Mark)和空格校验(Space) 。无校验是最常用的设置,它不进行校验位的计算和传输,适用于对数据准确性要求不高或者通信环境较为稳定的场景 。奇校验和偶校验是根据数据位中 “1” 的个数来确定校验位的值,奇校验要求数据位和校验位中 “1” 的总数为奇数,偶校验要求数据位和校验位中 “1” 的总数为偶数 。例如,对于数据字节 0x55(二进制为 01010101),若采用偶校验,校验位为 0;若采用奇校验,校验位为 1 。

停止位表示每个数据字节后的停止信号位数,用于表示一个数据字节的结束 。常见的停止位有 1 位、1.5 位和 2 位,1 位停止位是最常用的设置 。停止位的作用是给接收设备提供一个数据接收结束的标志,同时也为设备之间的时钟同步提供了校正机会 。停止位的位数越多,不同时钟同步的容忍程度越大,但数据传输率也会相应降低 。在一些对传输速度要求不高,但对数据准确性和稳定性要求较高的应用中,可以选择 2 位停止位 。

这些参数通常简称为 “波特率 - 数据位 - 校验位 - 停止位” 的格式,例如 “9600 - 8 - N - 1” 表示波特率为 9600,数据位为 8 位,无校验位,停止位为 1 位 。在进行串口通信编程时,需要根据硬件设备和通信需求正确设置这些参数,以实现可靠的数据传输 。例如,在使用 Python 的 PySerial 库进行串口通信时,可以通过以下代码设置串口参数:


import serial

# 打开串口

ser = serial.Serial('COM1', 9600, bytesize=8, parity='N', stopbits=1)

# 检查串口是否打开成功

if ser.is_open:

print('串口已打开')

# 进行数据发送和接收操作

ser.write(b'Hello, World!')

data = ser.read(10)

print('接收到的数据:', data)

ser.close()

else:

print('无法打开串口')

在上述代码中,通过serial.Serial函数设置了串口的端口号(COM1)、波特率(9600)、数据位(8)、校验位(无校验)和停止位(1),然后进行了数据的发送和接收操作 。

三、Modbus RTU 协议详解
(一)Modbus RTU 帧结构
Modbus RTU 的数据帧结构紧凑,包含多个关键字段,每个字段都有特定的作用和取值范围 。

地址字段:占用 1 个字节,取值范围为 0 - 255 。其中,1 - 247 为有效的从设备地址,每个从设备在网络中都有一个唯一的地址,用于主设备识别和选择与之通信的从设备 。例如,在一个包含多个传感器的 Modbus RTU 网络中,每个传感器都被分配一个唯一的地址,主设备通过该地址向特定的传感器发送指令 。地址 0 通常用于广播通信,主设备发送的广播消息会被所有从设备接收,但从设备不会对广播消息进行响应 ;248 - 255 为保留地址,用于特殊用途 。
功能码字段:同样占用 1 个字节,取值范围为 1 - 255 。功能码用于定义主设备请求的操作类型,以及从设备响应的操作类型 。例如,功能码 03 表示读取保持寄存器,功能码 06 表示写入单个寄存器 。不同的功能码对应不同的操作,主设备通过功能码告诉从设备需要执行的操作,从设备根据功能码执行相应的操作并返回结果 。常见的功能码有读取线圈状态(功能码 01)、读取输入状态(功能码 02)、读取保持寄存器(功能码 03)、读取输入寄存器(功能码 04)、强置单线圈(功能码 05)、预置单寄存器(功能码 06)、写多个线圈(功能码 15)、写多个寄存器(功能码 16)等 。
数据字段:数据字段的长度和内容取决于功能码和具体的操作 。它包含了主设备请求的数据或从设备响应的数据 。例如,在读取保持寄存器(功能码 03)的操作中,数据字段包含要读取的寄存器起始地址和寄存器数量;在写入单个寄存器(功能码 06)的操作中,数据字段包含要写入的寄存器地址和数据值 。对于读取操作,从设备将请求的数据放在数据字段中返回给主设备;对于写入操作,主设备将要写入的数据放在数据字段中发送给从设备 。
CRC 校验字段:占用 2 个字节,用于检测数据传输过程中是否发生错误 。CRC(Cyclic Redundancy Check)即循环冗余校验,是一种基于多项式除法的校验方法 。发送方根据数据字段的内容计算出 CRC 校验值,并将其附加在数据帧的末尾;接收方在接收到数据帧后,重新计算 CRC 校验值,并与接收到的 CRC 校验值进行比较 。如果两者相等,则认为数据传输正确;如果不相等,则认为数据传输过程中发生了错误,接收方会丢弃该数据帧,并可能要求发送方重新发送数据 。
(二)功能码解析
Modbus RTU 定义了一系列功能码,用于实现不同的操作 。下面列举并解释一些常见的功能码及其使用场景和数据格式 。

读取保持寄存器(功能码 03):用于读取从设备中的保持寄存器数据 。保持寄存器通常用于存储可读写的数据,如设备的设定值、状态信息等 。例如,在一个温度控制系统中,主设备可以使用功能码 03 读取温度传感器的设定温度值 。
数据格式:主机发送的请求帧中,数据字段包含要读取的寄存器起始地址(2 个字节)和寄存器数量(2 个字节) 。从机响应的帧中,数据字段包含返回的字节数(1 个字节)和寄存器数据(每个寄存器 2 个字节) 。例如,主机要读取地址为 01 的从机中,起始地址为 0002 的 3 个寄存器数据,主机发送的报文格式为:01 03 00 02 00 03 CRC_L CRC_H,其中 01 为从机地址,03 为功能码,00 02 为起始寄存器地址,00 03 为寄存器数量,CRC_L 和 CRC_H 为 CRC 校验码 。从机响应的报文格式为:01 03 06 01 02 03 04 05 06 CRC_L CRC_H,其中 01 为从机地址,03 为功能码,06 为返回的字节数(3 个寄存器,每个寄存器 2 个字节,共 6 个字节),01 02 为第一个寄存器的数据,03 04 为第二个寄存器的数据,05 06 为第三个寄存器的数据,CRC_L 和 CRC_H 为 CRC 校验码 。
读取输入寄存器(功能码 04):用于读取从设备中的输入寄存器数据 。输入寄存器通常用于存储只读的数据,如传感器的测量值等 。例如,在一个压力监测系统中,主设备可以使用功能码 04 读取压力传感器的实时压力值 。
数据格式:与读取保持寄存器类似,主机发送的请求帧中,数据字段包含要读取的寄存器起始地址(2 个字节)和寄存器数量(2 个字节) 。从机响应的帧中,数据字段包含返回的字节数(1 个字节)和寄存器数据(每个寄存器 2 个字节) 。
写入单个寄存器(功能码 06):用于向从设备的单个寄存器写入数据 。例如,在一个电机控制系统中,主设备可以使用功能码 06 向电机驱动器的寄存器写入转速设定值 。
数据格式:主机发送的请求帧中,数据字段包含要写入的寄存器地址(2 个字节)和数据值(2 个字节) 。从机响应的帧与主机发送的帧相同,用于确认写入操作成功 。例如,主机要将数据 1E34 写入地址为 000B 的从机寄存器中,主机发送的报文格式为:01 06 00 0B 1E 34 CRC_L CRC_H,从机响应的报文格式也为:01 06 00 0B 1E 34 CRC_L CRC_H 。
写入多个寄存器(功能码 16):用于向从设备的多个连续寄存器写入数据 。例如,在一个复杂的工业控制系统中,主设备可能需要一次性向从设备写入多个参数,如多个传感器的校准系数等 。
数据格式:主机发送的请求帧中,数据字段包含要写入的寄存器起始地址(2 个字节)、寄存器数量(2 个字节)、字节数(1 个字节,表示后面数据的字节数)和数据值(每个寄存器 2 个字节) 。从机响应的帧中,数据字段包含要写入的寄存器起始地址(2 个字节)和寄存器数量(2 个字节) 。例如,主机要将数据 2D7A、42B1、15E8 写入地址为 002A、002B、002C 的从机寄存器中,主机发送的报文格式为:01 16 00 2A 00 03 06 2D 7A 42 B1 15 E8 CRC_L CRC_H,其中 01 为从机地址,16 为功能码,00 2A 为起始寄存器地址,00 03 为寄存器数量,06 为字节数(3 个寄存器,每个寄存器 2 个字节,共 6 个字节),2D 7A 为第一个寄存器的数据,42 B1 为第二个寄存器的数据,15 E8 为第三个寄存器的数据,CRC_L 和 CRC_H 为 CRC 校验码 。从机响应的报文格式为:01 16 00 2A 00 03 CRC_L CRC_H 。
(三)CRC 校验
CRC 校验是 Modbus RTU 协议中确保数据传输准确性的重要机制 。它基于多项式除法的原理,通过对数据帧中的数据进行计算,生成一个 16 位的校验值 。

1. CRC 校验原理
在 Modbus RTU 中,使用的 CRC 多项式为 0xA001(即 1010 0000 0000 0001) 。计算 CRC 校验值时,将数据帧中的数据(不包括 CRC 校验字段本身)看作一个多项式,与 CRC 多项式进行异或运算 。具体计算步骤如下:

初始化一个 16 位的 CRC 寄存器为 0xFFFF 。
从数据帧的第一个字节开始,将该字节与 CRC 寄存器的低 8 位进行异或运算,结果存入 CRC 寄存器 。
将 CRC 寄存器向右移动 1 位,如果移出的位为 1,则将 CRC 寄存器与多项式 0xA001 进行异或运算;如果移出的位为 0,则不进行异或运算 。
重复上一步,直到移动 8 次,完成对一个字节的处理 。
对数据帧中的下一个字节重复上述步骤,直到处理完所有字节 。
最终 CRC 寄存器中的值即为 CRC 校验值 。
2. CRC 校验计算方法示例
假设有一个数据帧为:01 03 00 00 00 01,计算其 CRC 校验值 。

初始化 CRC 寄存器为 0xFFFF 。
第一个字节 01 与 CRC 寄存器的低 8 位进行异或运算:0xFFFF ^ 0x01 = 0xFFFE 。
将 0xFFFE 向右移动 1 位,移出位为 0,不进行异或运算,得到 0x7FFF 。
继续向右移动 1 位,移出位为 1,将 0x7FFF 与 0xA001 进行异或运算:0x7FFF ^ 0xA001 = 0x3FFF 。
重复上述步骤,直到处理完所有字节,最终得到 CRC 校验值为 0x840A 。
3. CRC 校验代码实现示例(以 C 语言为例)

#include <stdint.h>

// 计算Modbus RTU CRC16校验码

uint16_t modbus_crc16(uint8_t *buf, uint16_t len) {

uint16_t crc = 0xFFFF;

uint8_t i;

while (len--) {

crc ^= *buf++;

for (i = 0; i < 8; i++) {

if (crc & 0x0001) {

crc >>= 1;

crc ^= 0xA001;

} else {

crc >>= 1;

}

}

}

return crc;

}

在上述代码中,modbus_crc16函数接受一个指向数据缓冲区的指针buf和数据长度len,返回计算出的 CRC16 校验码 。函数内部首先初始化 CRC 寄存器为 0xFFFF,然后遍历数据缓冲区中的每个字节,对每个字节进行 CRC 计算,最终返回计算得到的 CRC 校验码 。
————————————————
版权声明:本文为CSDN博主「计算机毕设定制辅导-无忧」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42190530/article/details/149021339

从代码实现角度看,RS232和RS485的串口编程逻辑基本一致,区别主要体现在硬件接口和电气特性上‌。以下是具体分析:

通信协议一致性

UART(通用异步收发器)是底层通信协议,RS232和RS485均基于UART实现。代码中涉及的帧格式(起始位、数据位、停止位)、波特率设置等逻辑完全相同,无需修改。 ‌12

硬件接口差异

  • RS232:使用单端信号传输,需3-9根线(常见3线制:TXD、RXD、GND),支持全双工通信。 ‌23
  • RS485:采用差分信号传输,需2-4根线(2线制可半双工,4线制可全双工),抗干扰能力更强。 ‌13

电平转换需求

  • RS232:需电平转换芯片(如MAX232)将TTL电平(0-5V)转换为±12V电平。 ‌12
  • RS485:需差分电平转换芯片(如MAX485),将TTL电平转换为差分信号(+2V至+6V为逻辑0,-2V至-6V为逻辑1)。 ‌13

代码实现示例

QSerialPort serial;
serial.setPortName("COM1");
serial.setBaudRate(QSerialPort::Baud9600);
serial.setDataBits(QSerialPort::Data8);
serial.setParity(QSerialPort::NoParity);
serial.setStopBits(QSerialPort::OneStop);
serial.setFlowControl(QSerialPort::NoFlowControl);

  • RS232:无需额外配置,直接连接3线制接口。
  • RS485:需外接电平转换模块,并确保差分信号线正确连接。 ‌12

适用场景建议

  • 短距离、低速率(<15米,<20Kbps):优先选RS232。 ‌23
  • 长距离、高抗干扰(<1200米,<50Mbps):优先选RS485。

(1)Qt实现使用TCP与RS485串口设备通信————附带详细实践方法



(https://blog.csdn.net/qq_33375598/article/details/144867483

(2)基于STM32的传感器数据采集系统设计:Qt、RS485、Modbus Rtu协议(代码示例)

https://blog.csdn.net/qq_40431685/article/details/144168282

(3)如何使用qt实现串口通信的一对多通信

https://wenku.csdn.net/answer/804f5dbdc9604f3f8fad48e0f72e1f47

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐