☰
Python与三菱MC协议通信:批量读写优化实战指南
2026/9/26 1:10:26 网站建设 项目流程

做工厂数采或者设备联网项目的朋友,碰到三菱PLC应该都不陌生。不管是老牌的Q系列、高性价比的FX5U,还是小型的L系列,只要你想用Python把设备状态、工艺参数捞上来,或者把MES指令发下去,绕不开的都是三菱的MC协议。这个项目标题——Python与三菱MC协议通信 批量读写优化,基本就是我最近一个产线数据采集项目里完整走了一遍的链路:协议选型、环境搭建、报文解析、批量读写改造,最后还专门压了一把性能。这篇不是复读官方手册,而是把我实际踩过的坑、对比过的方案、压测出来的数据都摊开讲讲。如果你正准备做三菱PLC数采,或者已经在用Python读写但总觉得效率不对,这篇应该能帮你省不少时间。

1. 项目背景与需求拆解

1.1 为什么是Python,为什么是MC协议

先说结论:Python + MC协议这套组合,最大的优势是上手快、依赖轻、落地直接。

三菱PLC常用的通信方式无非这么几种:MC协议、OPC UA、Modbus TCP以及路由功能。OPC UA功能强、语义丰富,适合异构系统大平台,但部署相对重,需要建信息模型、配安全策略,很多现场工程师看着就头大。Modbus TCP在三菱PLC上属于"能用但别扭",软元件映射要自己维护,点位一多表格就很乱。而MC协议是三菱的原生协议,PLC侧只要在以太网模块里开放端口,不需要额外授权和中间件,Python侧一个pymcprotocol库就能直接对话,可以说是成本最低的入门路径。

MC协议本身也有几种帧格式,常见的是A-1E帧、QnA-3E帧等,以太网场景下基本都用3E帧(二进制帧),报文结构紧凑,适合高频次读写。我们做的批量读写优化,本质上就是针对3E帧里的批量读命令、批量写命令和数据长度做文章。

1.2 批量读写优化的核心痛点

这个项目最开始接到的痛点特别典型:现场原本有个采集程序,每隔100毫秒轮询PLC,但点位是逐个读的。100个字寄存器就要发100条指令,PLC以太网模块的通信负载很高,网络抓包全是小而碎的报文,偶尔还出现读写超时。产线工程师的反馈就一句话:"你们要么把频率降下来,要么把读写方式改一下,不然PLC通信模块要扛不住了。"

这就是批量化改造的意义。单个读写指令一次只能处理一个软元件,批量读写指令一次可以处理一批连续或不连续的软元件。把100条精简成1条,网络报文数量直线下降,PLC侧的压力也大幅缓解。后面我会给实测对比数据,差距真的不是一点半点。

2. 环境准备与通信建连

2.1 Python环境与依赖库安装

我用的Python版本是3.9,其实3.8以上的版本问题都不大,主流Linux发行版和Windows都可以跑。依赖库只需要一个核心库:pymcprotocol。

pip install pymcprotocol

安装完成后顺手验证一下:

import pymcprotocol print(pymcprotocol.__version__)

如果没有报错,说明环境OK。pymcprotocol这个库帮我们把MC协议3E帧的组帧、拆帧、字节序、校验等底层细节都封装好了,我们只需要关注软元件地址和读写接口,不用自己用socket拼报文,这对项目交付速度来说非常关键。

注意:pymcprotocol库本身是纯Python实现,依赖pyserial(串口场景)等少量包,安装很轻量。如果生产环境不能联网,可以用离线包方式安装。

2.2 PLC端通信参数配置

Python这边准备得再好,PLC侧不开放端口,通信也是一句空话。用GX Works2或者GX Works3打开PLC参数,我一般重点确认这几项:

  • CPU的IP地址和子网掩码,必须和上位机在同一个网段。
  • 内置以太网端口启用,且开放MC协议通信。
  • 端口号,Q系列默认通常是6000,FX5U默认也是6000,当然也能自定义,但要和代码里保持一致。
  • 确认没有多余的访问限制功能开启。三菱有些安全选项(比如远程密码、IP过滤)如果开了,测试阶段先关掉,等通信链路验证通过再按生产规范开启。

这些参数改完需要重启PLC模块才生效,现场切换的时候一定提前跟工艺人员打招呼,别在设备运行的时候直接重启PLC。

2.3 连接测试与软元件地址体系

通信配置完成后,先用一小段代码验证连通性:

import pymcprotocol plc = pymcprotocol.mcprotocol() plc.setaccess(port=0) # 0=以太网接入 plc.connect("192.168.10.1", 6000) print("PLC连接成功") plc.close()

