简介《三菱MC协议配置和使用简析》是一份面向工业自动化初学者与PLC通信调试人员的入门文档聚焦外部设备通过MC协议读写三菱PLC内部寄存器这一核心场景帮助读者理解协议目的、通信方式与实际配置流程。压缩包内仅含1个docx文件大小约1.12MB内容以图文说明为主便于按章节查阅。文档围绕TCP/IP下的一发一收模式展开重点讲解3E帧与二进制传输并给出PLC内置以太网端口设置、允许RUN中写入等参数配置要点同时通过向D7000写入H000C、连续读取5个软元件等报文实例逐段解析发送帧与响应帧。文中还整理了软元件类型与软元件代码对应关系、指令及子指令列表以及返回错误帧时的排查思路适合需要快速上手三菱MC协议通信的读者作为案头参考。目前已有4092人学习。1. 先定帧格式再配环境MC 协议最容易搞错的一步三菱 MC 协议这套东西说“安装”其实分两半一半是在 PLC 侧把内置以太网端口配出来一半是在上位机把 3E 二进制帧拼对。我见过太多人卡在第二步——ping通了端口 4999 也能连上socket.sendall()没有报错就以为环境装完了结果往 D7000 写一个 H000CPLC 回一帧C0开头的错误码对着文档看半天也不知道哪儿错了。问题不在网络在于帧头里那个请求数据长度少写了两字节或者软元件代码把A8写成了 ASCII 表的44 2A。这篇笔记按我自己的顺序来先钉版本和网口再把 PLC 参数配好然后手搓一帧写指令、一帧读指令最后给一条装完之后能自证的验证路径。适合第一次接 MC 协议的上位机开发也适合换 PLC 型号时要重新配以太网端口的运维。2. 环境清单Python 3.10 加一个能 ping 通的网口MC 协议的环境搭建没有想象中那么“重”——它不是一套要装服务的中间件而是“一个能发 TCP 字节流的客户端 一台参数开好的 PLC”。所以环境清单要分两栏列上位机装什么PLC 侧具备什么。我最怕的是有人把注意力全放在pip install上到最后发现 PLC 那台设备根本没开 MC 协议连接抓包只能看到三次握手之后一片沉默。2.1 上位机装什么标准库就够第三方库只做交叉验证先把版本定下来。Python 我用 3.10 或 3.113.8 以上都能跑因为整套代码只用socket和struct两个标准库没有编译依赖也不需要额外安装其他软件。真要装东西只有一个可选的pymcprotocol——它的价值不在于“省代码”而在于当你手搓的帧被 PLC 拒绝时用它发同一批数据做对比它能通、你不能通问题就在你的拼帧逻辑它也不通问题回到 PLC 参数或网络。这个对照法能省掉大量猜测。组件取值说明Python3.10 / 3.113.8 均可只用socket、struct无编译依赖虚拟环境python -m venv .venv避免和机器上其他工程串包pymcprotocol装最新版即可不锁死只做交叉验证不进生产链路Wireshark任意较新版本过滤器tcp.port 4999看原始字节PLC 工程软件与 CPU 型号匹配的版本只用来改参数和烧写不写梯形图装虚拟环境和可选库的命令就三行注意activate的路径在不同 shell 下不一样PowerShell 是.venv\Scripts\Activate.ps1bash 是source .venv/bin/activatepython -m venv .venv source .venv/bin/activate # Windows PowerShell: .venv\Scripts\Activate.ps1 pip install pymcprotocol # 可选仅用于交叉验证 python -c import socket, struct; print(ok) # 确认标准库可用第三条命令看着像凑数实际是我必做的一步有些精简版 Python 发行包会裁掉部分模块struct缺失时你不会在拼帧那一刻才发现而是在最不该出问题的地方翻车。struct在这套方案里承担了所有“低位字节在前”的打包工作H表示小端 2 字节无符号整数I是小端 4 字节MC 协议 3E 帧里除了软元件号是 3 字节需要to_bytes(3, little)之外其余多字节字段都能用struct一行搞定。还有一个取舍要提前说清通信内容分二进制和 ASCII 两种形式传输内容一致只是形式不同。二进制一帧更短数据不需要做字符转换我全部走二进制。ASCII 形式的唯一好处是可以用串口助手之类工具肉眼读帧调试期看着舒服但一旦点数上去帧长翻倍、解析还要多一层字符串处理得不偿失。2.2 网口预检ping 和 4999 必须先过再谈帧在动 PLC 参数之前先把物理层和网络层确认掉这一步能过滤掉后面一半的“玄学问题”。做法很简单上位机和 PLC 配到同一网段比如上位机192.168.1.100/24PLC192.168.1.10/24直连或者过交换机都行然后跑三条探测。# 1) 先看链路通不通-c 4 表示发 4 个包就停 ping -c 4 192.168.1.10 # 2) Linux/macOS 探 TCP 端口是否被监听-v 详细 -z 只探不传数据 nc -vz 192.168.1.10 4999 # 3) Windows PowerShell 等价写法 # Test-NetConnection 192.168.1.10 -Port 4999ping不通就别往下走了先查网线、网段、PLC 的 IP 有没有真正烧进去。ping通但nc报Connection refused说明 IP 层到了、TCP 层没人监听基本可以判定 PLC 侧的“打开设置”没配或者没烧写生效——这一步是最典型的翻车点很多人改完参数点了“设置结束”就以为完事忘了把配置写入 PLCPLC 断电重启后跑的还是老参数。提示nc在部分发行版里是netcat-openbsd或ncat参数略有差异没有nc就直接用 Python 的socket.create_connection((host, port), timeout3)试连效果一样。端口号我一般避开 5000、5001 这类常被其他功能占用的值直接用 4999 这种不容易冲突的。如果一台 PLC 上要同时接两台上位机比如一个采集程序加一个调试脚本就在“打开设置”里开两条连接、配两个端口不要指望两条连接共用一个端口——MC 协议是一发一收的问答模式共享端口会让响应帧串台。3. PLC 侧参数设置从内置以太网端口到“打开设置”PLC 侧的好处是不需要写一行程序纯参数。但参数项之间的联动关系容易漏IP 在一个页面配协议和端口在另一个页面配数据代码二进制/ASCII在第三个位置勾任何一项和上位机对不上表现都是“连得上但读写失败”。这一章按我实际点的顺序走一遍。3.1 内置以太网端口设置IP、数据代码、RUN 中写入打开工程在右侧工程栏里双击“PLC 参数”弹框里找到“内置以太网端口设置”。这一页要落三件事IP 地址和子网掩码配成和上位机同网段别用0.0.0.0、别和上位机撞地址通信数据代码选二进制和上位机的拼帧方式严格一致选 ASCII 就得整套改成 ASCII 帧勾上“允许 RUN 中写入”。第三项是最容易被忽略的一条。PLC 在 RUN 状态下默认拒绝写入请求你不勾读指令照样能通写指令却会返回错误帧。这种情况最麻烦读通了你会默认整条链路是好的然后花半天怀疑自己的写入帧拼错了实际是 PLC 侧的一个复选框在拦你。注意 通信数据代码一旦改动必须重新烧写并让 PLC 复位生效。改完不烧写PLC 跑的还是旧参数你会看到“配置明明改成二进制了回帧还是 ASCII 的”。如果 CPU 没有自带网口走的是插在机架上的以太网模块那就在“网络参数”里选对应模块设置页面名字不同条目基本一一对应IP、数据代码、打开设置。我习惯先把模块的槽位号记在便签上多模块机型很容易在错误的那个模块上改参数改完发现连的还是另一个网口。3.2 打开设置TCP MC 协议 端口号回到内置以太网端口设置页点“打开设置”。这一页是真正决定“谁来连、用什么协议连”的地方配错一项就是连不上或者连上不响应。协议选 TCP。485 那条路本文不展开配置逻辑一致但物理层和帧定界完全不同别把 TCP 的参数照抄过去。打开方式选 MC 协议。这里如果选成其他通信方式比如固定缓冲通信上位机发标准 3E 帧过去PLC 不会按 MC 协议解析表现就是发出去没有回帧。端口号按自己需求填我用 4999。同一台 PLC 上多个连接就分别填不同端口。点“设置结束”再点一次外层页面的“设置结束”把配置烧写到 PLC。“设置结束”点两次这件事不是冗余第一次是关子页面第二次才是把改动收进工程参数。只点一次然后直接烧写改动有可能没进参数表。烧写完成后让 PLC 复位一次断电重启或者用工程软件做一次复位再回到 2.2 节跑nc -vz验端口通了才算 PLC 侧配完。3.3 烧写后必做的两个确认第一个确认是端口是否真的在监听。第二个确认是数据代码是否真的生效——这个没法从网络上直接看出来得靠发一帧已知内容的请求去试。我的做法是先只发读请求读比写安全不会改动现场数据如果回帧的副标题是D0 00、结束代码是00 00说明帧格式和二进制模式都对上了如果回帧是乱码或者根本对不上长度八成是 PLC 还在 ASCII 模式。这一步不要跳。我踩过一次参数页面显示二进制实际烧写的是旧工程读回来的数据每个字都像被拆成了两个字节的字符折腾了快一小时才想起比对工程里参数页的当前值。4. 手搓 3E 二进制帧写 D7000 H000C 逐字节拆解帧格式这块MC 协议有 3E、3C、4C、4E 几种。我用 3E原因是它帧头里有明确的“请求数据长度”字段一发一收的问答模式下定界最省心先读 9 字节头从长度字段算出后面还有多少字节再精确读满不需要靠超时判断帧尾。4E 在帧头里多了两字节序列号可以在一条连接上并发多个请求再按序列号配对吞吐更高但客户端要做请求队列3C、4C 不带数据长度字段靠帧结构本身定界在老设备上见得多。入门和绝大多数采集场景3E 二进制足够。TCP/IP 的头部由协议栈自动加我们只需要关心应用层的字节。两个字节的字段一律低位字节在前、高位字节在后这是后面所有拆解的前提。4.1 写请求 23 字节把 D7000 H000C 排出来向 D7000 写入 H000C发送帧一共 23 字节50 00 00 FF FF 03 00 0E 00 10 00 01 14 00 00 58 1B 00 A8 01 00 0C 00逐段拆开看字节位置内容含义0–150 00副标题3E 帧固定值200网络号自身网络填 003FFPC 号访问自身站填 FF4–5FF 03请求目标模块 I/O 编号03FF 表示本站 CPU600请求目标模块站号7–80E 00请求数据长度 14 字节即第 9 字节起的全部长度9–1010 00CPU 监视定时器单位 250ms0010 即 4 秒11–1201 14指令 1401批量写入13–1400 00子指令 0000按字处理0001 为按位15–1758 1B 00起始软元件号7000 的十进制转十六进制是 1B58低位在前18A8软元件代码二进制下 D 寄存器是 A819–2001 00写入点数1 个点21–220C 00写入的数据H000C按字低位在前校验一下长度23 字节减去前 9 字节帧头等于 14和0E 00对得上这帧就是合法的。写完 PLC 返回 11 字节响应D0 00 00 FF FF 03 00 02 00 00 00副标题变成D0 00响应帧标志长度字段02 00表示后面只有 2 字节即结束代码00 00正常结束。写指令的响应没有数据区所以永远是这个 11 字节的短帧——如果你的接收缓冲区按读响应的大小去收recv会一直等表现出来就是“写成功了但程序卡住”。这是新手最常遇到的坑之一后面第 6 章会讲怎么绕。4.2 读请求 21 字节与响应解析以及软元件代码表接着读 D7000 开始的连续 5 个点发送50 00 00 FF FF 03 00 0C 00 10 00 01 04 00 00 58 1B 00 A8 05 00 响应D0 00 00 FF FF 03 00 0C 00 00 00 0C 00 00 00 00 00 00 00 00 00和写帧比只有三处变化指令从01 14变成01 04批量读取点数字段从01 00变成05 00请求数据长度从0E 00变成0C 00少了 2 字节数据区。响应的长度字段0C 00是 12 字节正好等于 2 字节结束代码加 5 个字的数据。数据区0C 00 00 00 00 00 00 00 00 00就是 5 个字的回读值第一个是 12H000C上一次写进去的后面四个是 0。软元件代码这块必须查表不能靠猜。二进制模式下基本型 CPU 的常用代码软元件二进制代码软元件二进制代码D 数据寄存器A8M 内部继电器90W 链接寄存器B4X 输入9CR 文件寄存器AFY 输出9DZR 文件寄存器扩展B0B 链接继电器A0定时器/计数器的当前值、SM/SD 这类特殊软元件代码表在对应 CPU 系列的手册里不同系列会有差异别把 Q 系列的代码直接套到 L 系列上。ASCII 模式下的代码又是另一套比如 D 是44 2A所以改数据代码这件事是牵一发动全身的。4.3 用 Python 把字节流跑起来拼帧我用struct加to_bytes好处是字段边界清楚改点数、改软元件只动一个参数import socket import struct HOST, PORT 192.168.1.10, 4999 DEV_D 0xA8 # D 寄存器在二进制模式下的软元件代码 def build_read(start7000, points5, devDEV_D): # 请求数据区监视定时器 指令 子指令 起始软元件(3B) 代码(1B) 点数(2B) data struct.pack(H, 0x0010) # 250ms x 16 4 秒超时 data struct.pack(H, 0x0401) # 0401 批量读取 data struct.pack(H, 0x0000) # 0000 按字 data start.to_bytes(3, little) # 起始软元件号3 字节低位在前 data bytes([dev]) data struct.pack(H, points) # 3E 帧头副标题 网络号 PC 号 目标模块 I/O 站号 数据长度 head b\x50\x00 b\x00 b\xFF b\xFF\x03 b\x00 return head struct.pack(H, len(data)) data def send(frame, expect): # 一发一收短连接发完就关避免旧响应残留在缓冲里 with socket.create_connection((HOST, PORT), timeout3) as s: s.sendall(frame) buf b while len(buf) expect: # 按定长收满不依赖单次 recv 返回 chunk s.recv(expect - len(buf)) if not chunk: break buf chunk return buf resp send(build_read(), expect21) # 读 5 点固定 21 字节 end_code struct.unpack(H, resp[9:11])[0] if end_code 0: words struct.unpack(5H, resp[11:21]) # 数据区 5 个字小端 print(D7000..D7004 , words) else: print(错误结束代码0x%04X % end_code)几个关键点。build_read里数据区的拼装顺序就是 PLC 解析的顺序中间少一个字节后面全部错位表现为结束代码非 0 或者收到的数据完全对不上。send里的while循环是必需的TCP 是字节流一次recv不保证返回你要的全部字节尤其读响应数据区大的时候。expect是算出来的不是猜的——响应总长 9 字节帧头 2 字节结束代码 点数 × 2按字。同一批数据用pymcprotocol再跑一遍做对照from pymcprotocol import Type3E plc Type3E() plc.setaccessopt(commtypebinary) # 必须和 PLC 侧数据代码一致 plc.connect(HOST, PORT) plc.batchwrite_wordunits(headdeviceD7000, values[0x000C]) print(plc.batchread_wordunits(headdeviceD7000, readsize5)) plc.close()两段输出一致说明你的拼帧逻辑没问题pymcprotocol通而手搓不通就回头查软元件号的三字节顺序、请求数据长度、子指令这三个位置九成的偏差在它们身上。5. 避坑与排查帧能发出去但数据不对问题在哪排错要分层别一上来就怀疑帧。我的顺序是先看 TCP 有没有连上再看 PLC 有没有回帧最后看回帧的结束代码三层都过了才去怀疑数据区。下面五条是我自己踩过或者帮人定位过的按现象、原因、解决写。5.1 连不上、连上没响应网络与协议层的三条现象一nc -vz直接Connection refused。原因PLC 侧“打开设置”里的连接没配、配了没烧写、或者烧写了没复位生效端口根本没在监听。 解决回 3.2 节把 TCP MC 协议 端口号配一遍记得两次“设置结束”然后烧写并复位。复位后再nc通了再写代码。顺便确认上位机防火墙没拦出站——Windows 上第一次跑脚本时弹的允许框点了拒绝就会一直静默失败。现象二端口通了发帧后一直等不到数据超时。原因协议选错了比如“打开设置”里选了固定缓冲通信而不是 MC 协议或者上位机发的是 3E 帧PLC 侧期待的却是 ASCII 模式的数据代码。另一种可能是端口号填的不是打开设置里那个碰巧被别的服务接住了。 解决先抓包确认发出去的字节和你拼的完全一致过滤器tcp.port 4999再看 PLC 有没有回任何字节。一字节都不回基本是协议不匹配回了但你没收是接收长度算错。现象三读能通写一定失败返回错误帧。原因RUN 中写入没勾选PLC 在运行状态下拒绝写请求。这条太常见因为读指令不受影响很容易误判成帧格式问题。 解决回 PLC 参数勾上“允许 RUN 中写入”烧写、复位再试写。生产环境要评估这个开关的影响别在没确认联锁逻辑的情况下随手打开。5.2 帧被拒看结束代码别猜现象四响应副标题是D0 00但结束代码不是00 00形如C0 5x。原因请求帧本身有问题。C0段的错误基本集中在几类指令或子指令不被支持、起始软元件号加点数超出范围、请求数据长度与实际字节数不符、当前状态下不允许执行该操作。 解决把请求帧按字段重新数一遍字节重点核对请求数据长度字段是否等于“总长减 9”。软元件号越界这种把点数改小到 1 先试能通再逐步加。错误码的具体含义各 CPU 系列手册里有一张表L 系列、Q 系列措辞不完全一样对着自己型号的手册查别拿别的手册硬套。提示 结束代码是 2 字节小端。抓包工具里看到59 C0实际值是0xC059别按大端读成0x59C0去手册里翻翻不到会怀疑人生。5.3 通了但数据不对字节序、代码、点数现象五读回来的数和 PLC 监视里看到的不一致比如监视是 12读回来是 3072。原因字节序搞反了。0x000C按大端读成0x0C00就是 3072。MC 协议 3E 二进制帧里两个字节的字段低位在前软元件号是 3 字节也是低位在前。 解决所有多字节字段统一用struct的前缀打包和解包别手写bytes([hi, lo])。软元件号用to_bytes(3, little)不要写成 4 字节多一个字节后面全部错位。改完拿 D7000 H000C 这条已知数据回归一遍读回来是 12 才算对。6. 验证跑通三条对照和一个写完就回读的习惯装完不等于跑通。我判断一套 MC 协议环境是否真的可用用的是三条对照缺一条都不算完。第一条是回读对照写完立刻读。写完 D7000 H000C紧接着读 D7000 开始的 5 个点第一个字必须是 12其余是 0。这条能同时验证写指令、读指令、字节序、软元件代码四件事性价比最高。写完不读等于把写失败藏起来了——写请求的响应只有结束代码没有任何数据可供核对。第二条是工具对照手搓脚本和pymcprotocol各跑一遍输出必须一致。不一致时以抓包字节为准逐字段比对两边的请求帧差异点就是你的 bug。第三条是现场对照读一个 PLC 上正在变化的软元件比如程序里在累加的寄存器连续读三次值应该单调变化。读固定不变的寄存器容易骗过自己——地址错位读到一片 0看起来也很“正常”。# 验证脚本骨架写 - 立即读 - 连读三次看变化 send(build_write(7000, [0x000C]), expect11) # 写响应固定 11 字节 first send(build_read(7000, 5), expect21) print(回读:, struct.unpack(5H, first[11:21])) for i in range(3): r send(build_read(7000, 5), expect21) print(第%d次: % (i 1), struct.unpack(5H, r[11:21]))写完立刻回读这个习惯是从一次现场事故之后固定下来的。当时给一条产线做数据采集写指令全部返回正常结束码我就收工了。第二天对方说参数没生效回头抓包才发现写的是 D7000程序里用的是 D7100——结束码00 00只代表“这帧被正确接收并执行”不代表“写到了你想写的那个地址”。从那以后我每次接新设备都强制走一遍“写 → 立即读 → 连读三次”的回路宁可多花五分钟也不想再吃一次回头查的亏。这套东西不难难的是每一步都真的验过。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?