:MC協(xié)議與SLMP幀解析)
簡介本資源面向工業(yè)自動化與上位機開發(fā)人員提供一套基于C#與WinForm實現(xiàn)三菱FX5U PLC以太網通信的完整示例工程重點解決3E幀報文構造、連接建立與數據讀寫等核心問題適用于自動化立體倉庫等場景下的上位機開發(fā)學習。壓縮包共35個文件約79KB以cs源碼為主輔以config配置、resx與resources資源、csproj與sln工程文件及exe可執(zhí)行程序結構完整可直接在Visual Studio中打開運行。目前已有465人學習下載。讀者可從中獲取3E幀報文封裝、網絡接口篩選、指令收發(fā)與錯誤重試等關鍵實現(xiàn)思路并參考WinForm界面與業(yè)務邏輯結合方式快速搭建可復用的PLC通信框架減少從零調試通信協(xié)議的時間成本。1. 三菱FX5U上位機以太網通信從一根網線到穩(wěn)定讀寫車間里一臺 FX5U 已經跑了半年PLC 程序沒人動但上位機那邊天天喊“讀不到數”。到現(xiàn)場一看網線插在 CPU 本體自帶的以太網口上上位機用 MC 協(xié)議按 Q 系列的地址格式去讀 D 區(qū)結果全是超時。把地址換成 FX5U 的軟元件編碼、端口確認是 5000 之后數據立刻就上來了。這類問題在 FX5U 上位機以太網通信里非常典型硬件沒問題、網絡通、ping 也通卡在協(xié)議細節(jié)和地址映射上。這篇筆記講的就是三菱 FX5U 通過內置以太網口和上位機做數據交互這件事。它解決的是“上位機怎么穩(wěn)定讀寫 FX5U 的 D/M/X/Y 軟元件”適合做設備數據采集、產線看板、C#/Qt 上位機開發(fā)、以及用 Python 做快速驗證的工程師。下面按“協(xié)議怎么選 → 報文怎么拼 → 代碼怎么寫 → 坑在哪 → 怎么驗證”的順序展開每一步都給到能直接抄的參數和命令。2. 先定協(xié)議再動手MC 協(xié)議、SLMP 與 FX5U 的以太網口2.1 為什么 FX5U 上位機通信優(yōu)先選 MC 協(xié)議而不是 Modbus TCPFX5U 本體自帶以太網口支持兩種主流上位機通信方式一是三菱自家的 MC 協(xié)議也叫 SLMPSeamless Message Protocol二是通過擴展模塊或內置功能做 Modbus TCP。很多人一上來就想用 Modbus TCP理由是“通用、庫多”但在 FX5U 這個場景里MC 協(xié)議才是第一選擇。原因很直接。FX5U 的 D、M、X、Y、T、C 這些軟元件在 MC 協(xié)議里是原生尋址的你寫 D100 就是 D100不需要像 Modbus 那樣先查一張“軟元件到保持寄存器”的映射表。Modbus TCP 在 FX5U 上要么依賴擴展模塊要么用內置的 Modbus 功能做映射地址偏移和數據類型轉換會額外引入一層心智負擔。對于只做數據采集和少量寫入的上位機MC 協(xié)議報文結構固定、響應快、沒有映射歧義。代價是 MC 協(xié)議是三菱私有協(xié)議你得自己拼二進制報文或者用現(xiàn)成的庫。常見做法是 C# 用 HslCommunication、Python 用 pymcprotocol 或自己按 SLMP 幀格式封裝。選型上我的建議是如果上位機只跟三菱設備打交道直接上 MC 協(xié)議如果現(xiàn)場還有西門子、匯川混在一起再考慮用 Modbus TCP 統(tǒng)一但 FX5U 這邊要接受映射帶來的額外配置。2.2 FX5U 以太網參數IP、端口和二進制/ASCII 的選擇動手前先把 FX5U 側的網絡參數確認清楚。用 GX Works3 打開工程在“模塊參數 → 以太網端口設置”里看這幾項參數典型值說明IP 地址192.168.1.10與上位機同網段避免跨網段子網掩碼255.255.255.0常規(guī)默認網關按現(xiàn)場同網段通信可不填通信數據代碼二進制強烈建議報文更短端口號5000MC 協(xié)議常用可改但要一致打開方式MC 協(xié)議對應 SLMP這里有兩個容易翻車的點。第一是“通信數據代碼”如果設成 ASCII報文里所有數值都是 ASCII 字符長度翻倍解析邏輯完全不同新手經常拿二進制示例去連 ASCII 端口怎么都對不上。第二是端口號FX5U 默認 MC 協(xié)議端口常見是 5000但有些工程被改成 5001 或別的值上位機不改就一直連不上。確認完這些再確認上位機 IP 和 FX5U 在同一網段用ping 192.168.1.10先通一下。2.3 最小連通性驗證先 ping 再 telnet 端口不要一上來就寫代碼。先用系統(tǒng)自帶工具確認網絡層和傳輸層都通# 1. 確認網絡可達 ping 192.168.1.10 # 2. 確認 MC 協(xié)議端口開放Windows 可用 Test-NetConnection # PowerShell: Test-NetConnection 192.168.1.10 -Port 5000 # Linux/macOS: nc -vz 192.168.1.10 5000ping通只說明 IP 層沒問題不代表 5000 端口在監(jiān)聽。Test-NetConnection或nc返回成功才說明 FX5U 的 MC 協(xié)議服務已經起來。如果端口不通回到 GX Works3 檢查以太網端口設置是否下載進 PLC、PLC 是否處于 RUN 狀態(tài)、有沒有別的設備占用了這個端口。這一步花兩分鐘能省掉后面半小時的“代碼明明沒錯”的自我懷疑。3. 把報文拼對SLMP 幀結構、軟元件編碼與批量讀取3.1 SLMP 二進制幀的組成從副頭部到結束代碼MC 協(xié)議SLMP的二進制請求幀結構是固定的以 3E 幀QnA 兼容 3E為例一次批量讀 D 區(qū)的請求大致長這樣字段長度典型值含義副頭部2 字節(jié)0x50 0x003E 幀固定網絡號1 字節(jié)0x00站內PLC 號1 字節(jié)0xFF固定請求目標模塊 IO2 字節(jié)0xFF 0x03上位機訪問請求目標模塊站號1 字節(jié)0x00站內請求數據長度2 字節(jié)變長后續(xù)字節(jié)數監(jiān)視定時器2 字節(jié)0x0010單位 250ms指令2 字節(jié)0x0401批量讀子指令2 字節(jié)0x0000字單位首軟元件編號3 字節(jié)變長小端軟元件代碼1 字節(jié)0xA8D 區(qū)軟元件點數2 字節(jié)變長讀取個數響應幀在請求基礎上把“指令/子指令”之后換成“結束代碼 數據”。結束代碼 0x0000 表示成功非零就是出錯比如 0xC059 是軟元件指定錯誤0xC056 是點數超限。理解這張表你就能看懂任何一份 SLMP 抓包也能在庫不好用的時候自己拼。3.2 FX5U 軟元件代碼對照D/M/X/Y 分別對應哪個字節(jié)FX5U 的軟元件代碼和 Q 系列基本一致但地址范圍不同。常用對照如下軟元件代碼說明讀寫D0xA8數據寄存器16 位讀寫M0x90內部繼電器位讀寫X0x9C輸入位只讀Y0x9D輸出位讀寫T0xC2定時器當前值讀C0xC5計數器當前值讀位軟元件M/X/Y按位讀時子指令用 0x0001返回的是按位打包的數據按字讀時用 0x0000一次讀 16 位。新手最容易錯的是把 X/Y 當成字來讀結果數據錯位。另一個坑是軟元件編號的字節(jié)序SLMP 里首軟元件編號是 3 字節(jié)小端比如 D100 要寫成0x64 0x00 0x00不是0x00 0x00 0x64。3.3 用 Python 跑通一次批量讀 D 區(qū)先用腳本驗證協(xié)議比直接上 C# 工程快得多。裝pymcprotocolpip install pymcprotocol然后寫最小讀取腳本import pymcprotocol # 參數說明 # type3e 對應 SLMP 3E 幀F(xiàn)X5U 內置以太網口常用 # host/port 必須與 GX Works3 里的以太網設置一致 plc pymcprotocol.Type3E() plc.connect(192.168.1.10, 5000) # 批量讀 D100 開始的 10 個字 # 返回是一個列表每個元素是 0~65535 的整數 values plc.batchread_wordunits(headdeviceD100, readsize10) print(values) # 批量讀 M0 開始的 16 個位 bits plc.batchread_bitunits(headdeviceM0, readsize16) print(bits) plc.close()邏輯說明Type3E封裝的就是 3E 幀batchread_wordunits對應子指令 0x0000batchread_bitunits對應 0x0001。headdevice直接寫D100、M0這種軟元件名庫內部會轉成軟元件代碼和 3 字節(jié)編號。參數上readsize不要超過 FX5U 單次允許的點數字單位一般一次別超過 960 個位單位別超過 7168 個超了會返回 0xC056。如果連接報錯先看 IP/端口再看type是不是選錯有的老設備用 1E 幀。3.4 寫入 D 區(qū)和 M 點子指令與數據格式寫入和讀取結構類似指令換成 0x1401批量寫。用同一個庫import pymcprotocol plc pymcprotocol.Type3E() plc.connect(192.168.1.10, 5000) # 寫 D200 開始的 3 個字值必須在 0~65535 plc.batchwrite_wordunits(headdeviceD200, values[1234, 5678, 90]) # 寫 M10 開始的 4 個位值用 0/1 plc.batchwrite_bitunits(headdeviceM10, values[1, 0, 1, 1]) plc.close()寫入時要注意數據范圍字單位是 16 位無符號寫負數要先轉成補碼比如 -1 寫成 65535。位單位只接受 0 和 1。如果寫入后 PLC 里沒變化先確認這些軟元件沒有被 PLC 程序里的其他邏輯覆蓋尤其是 M 點經常被程序當中間變量反復刷新。D 區(qū)如果被用作系統(tǒng)區(qū)域或掉電保持區(qū)也要確認寫入權限。4. 上位機工程化C# 與 Qt 的通信封裝和線程處理4.1 C# 上位機用 HslCommunication 連接 FX5UC# 上位機開發(fā)是熱詞里出現(xiàn)頻率很高的方向現(xiàn)場也確實是 C# 居多。用 HslCommunication 連 FX5U 的 MC 協(xié)議using HslCommunication; using HslCommunication.Profinet.Melsec; // 參數說明 // ip/port 與 PLC 以太網設置一致 // 構造函數最后一個參數指定數據格式二進制用 DataFormat.Binary var plc new MelsecMcNet(192.168.1.10, 5000); plc.ConnectTimeOut 3000; // 連接超時毫秒 plc.ReceiveTimeOut 3000; // 接收超時毫秒 OperateResult connect plc.Connect(); if (!connect.IsSuccess) { Console.WriteLine(連接失敗: connect.Message); return; } // 讀 D100 開始 10 個字 OperateResultshort[] read plc.ReadInt16(D100, 10); if (read.IsSuccess) { foreach (var v in read.Content) Console.WriteLine(v); } // 寫 D200 plc.Write(D200, new short[] { 1234, 5678 }); plc.ConnectClose();邏輯說明MelsecMcNet默認走 3E 幀二進制ReadInt16按有符號 16 位讀如果數據是無符號的用ReadUInt16。ConnectTimeOut和ReceiveTimeOut一定要設默認值偏長網絡抖動時界面會卡死。參數上ReadInt16(D100, 10)的第二個參數是點數不是結束地址。寫入時Write會自動判斷軟元件類型D 區(qū)按字寫M 區(qū)按位寫。4.2 Qt 上位機通信把 MC 協(xié)議讀寫放到獨立線程Qt 上位機通信的典型問題是把 socket 讀寫放在 UI 線程網絡一慢界面就假死。正確做法是把通信封裝成一個 QObjectmoveToThread 到工作線程// 通信類頭文件片段 class PlcWorker : public QObject { Q_OBJECT public slots: void doConnect(const QString ip, quint16 port); void doReadD(int start, int count); signals: void connected(); void dValuesReady(QVectorquint16 values); void errorOccurred(QString msg); private: QTcpSocket *socket nullptr; };// 連接與讀取實現(xiàn)片段 void PlcWorker::doConnect(const QString ip, quint16 port) { socket new QTcpSocket(this); connect(socket, QTcpSocket::connected, this, PlcWorker::connected); connect(socket, QTcpSocket::errorOccurred, this, [this](QAbstractSocket::SocketError){ emit errorOccurred(socket-errorString()); }); socket-connectToHost(ip, port); // 連接超時用 QTimer 單獨控制避免阻塞 } void PlcWorker::doReadD(int start, int count) { if (!socket || socket-state() ! QAbstractSocket::ConnectedState) { emit errorOccurred(未連接); return; } QByteArray req buildSlmpReadD(start, count); // 按 3.1 幀結構拼 socket-write(req); socket-waitForReadyRead(3000); QByteArray resp socket-readAll(); QVectorquint16 vals parseSlmpWordResponse(resp); emit dValuesReady(vals); }邏輯說明buildSlmpReadD和parseSlmpWordResponse需要自己按 SLMP 幀結構實現(xiàn)或者引入第三方庫。關鍵點是 socket 的waitForReadyRead只在工作線程里調用UI 線程通過信號槽拿結果。參數上waitForReadyRead的超時要和 PLC 的監(jiān)視定時器匹配PLC 側設 4 秒0x0010 × 250ms上位機側給 3 秒比較穩(wěn)。如果現(xiàn)場網絡質量差把超時放大到 5 秒同時加斷線重連。4.3 輪詢周期與批量策略別一個點一個請求上位機采集最常見的性能問題是“一個變量發(fā)一次請求”。FX5U 的 MC 協(xié)議單次請求有開銷100 個 D 點如果分 100 次讀周期會拉到幾百毫秒甚至更久。正確做法是按連續(xù)地址批量讀把要采集的 D 點按地址排序合并成連續(xù)區(qū)間比如 D100~D199 一次讀 100 個。不連續(xù)的區(qū)間分開請求但盡量控制在 3~5 個請求以內。輪詢周期根據工藝定一般 100~500ms太快沒意義PLC 掃描周期本身也有延遲。位軟元件和字軟元件分開批量讀不要混在一個請求里。如果上位機還要顯示波形熱詞里有人問“有沒有什么能給上位機顯示波形好用點的庫”C# 可以用 ScottPlot、Qt 可以用 QCustomPlot數據源就是批量讀回來的數組不要在繪圖庫里直接發(fā)通信請求。5. 避坑與排查FX5U 以太網通信最常見的 5 個翻車點5.1 現(xiàn)象ping 通但連接超時原因FX5U 的以太網端口設置沒下載進 PLC或者端口號不是 5000或者 PLC 處于 STOP 狀態(tài)。還有一種情況是上位機用了 1E 幀去連只支持 3E 的端口。解決GX Works3 里重新確認“以太網端口設置”下載后斷電重啟一次用Test-NetConnection確認端口把幀類型統(tǒng)一成 3E 二進制。5.2 現(xiàn)象讀回來的數據全是 0 或明顯錯位原因軟元件代碼寫錯比如把 D 區(qū)代碼 0xA8 寫成 0xA9或者首軟元件編號字節(jié)序搞反或者位/字單位用錯。解決對照 3.2 的軟元件代碼表逐項核對用抓包工具看請求幀的軟元件代碼和編號位軟元件確認子指令是 0x0001。5.3 現(xiàn)象寫入成功但 PLC 里值馬上變回去原因該軟元件被 PLC 程序里的邏輯覆蓋M 點尤其常見或者寫的是掉電保持區(qū)PLC 上電時被初始值覆蓋。解決在 GX Works3 里監(jiān)控該軟元件看是不是被其他指令驅動換一個沒被程序占用的 D 區(qū)做測試確認掉電保持范圍。5.4 現(xiàn)象運行一段時間后連接斷開重連才能恢復原因FX5U 的 MC 協(xié)議連接有超時機制上位機長時間不發(fā)請求或請求間隔過大PLC 側會主動斷開也可能是網絡交換機或網線質量導致丟包。解決上位機加心跳每隔幾秒發(fā)一次讀請求加斷線重連邏輯檢測到 socket 斷開后自動重連換屏蔽網線避開變頻器干擾。5.5 現(xiàn)象批量讀點數多時報 0xC056原因單次請求點數超過 FX5U 允許上限字單位一般 960 點、位單位 7168 點。解決把大區(qū)間拆成多次請求每次控制在 500 點以內更穩(wěn)或者優(yōu)化采集點表只讀需要的地址。6. 進階驗證用抓包和壓力測試確認通信真的穩(wěn)協(xié)議調通只是第一步現(xiàn)場要的是“連續(xù)跑一周不出問題”。我一般會做兩件事抓包和壓力測試。抓包用 Wireshark過濾tcp.port 5000看請求和響應的時序。重點看三件事一是響應時間是否穩(wěn)定正常應該在幾毫秒到幾十毫秒二是結束代碼是否始終為 0x0000三是有沒有 TCP 重傳。如果看到大量重傳說明網絡鏈路有問題先換網線或交換機別急著改代碼。壓力測試寫一個循環(huán)腳本按實際輪詢周期連續(xù)讀寫 24 小時import time import pymcprotocol plc pymcprotocol.Type3E() plc.connect(192.168.1.10, 5000) fail 0 start time.time() while time.time() - start 86400: # 跑 24 小時 try: vals plc.batchread_wordunits(headdeviceD100, readsize100) plc.batchwrite_wordunits(headdeviceD300, values[int(time.time()) % 65535]) except Exception as e: fail 1 print(異常:, e) try: plc.close() plc.connect(192.168.1.10, 5000) except Exception: pass time.sleep(0.2) # 200ms 輪詢 print(總失敗次數:, fail) plc.close()參數說明readsize100模擬實際采集規(guī)模sleep(0.2)對應 200ms 輪詢周期fail統(tǒng)計異常次數。跑完之后看失敗次數如果是個位數且都能重連恢復說明通信基本穩(wěn)定如果失敗頻繁回到第 5 章逐條排查。這個腳本我一般會在項目上線前跑一遍比在界面上點一天靠譜得多。最后說個習慣每次改完 PLC 以太網參數我一定會在上位機側重新抓一次包確認幀結構和端口都對得上再繼續(xù)。血淚經驗是很多“代碼 bug”其實是 PLC 側參數被人改過。希望幫到你。本文還有配套的精品資源點擊獲取