You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python pyserial读取Modbus RTU设备RS485响应不稳定/不完整的问题求助

Python pyserial读取Modbus RTU设备RS485响应不稳定/不完整的问题求助

大家好,我最近在做和I-7000 IO模块的Modbus RTU通信,用的是RS485接口,pyserial库。目前写线圈、发起读线圈命令这些操作都能成功,但读取设备返回的响应时遇到了大问题:响应总是不完整或者不稳定——大多数时候只能读到末尾的CRC字节,偶尔能读到部分内容,极少能拿到预期的完整6字节响应(包含设备ID、功能码、线圈地址、值和CRC)。

我先贴一段简化的调试代码,和主代码遇到的问题完全一致:

import serial
import time
import crcmod.predefined

# 计算Modbus RTU CRC的函数
def calculate_rs_485_crc(message):
    crc16 = crcmod.predefined.mkCrcFun("modbus")
    return crc16(message)

port = "/dev/ttyS0"
baudrate = 115200
timeout = 1

ser = serial.Serial(
    port,
    baudrate,
    timeout=timeout,
    parity=serial.PARITY_NONE,
)

# 构造Modbus RTU命令
message = "020100060001"
command = bytes.fromhex(message)
crc = calculate_rs_485_crc(command)
command_with_crc = command + crc.to_bytes(2, "little")
    
print(f"Sent: {command_with_crc.hex()}")
ser.write(command_with_crc)

byteData = ser.read_until(b"\xcc")

print(f"byteData: {byteData}")

ser.close()

波特率和端口我都确认过是对的,不管设置多少超时时间,结果都一样。试过在写和读之间加time.sleep(0.5),结果更糟——完全收不到任何数据;不加sleep至少还能拿到部分内容。

终端输出的例子:

  • 大多数时候只收到CRC相关字节:
    Sent: 0201000600011df8
    byteData: b'Q\xcc'
    
  • 偶尔能拿到完整预期响应,或者中间状态:
    Sent: 0201000600011df8
    byteData: b'\x02\x01\x01\x00Q\xcc'
    

我试过read_until、readline、指定字节数的read(6),结果都一样。网上查了很多资料,大多说是时序或者硬件问题,但硬件肯定没问题——厂商提供的工具能和设备正常通信。如果是时序问题,我加sleep等响应完全到达反而收不到,这就很矛盾了。


EDIT1:补充mbpoll测试结果
我用mbpoll工具测试了一下,居然超时了:

user@localhost:~$ sudo mbpoll -m rtu -b 115200 -d 8 -s 1 -p none /dev/ttyS0 -a 2 -r 1 -1 -t 0
mbpoll 1.0-0 - FieldTalk(tm) Modbus(R) Master Simulator
Copyright © 2015-2019 Pascal JEAN, https://github.com/epsilonrt/mbpoll
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; type 'mbpoll -w' for details.

Protocol configuration: Modbus RTU
Slave configuration...: address = [2]
                        start reference = 1, count = 1
Communication.........: /dev/ttyS0,     115200-8E1
                        t/o 1.00 s, poll rate 1000 ms
Data type.............: discrete output (coil)

-- Polling slave 2...
Read discrete output (coil) failed: Connection timed out

EDIT2:发现一个奇怪的现象
我有个封装好的RS485发送函数,功能和主代码差不多,只是在函数里创建串口实例、写命令、读响应然后关闭。我在主代码里先调用这个函数,然后再执行原来的读写逻辑,居然就能拿到完整响应了!但这个方法其实是创建了两个串口实例,感觉不是合理的解决方案。

封装的函数代码:

import serial
import time
import logging
import os
import crcmod.predefined
    
logger = logging.getLogger(__name__)
    
