Python程序Serial Port读取与ModbusTCP Server寄存器更新延迟问题咨询
Hey there, let's break down this problem and fix it properly!
The core issue here is that your original code was probably running serial port reading and Modbus Server operations in a single thread, relying on a fixed delay to update registers. When you set time=5, the delay was too short—your serial port hadn't received any data yet when you tried to update the registers, so clients got nothing. Setting it to 10 worked but was way too slow because you were forcing a wait even when new data was already available.
The solution is to decouple serial port reading and Modbus register updates using threads, so each task runs independently. Here's a step-by-step implementation:
1. Key Concepts to Apply
- Use a separate thread for serial port listening: this thread will wait for incoming data and update a shared variable as soon as data arrives.
- Use another thread (or a periodic task) to sync the shared variable to Modbus registers: this ensures clients always get the latest data without unnecessary delays.
- Add a thread lock to safely access the shared variable (prevents race conditions between threads).
2. Full Example Code
First, make sure you have the required libraries installed:
pip install pymodbus pyserial
Then here's the code:
import threading import time import serial from pymodbus.server import StartTcpServer from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSlaveContext, ModbusServerContext # Shared storage for register data (adjust size based on your needs) register_data = [0] * 10 # 10 holding registers starting at address 0 # Thread lock to safely read/write shared data data_lock = threading.Lock() def serial_listener(port, baud_rate): """Thread function to listen for serial data and update shared register values""" try: ser = serial.Serial(port, baud_rate, timeout=1) print(f"Connected to serial port {port} at {baud_rate} baud") while True: # Read and parse serial data (adjust parsing based on your actual serial protocol) raw_data = ser.readline().decode('utf-8').strip() if raw_data: # Example: convert received string to integer (modify this for your data format) try: parsed_value = int(raw_data) # Safely update shared register data with data_lock: register_data[0] = parsed_value # Update register 0 with new value print(f"Updated register 0 to: {parsed_value}") except ValueError: print(f"Invalid serial data received: {raw_data}") except Exception as e: print(f"Serial port error: {str(e)}") finally: if 'ser' in locals() and ser.is_open: ser.close() print("Serial port closed") def modbus_register_updater(context): """Thread function to sync shared data to Modbus registers at regular intervals""" while True: with data_lock: # Copy latest shared data to Modbus holding registers context[0].setValues(3, 0, register_data) # 3 = holding register, 0 = start address # Check for updates every 100ms (adjust this based on your desired refresh rate) time.sleep(0.1) if __name__ == "__main__": # Initialize Modbus server context data_block = ModbusSequentialDataBlock(0, register_data) slave_context = ModbusSlaveContext(hr=data_block) # hr = holding registers server_context = ModbusServerContext(slaves=slave_context, single=True) # Start serial listener thread serial_thread = threading.Thread( target=serial_listener, args=('COM3', 9600) # Replace with your serial port and baud rate ) serial_thread.daemon = True # Thread exits when main program exits serial_thread.start() # Start Modbus register updater thread updater_thread = threading.Thread( target=modbus_register_updater, args=(server_context,) ) updater_thread.daemon = True updater_thread.start() # Start Modbus TCP Server print("Starting Modbus TCP Server on 0.0.0.0:502...") StartTcpServer(context=server_context, address=("0.0.0.0", 502))
3. Important Notes
- Serial Data Parsing: The
serial_listenerfunction usesreadline()and decodes as UTF-8—adjust this to match your actual serial protocol (e.g., binary data would useser.read()andstruct.unpack()instead). - Thread Safety: The
data_lockensures that we don't have one thread writing toregister_datawhile another is reading it, which could cause corrupted values. - Refresh Rate: The
modbus_register_updaterchecks for updates every 100ms—you can make this smaller (like 0.05s) for faster updates, as long as it doesn't cause excessive CPU usage.
This setup means:
- Your serial port is always listening for new data, updating the shared variable instantly when data arrives.
- The Modbus registers are synced to the latest data frequently, so clients will see updates as soon as possible without waiting for a fixed 10-second delay.
内容的提问来源于stack exchange,提问作者coddr