这段代码只要能跑通,后续就是稳定链路。真正容易写错的是软元件地址。三菱的软元件地址有一套自己的命名体系,跟Modbus那种纯数字寄存器号完全不是一回事,我整理了一份高频用到的地址表:

软元件类型前缀操作单位典型用途
数据寄存器D字工艺参数、模拟量数值
内部继电器M位状态位、报警位
输入继电器X位PLC数字输入
输出继电器Y位PLC数字输出
链接寄存器W字网络共享数据区
文件寄存器R/ZR字大容量数据存储

举个例子,D100表示第100号数据寄存器,M50表示第50号内部继电器。批量读的时候,字寄存器用16bit数据宽度,位寄存器用bitunits系列接口。这个基础概念不清,后面读写概率性出错,栽跟头的例子我见太多了。

3. 批量读写核心实现

3.1 为什么必须用批量读写

前面提了痛点,这里直接说数据。我在现场用一台FX5U做过一次简单压测:读取连续100个字寄存器(D100~D199)。

  • 单条读写方式:100次读,每次都是独立的读请求,一次完整请求约70字节报文,加上响应,总共网络往返100次。
  • 批量读写方式:1条批量读请求读取100个字,请求帧约80字节,响应帧稍大(100个字约200字节),网络往返只有1次。

实测下来,100毫秒轮询周期里,单条读写方式不仅CPU占用高,而且偶尔因为网络抖动出现超时重试;改成批量读之后,轮询周期压到20毫秒都没压力,报文数量直接下降99%。这还不是最夸张的,点位越多,批量优势越明显。

这就是我在所有项目里都坚持批量读写的原因:通信链路的开销不是按字节算的,而是按指令条数算的。一条指令的帧头开销相对固定,把多个数据点塞进一条指令,等于摊薄了所有帧头成本。

3.2 批量读取的完整实现

pymcprotocol里批量读用batchread方法,传入一个地址列表和数据类型,返回对应值列表。

import pymcprotocol plc = pymcprotocol.mcprotocol() plc.setaccess(port=0) plc.connect("192.168.10.1", 6000) # 批量读取连续地址 D100~D109,以16位整数读取 devices = [f"D{i}" for i in range(100, 110)] values = plc.batchread(devices=devices, datacode="16bit") print(values)

这个方法适用的是"地址连续或者用户明确列出地址列表"的场景。实际生产里,我更多会封装一层:把点位表(点位清单)传进来,代码自动切分连续段、组装列表,最终一次batchread拉回来。

批量读取时还有一个细节:datacode参数控制数据解析宽度。字寄存器通常用16bit;如果是32位整数,用32bitint;如果是浮点数,用32bitfloat。但要注意,32位数据在PLC里占用两个连续字,比如一个32位浮点数存在D100和D101两个寄存器里,读取时地址列表要按"每个32位数据占2个字"来组织,否则数值会错位。这个坑特别隐蔽,后面专项说。

3.3 批量写入的完整实现

批量写入对应batchwrite方法,地址列表和值列表一一对应,数量和顺序必须完全一致。

# 批量写入 D100~D102,分别写入 100、200、300 write_devices = ["D100", "D101", "D102"] write_values = [100, 200, 300] plc.batchwrite(devices=write_devices, values=write_values, datacode="16bit")

写入指令在生产现场比读取更敏感,因为一个错误的地址和值就可能让设备动作。我的习惯是:

  • 写之前打印一份日志,记录时间、地址、值,便于回溯。
  • 对于控制类点位(如启动、急停),确认PLC梯形图侧有没有互锁逻辑,软件侧不要绕过安全逻辑直接改线圈。
  • 写入失败时不要盲目重试,先判断是地址错误还是通信异常,避免连续重复写入造成异常动作。

3.4 随机读写与位读写

batchread适合地址列表已知的场景,但有些时候我们关心的是"从D100开始连续读20个字",用randomread这类接口更直观:

# 从D100开始连续读20个字 values = plc.randomread( devicetype="D", start_device=100, read_points=20, datacode="16bit" )

位软元件(M、X、Y)的批量操作与字元件不同,要用专门接口:

# 批量读取位元件 M100~M102 bit_values = plc.batchread_bitunits(devices=["M100", "M101", "M102"]) # 批量写入位元件 plc.batchwrite_bitunits(devices=["M100", "M101"], values=[True, False])

位元件适合存设备状态、启停标志、报警信号。在数据采集点位规划时,我一般会把位状态单独验收,不要混在字寄存器里解析,逻辑会清爽很多。

4. 性能优化实战

4.1 报文分析与通信频率优化

