Raspberry Pi实现Modbus TCP网关的完整方案
在老旧产线改造中,常有大量RS485 Modbus RTU设备(温湿度、电表、变频器)需要接入上位机或MES,但它们不支持TCP。此时用Raspberry Pi(树莓派)运行Modbus TCP↔RTU网关程序,既能做协议转换,又能充当边缘数据预处理节点。本文基于Raspberry Pi OS + pymodbus库,给出可落地的完整实现方案。
一、硬件连接与系统准备
1. 硬件连接
• RS485转USB(推荐):Waveshare USB↔RS485,A/B接现场设备,USB插Pi
• RS485 HAT(更稳):如Waveshare RS485 CAN HAT,直接占用/dev/ttyS0或/dev/ttyAMA0
- 终端电阻:总线两端各接120Ω电阻(含Pi侧HAT常带跳线)
[上位机/SCADA] ←─ Modbus TCP (:502) ─→ [Raspberry Pi + pymodbus gateway]
│ UART / USB-RS485
▼
[设备1 Addr=1]─[设备2 Addr=2]─[设备3 Addr=3] (菊花链)
2. 系统配置
# 启用串口(若用HAT,关闭串口控制台)
sudo raspi-config
# Interface Options → Serial → Login: NO, Shell: YES
# 确认设备节点
ls /dev/ttyUSB* # USB-RS485
ls /dev/ttyS0 # HAT(需关闭Bluetooth占用)
安装Python依赖:
sudo apt update
sudo apt install python3-pip
pip3 install pymodbus[twisted] --break-system-packages
二、pymodbus实现Modbus TCP网关
pymodbus自带ModbusTcpServer + ModbusRtuClient,可在一个进程中桥接两边。
gateway.py(核心代码)
#!/usr/bin/env python3
"""
Raspberry Pi Modbus TCP → RTU Gateway
- 监听 TCP 502(需sudo或改端口>1024)
- 转发请求到指定串口RTU从站
"""
from pymodbus.server import StartTcpServer
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.client import ModbusRtuFramer
from pymodbus.device import ModbusDeviceIdentification
import logging
# ── RTU串口配置 ──────────────────────────────
RTU_PORT = '/dev/ttyUSB0' # 或 '/dev/ttyS0'
RTU_BAUDRATE = 9600
RTU_PARITY = 'N'
RTU_TIMEOUT = 1.0
# ──────────────────────────────────────────────
logging.basicConfig(level=logging.INFO)
_log = logging.getLogger(__name__)
def setup_context():
"""
为每个RTU从站地址建立SlaveContext,
实际数据通过RTU转发,这里仅做占位(pymodbus自动代理)
"""
slaves = {}
for slave_id in range(1, 32): # 支持地址1~31
slaves[slave_id] = ModbusSlaveContext()
return ModbusServerContext(slaves=slaves, single=False)
def setup_identification():
ident = ModbusDeviceIdentification()
ident.VendorName = 'RPi_Gateway'
ident.ProductCode = 'MODGW'
ident.ProductName = 'ModbusTCP-RTU Gateway'
ident.ModelName = 'Raspberry Pi 4'
ident.MajorMinorRevision = '1.0'
return ident
if __name__ == '__main__':
context = setup_context()
identity = setup_identification()
_log.info(f"Starting Modbus TCP Gateway on :502 → RTU {RTU_PORT} "
f"{RTU_BAUDRATE},{RTU_PARITY},8,1")
# pymodbus自动把TCP请求经RTU framer转发到串口
StartTcpServer(
context=context,
identity=identity,
address=('0.0.0.0', 502),
# 关键:指定RTU framer用于后端通信
framer=ModbusRtuFramer,
rtu=dict(port=RTU_PORT,
baudrate=RTU_BAUDRATE,
parity=RTU_PARITY,
timeout=RTU_TIMEOUT,
inter_char_timeout=0.05,
inter_frame_timeout=0.1)
)
⚠️ 注意:默认502端口需root权限,开发阶段可改port=1502然后用iptables转发或SCADA直接连1502。
三、SCADA/MES侧访问示例
上位机(如Ignition、WinCC、Python client)只需配Modbus TCP:
- IP:Raspberry Pi IP(如192.168.1.50)
• Port:502(或1502)
• Slave ID:现场设备地址(1/2/3…)
• Function Code:03(读保持寄存器)、06/16(写)
Python测试客户端:
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('192.168.1.50', port=502)
client.connect()
# 读设备地址=1,保持寄存器起始40001(协议地址0),长度2
rr = client.read_holding_registers(address=0, count=2, slave=1)
if rr.isError():
print("Read error:", rr)
else:
print("Reg values:", rr.registers)
client.close()
四、现场调试与故障排查
现象 检查点
SCADA报“连接拒绝” ss -lntp | grep 502 确认网关运行;防火墙 sudo ufw allow 502/tcp
读回全是0或超时 A/B是否反接;终端电阻;波特率/奇偶校验是否与设备一致
偶发CRC错 串口干扰 → 屏蔽双绞线;降低波特率(9600→4800试)
只能访问一个Slave ID 确认设备地址不冲突;pymodbus context配置 single=False(已做)
树莓派重启后串口消失 检查 /boot/config.txt 中 enable_uart=1 且 console=serial0 已移除
附加建议:用modpoll(Linux)或Modbus Poll(Windows)先直连RTU设备验证串口参数,再接网关——分层排查最高效。
五、进阶:边缘数据缓存与MQTT上报
网关可在转发同时周期性读取关键寄存器并发布MQTT:
import paho.mqtt.client as mqtt
import threading, time
def poll_loop(cli):
while True:
res = cli.read_holding_registers(0, 2, slave=1)
if not res.isError():
payload = {'temp': res.registers[0]/10}
mqtt_cli.publish('factory/zone1/temp', str(payload))
time.sleep(2)
mqtt_cli = mqtt.Client()
mqtt_cli.connect('broker.local', 1883)
threading.Thread(target=poll_loop, args=(client,), daemon=True).start()
六、结语
Raspberry Pi做Modbus TCP网关的核心工作是:RS485串口参数匹配 + pymodbus的TcpServer挂RTU framer + 正确Slave ID映射。该方案成本低(一块Pi)、扩展性强(可加MQTT/SQLite/Web UI),是传统RTU设备平滑接入工业物联网的经典过渡方案。





