)
簡介工業(yè)上位機開發(fā)中三菱PLC是最常見的控制設(shè)備之一。理解其通訊協(xié)議是打通上層應(yīng)用與現(xiàn)場設(shè)備的關(guān)鍵。三菱PLC根據(jù)系列不同分別支持FX編程口與MC 3E/4E等協(xié)議物理鏈路則分為串口和以太網(wǎng)。通信幀的本質(zhì)是地址編碼、長度與校驗三者的正確組合任何一項偏差都會導(dǎo)致NAK或無響應(yīng)。掌握這些基礎(chǔ)后開發(fā)者可以在Visual Studio 2019中用C與Qt快速封裝通訊類實現(xiàn)D/M/X/Y寄存器的可靠讀寫。這類能力適用于產(chǎn)線數(shù)據(jù)采集、設(shè)備監(jiān)控等典型工控場景配合源碼的輪詢調(diào)度與異常重試機制能有效縮短上位機調(diào)試周期避免在現(xiàn)場反復(fù)踩坑。1. 用 C 在 VS2019 里寫 QT 上位機調(diào)三菱 PLC這套通訊源碼就是干這個的手里同時壓著 QT 界面、C 業(yè)務(wù)邏輯和一臺三菱 PLCVisual Studio 2019 里來回切窗口通訊卻一封幀都發(fā)不出去——這種狀態(tài)我太熟悉了。這套源碼解決的正是“ QT VS2019 C 與三菱 PLC 通訊”這個組合里的最后一公里不糾結(jié)界面先把 FX 編程口和 MC 3E 幀兩種協(xié)議封裝成能直接調(diào)用的類串口、以太網(wǎng)兩條鏈路都給你鋪好。適合剛接手工控上位機的新手也適合不想再翻三菱協(xié)議文檔、只想快速驗證讀寫功能的熟手。拿到手先跑通 Demo再替換成自己的業(yè)務(wù)邏輯比從空工程一點點焊要省太多事。2. 協(xié)議選型和通訊鏈路FX 編程口、MC 3E 幀串口還是以太網(wǎng)2.1 三菱 PLC 常見通訊方式FX 編程口、MC 3E/4E、A-1E 怎么選三菱 PLC 不是一個協(xié)議打天下。FX 系列走自家編程口協(xié)議Q/L/iQ-R 系列走 MC 協(xié)議MC 協(xié)議本身又分以太網(wǎng) 3E 幀、4E 幀和串口 A-1E 幀。選錯協(xié)議后面的幀構(gòu)造全是白費。先看這張對照表基本能定方向通訊方式物理層典型命令適用系列特點FX 編程口RS-422/48530H 讀、31H 寫FX3U / FX3G接線簡單默認 9600 8 Even 1單幀點數(shù)受限MC 3E 幀TCP / UDP0401 批量讀、1401 批量寫Q / L / iQ-R二進制幀緊湊以太網(wǎng)速率高Qna-3E 最經(jīng)典MC 4E 幀TCP / UDP在 3E 基礎(chǔ)上帶 CPU 編號FX5U / iQ-R多 CPU 尋址FX5U 默認走這個變體MC A-1E 幀串口 RS-232/422ASCII 幀Q 系列串口模塊報文可讀性好但效率低老設(shè)備還在用我的選擇標準很樸素FX3U 這種小 PLC項目里只要讀寫 D 寄存器、M 位、X/Y 狀態(tài)直接上 FX 編程口一條 USB-RS422 線就夠Q 系列或 iQ-R 這種走以太網(wǎng)的用 MC 3E 幀F(xiàn)X5U 則要留意 4E 幀——很多人在 FX5U 上套 3E 幀死活連不上其實就是少了兩字節(jié) CPU 編號。A-1E 幀現(xiàn)在只在改造老產(chǎn)線時遇到新項目基本不碰。2.2 這套源碼為什么要做串口 以太網(wǎng)雙通道仔細看這份源碼的類結(jié)構(gòu)你會發(fā)現(xiàn)它沒有把寶押在一條鏈路上。抽象基類只管收發(fā)字節(jié)流底下掛了兩個實現(xiàn)一個用 QSerialPort 走 FX 編程口一個用 QTcpSocket 走 MC 3E 幀。界面層通過同一個接口調(diào)讀寫底層切協(xié)議上層一行不用改。這樣設(shè)計不是拍腦袋。實際產(chǎn)線里以太網(wǎng)口經(jīng)常被交換機、防火墻、IP 沖突卡住現(xiàn)場工程師最后掏出老式 USB-RS422 線救急是常事。雙通道意味著你在辦公室用網(wǎng)口調(diào)試到了現(xiàn)場發(fā)現(xiàn)網(wǎng)線沒布切換串口只是改一行配置的事。邊界條件也說清楚串口是一主一從的令牌式通訊別想在一條 RS-485 總線上同時輪詢好幾臺 FX 系列還能保證實時性重載嚴重時會偶發(fā)超時TCP 鏈路倒是可以按站號區(qū)分多臺 PLC每臺連接獨立端口輪詢各自維護。源碼里默認網(wǎng)口端口是 6001三菱 PLC 以太網(wǎng)模塊常見端口可改。2.3 幀格式的本質(zhì)地址編碼、長度、校驗三件事決定能不能通為什么三菱協(xié)議比 Modbus 難上手Modbus 的寄存器地址就是一個 16 位編號三菱偏要搞“軟元件代碼 軟元件編號”的組合編址。比如 D100 在 MC 協(xié)議里是軟元件代碼 0x00A8、偏移地址 0x0064到了 FX 編程口D0 的通訊地址又變成 0x1000。同一個 D 寄存器兩種協(xié)議地址起點完全不同新手常在這里翻車。所以別把協(xié)議當黑匣子背。通訊成功的本質(zhì)就三件事地址編碼對得上、幀長度字段對得上、校驗和對得上。地址錯了PLC 回結(jié)束碼錯誤長度錯了整個幀解析錯位校驗錯了直接 NAK。這套源碼把三件事都封裝在幀構(gòu)造函數(shù)里你要做的只是傳入軟元件號和數(shù)量它幫你算地址、算長度、算校驗。3. VS2019 QT 集成裝插件、跑通第一個串口 Demo、把 DLL 送到真機3.1 工具鏈三件套Qt 5.15.2、VS2019 v142、Qt VS Tools先把環(huán)境固定下來。我一般固定用 Qt 5.15.2 MSVC2019 64 位、Visual Studio 2019 的 v142 工具集、Qt VS Tools 擴展。這套組合對應(yīng)關(guān)系最穩(wěn)網(wǎng)上報錯也最少。安裝順序先裝 VS2019確認“使用 C 的桌面開發(fā)”工作負載勾上再裝 Qt 5.15.2裝的時候勾上 MSVC 2019 64-bit最后在 VS 里打開“擴展 → 管理擴展”搜索 Qt Visual Studio Tools 安裝。裝完在“擴展 → Qt VS Tools → Qt Versions”里把 msvc2019_64 的路徑指進去。新建工程不要選錯模板。源碼里是 Qt Console 起手把 widget 界面后加或者直接選 Qt Widgets Application。工程文件里要顯式聲明串口和網(wǎng)絡(luò)模塊QT core gui serialport network greaterThan(QT_MAJOR_VERSION, 4): QT widgets CONFIG c17 DEFINES QT_DEPRECATED_WARNINGSserialport對應(yīng) QSerialPortnetwork對應(yīng) QTcpSocket 和 QUdpSocket。漏掉其中一個模塊編譯直接報“未定義引用”或頭文件找不到。CONFIG c17是讓源碼里的結(jié)構(gòu)化綁定、lambda 捕獲這些語法能過 v142 編譯器。3.2 無插件備選CMake 打開文件夾適合不想被 IDE 綁死的團隊如果你的團隊不用 Qt VS Tools或者 VS 版本和 Qt 庫工具集對不上可以走 CMake 路線。VS2019 原生支持“打開文件夾”模式CMakeLists 寫對就能跑cmake_minimum_required(VERSION 3.16) project(plc_demo) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt5 5.15 REQUIRED COMPONENTS Widgets SerialPort Network) add_executable(plc_demo main.cpp PlcClient.cpp PlcClient.h ) target_link_libraries(plc_demo Qt5::Widgets Qt5::SerialPort Qt5::Network )關(guān)鍵在find_package能不能找到 Qt。如果 Qt 裝在 D 盤要在 CMakeSettings.json 或環(huán)境變量里加CMAKE_PREFIX_PATHD:/Qt/5.15.2/msvc2019_64否則會報“找不到 Qt5Config.cmake”。這條路徑是 Qt 庫的根目錄不是 src 目錄很多新手在這寫錯。CMake 路線的優(yōu)點是工程文件可提交到 Git同事拉下來不用配插件路徑缺點是首次配置慢且 Qt 版本切換需要手動改路徑。二選一即可源碼兩者都支持。3.3 最小串口驗證程序和部署時缺 DLL 的坑環(huán)境通了先別急著跑完整界面寫個最小程序驗證串口物理鏈路。FX 編程口默認參數(shù)是 9600、8 數(shù)據(jù)位、偶校驗、1 停止位注意是 Even不是 NoParityQSerialPort ser; ser.setPortName(COM3); ser.setBaudRate(QSerialPort::BaudRate9600); ser.setDataBits(QSerialPort::Data8); ser.setParity(QSerialPort::EvenParity); // FX編程口默認偶校驗 ser.setStopBits(QSerialPort::OneStop); ser.setFlowControl(QSerialPort::NoFlowControl); if (!ser.open(QIODevice::ReadWrite)) { qWarning() ser.errorString(); return false; } QObject::connect(ser, QSerialPort::readyRead, []() { qDebug() ser.readAll().toHex(); }); ser.write(frame); ser.waitForBytesWritten(500);waitForBytesWritten只保證數(shù)據(jù)進了驅(qū)動緩沖區(qū)不代表 PLC 已經(jīng)收到。真正判斷成功要看readyRead回調(diào)里有沒有回幀。掉進 NoParity 的坑現(xiàn)象是寫入后 PLC 完全無視因為校驗位對不上串口驅(qū)動層面就已經(jīng)把幀丟了。把程序拷到工控機之前一定要在開發(fā)機 Release 目錄跑一遍windeployqtwindeployqt --release --no-translations .\plc_demo.exe它會自動拷入 Qt 的 platforms 插件、串口插件和依賴 DLL。最典型的部署事故是exe 拷過去了雙擊閃退事件查看器里報“無法加載平臺插件”其實就是platforms/qwindows.dll沒帶。還有 MSVC 運行庫目標機器沒裝 vc_redist 也會靜默失敗。4. 核心實現(xiàn)拆解幀結(jié)構(gòu)、軟元件地址映射、校驗和與輪詢線程4.1 通訊類架構(gòu)一個接口、兩個協(xié)議實現(xiàn)源碼里最值得抄的不是某一段幀代碼而是這套分層IPlcClient (抽象接口) ├── FxSerialClient // FX編程口QSerialPort ├── Mc3eTcpClient // MC 3E幀QTcpSocket └── Mc4eTcpClient // MC 4E幀F(xiàn)X5U變體接口只暴露readD(addr, count)、writeD(addr, values)、readM(addr, count)之類的業(yè)務(wù)方法。界面層不關(guān)心底層是串口還是網(wǎng)口切換協(xié)議就是換一個對象實例。這也是我自己做上位機時的固定習慣先把協(xié)議封裝從界面里剝出去后面加功能才不用動 UI 代碼。4.2 FX 編程口讀寫命令碼 30H/31HSTX/ETX 與兩位校驗和FX 編程口幀結(jié)構(gòu)不長但非??简灱毠?jié)點。讀命令幀格式是STX(0x02) 命令碼0(0x30) 首位地址(4位ASCII) 讀取點數(shù)(2位ASCII) ETX(0x03) 校驗和(2位ASCII)注意地址和數(shù)量都是 ASCII 十六進制字符不是二進制數(shù)。D0 的協(xié)議地址是 0x1000則地址字段是1000。點數(shù) 2 寫成02。讀 8 個 D 寄存器代碼如下QByteArray buildFxReadFrame(quint16 addr, quint16 count) { QByteArray body; body.append(char(0x30)); // 讀命令 0 body.append(QString::number(addr, 16) .rightJustified(4, 0) .toLatin1().toUpper()); // 地址4位ASCII body.append(QString::number(count, 16) .rightJustified(2, 0) .toLatin1().toUpper()); // 點數(shù)2位ASCII body.append(char(0x03)); // ETX quint8 sum 0; for (int i 0; i body.size(); i) { sum static_castquint8(body.at(i)); // 校驗CMD到ETX累加 } QByteArray frame; frame.append(char(0x02)); // STX frame.append(body); frame.append(QString::number(sum, 16) .rightJustified(2, 0) .toLatin1().toUpper()); return frame; }邏輯說明校驗和是所有有效字節(jié) ASCII 碼累加的低 8 位轉(zhuǎn)成兩位大寫十六進制。最容易錯的點是校驗范圍和是否轉(zhuǎn)大寫沒有轉(zhuǎn)大寫PLC 回 NAK把 STX 也算進去同樣 NAK。所以我把 STX 放在 body 外面累加從源頭避免這個坑。地址參數(shù)addr是協(xié)議地址不是 D 寄存器號。D0 對應(yīng) 0x1000D100 對應(yīng) 0x1064。單幀讀取點數(shù)受協(xié)議限制FX3U 建議單次不超過 64 點超過就拆幀。響應(yīng)數(shù)據(jù)是每個 D 值返回 4 個 ASCII 字符低字節(jié)在前解析時按 4 字符一組還原 16 位值。寫命令和讀命令結(jié)構(gòu)幾乎一樣只是命令碼變10x31在 ETX 前把數(shù)據(jù)按“先低字節(jié)后高字節(jié)、每個字節(jié)拆兩個 ASCII 字符”塞進去。注意寫 D 值是 16 位32 位數(shù)據(jù)要連續(xù)寫兩個 D低 D 在前。4.3 MC 3E 幀構(gòu)造Little Endian 字節(jié)序與軟元件代碼MC 3E 幀比 FX 編程口復(fù)雜在頭部長且全部走二進制小端序。以 Q 系列批量讀 D 區(qū)為例最少字節(jié)的請求幀長這樣副頭 0x5000 網(wǎng)絡(luò)號 00 PC號 FF IO編號 03FF 站號 00 請求數(shù)據(jù)長度 監(jiān)視定時器 指令 0401 子指令 0000 軟元件代碼 00A8 軟元件地址(3字節(jié)) 點數(shù)代碼實現(xiàn)如下QByteArray buildMc3eRead(quint16 deviceCode, quint32 offset, quint16 points) { QByteArray req; req.append(char(0x50)); req.append(char(0x00)); // 副頭 0x5000 req.append(char(0x00)); // 網(wǎng)絡(luò)號 req.append(char(0xFF)); // PC號 req.append(char(0xFF)); req.append(char(0x03)); // IO編號 03FF, 小端 req.append(char(0x00)); // 站號 req.append(char(0x00)); req.append(char(0x00)); // 請求長度, 稍后回填 req.append(char(0x10)); req.append(char(0x00)); // 監(jiān)視定時器 req.append(char(0x01)); req.append(char(0x04)); // 批量讀 0401, 小端 req.append(char(0x00)); req.append(char(0x00)); // 子指令, 字訪問 req.append(char(deviceCode 0xFF)); // 軟元件代碼低字節(jié) req.append(char((deviceCode 8) 0xFF)); // 軟元件代碼高字節(jié) req.append(char(offset 0xFF)); // 地址第1字節(jié) req.append(char((offset 8) 0xFF)); // 地址第2字節(jié) req.append(char((offset 16) 0xFF)); // 地址第3字節(jié) req.append(char(points 0xFF)); // 點數(shù)低字節(jié) req.append(char((points 8) 0xFF)); // 點數(shù)高字節(jié) quint16 len req.size() - 9; // 長度從監(jiān)視定時器開始算 req[7] static_castchar(len 0xFF); req[8] static_castchar((len 8) 0xFF); return req; }邏輯說明deviceCode是軟元件代碼D 區(qū)是 0x00A8M 區(qū)是 0x0090X 區(qū)是 0x009CY 區(qū)是 0x009D。offset是軟元件地址D0 就是 0D100 就是 0x64。整個幀從頭到尾都是小端一眼就能看到大量“先低后高”的字節(jié)排布。長度字段的回填是我踩過最多次的地方。不同版本的協(xié)議文檔對“請求數(shù)據(jù)長度”的定義有差異有的從監(jiān)視定時器開始算有的從指令開始算。源碼統(tǒng)一采用“從監(jiān)視定時器到幀尾的字節(jié)數(shù)”實測能通過 Q 系列通訊測試。如果你拿到的協(xié)議文檔算出的長度和這里不一致不要糾結(jié)以實際回顯的結(jié)束代碼為準調(diào)。響應(yīng)幀的結(jié)構(gòu)是副頭、網(wǎng)絡(luò)號、PC 號、IO、站號、長度、結(jié)束代碼再跟數(shù)據(jù)。結(jié)束代碼 0x0000 代表正常0xC051 是范圍外地址0xC052 是指定點數(shù)超限。解析響應(yīng)時先檢查結(jié)束代碼再按點數(shù)解析數(shù)據(jù)別一上來就拿數(shù)據(jù)算。MC 4E 幀和 3E 幀的區(qū)別是在請求里多了一個 2 字節(jié)的 CPU 編號放在監(jiān)視定時器之后、指令之前。FX5U 和 iQ-R 多 CPU 環(huán)境下必須帶單 CPU 時填 0x0000 也能通。4.4 軟元件地址映射表D/M/X/Y 在兩種協(xié)議里的不同起點兩種協(xié)議最大的混淆點就是地址起點。我整理了一張常用映射表源碼的 SoftElementTable 里也是同一份軟元件類型MC 3E 代碼FX 編程口協(xié)議地址說明D字0x00A80x1000 D號16位數(shù)據(jù)寄存器32位用連續(xù)兩個DM位0x00900x0100 M號M8000特殊繼電器同樣按此編址X位0x009C0x03E0 八進制地址輸入注意八進制編號Y位0x009D0x0400 八進制地址輸出注意八進制編號X 和 Y 的編號是八進制的X0 到 X7 是 0x03E0 到 0x03E7X10 是 0x03E8中間跳過 8 和 9。很多新手用十進制遞增算地址結(jié)果讀寫的是別的點排查半天才發(fā)現(xiàn)是進制問題。D 區(qū)相對規(guī)矩D0 0x1000 這個錨點要死記MC 協(xié)議里 D0 卻是 0x000000不要混。4.5 輪詢調(diào)度QTimer 異步隊列、超時重試和斷線判定上位機讀 PLC 最常見的設(shè)計錯誤是發(fā)完請求后用while死等響應(yīng)把 UI 線程堵死。正確做法是異步隊列QTimer 定時發(fā)請求等響應(yīng)的窗口期用單次定時器做超時判斷超時丟幀但不阻塞界面。class PlcPoller : public QObject { Q_OBJECT public: explicit PlcPoller(IPlcClient *client, QObject *parent nullptr) : QObject(parent), io(client) { timer.setInterval(100); connect(timer, QTimer::timeout, this, PlcPoller::tick); connect(io, IPlcClient::responseReady, this, PlcPoller::onResponse); } private slots: void tick() { if (busy) return; if (retry 3) { emit linkLost(); // 連續(xù)超時, 判定斷線 return; } busy true; io-send(buildMc3eRead(0x00A8, 0x0000, 8)); QTimer::singleShot(500, this, [this]() { if (busy) { retry; busy false; } // 500ms無響應(yīng), 丟幀重試 }); } void onResponse(const QByteArray resp) { if (!busy) return; retry 0; busy false; emit dataReady(parseDWords(resp)); } private: IPlcClient *io; QTimer timer; bool busy false; int retry 0; };邏輯說明這個狀態(tài)機只有兩個狀態(tài)空閑和等待響應(yīng)。busy防止上一次還沒回應(yīng)就發(fā)下一次retry超過 3 次就通知界面層顯示斷線同時停止發(fā)請求等 PLC 側(cè)鏈路恢復(fù)后再自動重連。這種設(shè)計下線纜松了、PLC 停電界面不會卡死只會彈狀態(tài)。參數(shù)調(diào)優(yōu)100ms 輪詢適合人機界面顯示、數(shù)據(jù)采集這類場景運動控制或高速配方切換建議把間隔壓到 20ms并且把 poller 放到獨立線程。注意 QTimer 的最小有效間隔是系統(tǒng)定時器分辨率Windows 下大約是 15.6ms所以別寫 1ms 這種虛參數(shù)。5. 避坑與排查串口沒反應(yīng)、NAK、數(shù)據(jù)錯位5 個高頻坑5.1 串口能打開但 PLC 無響應(yīng)多半是偶校驗或 RS-485 方向控制現(xiàn)象COM 口能打開TX 燈在閃但 PLC 端毫無動作readyRead一直不觸發(fā)。原因兩種情況最常見。一是 FX 編程口默認是偶校驗代碼里卻配了 NoParity這一項不對底層串口就把幀過濾了二是用的 RS-485 轉(zhuǎn)接器沒有自動方向控制數(shù)據(jù)發(fā)出去后發(fā)送使能沒拉回接收狀態(tài)PLC 的回應(yīng)發(fā)不出來。解決先把串口參數(shù)改成 9600、8、Even、1寫死在初始化代碼里并標注注釋方向控制問題買帶“自動收發(fā)切換”的 USB-RS422 轉(zhuǎn)換器或者軟件層面在write之后QThread::msleep(2)讓發(fā)送使能穩(wěn)定切回接收再開始讀。5.2 返回 NAK 15H校驗和算錯而且錯得很隱蔽現(xiàn)象串口收發(fā)都正常發(fā)送讀命令后立刻收到 0x15NAK或者收到幀頭但數(shù)據(jù)全是亂碼。原因FX 編程口的校驗和是從命令碼到 ETX 結(jié)束符之間所有字節(jié) ASCII 碼的累加和低 8 位轉(zhuǎn)成兩個大寫十六進制字符。有人把 STX 納入了校驗范圍有人忘了轉(zhuǎn)大寫有人用 Unicode 字符串求和導(dǎo)致每個字符多算一個 0x00。解決嚴格按代碼里的sum static_castquint8(body.at(i))方式累加。調(diào)試時把這個校驗和打印出來和 PLC 調(diào)試軟件的監(jiān)視窗對比一眼就能看出差異。5.3 數(shù)據(jù)高低字節(jié)錯位D 值讀回來變成 0x4321現(xiàn)象寫入 D0 0x1234讀回來顯示 0x3412或者數(shù)值對但翻倍、變小。原因三菱協(xié)議全部是小端傳輸數(shù)據(jù)區(qū)先到的是低字節(jié)。界面層用qFromBigEndian解析或者從 QByteArray 取數(shù)時直接按大端索引就會錯位。解決統(tǒng)一用qFromLittleEndianquint16解析每個 D 值不要手動(buf[0] 8) | buf[1]。如果你做的是跨平臺代碼還要注意不同編譯器對char符號性的處理取字節(jié)前一律static_castquint8。5.4 輪詢 10ms CPU 吃滿、UI 卡死現(xiàn)象程序一啟動就有一個核跑滿界面拖拽明顯掉幀最小化再恢復(fù)要等好幾秒。原因輪詢間隔設(shè)成 10ms且讀寫邏輯放在主線程里同步執(zhí)行。Windows 下 QTimer 實際觸發(fā)頻率受系統(tǒng)計時器限制密集的短周期同步 I/O 會把 UI 事件循環(huán)餓死。解決把輪詢類移到 QThread 子線程界面只通過信號槽接收最終數(shù)據(jù)輪詢間隔按場景設(shè)置界面顯示類 100ms 足夠高速采集最低也建議 20ms。不要為了“實時性”盲調(diào) 1ms三菱 PLC 的響應(yīng)周期本身就在 10ms 量級發(fā)太快只會把總線打滿。5.5 VS2019 報 MSB8020 / 找不到 Qt 頭文件現(xiàn)象解決方案資源管理器里工程有黃色嘆號編譯報“MSB8020 找不到 v142 生成工具”或者#include QSerialPort直接提示找不到。原因MSB8020 是 Qt 庫用 v141 編譯而你工程用 v142或反之找不到頭文件是 Qt VS Tools 的 Qt Version 路徑配錯常見是把路徑指到了 Qt 的安裝目錄而不是 msvc2019_64 庫目錄。解決在“項目屬性 → 常規(guī) → 平臺工具集”里明確選 Visual Studio 2019 (v142)和 Qt 庫一致再到 Qt VS Tools → Qt Versions 里檢查路徑必須是D:\Qt\5.15.2\msvc2019_64而不是D:\Qt。改完清一遍解決方案再重新生成不要只按增量編譯。6. 不連 PLC 先驗證幀Python 回環(huán)腳本和三件套檢查法6.1 回環(huán)腳本先驗證幀PLC 不在手邊或者現(xiàn)場設(shè)備在別人產(chǎn)線里跑著沒法反復(fù)試。這種時候我用一個 Python 回環(huán)腳本在本地模擬 PLC 的 TCP 響應(yīng)先用它驗證客戶端發(fā)幀和解析邏輯是不是自洽import socket, binascii srv socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.bind((0.0.0.0, 6000)) # 與上位機 PLC 端口保持一致 srv.listen(1) conn, _ srv.accept() while True: data conn.recv(256) if not data: break print(recv:, binascii.hexlify(data, )) # 偽造一個結(jié)束碼0000、帶8個D值的響應(yīng) body bytes.fromhex( 500000ff0300000c000000010 0000000010203040506070809 ) conn.send(body)邏輯說明腳本不做協(xié)議解析只把收到的幀按十六進制打印出來再返回一個結(jié)構(gòu)合法的 3E 響應(yīng)。這個過程用來驗證三件事客戶端有沒有發(fā)出完整幀、長度字段是否穩(wěn)定、響應(yīng)解析會不會崩。如果客戶端本身幀構(gòu)造錯了回環(huán)腳本打印出來的十六進制一眼就能看出頭部偏位。6.2 真機三件套檢查回環(huán)腳本通過后再上真機。我每次連真機固定走三件套檢查第一確認 PLC 的 IP 和端口能 ping 通telnet 端口能建立連接第二確認 PLC 側(cè)通訊參數(shù)里的幀類型是 3E 幀還是 4E 幀Q 系列選 Qna-3EFX5U 選 4E選錯連握手都過不去第三確認軟元件地址沒超范圍D 區(qū)范圍外的地址會回 C051 結(jié)束碼?;丨h(huán)腳本還有個額外好處可以拿來做回歸測試。改了解析代碼先跑一遍回環(huán)再連真機能把“協(xié)議問題”和“現(xiàn)場問題”隔離開。從那以后我每次接到新 PLC 型號都強制先做一遍幀回環(huán)驗證再上真機調(diào)參數(shù)這套流程幫我省掉過太多現(xiàn)場翻車的狼狽。希望幫到你。本文還有配套的精品資源點擊獲取