理解了3E帧结构,优化入手点就非常清晰了。一次批量读请求的报文大概是这样的骨架(二进制3E帧):

  • 帧头:固定字节
  • 命令字段:批量读/写标识
  • 子命令:字/位类型
  • 数据体:起始地址、数据长度、数据内容

批量操作的本质,就是把原来N个请求的数据体合并进一个请求里,只保留一份帧头开销。所以优化不只是"代码层面用batchread",而是要从通信报文角度倒推:每条指令当前处理了多少数据点,是否已经到了接近单帧上限。

频率优化则是另一个维度。原来100毫秒轮询一次所有点位,但如果这些点位里有一部分是温度、液位这类慢变量,完全没必要1秒读10次。我们的做法是把点位按变化速度分级:

  • 高速点位(编码器计数、伺服状态):20~50毫秒采集一次。
  • 中速点位(压力、流量、电机电流):200~500毫秒采集一次。
  • 低速点位(温度、累积量、设备模式):1~5秒采集一次。

这样整体通信负载大幅下降,关键数据的速度反而更快了,这是纯协议优化之外性价比很高的一步。

4.2 地址连续性与分批策略

批量读写最舒服的场景就是地址连续。D100到D199连续100个字,一条batchread搞定。但现实点位表往往东一个西一个,D100、D150、D200、D201……这时候如果硬拼一个地址列表进去,指令就会很碎或者效率下降。

pymcprotocol的batchread其实对地址列表没有连续性要求,它会自动做处理。但从PLC协议效率和网络负载角度,我建议自行做"连续段合并":

  • 把点位表按地址排序,相邻地址合并成连续区间。
  • 每个连续区间用一条批量指令从起始地址开始读对应长度。
  • 不连续的区间分别读取。

这样既能用批量指令,又避免了地址列表里大量零散地址带来的分包开销。我写过一个简单的分段函数,大意是把地址列表按连续区间切成多个小组,每组调用一次randomread或batchread,实测相比逐个读,同样点位下耗时能降低90%以上。

另一个必须考虑的是单次读取长度上限。虽然3E帧协议本身能表示较大的读取点数,但PLC以太网模块有缓存和响应帧长度的实际限制。按Q系列通用手册,3E帧二进制模式下批量读寄存器一般建议不要超过960字,有的模块更保守。我在项目里统一采用512字一批,既安全又够用。数据量大的时候就多分几批,批次之间加极短的间隔,避免打爆PLC通信模块。

4.3 连接复用与超时配置

新手最容易犯的错,就是每次读写都新建连接、用完关闭。TCP建连本身有三次握手开销,PLC侧以太网模块还要做会话管理,频繁建连会徒增延迟,甚至导致PLC连接数满被拒。

正确做法是:进程启动时建立连接,然后一直复用,配合断线重试机制。如果项目是多线程并发访问PLC,建议用一个专用的通信连接实例,通过线程锁或者队列串行化访问,不要多个线程同时往同一个socket上写指令。

超时配置也要认真调。TCP连接超时、读取响应超时要区分开:

plc.connect("192.168.10.1", 6000, timeout=3.0)

超时设太短(比如0.5秒),PLC响应稍慢就误判为故障,然后重连,反而更糟;设太长(比如30秒),现场出问题要半天才报出来。我一般以1~3秒为基准,根据网络稳定性和PLC性能微调。

4.4 异常处理与自动重连

工业现场网络环境不像办公室那么干净,交换机老化、网线松动、PLC模块重启都可能导致连接中断。所以完整方案必须包含异常捕获和自动重连。

我习惯封装一个PLCConnection类,核心逻辑是:

  • 每次读写操作前检查连接状态,断了就自动重连。
  • 连续重连失败达到阈值(比如5次)后,抛出报警事件,通知上层监控。
  • 重连成功之后做一个轻量握手读取(读PLC运行状态寄存器),确认链路真正恢复。

另外,读写操作本身要包一层try/except,捕获通信异常和值解析异常。批量读返回的数据结构是list,解析时如果长度和点位表不一致,大概率是通信半包或者地址越界,这时候不要继续处理这批数据,直接丢弃并重读。

4.5 异步采集架构扩展

对点位特别多的场景,可以再进一步,使用asyncio做异步采集。pymcprotocol本身是同步阻塞接口,直接包进async代码会卡住事件循环。工程上常用的做法是把通信封装成一个独立线程负责收发,主线程通过队列来消费数据。

一个典型的采集线程结构是:

  • 采集线程循环执行批量读、解析、把数据放进queue.Queue。
  • 主业务线程从队列取数据,做落库、上报、异常判断。
  • 队列设置maxsize,消费不过来时丢弃旧数据,避免内存堆积。

