ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python 采集三菱 PLC 数据并入库:MC 协议与时序设计实战

Python 采集三菱 PLC 数据并入库:MC 协议与时序设计实战 1. 项目缘起与整体架构思路三菱 PLC 在制造业现场保有量极大尤其是 FX 系列和 Q 系列很多产线设备、检测工位、包装机械的控制核心都是它。我接触过的项目中十有八九需要把 PLC 里的实时数据采集上来存进数据库用于追溯、报表或者 MES 对接。早期大家习惯用组态软件或者触摸屏自带的记录功能但一旦涉及自定义逻辑、复杂清洗规则、多设备并发组态软件就捉襟见肘了。用 Python 直接和三菱 MC 协议通信再配合数据库做时序入库是我这几年用得最顺手的一套方案。这个项目的核心目标很明确让 Python 程序周期性地从三菱 PLC 读取指定软元件的数据经过必要的解析和清洗后按照合理的时间节奏写入数据库保证数据不丢、不重、不乱序。听起来简单但真正落地时会遇到一堆细节问题——MC 协议是二进制还是 ASCII、软元件地址怎么换算、批量读取和单点读取怎么选、入库频率和 PLC 扫描周期怎么匹配、断线重连后数据怎么补、数据库写入压力怎么控制。这些才是决定项目成败的关键。适合阅读这篇内容的人应该是有一定 Python 基础、手头有三菱 PLC 或者准备对接三菱设备的工程师。如果你完全没接触过 PLC也没关系我会把 MC 协议的基本概念和软元件寻址方式讲清楚保证你能跟着思路走。如果你已经是老手可以直接跳到时序设计和避坑部分那里有我踩过的坑和总结出来的参数。整体架构上我采用的是分层设计最底层是通信层负责和 PLC 建立 TCP 连接、收发 MC 协议报文中间是采集调度层负责按周期触发读取、管理软元件列表、处理异常重连上层是数据处理与入库层负责解析原始字节、做类型转换、按时间窗口批量写入数据库。三层之间通过队列或者回调解耦避免因为数据库写入慢而阻塞 PLC 读取。这个设计的好处是每一层都可以独立调试和替换比如通信层换成串口或者换成其他品牌协议上层逻辑基本不用动。为什么选择 TCP 而不是串口因为现在大部分三菱 Q 系列和 FX5U 都自带以太网口FX3U 加个 ENET 模块也能走以太网。TCP 的速率和稳定性远高于 RS485而且 Python 的 socket 编程非常成熟。MC 协议本身支持二进制和 ASCII 两种帧格式二进制效率更高我一般优先用二进制。至于数据库MySQL 和 PostgreSQL 都用过时序数据量大的话 PostgreSQL 配合 TimescaleDB 或者直接用 InfluxDB 会更合适但考虑到很多工厂的 IT 环境还是 MySQL 为主这篇内容以 MySQL 为例来展开。2. MC 协议通信核心细节与实操要点2.1 MC 协议帧结构与软元件寻址三菱 MC 协议全称是 MELSEC Communication Protocol是三菱为自己 PLC 定义的一套应用层协议。它跑在 TCP 或者 UDP 之上默认端口是 5000也有用 6000 的。MC 协议有两种帧格式3E 帧和 4E 帧3E 帧最常用结构也简单。一个典型的二进制 3E 帧请求报文包含以下部分副头部5000 表示请求、网络号、PLC 号、请求目标模块 IO 号、请求目标模块站号、请求数据长度、CPU 监视定时器、指令代码、子指令代码、软元件地址、软元件点数。软元件地址的表示方式是很多人第一次接触时最懵的地方。三菱的软元件用字母加数字表示比如 D100 表示数据寄存器第 100 号M50 表示内部继电器第 50 号X10 表示输入继电器第 10 号。但在 MC 协议报文里地址需要转换成十六进制并且要区分位软元件和字软元件。D 寄存器是字软元件地址直接就是数值M、X、Y 是位软元件地址需要按位计算。比如 D100 在报文里就是 0x0064M50 是 0x0032但读取位软元件时返回的数据是按位打包的每两个字节包含 16 个位的状态。我刚开始做的时候在地址换算上栽过跟头。当时读 D 寄存器没问题读 M 点就总是错位。后来才发现位软元件的地址在 MC 协议里是按位编号的但报文里传的是起始位地址返回的数据需要自己按位解析。比如读取 M0 开始的 16 个位返回两个字节第一个字节的 bit0 对应 M0bit1 对应 M1以此类推。如果读取的点数不是 16 的整数倍最后一个字节的高位是无效的需要自己屏蔽掉。2.2 Python 通信库选型与连接管理Python 和三菱 PLC 通信可选的路子有几条。一是用pymcprotocol这个第三方库它封装了 MC 协议的 3E 帧和 4E 帧支持二进制和 ASCII用起来比较省心。二是用python-snap7但那个是西门子的 S7 协议不适用三菱。三是自己用 socket 拼报文灵活性最高但开发量大。我一般推荐pymcprotocol它的 API 设计比较直观batchread_wordunits和batchread_bitunits两个方法就能覆盖大部分场景。安装很简单pip install pymcprotocol就行。连接的时候需要指定 PLC 的 IP 和端口还有网络号、PLC 号这些参数。大部分情况下网络号和 PLC 号都是 0站号也是 0。连接建立后建议做一个心跳检测定期读一个固定的寄存器确认连接还活着。我见过太多因为网线松动或者交换机重启导致连接假死的情况如果不做心跳程序会一直卡在 recv 上数据就断了。连接管理上我习惯用一个单独的类来封装内部维护 socket 连接和重连逻辑。重连策略是检测到异常后先关闭旧连接等待一个退避时间比如 1 秒、2 秒、4 秒递增然后尝试重新连接。连续失败超过一定次数比如 10 次就记录错误日志并告警。重连成功后要把采集调度层里缓存的待读任务重新激活避免数据出现大段空白。注意三菱 PLC 的 MC 协议连接数有限制FX5U 一般支持 8 个以内的 TCP 连接Q 系列多一些但也有限。如果多个程序同时连可能会被拒绝。建议一个 PLC 只用一个采集程序需要多路数据就在程序内部做多线程或者异步。2.3 批量读取与单点读取的取舍MC 协议支持批量读取一次可以读连续的一段软元件。比如一次读 D100 到 D199共 100 个字。批量读取的效率远高于单点读取因为报文开销是固定的读 1 个点和读 100 个点的报文长度差不多。我实测过读 100 个 D 寄存器批量读取耗时大约 5 到 8 毫秒而单点读 100 次需要 300 毫秒以上。所以在设计采集方案时一定要把需要读取的软元件按地址连续性分组尽量批量读。但批量读取也有坑。一是地址必须连续如果需要的软元件分散在 D100、D200、D500那就得分成三组。二是单次读取的点数不能超过 MC 协议的限制3E 帧二进制模式下一次最多读 960 个字或者 7168 个位。超过这个数就得拆分。三是有些 PLC 型号对批量读取的响应时间较长如果一次读太多可能会触发 CPU 监视定时器超时。我一般把单次批量读取控制在 200 个字以内兼顾效率和稳定性。对于位软元件批量读取的返回数据是打包的解析时需要按位展开。我写了一个辅助函数把返回的字节数组转换成布尔列表再根据软元件起始地址映射到具体的 M 点或者 X 点。这个函数看起来简单但边界条件很多比如点数不是 16 的整数倍时最后一个字节的有效位要截断。3. 数据入库时序设计的核心逻辑3.1 采集周期与 PLC 扫描周期的匹配时序设计的第一步是确定采集周期。这个周期不能拍脑袋定要和 PLC 的扫描周期匹配。PLC 的扫描周期是指 CPU 从读取输入、执行程序、刷新输出的一个循环时间通常在 1 到 20 毫秒之间取决于程序复杂度。如果采集周期比扫描周期还短那读到的数据可能是同一个扫描周期的重复值没有意义。如果采集周期太长又可能漏掉快速变化的信号。我的经验是采集周期至少是 PLC 扫描周期的 5 到 10 倍。比如扫描周期是 10 毫秒采集周期设为 50 到 100 毫秒比较合适。对于变化缓慢的温度、压力等模拟量采集周期可以放宽到 500 毫秒甚至 1 秒。对于高速计数或者位置数据可能需要 20 到 50 毫秒。但要注意采集周期越短网络和数据库的压力越大需要权衡。还有一个关键点是采集的触发方式。我一般用固定周期触发而不是事件触发。固定周期的好处是数据的时间戳均匀便于后续做趋势分析和报表。事件触发虽然能捕捉突变但容易造成数据稀疏不均入库和查询都麻烦。如果确实需要捕捉突变可以在固定周期的基础上增加一个变化检测逻辑当某个关键值变化超过阈值时额外记录一条。3.2 时间戳的生成与对齐策略时间戳是时序数据的灵魂。没有准确的时间戳数据就是一堆无意义的数字。时间戳的生成有两种方式一是用 Python 程序所在服务器的系统时间二是用 PLC 内部的时钟。我强烈建议用服务器时间因为 PLC 的时钟往往不准而且不同 PLC 之间可能有偏差。服务器可以配置 NTP 同步精度有保障。但用服务器时间也有讲究。采集到数据的那一刻和写入数据库的那一刻时间可能差了几十毫秒甚至几秒。如果直接用写入时间做时间戳数据的时序就会失真。我的做法是在采集调度层触发读取之前先记录一个时间戳这个时间戳代表“本次采集的基准时间”。然后把这个时间戳附加到读取到的每一条数据上。这样即使入库有延迟时间戳仍然是准确的。对于批量读取的多个软元件它们共享同一个时间戳因为它们在同一个报文里返回可以认为是同一时刻的值。如果对时间精度要求极高可以在报文返回后立即记录时间但差别通常在毫秒级对大部分工业场景够用了。时间戳的格式我一般用datetime对象入库时转成数据库的DATETIME或者TIMESTAMP类型。如果数据量特别大可以考虑用 Unix 时间戳的整数形式节省存储空间查询时再转换。MySQL 的TIMESTAMP精度默认到秒需要毫秒精度的话要用DATETIME(3)或者TIMESTAMP(3)。3.3 批量入库与事务控制数据入库的频率和采集频率不一定一致。如果每采集一次就写一次数据库数据库的压力会很大尤其是采集周期在 100 毫秒以下的时候。我的做法是采集层把数据放入一个内存队列入库层从队列里批量取数据攒够一定数量或者达到一定时间间隔后一次性写入数据库。这个批量大小和时间间隔需要根据数据量和数据库性能来调。批量入库的好处很明显减少数据库连接开销、减少事务提交次数、提高写入吞吐量。我实测过单条插入 MySQL每秒大概能写 500 到 1000 条批量插入 100 条一批每秒能写 5000 到 10000 条。差距是数量级的。但批量也不能太大否则一次事务占用太多内存和锁资源反而影响数据库的并发性能。我一般把批量大小设在 100 到 500 条之间时间间隔设在 1 到 5 秒之间。事务控制上我建议用自动提交关闭的方式显式地commit。如果一批数据写入失败可以回滚避免部分写入导致数据不一致。但要注意如果数据库连接断了回滚可能失败需要有补偿机制。我的做法是入库失败时把数据重新放回队列头部等待下次重试。同时记录错误日志如果连续失败超过阈值就告警。注意MySQL 的max_allowed_packet参数限制了单次 SQL 语句的大小。如果批量插入的数据量太大可能会超过这个限制导致报错。建议在数据库配置里把这个参数调大比如 16MB 或者 64MB。同时innodb_buffer_pool_size也要根据服务器内存适当调大提升写入性能。4. 完整实操流程与关键代码解析4.1 环境准备与依赖安装先说一下环境。我用的 Python 版本是 3.9 到 3.11 之间太老的版本有些库不支持太新的版本有些库还没适配。操作系统 Linux 和 Windows 都用过生产环境建议用 Linux稳定性和资源占用都更好。依赖库主要有三个pymcprotocol用于 MC 协议通信pymysql或者mysql-connector-python用于 MySQL 连接schedule或者直接用threading.Timer做周期调度。如果要用异步可以上asyncio和aiomysql但复杂度会高一些。安装命令很简单pip install pymcprotocol pymysql如果网络环境不好可以换国内源pip install pymcprotocol pymysql -i https://pypi.tuna.tsinghua.edu.cn/simple数据库这边需要提前建好库和表。表结构我一般设计成宽表每个软元件一列加上一个时间戳主键。比如CREATE TABLE plc_data ( ts DATETIME(3) NOT NULL, d100 FLOAT, d101 FLOAT, d102 FLOAT, m50 TINYINT, m51 TINYINT, PRIMARY KEY (ts) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;如果软元件很多宽表会有很多列维护起来麻烦。另一种设计是窄表每个软元件一行用device_id和ts做联合主键。窄表更灵活但查询时需要做行转列性能差一些。我一般根据软元件数量来选少于 50 个用宽表多于 50 个用窄表加分表。4.2 通信层代码实现通信层的核心是封装pymcprotocol的Type3E类。下面是我常用的一个简化版本import time import logging from pymcprotocol import Type3E class PlcClient: def __init__(self, ip, port5000): self.ip ip self.port port self.plc None self.connect() def connect(self): try: self.plc Type3E() self.plc.connect(self.ip, self.port) self.plc.setaccessopt(commtypebinary) logging.info(fPLC connected: {self.ip}:{self.port}) except Exception as e: logging.error(fPLC connect failed: {e}) self.plc None def read_words(self, device, start, count): if self.plc is None: self.connect() if self.plc is None: return None try: return self.plc.batchread_wordunits(headdevicedevice, readsizecount) except Exception as e: logging.error(fRead words failed: {e}) self.plc.close() self.plc None return None def read_bits(self, device, start, count): if self.plc is None: self.connect() if self.plc is None: return None try: return self.plc.batchread_bitunits(headdevicedevice, readsizecount) except Exception as e: logging.error(fRead bits failed: {e}) self.plc.close() self.plc None return None这里有几个细节。setaccessopt(commtypebinary)是设置二进制模式必须在连接后、读取前调用。batchread_wordunits的headdevice参数是字符串比如D100readsize是点数。返回的是一个列表每个元素是一个整数对应一个寄存器的值。对于位软元件batchread_bitunits返回的也是列表每个元素是 0 或 1。重连逻辑我放在read_words和read_bits里一旦读取异常就关闭连接并置空下次读取时会自动重连。这种懒重连的方式比较简单但要注意重连频率避免疯狂重试把 PLC 的连接数耗尽。可以在connect里加一个退避比如失败后time.sleep(1)再试。4.3 采集调度与队列管理采集调度层我一般用一个独立的线程按固定周期触发读取。读取到的数据放入queue.Queue入库线程从队列里取。下面是一个简化的调度器import threading import queue import time from datetime import datetime class Collector(threading.Thread): def __init__(self, plc_client, data_queue, interval0.1): super().__init__() self.plc_client plc_client self.data_queue data_queue self.interval interval self.running True def run(self): while self.running: ts datetime.now() words self.plc_client.read_words(D, 100, 10) bits self.plc_client.read_bits(M, 50, 4) if words is not None and bits is not None: record {ts: ts, words: words, bits: bits} self.data_queue.put(record) time.sleep(self.interval) def stop(self): self.running False这里interval是采集周期单位秒。time.sleep的精度在 Windows 上可能只有 15 毫秒左右Linux 上好一些。如果对周期精度要求高可以用time.perf_counter做补偿或者用asyncio的call_later。但大部分工业场景几十毫秒的抖动是可以接受的。队列的作用是解耦采集和入库。如果入库慢了队列会积压但采集不会停。但队列也不能无限积压否则内存会爆。我一般给队列设一个最大长度比如 10000 条满了就丢弃最老的数据并告警。丢弃数据是下策但比程序崩溃好。更好的做法是监控队列长度超过阈值就告警让人来排查入库为什么慢。4.4 入库层批量写入实现入库层从队列里取数据攒够一批就写数据库。下面是一个简化的入库线程import pymysql import threading import time class DbWriter(threading.Thread): def __init__(self, data_queue, db_config, batch_size200, flush_interval2.0): super().__init__() self.data_queue data_queue self.db_config db_config self.batch_size batch_size self.flush_interval flush_interval self.running True self.buffer [] self.last_flush time.time() def run(self): conn pymysql.connect(**self.db_config) cursor conn.cursor() while self.running: try: record self.data_queue.get(timeout0.5) self.buffer.append(record) except queue.Empty: pass if len(self.buffer) self.batch_size or (time.time() - self.last_flush) self.flush_interval: if self.buffer: self.flush(cursor, conn) if self.buffer: self.flush(cursor, conn) cursor.close() conn.close() def flush(self, cursor, conn): sql INSERT INTO plc_data (ts, d100, d101, d102, m50, m51) VALUES (%s, %s, %s, %s, %s, %s) values [] for r in self.buffer: ts r[ts] words r[words] bits r[bits] values.append((ts, words[0], words[1], words[2], bits[0], bits[1])) try: cursor.executemany(sql, values) conn.commit() except Exception as e: conn.rollback() logging.error(fDB write failed: {e}) self.buffer.clear() self.last_flush time.time()executemany是批量插入的关键它会把多条INSERT合并成一个网络往返效率比循环单条插入高得多。batch_size和flush_interval是两个调节旋钮根据实际数据量和数据库性能来调。如果数据量很大可以适当增大batch_size但要注意max_allowed_packet的限制。5. 常见问题与排查技巧实录5.1 连接类问题速查问题现象可能原因排查方法解决方案连接被拒绝IP 或端口错误ping PLC IPtelnet 端口确认 PLC 的 MC 协议端口默认 5000连接超时网络不通或防火墙拦截检查网线、交换机、防火墙规则放行端口确认 PLC 的 IP 设置连接数超限多个程序同时连接查看 PLC 连接数设置合并采集程序或增大 PLC 连接数连接假死网线松动或交换机重启心跳检测读固定寄存器加心跳超时后重连读取返回错误码软元件地址错误或点数超限检查地址格式和点数修正地址拆分批量读取连接类问题里最常见的是 IP 和端口搞错。三菱 PLC 的 MC 协议端口不一定是 5000有些项目会改成 6000 或者别的。另外PLC 的 IP 地址要和采集服务器在同一网段或者路由可达。如果中间有防火墙要放行对应端口。5.2 数据异常类问题排查数据异常通常表现为读到的值不对、值不变、或者跳变。值不对可能是地址错了比如把 D100 写成了 D101。值不变可能是 PLC 程序没有在刷新那个寄存器或者采集周期太短读到了缓存。跳变可能是数据类型解析错了比如把有符号数当成无符号数或者把 32 位浮点数的两个寄存器顺序搞反了。三菱 PLC 的浮点数在 D 寄存器里占两个连续的字低字在前还是高字在前取决于 PLC 的参数设置。我遇到过读出来的浮点数完全不对后来发现是高低字反了。解决方法是先读两个寄存器按两种顺序拼成 32 位整数再转成浮点数看哪个结果合理。这个坑很隐蔽但一旦踩过就记住了。注意三菱 PLC 的 D 寄存器默认是 16 位有符号整数范围 -32768 到 32767。如果要存大于 32767 的数需要用两个寄存器拼成 32 位或者用浮点数。读取时要注意符号位Python 的int是无符号的需要自己处理负数。5.3 入库性能与稳定性问题入库慢是最常见的性能问题。原因可能是批量太小、索引太多、磁盘 IO 瓶颈、或者数据库连接池不够。我一般先看批量大小如果每次只插几条那肯定慢。然后看表索引时序数据表的主键是时间戳如果还有别的索引写入时会额外维护拖慢速度。建议时序表只保留必要的主键索引查询用的索引可以后续再加。另一个问题是数据重复。如果采集程序重启可能会把之前已经入库的数据重新读一遍。解决方法是在 PLC 里做一个递增的计数器每次采集时读这个计数器入库时用计数器做去重。或者用时间戳做唯一约束重复插入时忽略。MySQL 的INSERT IGNORE或者ON DUPLICATE KEY UPDATE可以实现。数据丢失也是要关注的。如果采集程序崩溃队列里的数据就没了。我的做法是队列用持久化的方式比如写入本地文件或者 Redis程序重启后从持久化存储里恢复。但这样会增加复杂度对于大部分场景只要保证程序稳定运行偶尔丢几条数据是可以接受的。如果数据绝对不能丢那就得上更重的方案比如 Kafka 或者 RabbitMQ。5.4 实操心得与避坑清单心跳不能省我见过太多因为没心跳导致连接假死、数据断了几小时才发现的情况。心跳间隔建议 5 到 10 秒读一个固定的寄存器超时 3 次就重连。批量读取要分组把地址连续的软元件放在一组一次读回来。地址不连续的不要硬凑分开读反而快。时间戳在采集时生成不要等到入库时才取时间那样会有延迟。采集触发时立即取时间附加到数据上。队列要有上限无限队列在入库故障时会吃光内存。设一个上限比如 10000 条满了就丢弃并告警。数据库连接要复用不要每次写入都新建连接用连接池或者长连接。但长连接要注意空闲超时定期做 ping。日志要详细记录每次采集的时间、点数、耗时每次入库的批量大小、耗时、结果。出问题时日志是唯一的线索。测试要覆盖异常拔网线、关 PLC、关数据库、重启程序这些场景都要测一遍确保程序能自动恢复。6. 时序设计的进阶优化方向6.1 多 PLC 并发采集的时序协调一个项目里往往不止一台 PLC可能有多台设备需要同时采集。如果每台 PLC 用一个独立的线程线程数多了之后GIL 和上下文切换会成为瓶颈。更好的方式是用asyncio做异步 IO一个线程就能管理多个连接。但pymcprotocol是同步库要配合asyncio需要自己封装或者用run_in_executor。另一种方式是每个 PLC 一个进程进程间用消息队列通信这样能充分利用多核 CPU。多 PLC 采集时时间戳的对齐很重要。如果各台 PLC 的采集时间不一致后续做关联分析时会很麻烦。我的做法是用一个全局的调度器统一触发所有 PLC 的采集尽量让它们在同一个时间窗口内完成。如果某台 PLC 响应慢可以给它单独放宽周期但时间戳仍然用触发时间。6.2 数据压缩与冷热分离时序数据的特点是量大、写入密集、查询集中在近期。如果全部存在 MySQL 里时间长了表会非常大查询和备份都成问题。我的做法是热数据最近 7 天存 MySQL冷数据7 天以上定期归档到文件或者专门的时序数据库。归档可以用 Python 脚本定时跑把旧数据导出成 CSV 或者 Parquet然后从 MySQL 删除。如果数据量特别大可以考虑用 TimescaleDB它是 PostgreSQL 的时序扩展支持自动分区和压缩。或者用 InfluxDB专门为时序数据设计写入和查询性能都很好。但引入新数据库会增加运维成本要根据团队的技术栈来选。6.3 采集程序的监控与告警采集程序跑在生产环境不能等出问题了才去查。我一般会加几个监控指标采集成功率、平均采集耗时、队列长度、入库成功率、入库耗时。这些指标可以写到日志里也可以用 Prometheus 的 Python 客户端暴露出来配合 Grafana 做可视化。告警规则可以设采集成功率低于 99%、队列长度超过 5000、入库连续失败 3 次触发邮件或者钉钉告警。监控的另一个作用是容量规划。通过观察队列长度和入库耗时的趋势可以判断当前配置是否够用什么时候需要扩容。比如队列长度持续增长说明入库速度跟不上采集速度需要优化入库或者降低采集频率。6.4 断线重连后的数据补采如果 PLC 断线了一段时间重连后这段时间的数据就缺失了。对于大部分场景缺失就缺失了补不回来。但如果 PLC 里有历史数据缓存比如用 D 寄存器做环形缓冲那可以在重连后把缺失的数据读回来。这需要 PLC 程序配合在采集程序里记录上次读取的位置重连后从那个位置继续读。另一种补采方式是在数据库里记录采集的连续性如果发现时间戳有跳跃就标记为数据缺失后续分析时注意。补采的实现复杂度较高除非业务对数据完整性要求极高否则不建议做。大部分工业场景只要保证实时数据不丢历史数据偶尔缺几秒是可以接受的。7. 个人实操体会与建议这套方案我在多个项目里用过从单台 FX5U 到十几台 Q 系列组网整体稳定性还是不错的。最关键的经验是不要追求极致的采集频率要根据实际需求来定。很多新手一上来就想 10 毫秒采一次结果数据库扛不住程序也不稳定。实际上大部分工业过程的变化速度远没有那么快100 毫秒甚至 500 毫秒的采集周期完全够用。另一个体会是异常处理比正常逻辑更重要。正常读取谁都会写但断线、超时、数据异常这些情况才是决定程序能不能长期稳定运行的关键。我现在的习惯是每写一个功能先想它可能怎么失败然后把失败的处理逻辑写好。这样虽然开发慢一点但上线后省心很多。最后分享一个小技巧如果 PLC 的 MC 协议读取总是超时可以试试把setaccessopt里的commtype从binary改成ascii。虽然 ASCII 效率低一些但有些老型号的 PLC 对二进制模式支持不好换成 ASCII 反而稳定。这个是我在一个 FX3U 加 ENET 模块的项目里发现的当时二进制模式读十次错三次换成 ASCII 后一次都没错过。
返回列表