動(dòng)器內(nèi)嵌EtherNet/IP的SPI通訊方案改造實(shí)踐)
做伺服驅(qū)動(dòng)器的朋友應(yīng)該都有同感好幾年都是CANopen和脈沖接口打天下突然有一天銷售說(shuō)“客戶要求EtherNet/IP而且必須是內(nèi)嵌到驅(qū)動(dòng)器里不能外掛轉(zhuǎn)換網(wǎng)關(guān)”。我當(dāng)時(shí)接到的這個(gè)改造項(xiàng)目就是這么來(lái)的。項(xiàng)目本質(zhì)是把一塊帶EtherNet/IP協(xié)議棧的嵌入式通訊小板通過(guò)SPI接口接到伺服主控板上然后把手上的供應(yīng)商Demo程序適配成真正能用的伺服通訊固件。這篇文章會(huì)從方案選型開(kāi)始把SPI通訊機(jī)制、Demo程序改造步驟、現(xiàn)場(chǎng)排錯(cuò)經(jīng)驗(yàn)完整過(guò)一遍適合做伺服控制器、嵌入式通訊、以及準(zhǔn)備在自家產(chǎn)品里內(nèi)嵌EtherNet/IP從站技術(shù)的朋友參考。1. 先想明白為什么EtherNet/IP選擇“內(nèi)嵌SPI小板”方案1.1 面見(jiàn)客戶之后的隱藏需求客戶是給汽車產(chǎn)線做集成的產(chǎn)線上清一色AB PLC上位用了FactoryTalk設(shè)備聯(lián)網(wǎng)點(diǎn)名要EtherNet/IP。當(dāng)時(shí)我們拿了一臺(tái)開(kāi)發(fā)中的樣機(jī)過(guò)去主控MCU是STM32H7系列目前只有CANopen和脈沖接口。客戶的意思很直接不要外面掛一個(gè)協(xié)議轉(zhuǎn)換器要驅(qū)動(dòng)器本體上直接出網(wǎng)口。原因有三條柜內(nèi)空間緊張、不希望額外供電和接線、維護(hù)備件時(shí)不想多一個(gè)“莫名奇妙的盒子”。外掛網(wǎng)關(guān)當(dāng)然是最快的方案市面上各種協(xié)議轉(zhuǎn)換器一抓一大把但客戶不接受。剩下的路就是要么自己寫(xiě)EtherNet/IP從站協(xié)議棧要么買協(xié)議芯片/通訊模塊內(nèi)嵌到驅(qū)動(dòng)器里。這個(gè)決定會(huì)直接影響項(xiàng)目周期和后期維護(hù)成本需要仔細(xì)權(quán)衡。1.2 三種實(shí)現(xiàn)路徑的對(duì)比我實(shí)際調(diào)研了三種內(nèi)嵌方案自研從站協(xié)議棧純軟件跑在主控MCU上用帶EtherNet/IP能力的MCU或者通訊協(xié)處理器協(xié)議芯片/模塊如Hilscher netX、Anybus CompactCom通過(guò)SPI接口與主控板連接三種路徑的對(duì)比我整理了一張表方案開(kāi)發(fā)周期認(rèn)證成本維護(hù)難度適合場(chǎng)景自研協(xié)議棧6-12個(gè)月起步需要ODVA認(rèn)證CT測(cè)試很苛刻協(xié)議更新、bug修復(fù)靠團(tuán)隊(duì)自身出貨量很大的平臺(tái)級(jí)產(chǎn)品帶協(xié)議MCU3-6個(gè)月仍需認(rèn)證依賴芯片廠商協(xié)議庫(kù)質(zhì)量通訊需求固定的產(chǎn)品線協(xié)議芯片/模塊1-3個(gè)月模塊通過(guò)認(rèn)證即可驅(qū)動(dòng)器做集成測(cè)試協(xié)議棧維護(hù)由芯片廠商負(fù)責(zé)快速切入新通訊協(xié)議我最終選了協(xié)議芯片方案。原因很樸素團(tuán)隊(duì)最熟的是伺服控制算法和電機(jī)驅(qū)動(dòng)對(duì)CIP協(xié)議棧的細(xì)節(jié)了解有限ODVA的CT測(cè)試不是三五天能準(zhǔn)備的。用協(xié)議芯片相當(dāng)于把“協(xié)議棧翻譯”這個(gè)活外包給了芯片我只需要通過(guò)SPI把數(shù)據(jù)喂進(jìn)去/取出來(lái)就行。后面的事實(shí)證明這個(gè)選擇確實(shí)幫我們避開(kāi)了很多協(xié)議層面的坑。1.3 為什么Demo程序拿過(guò)來(lái)還要改造凡是接觸過(guò)這類通訊板的朋友都知道芯片廠商會(huì)給你一份完整的Demo工程。它通常跑在一套開(kāi)發(fā)板或者通用評(píng)估板上邏輯正確、協(xié)議流程完整。但你把它放到伺服驅(qū)動(dòng)器里會(huì)發(fā)現(xiàn)Demo里跑的是“演示數(shù)據(jù)”——輸入IO區(qū)放著遞增計(jì)數(shù)器、撥碼開(kāi)關(guān)狀態(tài)、LED閃爍控制輸出IO區(qū)放著VLAN設(shè)置、自定義心跳值。這些數(shù)據(jù)流和伺服的“狀態(tài)字、速度反饋、報(bào)警碼”沒(méi)有任何關(guān)系。所以這個(gè)項(xiàng)目的核心工作可以概括為三句話把Demo的通訊框架理解透SPI收發(fā)、事件處理、狀態(tài)機(jī)把虛假的演示數(shù)據(jù)流替換成伺服的實(shí)時(shí)控制數(shù)據(jù)把協(xié)議棧暴露出來(lái)的配置接口映射到伺服的實(shí)際參數(shù)這也是我在這個(gè)改造項(xiàng)目里踩過(guò)最多坑的地方。下面先講系統(tǒng)架構(gòu)再講具體的固件改造最后把我實(shí)際遇到的問(wèn)題和一個(gè)比較典型的排錯(cuò)鏈路完整寫(xiě)出來(lái)。2. 硬件鏈路與數(shù)據(jù)流主控MCU是怎么“看見(jiàn)”EtherNet/IP的2.1 SPI連通后的內(nèi)存映射關(guān)系先看硬件層面的連接方式。伺服主控MCU以STM32H7為例有多個(gè)SPI外設(shè)其中一個(gè)SPI接口連接著一塊獨(dú)立的通信小板。小板的核心是一顆EtherNet/IP協(xié)議芯片芯片外接一個(gè)標(biāo)準(zhǔn)RJ45網(wǎng)口小板上還有一顆負(fù)責(zé)上電時(shí)序、復(fù)位和配置的EEPROM。協(xié)議芯片對(duì)主控來(lái)說(shuō)看起來(lái)就是“一個(gè)帶中斷的SPI從設(shè)備”。主控MCU需要做的事情就是協(xié)議芯片上電后主控向它寫(xiě)入握手命令這里通常有個(gè)“握手寄存器”的超時(shí)等待機(jī)制建立通訊后主控周期性讀取輸入緩沖區(qū)寫(xiě)入輸出緩沖區(qū)當(dāng)PLC發(fā)來(lái)連接請(qǐng)求、參數(shù)讀寫(xiě)請(qǐng)求時(shí)協(xié)議芯片會(huì)拉高中斷引腳主控產(chǎn)生中斷并處理事件我用一個(gè)生活化的類比協(xié)議芯片像一位“外聘翻譯”。PLC說(shuō)英語(yǔ)EtherNet/IP報(bào)文伺服說(shuō)中文伺服控制字、狀態(tài)字翻譯已經(jīng)把英語(yǔ)語(yǔ)法全學(xué)會(huì)了你只需要把中文內(nèi)容寫(xiě)在紙條SPI數(shù)據(jù)區(qū)上遞給它它翻完會(huì)把英文回復(fù)再放回紙條上遞給你。你不需要學(xué)英語(yǔ)但你得知道紙條放哪個(gè)抽屜、對(duì)方什么時(shí)候取走、什么時(shí)候放回。2.2 雙向通路上的三類數(shù)據(jù)實(shí)際在SPI鏈路上跑的數(shù)據(jù)可以分成三類IO實(shí)時(shí)數(shù)據(jù)最重要的周期性數(shù)據(jù) 每次通訊周期主控MCU從協(xié)議芯片讀取Input緩沖區(qū)內(nèi)容PLC發(fā)給伺服的命令速度指令、控制字、目標(biāo)位置同時(shí)把Output緩沖區(qū)內(nèi)容寫(xiě)進(jìn)去伺服回給PLC的狀態(tài)實(shí)際速度、實(shí)際位置、狀態(tài)字、報(bào)警碼。這個(gè)數(shù)據(jù)在CIP里被組織成Assembly對(duì)象。對(duì)伺服來(lái)說(shuō)常用的做法是Output Assembly主控→協(xié)議芯片→PLC方向?qū)嶋H速度、實(shí)際位置、狀態(tài)字、報(bào)警碼Input AssemblyPLC→協(xié)議芯片→主控方向控制字、目標(biāo)速度/位置、模式字這里的“Input”“Output”很容易搞反我自己的記憶方法是站在PLC的角度看——PLC輸出的數(shù)據(jù)就是“Output”方向的數(shù)據(jù)PLC輸入的數(shù)據(jù)就是“Input”方向的數(shù)據(jù)。如果你站在自己的主控MCU角度看方向恰好相反所以做代碼對(duì)接時(shí)一定要先確認(rèn)你拿到的是哪個(gè)方向的緩沖區(qū)。顯式報(bào)文非周期性 這部分承載的是參數(shù)讀寫(xiě)請(qǐng)求。PLC通過(guò)MSG指令讀驅(qū)動(dòng)器的電子銘牌、運(yùn)行參數(shù)或修改增益。協(xié)議芯片把它解析之后以事件消息的形式通過(guò)SPI發(fā)給主控主控處理完后再把應(yīng)答數(shù)據(jù)寫(xiě)回SPI。配置管理數(shù)據(jù) 包含模塊信息讀取、MAC地址、IP設(shè)置、通道配置、通斷狀態(tài)等。一般在啟動(dòng)階段或調(diào)試階段使用。2.3 Demo初始化流程在做什么Demo程序的第一步不是直接開(kāi)始讀寫(xiě)IO數(shù)據(jù)而是先做一套“握手”。具體流程大概長(zhǎng)這樣主控上電拉高復(fù)位引腳等待協(xié)議芯片啟動(dòng)完成通常幾十毫秒到幾百毫秒主控通過(guò)SPI發(fā)送握手命令具體是向特定地址寫(xiě)入握手值然后回讀直到芯片回應(yīng)預(yù)期的值設(shè)置通訊參數(shù)如SPI驅(qū)動(dòng)速率、中斷極性、等待時(shí)間讀取通道信息確認(rèn)當(dāng)前固件加載的是EtherNet/IP從站驅(qū)動(dòng)注冊(cè)中斷進(jìn)入正常循環(huán)這部分代碼在Demo里往往是現(xiàn)成的我第一次做的時(shí)候以為不用動(dòng)。實(shí)際上它是最關(guān)鍵的“地基”——后續(xù)所有數(shù)據(jù)交換都依賴這套握手建立起來(lái)的通道。如果你的主控MCU SPI從模式配置有誤比如極性和相位不對(duì)可能連握手都完不成。我記得做硬件驗(yàn)證的時(shí)候邏輯分析儀抓SPI波形發(fā)現(xiàn)MISO一直在拉高全FF。排查了半天最后發(fā)現(xiàn)是協(xié)議芯片的SPI從設(shè)備要求CPOL1/CPHA1而Demo默認(rèn)代碼用CPOL0/CPHA0去通信。這就是典型的“Demo適配”第一課芯片手冊(cè)的時(shí)序圖一定要逐位對(duì)一遍別覺(jué)得供應(yīng)商給的代碼就一定是匹配你硬件設(shè)計(jì)的。3. Demo程序適配改造實(shí)操?gòu)膹S商標(biāo)桿到伺服產(chǎn)品3.1 差異化清單改哪些、留哪些、刪哪些拿到Demo程序后我建議先建一張改造對(duì)照表。別急著改代碼先把每一份源文件歸類這樣整個(gè)改造任務(wù)會(huì)清晰很多。文件/模塊處理方式改造內(nèi)容SPI底層驅(qū)動(dòng)保留引腳、DMA/中斷、時(shí)序參數(shù)按新板調(diào)整事件中斷處理保留僅調(diào)整與主控其他中斷的優(yōu)先級(jí)握手與初始化保留微調(diào)超時(shí)時(shí)間IO數(shù)據(jù)交換主循環(huán)改造數(shù)據(jù)源從Demo變量換成伺服狀態(tài)演示用途的界面/LED邏輯刪除改為伺服告警燈/狀態(tài)輸出Assembly配置改造長(zhǎng)度、實(shí)例編號(hào)、映射表信息對(duì)象改造廠商ID、產(chǎn)品代碼、序列號(hào)必須換成自己的參數(shù)對(duì)象映射新增或改造把PLC要讀寫(xiě)的參數(shù)接入伺服參數(shù)系統(tǒng)這樣分區(qū)之后改動(dòng)量很清晰。Demo程序的“通訊引擎”部分基本不用動(dòng)動(dòng)的是“業(yè)務(wù)數(shù)據(jù)”部分。這句話非常關(guān)鍵因?yàn)楹芏嗤履玫紻emo后東邊改一下西邊改一下最后SPI時(shí)序亂了通訊徹底癱瘓。我建議的原則是能不動(dòng)的地方絕對(duì)不動(dòng)能通過(guò)配置修改的就不改代碼。3.2 把示例計(jì)數(shù)器換成伺服狀態(tài)Assembly映射表的改造這一節(jié)是核心中的核心。EtherNet/IP的IO數(shù)據(jù)交換通過(guò)Assembly對(duì)象實(shí)例來(lái)承載。通俗講Assembly實(shí)例就是“一段定好格式的緩沖區(qū)”。PLC側(cè)配置IO大小的時(shí)候其實(shí)是在和這段緩沖區(qū)做對(duì)齊。Demo程序里Input Assembly可能長(zhǎng)這樣以我接觸過(guò)的某個(gè)示例為例字節(jié)0供應(yīng)商狀態(tài)字節(jié)字節(jié)1撥碼開(kāi)關(guān)狀態(tài)字節(jié)2~3內(nèi)部計(jì)數(shù)器每10ms加1字節(jié)4~7VLAN配置預(yù)留這些數(shù)據(jù)對(duì)伺服毫無(wú)意義。我在改造時(shí)把它換成了和伺服控制相關(guān)的結(jié)構(gòu)體對(duì)應(yīng)關(guān)系如下輸入端主控→PLC方向字節(jié)0~1驅(qū)動(dòng)器狀態(tài)字如0x0231表示運(yùn)行就緒字節(jié)2~5實(shí)際速度int32單位0.1 rpm或mm/min根據(jù)EDS定義字節(jié)6~9實(shí)際位置int32字節(jié)10~11報(bào)警代碼字節(jié)12~15扭矩反饋int16/int32輸出端PLC→主控方向字節(jié)0~1控制字bit0使能bit1復(fù)位故障bit2急停等字節(jié)2~5目標(biāo)速度字節(jié)6~9目標(biāo)位置字節(jié)10模式切換速度/位置/轉(zhuǎn)矩這里有個(gè)容易忽略的點(diǎn)字節(jié)序。CIP協(xié)議默認(rèn)使用Little-Endian小端字節(jié)序而很多伺服主控MCU在DSP/浮點(diǎn)運(yùn)算時(shí)習(xí)慣Big-Endian大端或者有獨(dú)立的字節(jié)序配置。改造時(shí)必須在賦值階段做字節(jié)序轉(zhuǎn)換否則你會(huì)發(fā)現(xiàn)PLC監(jiān)控到的速度反饋是百萬(wàn)級(jí)的隨機(jī)數(shù)。我這邊實(shí)際做法是在Assembly的讀寫(xiě)函數(shù)里邊統(tǒng)一加了一個(gè)字節(jié)序轉(zhuǎn)換工具函數(shù)。這樣通訊層看到的永遠(yuǎn)是標(biāo)準(zhǔn)Little-Endian伺服控制層內(nèi)部用自己習(xí)慣的字節(jié)序兩層通過(guò)透明轉(zhuǎn)換解耦。在聯(lián)調(diào)的時(shí)候先寫(xiě)一個(gè)固定值到Assembly里比如0x12345678然后看PLC側(cè)讀出來(lái)是不是0x12345678用來(lái)快速驗(yàn)證字節(jié)序是否正確這個(gè)辦法比看實(shí)際電機(jī)數(shù)據(jù)直觀得多。3.3 參數(shù)對(duì)象讀寫(xiě)讓PLC能改伺服增益IO數(shù)據(jù)只是“運(yùn)行時(shí)的血和肉”但PLC工程師還需要讀改參數(shù)。比如調(diào)試時(shí)在AB PanelView上寫(xiě)一個(gè)數(shù)值改動(dòng)伺服的PID比例增益或者讀取當(dāng)前報(bào)警歷史。這部分走的是EtherNet/IP的顯式報(bào)文通道。在CIP里伺服參數(shù)一般映射到Class 0x64Vendor Specific Object或Class 0x93/0x94之類的廠商自定義類。Demo程序里通常留了一個(gè)示例對(duì)象給你一個(gè)框架。改造任務(wù)就是把示例對(duì)象里那幾個(gè)假的屬性比如“示例參數(shù)1”“示例參數(shù)2”替換成真正讀伺服參數(shù)的接口。我當(dāng)時(shí)的做法是這樣定義一張伺服參數(shù)映射表把參數(shù)ID、名稱、讀寫(xiě)權(quán)限、數(shù)據(jù)長(zhǎng)度、最小值、最大值列清楚。在協(xié)議芯片的事件回調(diào)里當(dāng)收到CIP Set Attribute請(qǐng)求時(shí)先解析出請(qǐng)求的參數(shù)ID查映射表再調(diào)用伺服的參數(shù)寫(xiě)入函數(shù)。參數(shù)寫(xiě)入后返回成功狀態(tài)如果不允許寫(xiě)就返回CIP規(guī)定的錯(cuò)誤編碼比如0x0E表示屬性不支持。一個(gè)常見(jiàn)的坑是伺服參數(shù)中有一些是“運(yùn)行中禁止修改”的比如電機(jī)極對(duì)數(shù)、編碼器分辨率這類。在CIP顯式報(bào)文的處理函數(shù)里必須檢查伺服當(dāng)前狀態(tài)否則現(xiàn)場(chǎng)人員邊轉(zhuǎn)著電機(jī)邊改參數(shù)輕則報(bào)錯(cuò)重則設(shè)備異常。這種檢查邏輯Demo里沒(méi)有需要自己加。后來(lái)我們還做了一個(gè)“只讀/可寫(xiě)”屬性表讓PLC側(cè)編輯時(shí)能直觀看到灰色不可寫(xiě)項(xiàng)。這個(gè)功能一開(kāi)始沒(méi)人提但交付后現(xiàn)場(chǎng)工程師特別認(rèn)可因?yàn)槭〉袅舜罅俊盀槭裁磳?xiě)不進(jìn)去”的溝通。3.4 超時(shí)停機(jī)邏輯別讓伺服在通訊斷開(kāi)時(shí)還轉(zhuǎn)著幾乎每個(gè)通訊型伺服產(chǎn)品都有這個(gè)要求通訊超時(shí)后伺服要進(jìn)入安全狀態(tài)而不是保持最后一條指令繼續(xù)運(yùn)行。這在EtherNet/IP里對(duì)應(yīng)一個(gè)機(jī)制RPIRequested Packet Interval超時(shí)和Connection Timeout。協(xié)議芯片通常會(huì)提供一個(gè)超時(shí)回調(diào)或超時(shí)狀態(tài)位。Demo里一般只實(shí)現(xiàn)“超時(shí)后打印日志”或者“LED閃爍報(bào)警”。但對(duì)一個(gè)伺服驅(qū)動(dòng)器來(lái)說(shuō)超時(shí)后必須切斷使能讓電機(jī)停止輸出。我把超時(shí)處理接進(jìn)伺服主狀態(tài)機(jī)的高優(yōu)先級(jí)分支里if (timeout_flag 1) { drive_set_state(DRIVE_STATE_FAULT); drive_output_torque_enable(0); drive_status_word | STATUS_COMM_TIMEOUT; }這里特別注意一點(diǎn)從SPI讀出超時(shí)標(biāo)志到實(shí)際封鎖PWM輸出中間延遲必須控制在幾個(gè)毫秒以內(nèi)。如果你把超時(shí)判斷放在伺服環(huán)路比如1kHz外可能因?yàn)檎{(diào)度延遲讓電機(jī)多轉(zhuǎn)幾圈。我的做法是在SPI中斷里直接置一個(gè)原子標(biāo)志然后伺服環(huán)路每個(gè)周期檢查保證在下一個(gè)控制周期內(nèi)完成停機(jī)動(dòng)作。3.5 信息對(duì)象配置廠商ID、產(chǎn)品名、序列號(hào)這一塊看似細(xì)節(jié)實(shí)則非常重要。EtherNet/IP從站有一個(gè)Identity ObjectClass 0x01里面包含Vendor IDDevice TypeProduct CodeRevisionSerial NumberProduct Name這些信息在Demo里全是廠商的默認(rèn)值。適配改造時(shí)必須替換成自己公司的廠商ID在ODVA注冊(cè)和伺服型號(hào)信息。否則現(xiàn)場(chǎng)用RSLogix掃描網(wǎng)絡(luò)可能看到的是一臺(tái)評(píng)估板而不是你的伺服型號(hào)。另外EDS文件里的這些字段必須和固件內(nèi)實(shí)際值保持一致。我記得有一次就是因?yàn)镋DS里Product Code寫(xiě)了0x000A固件里寫(xiě)的是0x0009PLC掃描后連線一直失敗排查了很久才發(fā)現(xiàn)是這種“低級(jí)”不一致。所以后來(lái)我們?cè)诹慨a(chǎn)測(cè)試程序里加了一項(xiàng)自動(dòng)比對(duì)固件讀出的Identity信息要和標(biāo)書(shū)/EDS完全一致。4. 實(shí)測(cè)排錯(cuò)AB PLC掃描不到數(shù)據(jù)的一個(gè)根因4.1 現(xiàn)象設(shè)備在線、IO不交換改造完第一版固件我們拿AB的CompactLogix PLC做了聯(lián)調(diào)。硬件連接正常點(diǎn)擊RSLogix的“Go Online”能夠看到設(shè)備出現(xiàn)在網(wǎng)絡(luò)里設(shè)備名稱也正確顯示為我們的伺服型號(hào)。但建立IO連接后PLC側(cè)數(shù)據(jù)一直顯示為0且通訊樹(shù)里模塊狀態(tài)報(bào)“Connection Failure”。這里先說(shuō)說(shuō)我當(dāng)時(shí)的操作順序方便大家復(fù)現(xiàn)排查先用Wireshark在交換機(jī)鏡像口抓PLC到伺服之間的EtherNet/IP報(bào)文確認(rèn)PLC已經(jīng)發(fā)出了ForwardOpen請(qǐng)求查看伺服側(cè)是否回復(fù)了ForwardOpen響應(yīng)如果響應(yīng)是Error查看錯(cuò)誤代碼4.2 Wireshark抓包定位過(guò)程我抓包后看到了很典型的情況PLC發(fā)出了ForwardOpen報(bào)文目標(biāo)IP是我們的伺服但從站返回了一個(gè)Connection Manager錯(cuò)誤響應(yīng)錯(cuò)誤碼是0x0001Connection failure或者更具體一點(diǎn)指向“Packet Rate / Connection Size”這一類問(wèn)題。這里需要簡(jiǎn)單科普一下EtherNet/IP的連接建立過(guò)程PLC先廣播/單播一個(gè)ForwardOpen請(qǐng)求告訴從站“我要建立一個(gè)連接輸入數(shù)據(jù)X字節(jié)輸出數(shù)據(jù)Y字節(jié)RPI是10ms超時(shí)是100msAssembly實(shí)例ID是xx”。從站收到后會(huì)拿這個(gè)請(qǐng)求和自身固件里配置的Assembly對(duì)象實(shí)例長(zhǎng)度做匹配。如果從站覺(jué)得長(zhǎng)度對(duì)不上就會(huì)拒絕連接。我們的現(xiàn)象就是PLC側(cè)組態(tài)配置了32字節(jié)輸入/32字節(jié)輸出但從站側(cè)固件里對(duì)應(yīng)的Assembly實(shí)例長(zhǎng)度只有8字節(jié)。所以每次PLC發(fā)起ForwardOpen從站都直接拒絕。4.3 根因Assembly實(shí)例長(zhǎng)度與EDS不一致回看代碼發(fā)現(xiàn)Demo的Assembly實(shí)例結(jié)構(gòu)體定義是這樣的typedef struct { uint8_t status; uint8_t mode; uint16_t counter; } assembly_in_t;這確實(shí)只有4字節(jié)或8字節(jié)。而我改造時(shí)雖然寫(xiě)好了40多行的數(shù)據(jù)映射代碼但只改了映射函數(shù)忘了改Assembly實(shí)例的注冊(cè)表。也就是說(shuō)從站向協(xié)議棧注冊(cè)的IO長(zhǎng)度還是Demo默認(rèn)值但主控這邊一直以為要傳32字節(jié)。兩邊信息不對(duì)稱連接建立自然失敗。這個(gè)問(wèn)題的本質(zhì)是Demo里有兩處需要同步修改的地方實(shí)際的數(shù)據(jù)結(jié)構(gòu)體定義你往里面塞了多少字段協(xié)議棧的“裝配對(duì)象”配置向PLC廣播這個(gè)實(shí)例有多大4.4 修復(fù)與驗(yàn)證修復(fù)方法就是在Assembly配置里把長(zhǎng)度改為與EDS和結(jié)構(gòu)體一致。我梳理了一個(gè)三條對(duì)齊原則固件里Assembly結(jié)構(gòu)體長(zhǎng)度 固件向協(xié)議棧注冊(cè)的IO長(zhǎng)度固件向協(xié)議棧注冊(cè)的IO長(zhǎng)度 EDS文件里聲明的Input/Output大小三者不一致時(shí)以EDS文件為準(zhǔn)因?yàn)镻LC是按EDS組態(tài)的改完重新編譯燒錄PLC重新掃描IO連接幾秒鐘就建立起來(lái)了。之后用邏輯分析儀看SPI波形確認(rèn)周期IO數(shù)據(jù)在穩(wěn)定交換PLC側(cè)看到了實(shí)時(shí)的伺服狀態(tài)字。這里是第一次完整跑通了整個(gè)鏈路。這道坎過(guò)去以后我總結(jié)出一個(gè)調(diào)試經(jīng)驗(yàn)凡是IO數(shù)據(jù)建立不起來(lái)先別急著看SPI和伺服控制代碼第一件事就是核對(duì)Assembly實(shí)例長(zhǎng)度。這個(gè)排查鏈路特別快往往幾分鐘就能定位是不是“尺寸”問(wèn)題。另外如果固件和EDS是不同人維護(hù)的尤其容易出這種事。5. 現(xiàn)場(chǎng)穩(wěn)坑SPI布線、字節(jié)序、IP設(shè)置那些事后才懂的事5.1 字節(jié)序與數(shù)據(jù)對(duì)齊的細(xì)節(jié)前面提到了字節(jié)序這里展開(kāi)說(shuō)一下實(shí)際測(cè)試場(chǎng)景。伺服速度反饋是int32比如0x0001C200。如果字節(jié)序反了PLC讀到的可能是0x00C20100這種看起來(lái)毫無(wú)規(guī)律的數(shù)。工業(yè)現(xiàn)場(chǎng)很多人一看數(shù)據(jù)不對(duì)就說(shuō)“通訊有問(wèn)題”其實(shí)往往是字節(jié)序處理漏了。我們?cè)诠碳镒隽艘粋€(gè)統(tǒng)一策略所有跨平臺(tái)傳輸?shù)臄?shù)據(jù)統(tǒng)一切成Little-Endian并且寫(xiě)死了轉(zhuǎn)換宏。日后再接別的PLC或者換主控平臺(tái)這個(gè)策略可以復(fù)用。對(duì)于float類型的參數(shù)比如伺服增益建議按IEEE 754的字節(jié)序做轉(zhuǎn)換。很多協(xié)議棧支持“在對(duì)象定義里指定數(shù)據(jù)類型”但底層SPI搬運(yùn)的始終是裸字節(jié)轉(zhuǎn)換還得自己來(lái)。聯(lián)調(diào)的時(shí)候可以給PLC寫(xiě)一個(gè)浮點(diǎn)參數(shù)比如1.0然后PLC讀出來(lái)看符不符合預(yù)期。5.2 SPI速率與線纜地上的一點(diǎn)教訓(xùn)實(shí)驗(yàn)室階段SPI時(shí)鐘用10MHz跑得很穩(wěn)。到了現(xiàn)場(chǎng)出現(xiàn)偶發(fā)的通訊超時(shí)和參數(shù)讀失敗。檢查發(fā)現(xiàn)是連接主控板與通訊小板的那根排線太長(zhǎng)而且沒(méi)有和地線配合好。SPI和I2C的一個(gè)明顯區(qū)別是SPI沒(méi)有內(nèi)置的應(yīng)答機(jī)制主設(shè)備發(fā)出數(shù)據(jù)從設(shè)備沒(méi)有收到是不可能主動(dòng)告知的。如果信號(hào)線上有嚴(yán)重的反射或者干擾就可能搬運(yùn)到錯(cuò)誤數(shù)據(jù)而協(xié)議棧因?yàn)樾r?yàn)字段沒(méi)通過(guò)直接丟棄表現(xiàn)就是通訊超時(shí)或報(bào)文丟失。I2C雖然慢但有ACK機(jī)制至少能在協(xié)議層面感知到從設(shè)備是否應(yīng)答SPI要靠物理層設(shè)計(jì)和上層超時(shí)來(lái)兜底。我最后的處理是SPI時(shí)鐘從10MHz降到4MHz排線改短并且中間加GND隔離一信號(hào)線一地的蛇形走線方式在協(xié)議芯片的SPI片選和中斷引腳上并聯(lián)小電容濾波降速之后通訊穩(wěn)定性明顯改善。很多人誤以為SPI跑越快越好但在工業(yè)現(xiàn)場(chǎng)產(chǎn)品中穩(wěn)定大于速度。畢竟伺服通訊周期性數(shù)據(jù)也就幾十個(gè)字節(jié)4MHz完全夠用。5.3 MAC地址管理、EDS文件版本這些“小事”量產(chǎn)階段還有一個(gè)問(wèn)題容易被忽略MAC地址。協(xié)議芯片一般有全局唯一的MAC但如果每個(gè)模塊燒錄的固件版本不同或者你要求每個(gè)產(chǎn)品的IP地址由DHCP獲取MAC地址的唯一性就必須和固件燒錄流程綁定。我們最后在產(chǎn)線新增了一個(gè)工位讀取協(xié)議芯片的MAC和伺服本體序列號(hào)一并寫(xiě)入“出廠銘牌”數(shù)據(jù)庫(kù)同時(shí)把對(duì)應(yīng)的EDS版本號(hào)也記錄在案?,F(xiàn)場(chǎng)如果報(bào)通訊故障客服只需按序列號(hào)查庫(kù)就能知道這個(gè)驅(qū)動(dòng)器用的哪版EDS、哪版固件排查效率會(huì)高不少。另外EDS文件的版本管理也值得多說(shuō)一嘴。因?yàn)槭歉脑祉?xiàng)目固件迭代很快我們初期出現(xiàn)過(guò)一次EDS文件更新了但固件里Identity Object的Revision沒(méi)同步改導(dǎo)致PLC客戶端認(rèn)為設(shè)備固件版本過(guò)舊連接時(shí)提示版本不匹配。所以建議把“固件版本號(hào)”、“EDS版本號(hào)”、“CIP Revision”三方綁定在一起發(fā)布時(shí)一起更新。在這次適配改造過(guò)程中我最深的體會(huì)是Demo程序不是“拿來(lái)就能用”的它更像一張地圖讓你知道協(xié)議棧的接口在哪、數(shù)據(jù)流往哪走但真正讓它變成你自己的產(chǎn)品需要把業(yè)務(wù)邏輯一層層嵌進(jìn)去。伺服和EtherNet/IP的組合難點(diǎn)不在于SPI本身——SPI通訊是成熟的協(xié)議棧是現(xiàn)成的——而在于兩套完全不同的“世界觀”如何對(duì)齊一邊是電機(jī)的啟停和速度環(huán)另一邊是PLC的連接管理和數(shù)據(jù)塊交換。做這個(gè)項(xiàng)目之前我對(duì)“通訊協(xié)議”的理解更多停留在“收發(fā)字節(jié)”的層面做完之后才明白真正的通訊適配是數(shù)據(jù)語(yǔ)義的映射是把一個(gè)驅(qū)動(dòng)器的靈魂裝進(jìn)一個(gè)標(biāo)準(zhǔn)的殼里。希望這篇文章能幫同行的朋友少走一些彎路。