我在一个数据点超过2000个的项目里就用了这个模式,采集线程保持20毫秒一轮批量读,主线程负责写MySQL和推送到Web端,跑了两个月没出现过阻塞导致的丢点。对大多数场景,这种"单线程采集+队列分发"比多线程直接操作PLC连接更稳妥,也更容易排查问题。

5. 常见问题排查与避坑指南

5.1 连接类问题速查

现象可能原因排查方法
connect超时IP/端口错误、网段不通ping PLC IP,确认端口号配置一致
PLC拒绝连接MC协议未开放、访问限制开启检查GX Works参数,查看PLC模块状态
通信偶发超时网络抖动、PLC负载高降低轮询频率,检查交换机/网线
批量读写响应异常单次读取长度超过模块上限拆分成512字以下批次

我最想强调的是:连接不稳定时,别急着改代码,先抓包。用Wireshark过滤PLC的IP和端口,看一下是请求没到PLC、PLC没回,还是回了但Python侧解析失败。这三种情况的处理方向完全不同,有了抓包结论再动手,效率最高。

5.2 地址与数据格式的坑

这个类问题平时最容易忽略,但出问题最难查。先说地址大小写:三菱地址一般是大写字母,d100与D100,有的库内部做了大小写归一化,有的没做,稳妥起见统一大写。再说数据宽度:16bit读出来的就是一个字的值,范围是-32768~32767;如果PLC里存的是32位浮点,你用16bit去读,读到的只是半个浮点数的低字或高字,数值完全是垃圾值。

更隐蔽的是字节序。三菱PLC走以太网3E帧时,多字节数据默认是低字节在前(小端序),不过具体到软元件排列和转换还有细节。pymcprotocol对16bit处理通常没问题,32位数据如果发现高低字反了,就要检查地址列表里两个相邻字的排列顺序,必要时交换顺序。

我的实战经验是:正式开发前先手工建一个PLC测试程序,往D区写入一组已知数值(比如整数1、2、3,浮点数3.14、2.71),然后用Python读回来对照,把16bit、32bitint、32bitfloat都验证一遍确认无误,再开始接业务点位表。

5.3 性能瓶颈定位方法

如果批量改造之后性能还是不理想,按下面顺序查:

  1. 轮询周期太短,通信指令频率依然高。先看代码,计算一下每秒实际发出的指令条数,目标通常压到50条以内。
  2. 地址不连续导致分段过多。打印日志看看每次读操作实际分成几批,批次太多就说明点位规划有问题。
  3. PLC侧程序本身太忙。这个可以通过三菱编程软件监控CPU扫描周期,如果PLC扫描周期都因为通信负载拉长了,说明上位机改写已成拖累,必须进一步降低频率或减少点数。
  4. 网络链路质量差。看丢包率和重传率,TCP重传多的时候,即使批量读也快不起来。

瓶颈定位一定要靠数据说话,不要凭感觉。我做一个优化项目时,会在优化前后各跑一段压测脚本,统计平均响应时间、最长响应时间、指令条数、丢包重试次数,才有说服力。

5.4 现场实操的几条硬经验

参数配置好后先拿测试点位跑半天,确认无异常再切换到生产点位,别一上来就全量读写。

批量读响应数据量较大时,注意64KB边界问题。如果单次读取的字数非常大,响应帧可能超过TCP分片能处理的边界,导致解析异常,此时务必按上限分批。

如果你是在Windows下开发、部署到Linux服务器,一定要在网络参数上多验证一遍。Windows的TCP栈和Linux的有些默认行为差异,实际同网段内可能表现不明显,但跨交换机、跨防火墙时会突然冒问题。保险起见,部署后先连续跑24小时监控日志。

6. 实操总结与扩展建议

这个项目做完后,我个人的最大感受是:三菱MC协议的批量读写优化,本质上做的是"通信指令集约化"——从单点读写成批量读写,从被动高频轮询变成分级采集,从随意连接变成连接复用加异常自愈。整套改完,同样的PLC点位表,通信负载降到原来的百分之几,轮询周期反而可以更短。

最后分享一个小技巧:批量读写优化之后,数据采集能力有余量了,可以顺势把数据链路往下延伸。比如把采集到的实时值按时间戳写入时序数据库InfluxDB,或者通过MQTT推送到云端物联网平台,再叠加一个简单的可视化大屏。这一步做好后,整个数采系统的价值会从"能读PLC"变成"能支撑设备监控、能耗分析和预测维护",项目交付的含金量完全不是一个层级。

工业通信就是这样,协议是死的,工程思路是活的。先把批量读写和异常自愈做扎实,后面接什么平台、做什么分析,都有了可靠的数据底座。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询