
rs-485 半双工通信需精确控制收发方向(rts 引脚),否则设备响应易被截断或丢失;本文详解如何通过显式 rts 管理、合理延时与缓冲检测实现稳定读取完整 modbus 响应。
rs-485 半双工通信需精确控制收发方向(rts 引脚),否则设备响应易被截断或丢失;本文详解如何通过显式 rts 管理、合理延时与缓冲检测实现稳定读取完整 modbus 响应。
在使用 pyserial 与支持 Modbus RTU 的 RS-485 设备(如 I-7000 模块)通信时,频繁出现“只能读到 CRC 字节”或“响应不完整”的问题,并非源于波特率、接线或 CRC 计算错误,而是由 RS-485 收发器方向控制失配 导致的典型半双工同步问题。
RS-485 是半双工总线:同一时刻仅能单向传输。多数 USB/UART 转 RS-485 模块(如 MAX485 方案)依赖 RTS(Request To Send)信号 控制 DE/RE 引脚,决定当前是发送(TX)还是接收(RX)状态。若 RTS 未及时拉低,收发器仍处于发送态,即使从机已返回数据,主机也无法采样——这正是你观察到“有时只收到 \xcc(CRC 末字节)”、“第二次通信才稳定”的根本原因。
你的三次调试尝试揭示了关键线索:
- 单次长连接 +
read_until(b"\xcc")不稳定 → RTS 未切换,持续 TX 阻塞 RX; -
send_over_rs485()函数中每次新建串口实例后能成功 → 新建Serial()会重置 RTS 默认为False(RX 模式),且timeout=0.3更利于响应捕获; - 显式关闭再打开串口后第二次通信成功 → 同样因端口重初始化强制 RTS 复位。
✅ 正确解法:显式控制 RTS,而非依赖端口开关
import serial
import time
import crcmod.predefined
def calculate_modbus_crc(data: bytes) -> int:
crc16 = crcmod.predefined.mkCrcFun("modbus")
return crc16(data)
# 初始化串口(禁用自动 RTS 切换)
ser = serial.Serial(
port="/dev/ttyS0",
baudrate=115200,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=0.5, # 响应超时建议设为 0.3–0.5s(Modbus RTU 典型帧间隔 < 3.5 字符时间)
rtscts=False,
dsrdtr=False,
)
# 构造 Modbus RTU 请求:slave=2, fn=0x01 (read coils), start=6, count=1
message = bytes.fromhex("020100060001")
crc = calculate_modbus_crc(message)
command = message + crc.to_bytes(2, "little")
print(f"Sending: {command.hex()}")
# Step 1: 拉高 RTS 进入发送模式
ser.rts = True
time.sleep(0.001) # 确保硬件生效(微秒级延迟足够)
# Step 2: 发送命令
ser.write(command)
# Step 3: 立即拉低 RTS 切换至接收模式
ser.rts = False
# Step 4: 等待响应(避免过早读取空缓冲)
time.sleep(0.02) # 给从机处理时间(典型 <10ms)
# Step 5: 安全读取 —— 先查缓冲区长度,再按需读取
if ser.in_waiting >= 6: # 最小合法响应:slave(1) + fn(1) + byte cnt(1) + data(N) + CRC(2)
response = ser.read(ser.in_waiting) # 读取全部可用字节
print(f"Full response: {response.hex()}")
else:
print(f"Timeout or incomplete response. Bytes available: {ser.in_waiting}")
ser.close()? 关键注意事项:
-
RTS 电平逻辑需匹配硬件:多数模块为 RTS=High → TX,RTS=Low → RX;若反向,请改用
ser.dtr或查阅模块手册。 -
避免
read_until()依赖特定字节:Modbus RTU 响应末尾是 CRC(小端),b"\xcc"可能出现在数据段(如线圈值为0xcc),导致误截断。应基于协议长度校验(如读线圈响应至少 5 字节:[slv][fn][cnt][byte][crc_lo][crc_hi])。 -
不要依赖
time.sleep()替代 RTS 切换:单纯加延时无法解决方向锁死问题,反而可能因超时丢弃已到数据。 -
验证硬件是否支持 RTS 自动流控:部分驱动(如 Linux
ch341)可启用rtscts=True实现自动切换,但需内核支持及设备兼容;生产环境推荐手动控制更可靠。 - 添加 CRC 校验与功能码验证:读取后务必校验响应 CRC 并检查 slave ID / function code 是否匹配,防止噪声误判。
? 总结:RS-485 Modbus 稳定通信 = 正确的物理层控制(RTS) + 合理的时序管理(TX→RX 切换延时) + 协议层健壮解析(长度/CRC/功能码校验)。放弃“反复开关串口”的临时方案,转向显式 RTS 管理,即可一劳永逸解决响应碎片化问题。

















