據(jù)?從物理層到時(shí)序?qū)拥乃膶庸收吓挪橹改? alt=)
現(xiàn)場(chǎng)電話打過(guò)來(lái)的時(shí)候?qū)Ψ胶芎V定地說(shuō)了兩句話“程序是好的設(shè)備也沒(méi)壞可就是收不到 Modbus 數(shù)據(jù)?!蔽耶?dāng)時(shí)的第一反應(yīng)不是松口氣而是頭皮一陣發(fā)緊。程序崩了或者硬件燒了都好辦換一塊、重啟一次就有結(jié)論怕的就是這種“主站能發(fā)、從站能收、線也量得通”但數(shù)據(jù)就是死活上不來(lái)。這套系統(tǒng)其實(shí)不復(fù)雜PC 做 Modbus RTU 主站經(jīng)過(guò) USB 轉(zhuǎn) RS485 接一臺(tái)溫濕度變送器通信距離也就二三十米波特率 9600讀 4 個(gè)保持寄存器。本地測(cè)試時(shí)用 Modbus Poll 模擬主站讀同一個(gè)型號(hào)的設(shè)備讀數(shù)正常用 Modbus Slave 模擬從站主站程序也能正常收到數(shù)據(jù)??蛻魣?jiān)持說(shuō)設(shè)備是新的線是剛壓的萬(wàn)用表量過(guò)都通。后來(lái)花了快半天時(shí)間定位最后發(fā)現(xiàn)的問(wèn)題說(shuō)出來(lái)都有點(diǎn)不好意思——可它就是真實(shí)發(fā)生了。這也是我想把這次調(diào)試過(guò)程完整記錄下來(lái)的原因工業(yè)現(xiàn)場(chǎng)大量“收不到 Modbus 數(shù)據(jù)”的故障根子根本不在代碼里也不在設(shè)備好壞上而是藏在代碼和設(shè)備之間的物理層、配置層、協(xié)議層和時(shí)序?qū)永?。這篇文章就把這四層逐個(gè)拆開(kāi)講希望能給正在被類似問(wèn)題折磨的人一點(diǎn)方向感。1. “代碼沒(méi)問(wèn)題設(shè)備沒(méi)壞”的真相大多數(shù)故障藏在中間鏈路先說(shuō)結(jié)論所有“代碼沒(méi)問(wèn)題、設(shè)備沒(méi)壞”的故障都不是真的沒(méi)問(wèn)題而是問(wèn)題發(fā)生的層面不在你驗(yàn)證過(guò)的那個(gè)范圍內(nèi)。單獨(dú)驗(yàn)證主站、單獨(dú)驗(yàn)證從站恰恰把最關(guān)鍵的中間鏈路給繞過(guò)去了。1.1 一句“讀不到數(shù)據(jù)”背后至少藏著四個(gè)排查層Modbus 串口通信是一條完整的鏈拆開(kāi)來(lái)看至少四層物理層RS485 接線、電平、終端電阻、配置層波特率、數(shù)據(jù)位、校驗(yàn)位、從站地址、協(xié)議層功能碼、寄存器地址、CRC、幀格式、時(shí)序?qū)虞喸児?jié)奏、幀間隔、半雙工切換。每一層出問(wèn)題表現(xiàn)出來(lái)的現(xiàn)象都可能是一模一樣的“主站收不到數(shù)據(jù)”但排查方法完全不同。這也是為什么很多人遇到“收不到數(shù)據(jù)”會(huì)原地打轉(zhuǎn)因?yàn)槟阍诖a層面反復(fù)查、在設(shè)備層面反復(fù)重啟都繞開(kāi)了真正出問(wèn)題的層。我把這四層對(duì)應(yīng)的典型癥狀整理成了一張表現(xiàn)場(chǎng)對(duì)照著看會(huì)清晰很多排查層級(jí)典型癥狀常用定位手段物理層完全無(wú)響應(yīng)、時(shí)通時(shí)斷、間歇性錯(cuò)幀萬(wàn)用表、示波器看波形、換線、查 GND 和終端電阻配置層偶爾通偶爾斷、長(zhǎng)連接后必?cái)?、讀全 0 或異常碼串口參數(shù)比對(duì)、抓包看實(shí)際字節(jié)協(xié)議層有請(qǐng)求無(wú)響應(yīng)、錯(cuò)誤響應(yīng)幀、數(shù)據(jù)值明顯異常抓包看地址和功能碼、比對(duì)寄存器映射表時(shí)序?qū)痈哳l輪詢時(shí)丟包、改采集周期后故障出現(xiàn)調(diào)整輪詢周期、檢查幀間隔、查看方向切換邏輯1.2 單測(cè)正常恰恰是最迷惑人的證據(jù)這次項(xiàng)目里主站用 Modbus Poll 讀真實(shí)從站讀數(shù)正常從站用 Modbus Slave 模擬主站程序也能正常收到。但把真實(shí)設(shè)備接到真實(shí)主站上就收不到。這說(shuō)明主站代碼的邏輯和從站設(shè)備的響應(yīng)能力都沒(méi)有大問(wèn)題問(wèn)題必然出在“真實(shí)鏈路”這個(gè)連接環(huán)節(jié)上。單測(cè)是通過(guò)把鏈路人為縮短成兩段來(lái)進(jìn)行驗(yàn)證的它驗(yàn)證的是兩端的正確性恰恰沒(méi)有驗(yàn)證中間段的正確性。很多現(xiàn)場(chǎng)工程師把“兩端單測(cè)都通”當(dāng)成“整條鏈路沒(méi)問(wèn)題”的證據(jù)這就是誤判的根源。我后來(lái)習(xí)慣把單測(cè)理解成“單元測(cè)試”它只能證明單個(gè)模塊 OK永遠(yuǎn)替代不了“集成測(cè)試”。1.3 為什么這種故障最消耗時(shí)間因?yàn)樗谋硐筇衅垓_性。你問(wèn)現(xiàn)場(chǎng)人員他會(huì)很肯定地告訴你“代碼沒(méi)問(wèn)題”“設(shè)備沒(méi)壞”“線量過(guò)沒(méi)問(wèn)題”——這三句話把最可能的三個(gè)方向全部堵死。但實(shí)際上這三句話都只是直覺(jué)判斷沒(méi)有一條經(jīng)過(guò)報(bào)文物證。代碼沒(méi)報(bào)錯(cuò)不代表主站發(fā)出的請(qǐng)求幀是完整的設(shè)備能上電不代表從站配置的地址和波特率跟主站對(duì)得上萬(wàn)用表能量通更不代表信號(hào)波形沒(méi)有問(wèn)題。排查這種故障第一條鐵律就是不要聽(tīng)結(jié)論要看證據(jù)。2. 物理層第一道坎RS485 看似差分傳輸坑起來(lái)一點(diǎn)不含糊RS485 采用差分信號(hào)傳輸抗干擾能力確實(shí)強(qiáng)但很多坑恰恰因?yàn)椤安罘帧眱蓚€(gè)字被低估了。以為兩根線一接就行實(shí)際現(xiàn)場(chǎng)翻車的概率非常高。2.1 萬(wàn)用表能證明線沒(méi)斷證明不了信號(hào)能走我在現(xiàn)場(chǎng)遇到的第一件事就是對(duì)方拿著萬(wàn)用表告訴我A、B 兩根線都量過(guò)了通斷沒(méi)問(wèn)題。但 RS485 通信不是通斷能表征的??臻e狀態(tài)下A 相對(duì) B 的電壓應(yīng)該在 2V 到 6V 之間有些設(shè)備定義方向相反通信的時(shí)候電壓會(huì)在正負(fù)電平之間跳變。萬(wàn)用表只能量出一個(gè)直流電壓值而這個(gè)電壓值對(duì)不對(duì)、數(shù)據(jù)時(shí)能不能正常翻轉(zhuǎn)萬(wàn)用表根本看不出來(lái)。所以到了現(xiàn)場(chǎng)不要只帶萬(wàn)用表最好帶一個(gè)示波器或者至少帶一個(gè) USB 轉(zhuǎn) 485 調(diào)試工具。示波器看 A-B 之間的波形時(shí)重點(diǎn)看三件事空閑電平是否穩(wěn)定發(fā)送時(shí)波形翻轉(zhuǎn)幅度是否足夠波形邊沿有沒(méi)有明顯畸變。如果你發(fā)現(xiàn)波形幅度才一兩百毫伏或者邊沿像鋸齒一樣那物理層基本跑不掉。2.2 端子“標(biāo)簽貼反”和 GND 不共地的實(shí)測(cè)案例這次故障的第一層原因恰恰出在接線定義上。設(shè)備手冊(cè)和端子標(biāo)的是 A、B但實(shí)際內(nèi)部定義是反的。為什么單測(cè)沒(méi)測(cè)出來(lái)因?yàn)閱螠y(cè)時(shí)用的是另一根成品線那根線內(nèi)部的顏色定義正好把錯(cuò)誤抵消了?,F(xiàn)場(chǎng)重新壓線之后按手冊(cè)顏色來(lái)接結(jié)果 A、B 對(duì)調(diào)數(shù)據(jù)自然上不來(lái)。這類問(wèn)題在 485 設(shè)備上非常常見(jiàn)尤其是不同批次的產(chǎn)品端子定義的一致性未必可靠。越是看起來(lái)簡(jiǎn)單的接線越要實(shí)測(cè)驗(yàn)證。另一個(gè)容易被忽略的點(diǎn)是 GND。RS485 是差分信號(hào)很多人因此覺(jué)得共地?zé)o所謂實(shí)際并非如此。如果兩個(gè)設(shè)備的電源系統(tǒng)相互獨(dú)立共模電壓可能超過(guò)接收端允許的范圍輕則偶發(fā)錯(cuò)幀重則完全收不到數(shù)據(jù)?,F(xiàn)場(chǎng)沒(méi)有把主站的 RS485 地和從站地線連起來(lái)也會(huì)造成這種“什么都對(duì)卻沒(méi)有數(shù)據(jù)”的現(xiàn)象。正確的做法是把 A、B、GND 三根線都接上屏蔽層單端接地。別省那根地線很多“玄學(xué)通信故障”都是省地線省出來(lái)的。2.3 終端電阻短線無(wú)所謂長(zhǎng)線裝反了一樣“靜默”按標(biāo)準(zhǔn)RS485 總線兩端各要接一個(gè) 120Ω 終端電阻目的是匹配阻抗、防止信號(hào)反射。短距離低速9600 波特率幾十米一般不接也能跑但一旦距離超過(guò)一兩百米或者現(xiàn)場(chǎng)有變頻器、電機(jī)等干擾源終端電阻就非常重要。需要特別注意的是終端電阻接在“總線兩端”不是接在主站這一端就萬(wàn)事大吉。有一回我調(diào)試某項(xiàng)目從站端的電阻松了總線上產(chǎn)生反射數(shù)據(jù)時(shí)通時(shí)斷主站偶爾能收到、偶爾收不到。這種“間歇性收不到”比完全收不到更折磨人因?yàn)樗鼤?huì)誤導(dǎo)你懷疑程序邏輯而不是回頭檢查物理層。所以在物理層排查時(shí)除了量通斷還要確認(rèn)屏蔽層和終端電阻的狀態(tài)。2.4 一個(gè)容易被忽略的“半雙工鏈路拓?fù)洹眴?wèn)題RS485 是總線型拓?fù)渌性O(shè)備并聯(lián)在 A、B 兩根線上。如果現(xiàn)場(chǎng)有人把 485 線做成了“星型”或者“T 型分叉”并且分叉線很長(zhǎng)信號(hào)反射會(huì)大到足以讓通信徹底失敗。我見(jiàn)過(guò)一個(gè)項(xiàng)目從站設(shè)備裝在三個(gè)不同角落施工隊(duì)圖省事每個(gè)設(shè)備都單獨(dú)拉了一根線回中控室結(jié)果總線上出現(xiàn)了三個(gè)長(zhǎng)長(zhǎng)的“分支盲端”怎么調(diào)參數(shù)都通不了。后來(lái)把總線改成手拉手串聯(lián)問(wèn)題立刻消失。如果你在現(xiàn)場(chǎng)發(fā)現(xiàn)拓?fù)錄](méi)法改最短的補(bǔ)救辦法是把每個(gè)分支的長(zhǎng)度盡量縮短并且適當(dāng)降低波特率。3. 配置層第二道坎波特率、校驗(yàn)位、寄存器地址看著對(duì)不代表真的對(duì)物理層排完下一步就是配置。Modbus RTU 串口通信的配置項(xiàng)很多主從兩邊必須逐項(xiàng)對(duì)上差一個(gè)都會(huì)出問(wèn)題。3.1 波特率“相同”但主從兩側(cè)的實(shí)際偏差可能完全不同波特率按 9600 配置主站用的是 USB 轉(zhuǎn) 485 芯片晶振精度高從站如果是低成本 MCU內(nèi)部 RC 振蕩器誤差可能達(dá)到 ±2% 甚至更高。9600bps 下每一位約 104μs一個(gè)字節(jié)按 11 位算大約 1.15ms。如果從站時(shí)鐘偏快 2%發(fā)一整幀 8 個(gè)字節(jié)之后累積偏差已經(jīng)超過(guò) 180μs采樣點(diǎn)就可能漂出安全范圍。結(jié)果是短幀偶爾能通長(zhǎng)幀頻繁錯(cuò)CRC 校驗(yàn)錯(cuò)誤不斷主站表現(xiàn)為“超時(shí)”或“收不到”。這種問(wèn)題在示波器上能看到每一幀的脈寬和標(biāo)準(zhǔn)值有肉眼可見(jiàn)的偏差。遇到這種設(shè)備要么換更高精度的從站晶振要么把通信內(nèi)容拆短要么降低波特率到 4800 甚至 2400。不要迷信“配置里寫了 9600 就是 9600”實(shí)際頻率要以示波器測(cè)到為準(zhǔn)。3.2 8N1 和 8E1 的錯(cuò)位后果偶爾通、偶爾斷校驗(yàn)位是配置層最容易翻車的點(diǎn)。很多設(shè)備默認(rèn) 8N1但有些傳感器出廠默認(rèn) 8E1或者主板上有跳線帽控制校驗(yàn)方式。主站配置 8N1、從站默認(rèn) 8E1 時(shí)雙方收發(fā)字節(jié)會(huì)出現(xiàn)奇偶校驗(yàn)錯(cuò)誤。Modbus RTU 本身在串口層通常配置成 8N1、8E1 或 8O1 都可以8N2 極少用但兩邊必須完全一致。不一致時(shí)的表現(xiàn)很迷惑偶數(shù)字節(jié)能通過(guò)奇數(shù)字節(jié)錯(cuò)誤CRC 對(duì)不上主站表現(xiàn)為“時(shí)通時(shí)斷”。這類問(wèn)題用串口助手發(fā)送固定報(bào)文再對(duì)比收到的字節(jié)就能快速定位。還有一個(gè)隱藏點(diǎn)——有些設(shè)備的“校驗(yàn)位”設(shè)置本身并不生效實(shí)際按跳線帽或者 EEPROM 里的默認(rèn)值跑你改完配置必須斷電重啟最好再?gòu)恼净刈x確認(rèn)否則改了個(gè)寂寞。3.3 寄存器地址的 40001 與 0x0000 之差程序里最常見(jiàn)的“地址錯(cuò)位”配置層還有一個(gè)高頻坑就是寄存器地址的表示法。Modbus 協(xié)議報(bào)文里用的是 0 起始的寄存器偏移地址比如保持寄存器第一個(gè)寄存器是 0x0000但很多 PLC 和組態(tài)軟件用數(shù)據(jù)地址表示第一個(gè)保持寄存器是 40001。如果主站程序里讀的是 0x0000而設(shè)備手冊(cè)寫的是“寄存器地址 40001”很多人會(huì)直接把 40001 填進(jìn)報(bào)文。這里有個(gè)真實(shí)的換算關(guān)系40001 減去 40001 等于 0這才是協(xié)議地址。如果按 40001 直接填進(jìn)請(qǐng)求幀十六進(jìn)制是 0x9C41從站根本不知道你要讀哪大概率返回異常碼 02 或者干脆不響應(yīng)。更隱蔽的是一些設(shè)備手冊(cè)把“數(shù)據(jù)地址”和“協(xié)議地址”混用你加不加 40001 偏移、減不減 1讀出來(lái)的數(shù)據(jù)可能完全錯(cuò)位。排查這類問(wèn)題最直接的方法就是抓包看請(qǐng)求報(bào)文里實(shí)際攜帶的地址再跟設(shè)備手冊(cè)的寄存器映射表逐條對(duì)比不要靠猜。3.4 寄存器數(shù)量也要算清楚讀 2 個(gè)寄存器和讀 1 個(gè)寄存器結(jié)果完全不同有些數(shù)據(jù)是 16 位的占 1 個(gè)寄存器有些是 32 位浮點(diǎn)數(shù)占 2 個(gè)連續(xù)寄存器。如果主站只申請(qǐng)讀 1 個(gè)寄存器從站返回的數(shù)據(jù)長(zhǎng)度就是 2 字節(jié)你拿到高 16 位或低 16 位數(shù)值當(dāng)然不對(duì)。反向也一樣一次請(qǐng)求的寄存器數(shù)量如果超過(guò)從站允許上限從站會(huì)返回異常碼 03。調(diào)試時(shí)要先確認(rèn)目標(biāo)寄存器區(qū)間的連續(xù)性和數(shù)量再去配主站請(qǐng)求長(zhǎng)度。4. 協(xié)議層第三道坎RTU 一問(wèn)一答規(guī)矩不守就杳無(wú)音信配置層一致之后如果數(shù)據(jù)還上不來(lái)就要看協(xié)議交互。Modbus RTU 的協(xié)議規(guī)矩其實(shí)很簡(jiǎn)單主站發(fā)請(qǐng)求幀從站回響應(yīng)幀一問(wèn)一答從站永遠(yuǎn)不會(huì)主動(dòng)開(kāi)口。先把這個(gè)前提記住很多“為什么我沒(méi)發(fā)數(shù)據(jù)設(shè)備也說(shuō)話”的疑問(wèn)就沒(méi)有了。4.1 從站明明有應(yīng)答主站卻“看不見(jiàn)”的三種原因很多人以為“收不到數(shù)據(jù)”就是從站完全沒(méi)反應(yīng)實(shí)際上從站回了只是主站沒(méi)認(rèn)。常見(jiàn)原因有三種第一種是從站地址不匹配。主站向 01 號(hào)從站發(fā)請(qǐng)求現(xiàn)場(chǎng)設(shè)備地址卻是 02設(shè)備收到報(bào)文后一看地址不是自己整個(gè)包直接丟棄。這種問(wèn)題在抄寫設(shè)備地址時(shí)特別容易發(fā)生尤其是撥碼開(kāi)關(guān)設(shè)置地址的設(shè)備撥碼位看反一位地址就從 01 變成 02 或者 04。第二種是 CRC 校驗(yàn)錯(cuò)誤。只要報(bào)文里任何一個(gè)字節(jié)被干擾、錯(cuò)位CRC 算出來(lái)不對(duì)從站就會(huì)把整幀扔掉不做任何回應(yīng)。物理層有干擾、波特率有偏差、幀間隔有問(wèn)題都可能表現(xiàn)為 CRC 錯(cuò)誤。第三種是 RTU 幀間隔問(wèn)題。Modbus RTU 要求幀與幀之間有至少 3.5 個(gè)字符時(shí)間的靜默間隔9600bps 下大約是 4ms。如果主站連續(xù)發(fā)送太快或者 485 芯片在接收時(shí)產(chǎn)生了額外的噪聲位從站會(huì)把兩幀合并成一幀同樣不響應(yīng)。這三種情況都需要通過(guò)抓包或串口監(jiān)聽(tīng)來(lái)區(qū)分單靠“看代碼”“看設(shè)備燈”根本判斷不了。4.2 功能碼正確但數(shù)據(jù)不對(duì)浮點(diǎn)數(shù)解析和寄存器組合問(wèn)題有些時(shí)候響應(yīng)幀已經(jīng)回來(lái)了主站也收到了但數(shù)據(jù)明顯不對(duì)很多人又把鍋甩給“收不到”。其實(shí)這里已經(jīng)是協(xié)議解析的問(wèn)題。最常見(jiàn)的是浮點(diǎn)數(shù)的 4 字節(jié)組合。很多傳感器的溫度、濕度數(shù)據(jù)是 IEEE 754 格式浮點(diǎn)數(shù)占兩個(gè) 16 位寄存器共 4 個(gè)字節(jié)。需要把這 4 個(gè)字節(jié)按照正確的順序拼成一個(gè) float但不同廠家的字序和字節(jié)序不一定一致常見(jiàn)組合有四種組合方式高低位順序典型場(chǎng)景組合 1AB CD高字在前、高字節(jié)在前大多數(shù)大端設(shè)備組合 2CD AB高字在后、低字在前部分國(guó)產(chǎn)儀表組合 3BA DC高字在前、字內(nèi)字節(jié)交換少數(shù)字節(jié)序顛倒的設(shè)備組合 4DC BA低字在前、字內(nèi)字節(jié)交換少數(shù)小端設(shè)備如果組合順序不對(duì)讀出來(lái)可能是一個(gè)數(shù)量級(jí)完全錯(cuò)誤的數(shù)甚至 NaN、超大值。這個(gè)問(wèn)題經(jīng)常被誤判為“通信不穩(wěn)定”實(shí)際上報(bào)文一直是好的是解析層沒(méi)配對(duì)。我寫代碼時(shí)一般會(huì)把四種組合都做成可配置項(xiàng)現(xiàn)場(chǎng)一個(gè)個(gè)試哪個(gè)數(shù)值合理用哪個(gè)。4.3 異常碼讀懂從站的“拒絕回答”如果從站返回的是錯(cuò)誤響應(yīng)幀而不是不響應(yīng)那就是協(xié)議在明確告訴你問(wèn)題。常見(jiàn)異常碼有三個(gè)對(duì)應(yīng)關(guān)系如下異常碼含義常見(jiàn)原因01非法功能碼從站不支持該功能碼比如只支持讀保持寄存器主站卻去讀輸入寄存器02非法數(shù)據(jù)地址請(qǐng)求的寄存器地址越界或者地址偏移算錯(cuò)03非法數(shù)據(jù)值寄存器數(shù)量為 0 或超出上限這類響應(yīng)主站程序如果沒(méi)做異常碼解析通常會(huì)顯示“超時(shí)”或“收不到”。所以調(diào)試時(shí)不要只看結(jié)果要把原始報(bào)文打出來(lái)看到底是沒(méi)響應(yīng)還是異常響應(yīng)。能看到異常碼其實(shí)等于從站已經(jīng)幫你把問(wèn)題范圍縮小了一大半。5. 最容易被甩鍋的時(shí)序?qū)虞喸冾l率、幀間隔、485 方向切換物理、配置、協(xié)議都對(duì)了還有一類問(wèn)題藏在時(shí)序?qū)印_@一層最隱蔽也最容易被現(xiàn)場(chǎng)經(jīng)驗(yàn)帶偏因?yàn)樗谋憩F(xiàn)往往跟“設(shè)備壞了”或者“程序不穩(wěn)定”一模一樣。5.1 輪詢從 500ms 提到 100ms設(shè)備開(kāi)始“裝死”我經(jīng)歷過(guò)一次很典型的例子同一套從站設(shè)備Modbus Poll 每 500ms 讀一次穩(wěn)定得很把輪詢周期改成 200ms、100ms 后丟包率直線上升最后整個(gè)鏈路像死了一樣。問(wèn)題不是線壞了而是從站 MCU 的處理能力跟不上。很多從站用中斷接收數(shù)據(jù)報(bào)文到了以后還要做 CRC 計(jì)算、查表、準(zhǔn)備數(shù)據(jù)這些都需要時(shí)間。主站連續(xù)發(fā)請(qǐng)求從站還沒(méi)來(lái)得及回復(fù)上一個(gè)請(qǐng)求下一個(gè)請(qǐng)求就來(lái)了從站只能丟棄或者回復(fù)異常。更極端的場(chǎng)景是某些 PLC 做主站比如用西門子 1200 做輪詢讀取如果程序里沒(méi)有做“等上一幀完成再發(fā)下一幀”的聯(lián)鎖而是按固定周期拼命刷高頻輪詢會(huì)直接把從站“打蒙”。解決思路是把輪詢周期調(diào)回安全值或者在主站側(cè)實(shí)現(xiàn)“上一幀超時(shí)或完成后才發(fā)下一幀”的流控不要無(wú)腦提高采集頻率。5.2 半雙工方向切換幾微秒的延遲毀掉整幀回包RS485 是半雙工總線同一時(shí)刻只能有一個(gè)方向在發(fā)送。帶自動(dòng)收發(fā)切換的 485 芯片比如常見(jiàn)的 SP485 或者隔離模塊多數(shù)能自動(dòng)處理方向問(wèn)題但切換本身需要時(shí)間。剛從發(fā)送模式切換到接收模式時(shí)芯片內(nèi)部還沒(méi)完全穩(wěn)定從站回幀的開(kāi)頭幾個(gè)字節(jié)就可能被吞掉。主站側(cè)如果用了軟件控制方向的手動(dòng)切換更要注意“發(fā)完請(qǐng)求后必須等一小段時(shí)間再切回接收”否則從站的回應(yīng)會(huì)被自己的發(fā)送階段掩蓋。調(diào)試時(shí)如果發(fā)現(xiàn)“主站一發(fā)請(qǐng)求立刻出現(xiàn)一個(gè)錯(cuò)誤字節(jié)然后整幀消失”多半就是方向切換時(shí)序問(wèn)題。這里的延時(shí)不需要很長(zhǎng)幾十微秒到幾毫秒都行但必須存在而且最好放在發(fā)送完最后一個(gè)字節(jié)之后。5.3 RTU 3.5 字符間隔兩幀被“粘”成一幀Modbus RTU 規(guī)定報(bào)文幀之間必須有至少 3.5 個(gè)字符時(shí)間的靜默間隔。在主站高速輪詢時(shí)如果兩個(gè)請(qǐng)求幀的間隔小于這個(gè)值從站會(huì)認(rèn)為這是一幀超長(zhǎng)報(bào)文等待結(jié)束時(shí)發(fā)現(xiàn)長(zhǎng)度不對(duì)直接丟棄。同樣主站接收時(shí)從站回幀各字段之間的間隔如果超過(guò) 1.5 個(gè)字符時(shí)間主站也可能把一幀拆成多幀處理。這類問(wèn)題的典型表現(xiàn)就是數(shù)據(jù)“偶爾通、偶爾不通”而且跟負(fù)載和上位機(jī)調(diào)度時(shí)機(jī)相關(guān)。排查時(shí)把輪詢周期拉大或者給每次請(qǐng)求之間加一點(diǎn)延時(shí)如果數(shù)據(jù)恢復(fù)穩(wěn)定那基本就是幀間隔在作怪。尤其是用 Windows 上位機(jī)做定時(shí)器輪詢時(shí)線程調(diào)度抖動(dòng)很容易讓幀間隔忽長(zhǎng)忽短這也是為什么同樣的程序跑到 Linux 上或者換臺(tái)電腦表現(xiàn)就不同。6. 完整排查鏈路復(fù)盤從現(xiàn)象到根因的 7 步定位法把上面的坑一個(gè)個(gè)拆開(kāi)大家可能會(huì)覺(jué)得“原來(lái)這么多可能”。但實(shí)際現(xiàn)場(chǎng)調(diào)試最怕的不是坑多而是沒(méi)有順序地瞎試。下面講一下我這次完整定位問(wèn)題用的 7 步流程。6.1 第一步永遠(yuǎn)是抓包而不是換線換設(shè)備很多人遇到“收不到”第一反應(yīng)是換線、換 USB 轉(zhuǎn) 485、給設(shè)備斷電重啟這些都是盲試。最有效的第一步是在總線上掛一個(gè)只監(jiān)聽(tīng)的調(diào)試工具。把 USB 轉(zhuǎn) 485 的 A、B 并聯(lián)到主從總線之間打開(kāi)串口助手或 Modbus 調(diào)試工具設(shè)置為只接收。這樣你能同時(shí)看到主站發(fā)的請(qǐng)求幀和從站回的響應(yīng)幀到底有沒(méi)有、長(zhǎng)什么樣。比如主站向地址 01 的從站讀 2 個(gè)保持寄存器請(qǐng)求幀是01 03 00 00 00 02 C4 0B如果一切正常從站應(yīng)該回01 03 04 41 8C CC CD 5B 56這里有 6 個(gè)字節(jié)的信息01 是從站地址03 是功能碼04 表示后面有 4 個(gè)數(shù)據(jù)字節(jié)后 4 字節(jié)是 2 個(gè)寄存器的數(shù)據(jù)最后 2 字節(jié)是 CRC。如果抓包只看到請(qǐng)求沒(méi)有響應(yīng)從物理層和從站配置去查如果連請(qǐng)求都抓不到那就是主站側(cè)根本沒(méi)有把數(shù)據(jù)發(fā)出去如果有請(qǐng)求有響應(yīng)但主站程序報(bào)超時(shí)問(wèn)題在主站接收解析層。6.2 抓包之后的三個(gè)分支判斷抓包結(jié)果能把問(wèn)題分成不大不小三類每一類的排查方向截然不同有請(qǐng)求、無(wú)響應(yīng)問(wèn)題在從站側(cè)或中間鏈路優(yōu)先查從站地址、CRC、波特率、物理接線有請(qǐng)求、有響應(yīng)、主站報(bào)超時(shí)問(wèn)題在主站接收解析或方向切換優(yōu)先查主站接收線程、緩沖區(qū)、字節(jié)間隔處理完全無(wú)請(qǐng)求先查主站側(cè)配置和串口占用再查波特率和串口參數(shù)是否被其他地方改寫。6.3 工具組合Modbus Poll、Modbus Slave、串口嗅探的配合現(xiàn)場(chǎng)我習(xí)慣帶一套“三件套”一個(gè) USB 轉(zhuǎn) 485 調(diào)試器、Modbus Poll當(dāng)主站模擬器、Modbus Slave當(dāng)從站模擬器。這套組合的邏輯是“替換法”。先用 Modbus Poll 去讀真實(shí)從站能讀通說(shuō)明從站本身沒(méi)問(wèn)題。再用 Modbus Slave 模擬從站接在主站程序下能讀通說(shuō)明主站程序也沒(méi)問(wèn)題。如果這兩步都通但真實(shí)主站接真實(shí)從站不通那問(wèn)題一定在兩端的真實(shí)鏈路或時(shí)序上。Modbus 調(diào)試工具用正規(guī)渠道下載的正版或試用版即可重點(diǎn)看它的報(bào)文收發(fā)日志和 CRC 校驗(yàn)功能這比裸串口助手直觀得多。我一般會(huì)把串口助手的十六進(jìn)制顯示打開(kāi)從第一幀開(kāi)始逐字節(jié)看而不是只看“通沒(méi)通”這個(gè)結(jié)果。6.4 我的經(jīng)驗(yàn)排序先鏈路、再配置、后代碼這次故障最終定位就是抓包發(fā)現(xiàn)主站發(fā)了請(qǐng)求、從站也回了響應(yīng)但響應(yīng)幀的開(kāi)頭出現(xiàn)了方向切換問(wèn)題導(dǎo)致主站把不完整幀丟棄程序里沒(méi)有任何報(bào)錯(cuò)只表現(xiàn)為超時(shí)。代碼確實(shí)沒(méi)問(wèn)題設(shè)備也確實(shí)沒(méi)壞但中間鏈路配合出了岔子。這讓我重新整理了一套現(xiàn)場(chǎng)排查順序第一步抓包看報(bào)文第二步核對(duì)物理接線和終端電阻第三步逐項(xiàng)比對(duì)配置參數(shù)第四步才輪到代碼邏輯。不要一上來(lái)就懷疑自己的程序也不要一上來(lái)就懷疑設(shè)備出廠質(zhì)量先把通信鏈路本身驗(yàn)證清楚。通信問(wèn)題首先是鏈路問(wèn)題其次才是邏輯問(wèn)題。最后再分享一個(gè)實(shí)際體會(huì)排查 Modbus 通信問(wèn)題時(shí)最快的捷徑就是“不要相信單測(cè)也不要相信手冊(cè)”。單測(cè)通過(guò)不代表鏈路通過(guò)手冊(cè)標(biāo)注也不代表實(shí)物一定如此。把抓包工具掛在總線上用真實(shí)報(bào)文說(shuō)話絕大多數(shù)“看起來(lái)沒(méi)問(wèn)題”的詭異故障半小時(shí)內(nèi)都能定位到具體層面。這也是我從無(wú)數(shù)個(gè)現(xiàn)場(chǎng)里總結(jié)出來(lái)的最值錢的一條經(jīng)驗(yàn)。