如何使用Python控制RS-485模块Modbus RTU继电器的通断
Python基于Modbus RTU协议控制RS-485继电器实现方案
你当前编写的代码不符合Modbus RTU协议规范,无法实现继电器控制,核心问题有三个:
- 仅发送单字节
b'\x08'不满足Modbus RTU帧结构要求,完整有效帧必须包含从站地址、功能码、数据段、CRC校验四个部分,不符合规范的指令会被继电器直接丢弃 - 调用
readline()读取Modbus数据逻辑错误,Modbus RTU是二进制传输协议,不存在换行符作为帧结束标识,用该方法会出现读空、读残帧的问题 - 未做收发缓存清理,串口残留的历史数据会干扰响应判断
继电器控制的核心逻辑
RS-485继电器的通断控制本质是对Modbus线圈寄存器执行写操作,常用两个功能码:
0x05:写单个线圈,用于控制单路继电器的通断0x0F:写多个线圈,用于批量控制多路继电器的通断
以最常用的0x05写单个线圈功能码为例,标准指令帧结构如下(以从站地址1、第1路继电器吸合为例):
| 帧字段 | 取值示例 | 说明 |
|---|---|---|
| 从站地址 | 0x01 | 继电器模块自身的Modbus地址,可通过设备手册查询、修改 |
| 功能码 | 0x05 | 标识当前帧为写单个线圈操作 |
| 线圈地址高8位 | 0x00 | 继电器通道对应的寄存器地址,多数模块第1路默认地址为0x0000 |
| 线圈地址低8位 | 0x00 | |
| 写入值高8位 | 0xFF | 写入0xFF00表示继电器吸合通电,写入0x0000表示继电器断开断电 |
| 写入值低8位 | 0x00 | |
| CRC校验低8位 | 计算得到 | Modbus RTU要求CRC校验值低字节在前、高字节在后传输 |
| CRC校验高8位 | 计算得到 |
可直接运行的实现代码
代码仅依赖你已经在用的pyserial库,不需要额外安装第三方依赖,内置了Modbus CRC16校验计算逻辑:
import serial import time def modbus_crc16(data: bytes) -> bytes: """计算Modbus RTU标准CRC16校验值,返回符合传输要求的小端序2字节结果""" crc = 0xFFFF for byte in data: crc ^= byte for _ in range(8): if crc & 0x0001: crc >>= 1 crc ^= 0xA001 else: crc >>= 1 return crc.to_bytes(2, byteorder='little') # 串口初始化,参数需和继电器模块配置完全一致,否则无法通信 conn = serial.Serial( port='/dev/ttyUSB0', baudrate=9600, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=1, rtscts=False, dsrdtr=False ) def control_single_relay(slave_addr: int, channel: int, is_on: bool) -> bytes: """ 单路继电器控制函数 :param slave_addr: 继电器模块从站地址(十进制) :param channel: 继电器通道号,从0开始计数,第1路传0、第2路传1,以此类推 :param is_on: True为吸合通电,False为断开断电 :return: 模块返回的响应帧 """ # 清空接收缓存,避免历史残留数据干扰 conn.reset_input_buffer() frame = bytearray() # 拼接从站地址、功能码 frame.append(slave_addr) frame.append(0x05) # 拼接线圈地址(大端序2字节) frame.extend(channel.to_bytes(2, byteorder='big')) # 拼接通断控制值 frame.extend(b'\xFF\x00' if is_on else b'\x00\x00') # 拼接CRC校验值 frame.extend(modbus_crc16(frame)) # 发送指令 conn.write(bytes(frame)) # 读取8字节固定长度响应(0x05功能码响应固定为8字节) resp = conn.read(8) return resp # 功能测试 if __name__ == "__main__": # 控制地址为1的模块第1路吸合 resp_on = control_single_relay(slave_addr=1, channel=0, is_on=True) print(f"继电器吸合响应帧: {resp_on.hex(' ')}") time.sleep(2) # 等待2秒后控制第1路断开 resp_off = control_single_relay(slave_addr=1, channel=0, is_on=False) print(f"继电器断开响应帧: {resp_off.hex(' ')}") # 用完关闭串口连接 conn.close()
调试注意事项
- 首次使用务必对照继电器手册确认四个核心参数:从站地址、串口通信参数(波特率/校验位/停止位)、通道对应的线圈起始地址,部分厂商模块第1路线圈地址从0x0001开始,对应修改
channel参数即可 - 接线时注意RS-485总线A、B线不要接反,模块供电要符合额定要求,否则会出现通信失败的问题
- 不要使用
readline()方法读取Modbus数据,需根据功能码对应的响应帧固定长度读取,避免帧解析错误 - 如果你使用的是非自动收发型USB转RS-485模块,需要在发送指令前拉低RTS引脚切换为发送模式,发送完成后等待1-2个字符传输时间再拉高RTS切回接收模式,自动收发模块不需要额外操作
内容的提问来源于stack exchange,提问作者Murugesan
相关产品推荐
相关产品推荐

