三菱FX系列PLC串口通信开发:自定义协议实现多传感器数据读取
在小型自动化产线中,经常需要把多台温湿度、压力、流量传感器通过RS485总线汇总到FX3U PLC,再由上位机(PC/网关)统一轮询读取。三菱FX系列内置编程口默认走MC协议(9600/7/E/1),但若传感器是自定义串口协议或第三方非标设备,更实用的做法是:PLC侧用RS指令做无协议通信,自定义"帧头+站号+功能码+数据+CRC"的轻量协议,把多传感器数据汇聚到D寄存器区,再由上位机读取。本文以FX3U-485-BD + RS485总线为例,给出可落地的完整方案。
一、硬件连接与通信参数
FX3U本体不带RS485,需加装FX3U-485-BD通信板。RS485为差分信号,总线拓扑下所有站点A-A、B-B并联,末端并联120Ω终端电阻,屏蔽层单点接地。
通信参数通过特殊寄存器D8120配置,必须在PLC上电初始化时写入一次:
MOV H0081 D8120 ; 9600bps, 8数据位, 1停止位, 无校验, 无协议, RS485
H0081的位含义:bit0-3=0(无协议)、bit4-6=8位数据、bit7=无校验、bit8=1停止位、bit9-11=9600bps。D8120写入后需断电重启生效。
⚠️ 注意:FX编程口(RS422)默认是9600/7/E/1的MC协议,D8120只控制通道1的RS指令无协议通信,两者参数相互独立,别混淆。
二、自定义协议帧格式
为支持多传感器轮询,定义如下帧格式(16进制):
字段 长度 说明
Header 1B 固定0xAA
SlaveID 1B 传感器站号 0x01-0x0F
Function 1B 0x01=读数据,0x02=写命令
Length 1B 数据域字节数
Data N×B 传感器数据(如温度、湿度、状态)
CRC16 2B Modbus式CRC校验
PLC作为主站,主动向各站号传感器发送读命令,收到应答后解析并存入D区。
三、PLC侧RS指令编程
FX3U的RS指令(FNC 80)是实现无协议通信的核心:
RS D100 D0 D200 D1
D100:发送数据起始地址
D0:发送数据长度(点数)
D200:接收数据起始地址
D1:接收数据最大长度
关键特殊软元件:
M8122:发送请求标志,置ON触发发送,发送完成后自动复位(切勿在程序中手动复位)
M8123:接收完成标志,置ON后必须手动复位,否则无法接收下一帧
M8129:超时标志
M8161:8位/16位处理模式切换
梯形图核心逻辑(ST语言示意):
// 初始化
IF M8002 THEN
MOV H0081 D8120; // 配置通信格式
MOV K0 D0; // 发送长度初始0
END_IF;
// 轮询触发(每500ms轮询下一个传感器)
IF T0 THEN
INC D10; // 当前站号指针
AND D10 K0F; // 限制在1-15
CALL P0; // 调用组帧子程序
END_IF;
// 组帧子程序P0:把 0xAA + StationID + 0x01 + Len + CRC 写入D100~
P0:
MOV K0AA H0 D100; // 帧头
MOV D10 D101; // 站号
MOV K01 D102; // 功能码:读
MOV K02 D103; // 数据长度
CALL CRC16; // 计算CRC16写入D104,D105
MOV K6 D0; // 发送6字节
SET M8122; // 触发发送
RET;
// 接收处理
IF M8123 THEN
// 解析D200~D205,提取传感器数据
MOV D201 D300; // 例如温度存入D300
MOV D202 D301; // 湿度存入D301
RST M8123; // 必须手动复位!
END_IF;
M8123必须手动复位是新手最常踩的坑——不复位,下一帧数据永远不会进来。
四、上位机Python轮询读取
上位机通过编程口(MC协议)或RS485监听PLC汇总的D区数据。若直接对接传感器,则用Python模拟PLC的主站行为:
import serial, time, crcmod
class SensorPoller:
def __init__(self, port='/dev/ttyS0', baud=9600):
self.ser = serial.Serial(port, baud, bytesize=8,
parity='N', stopbits=1, timeout=1)
self.crc16 = crcmod.mkCrcFun(0x18005, rev=True, initCrc=0xFFFF)
def read_sensor(self, slave_id):
# 组帧: AA + ID + 01 + 00 + CRC_L + CRC_H
frame = bytes([0xAA, slave_id, 0x01, 0x00])
crc = self.crc16(frame)
frame += bytes([crc & 0xFF, crc >> 8])
self.ser.write(frame)
# 等待应答
resp = self.ser.read(12) # 假设应答12字节
if len(resp) < 6 or resp[0] != 0xAA:
return None
# 校验CRC
if self.crc16(resp[:-2]) & 0xFFFF != (resp[-2] | resp[-1] << 8):
return None
return resp[4:-2] # 返回数据域
def poll_all(self, slave_ids):
results = {}
for sid in slave_ids:
data = self.read_sensor(sid)
if data:
results[sid] = {
'temp': data[0] * 256 + data[1], # 大端温度
'humidity': data[2],
'status': data[3]
}
else:
results[sid] = {'error': 'timeout'}
time.sleep(0.05) # 帧间隔,避免总线冲突
return results
# 使用示例
poller = SensorPoller('/dev/ttyS0')
data = poller.poll_all([0x01, 0x02, 0x03, 0x04])
print(data)
关键点:
每帧之间需留20-50ms间隔,RS485半双工总线避免冲突
CRC16校验必须软硬件一致,传感器端通常用Modbus CRC
超时重发机制:连续3次失败标记传感器离线
五、PLC D区数据上交上位机
如果不想在上位机直接轮询传感器,而是让PLC做汇聚,上位机只需读D区:
# 使用python-snap7或自编MC协议读取D300~D320
# 这里以伪代码示意
for i in range(21):
value = read_plc_d_register(300 + i)
print(f"D{300+i} = {value}")
PLC程序需周期性地把各传感器最新值刷新到D300开始的连续寄存器区,配合时间戳和状态字。
六、调试三步走
串口调试助手验证:先用SSCOM/XCOM模拟主站,向传感器发帧,确认应答格式和CRC
PLC端RS指令单步:监视D100~D105发送内容、D200~D205接收内容,用M8123的ON/OFF确认接收正常
总线抓包:RS485总线挂逻辑分析仪或USB-RS485适配器,抓全帧分析时序
七、高频踩坑
⚠️ 坑1:D8120配置错误——MCU和PC端参数必须完全一致(波特率、数据位、校验、停止位),否则满屏乱码。
⚠️ 坑2:M8123未复位——接收一帧后必须RST M8123,否则后续数据全部丢失。
⚠️ 坑3:M8122手动复位——M8122发送完成后自动OFF,程序中若手动RST会打断发送过程。
⚠️ 坑4:RS485方向控制——DE/RE引脚时序要精确,发送完最后一字节再切换为接收,否则末尾字节丢失。
写在最后
用FX3U的RS指令做自定义协议的多传感器轮询,本质是把PLC变成"串口网关"——前端用自定义轻量协议与传感器对话,后端把汇聚数据放入D区供上位机读取。这套方案的成本极低(一片FX3U-485-BD约百元),却能稳定管理RS485总线上的十余个传感器节点。相比直接使用Modbus RTU从站协议,自定义协议更灵活:你可以把设备状态、报警标志、采样时间戳打包进一帧,减少轮询次数。当你的产线从"3个传感器"扩展到"30个传感器"时,只需在PLC轮询表里多加几条CALL,上位机侧逻辑零改动——这才是模块化设计真正的威力。
对于FX5U等新型号,无协议通信升级为RS2指令,并支持Modbus RTU主站ADPRW指令,若传感器原生支持Modbus,直接用ADPRW更省力;若是非标串口设备,RS2的自由度与RS指令一致,程序思路完全可迁移。