def send_over_rs485(message: str, max_attempts=3):
        attempts = 0
        while attempts < max_attempts:
            try:
                ser = serial.Serial("/dev/ttyS0", 115200, timeout=0.3)

                command = bytes.fromhex(message)
                crc = calculate_rs_485_crc(command)
                command_with_crc = command + crc.to_bytes(2, "little")

                ser.write(command_with_crc)
                 
                time.sleep(0.2) 

                if ser.in_waiting:
                    response = ser.readline()
                    return response.hex()
                else:
                    logging.debug("No response available rs-485 in serial buffer.")

            except serial.SerialException as e:
                logging.error(f"Serial communication error: {e}")
            except OSError as e:
                logging.error(f"OS-level communication error: {e}")
            except Exception as e:
                logging.error(f"Unexpected error: {e}")
            finally:
                if "ser" in locals() and ser.is_open:
                    ser.close()
                attempts += 1

        logging.warning(f"Reached maximum attempts without success: {attempts}")
        return None

# 计算Modbus RTU CRC的函数
def calculate_rs_485_crc(message):
    crc16 = crcmod.predefined.mkCrcFun("modbus")
    return crc16(message)

调用后的主代码:

import serial
import time
import crcmod.predefined
# 导入上面的函数
from serial_protocol import send_over_rs485

# 计算Modbus RTU CRC的函数
def calculate_rs_485_crc(message):
 crc16 = crcmod.predefined.mkCrcFun("modbus")
 return crc16(message)

port = "/dev/ttyS0"
baudrate = 115200
timeout = 1

ser = serial.Serial(
 port,
 baudrate,
 timeout=timeout,
 parity=serial.PARITY_NONE,
)

# 构造Modbus RTU命令
message = "020100060001"
# 先调用这个函数
send_over_rs485(message)

command = bytes.fromhex(message)
crc = calculate_rs_485_crc(command)
command_with_crc = command + crc.to_bytes(2, "little")
 
print(f"Sent: {command_with_crc.hex()}")
ser.write(command_with_crc)

byteData = ser.read_until(b"\xcc")

print(f"byteData: {byteData}")

ser.close()

执行后的响应(正常了):

Sent: 0201000600011df8
in waiting false 0
byteData: b'\x02\x01\x01\x00Q\xcc'

EDIT3:进一步定位问题
后来我发现不是那个函数的问题,而是第一次打开串口写读会失败,关闭后重新打开串口再写读,第二次就能正常拿到完整响应。我写了这段测试代码验证:

import serial
import time
import crcmod.predefined

# 计算Modbus RTU CRC的函数
def calculate_rs_485_crc(message):
    crc16 = crcmod.predefined.mkCrcFun("modbus")
    return crc16(message)


def setup_serial_port() -> serial.Serial:
    
    port = "/dev/ttyS0"
    baudrate = 115200
    timeout = 0.3
    
    ser = serial.Serial(
        port=port,
        baudrate=baudrate ,
        bytesize=serial.EIGHTBITS,
        parity=serial.PARITY_NONE,
        stopbits=serial.STOPBITS_ONE,
        timeout=timeout
    )

    return ser

# 构造Modbus RTU命令
message = "020100060001"

command = bytes.fromhex(message)
crc = calculate_rs_485_crc(command)
command_with_crc = command + crc.to_bytes(2, "little")

# 第一次打开串口读写
ser = setup_serial_port()
print(f"Sent: {command_with_crc}")
ser.write(command_with_crc)
time.sleep(0.2) 
byteData = ser.read(6)
print(f"byteData: {byteData}")
ser.close()

# 第二次打开串口读写
ser = setup_serial_port()
print(f"Sent: {command_with_crc}")
ser.write(command_with_crc)
time.sleep(0.5)  # 短延迟确保响应已到达

if ser.in_waiting:
    print(f"in waiting {ser.in_waiting}")
else:
    print(f"in waiting false {ser.in_waiting}")
byteData = ser.read(6)

print(f"byteData: {byteData}")
ser.close()

现在我完全搞不懂为什么会这样,第一次打开串口总是读不到完整响应,第二次就正常了?希望有大佬能帮我分析下原因,或者给出正确的解决方法,谢谢!

备注:内容来源于stack exchange,提问作者Angel Hadzhiev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 11:28:04