備通信選型:CAN總線(xiàn)可靠性的原理與SocketCAN實(shí)踐)
做批量設(shè)備開(kāi)發(fā)的工程師選通信方案時(shí)大概率都糾結(jié)過(guò)RS-485 便宜但主從輪詢(xún)效率低以太網(wǎng)帶寬高但布線(xiàn)成本和協(xié)議復(fù)雜度跟著漲無(wú)線(xiàn)省線(xiàn)束卻怕現(xiàn)場(chǎng)干擾。真正到了量產(chǎn)階段還要考慮每臺(tái)設(shè)備的裝配時(shí)間、售后服務(wù)能不能用統(tǒng)一工具排查。這個(gè)時(shí)候就會(huì)發(fā)現(xiàn)可選的方案其實(shí)沒(méi)有想象中那么多。我的判斷是如果一臺(tái)設(shè)備只有三五個(gè)傳感器串口和 RS-485 完全夠用但如果一臺(tái)設(shè)備上有十幾個(gè)甚至幾十個(gè)節(jié)點(diǎn)要求強(qiáng)干擾環(huán)境下不誤報(bào)、報(bào)警信息能實(shí)時(shí)到達(dá)、整機(jī)線(xiàn)束成本還能控制住CAN 總線(xiàn)是綜合工程風(fēng)險(xiǎn)最低的一條路。它不新也不花哨但它在批量設(shè)備里的價(jià)值恰恰是那些看起來(lái)更“高級(jí)”的通信方式給不了的。這篇文章不打算停留在“CAN 總線(xiàn)很可靠”這種正確廢話(huà)上。我會(huì)從協(xié)議原理、硬件連接、幀 ID 設(shè)計(jì)、SocketCAN 實(shí)操、現(xiàn)場(chǎng)排查幾個(gè)層面把“適合批量設(shè)備”這句話(huà)拆成可以落地的細(xì)節(jié)。讀完你會(huì)知道為什么選 CAN也知道在一臺(tái)幾十個(gè)節(jié)點(diǎn)的設(shè)備上怎么設(shè)計(jì)一套不容易出問(wèn)題的 CAN 通信方案。1. 批量設(shè)備通信選型先看這四件事批量設(shè)備有一個(gè)共同特征每一臺(tái)都在復(fù)用一個(gè)相對(duì)固定的硬件方案通信方案一旦定型后期改造成本遠(yuǎn)比單臺(tái)設(shè)備樣機(jī)要大。所以選型階段不能只看能不能跑通要看它能不能在生產(chǎn)、裝配、售后全鏈路里站住腳。我認(rèn)為以下四個(gè)維度最關(guān)鍵。第一是節(jié)點(diǎn)數(shù)量。一臺(tái)設(shè)備里往往同時(shí)存在主控板、電機(jī)驅(qū)動(dòng)器、溫度傳感器、IO 擴(kuò)展模塊等多個(gè)節(jié)點(diǎn)數(shù)據(jù)既有上行上報(bào)也有下行控制。通信協(xié)議必須天然支持多節(jié)點(diǎn)訪(fǎng)問(wèn)而不是靠主機(jī)挨個(gè)點(diǎn)名。第二是可靠性。車(chē)間里的變頻器、電機(jī)、開(kāi)關(guān)電源都會(huì)帶來(lái)電磁干擾偶發(fā)一幀錯(cuò)誤數(shù)據(jù)可能導(dǎo)致設(shè)備誤動(dòng)作這不是“重試一次”能解決的問(wèn)題。第三是實(shí)時(shí)性。急停、報(bào)警、位置同步這類(lèi)事件不能等主機(jī)輪詢(xún)到之后才處理。第四是成本與維護(hù)。線(xiàn)束、接插件、通信芯片、裝配工時(shí)、售后排查工具都會(huì)攤到每一臺(tái)設(shè)備上。從這四個(gè)維度回頭看RS-485 的典型工作模式是主從半雙工主機(jī)輪詢(xún)所有從機(jī)節(jié)點(diǎn)多了實(shí)時(shí)性迅速下降以太網(wǎng)能力強(qiáng)但交換機(jī)、連接器、線(xiàn)纜和協(xié)議棧成本都不低在批量設(shè)備里屬于“殺雞用牛刀”的典型CAN 總線(xiàn)正好落在中間多主通信、硬件優(yōu)先級(jí)仲裁、差分信號(hào)抗干擾、協(xié)議內(nèi)建錯(cuò)誤檢測(cè)和自動(dòng)重發(fā)節(jié)點(diǎn)容量和成本都適合批量設(shè)備。這里要強(qiáng)調(diào)一下CAN 總線(xiàn)并不是萬(wàn)能的。它不適合視頻流這類(lèi)需要大吞吐的業(yè)務(wù)也不適合動(dòng)不動(dòng)就要傳幾百米上公里的遠(yuǎn)程通信。批量設(shè)備場(chǎng)景里CAN 的適用邊界非常清晰中等距離、幾十個(gè)節(jié)點(diǎn)以?xún)?nèi)、可靠性要求高、總成本可控。選型最怕的不是選錯(cuò)而是沒(méi)想清楚自己的場(chǎng)景到底在哪一檔。2. CAN 總線(xiàn)基礎(chǔ)概念與核心原理2.1 從一個(gè)場(chǎng)景理解 CAN想象一套自動(dòng)化設(shè)備里有主控板、三個(gè)電機(jī)驅(qū)動(dòng)器、五個(gè)溫度傳感器、兩個(gè) IO 擴(kuò)展模塊。如果它們都掛在同一條通信總線(xiàn)上任何兩個(gè)節(jié)點(diǎn)之間都可以直接交換數(shù)據(jù)不需要一個(gè)中心節(jié)點(diǎn)轉(zhuǎn)發(fā)這就是“控制器局域網(wǎng)”的價(jià)值。CAN 是 Controller Area Network 的縮寫(xiě)最早由汽車(chē)電子領(lǐng)域推動(dòng)后來(lái)被 ISO 11898 標(biāo)準(zhǔn)化現(xiàn)在幾乎成了工業(yè)設(shè)備、醫(yī)療器械、工程機(jī)械里的默認(rèn)通信方式之一。它的設(shè)計(jì)目標(biāo)很明確在電磁干擾嚴(yán)重的環(huán)境里用盡量少的線(xiàn)束實(shí)現(xiàn)多個(gè)控制器之間可靠、實(shí)時(shí)的數(shù)據(jù)交換。理解 CAN 的每一層機(jī)制都要回到這個(gè)目標(biāo)上去。2.2 物理層差分信號(hào)為什么抗干擾CAN 的物理層用兩條線(xiàn)傳輸信號(hào)通常稱(chēng)為 CANH 和 CANL。數(shù)據(jù)不是靠某一條線(xiàn)上的絕對(duì)電壓高低來(lái)表示的而是靠?jī)蓷l線(xiàn)之間的電壓差。發(fā)送顯性位時(shí)CANH 被拉高、CANL 被拉低發(fā)送隱性位時(shí)兩條線(xiàn)都處于一個(gè)差不多的電平。這種差分傳輸方式的關(guān)鍵收益是共模抑制?,F(xiàn)場(chǎng)干擾通常同時(shí)作用在兩條線(xiàn)上比如電機(jī)啟動(dòng)瞬間在總線(xiàn)上感應(yīng)出共模電壓差分接收電路只關(guān)心兩線(xiàn)之差所以共模干擾很難直接變成錯(cuò)誤數(shù)據(jù)。相比單端信號(hào)CAN 在工業(yè)現(xiàn)場(chǎng)的抗干擾能力有原理層面的優(yōu)勢(shì)。另一個(gè)容易被忽略的好處是CAN 節(jié)點(diǎn)之間不強(qiáng)制要求共地這就給隔離設(shè)計(jì)留出了空間。2.3 數(shù)據(jù)鏈路層幀格式和仲裁機(jī)制CAN 協(xié)議的數(shù)據(jù)鏈路層定義了四種基本幀數(shù)據(jù)幀、遠(yuǎn)程幀、錯(cuò)誤幀、過(guò)載幀。最常用的是數(shù)據(jù)幀它由幀起始、仲裁段、控制段、數(shù)據(jù)段、CRC 段、ACK 段和幀結(jié)束組成。標(biāo)準(zhǔn)幀的標(biāo)識(shí)符是 11 位擴(kuò)展幀是 29 位批量設(shè)備里絕大多數(shù)場(chǎng)景用標(biāo)準(zhǔn)幀就夠了。仲裁機(jī)制是 CAN 最值得稱(chēng)道的設(shè)計(jì)??偩€(xiàn)上有多個(gè)節(jié)點(diǎn)同時(shí)發(fā)送時(shí)每個(gè)節(jié)點(diǎn)在發(fā)送仲裁段時(shí)同時(shí)監(jiān)聽(tīng)總線(xiàn)。顯性位能覆蓋隱性位所以標(biāo)識(shí)符數(shù)值越小的幀優(yōu)先級(jí)越高。發(fā)送過(guò)程中發(fā)現(xiàn)總線(xiàn)狀態(tài)和自己的發(fā)送不一致就自動(dòng)轉(zhuǎn)入接收狀態(tài)讓更高優(yōu)先級(jí)的幀繼續(xù)發(fā)送。整個(gè)過(guò)程不破壞任何一幀數(shù)據(jù)所以叫“非破壞性位仲裁”。這意味著什么在批量設(shè)備里最容易想到的應(yīng)用就是報(bào)警優(yōu)先于普通數(shù)據(jù)。急停信號(hào)、故障狀態(tài)可以分配較小的 ID硬件保證它在任何時(shí)刻都能優(yōu)先搶到總線(xiàn)不需要主機(jī)做調(diào)度。這對(duì)實(shí)時(shí)性要求高的設(shè)備來(lái)說(shuō)是 RS-485 很難替代的能力。2.4 錯(cuò)誤處理和錯(cuò)誤幀是怎么回事CAN 協(xié)議內(nèi)建了嚴(yán)格的錯(cuò)誤檢測(cè)機(jī)制包括位錯(cuò)誤、填充錯(cuò)誤、CRC 錯(cuò)誤、格式錯(cuò)誤、應(yīng)答錯(cuò)誤。任何一個(gè)節(jié)點(diǎn)發(fā)現(xiàn)錯(cuò)誤都會(huì)立即發(fā)送錯(cuò)誤幀通知總線(xiàn)上所有節(jié)點(diǎn)當(dāng)前數(shù)據(jù)無(wú)效發(fā)送節(jié)點(diǎn)隨后會(huì)自動(dòng)重發(fā)。這個(gè)機(jī)制讓 CAN 在單條幀數(shù)據(jù)出錯(cuò)時(shí)不會(huì)把錯(cuò)誤數(shù)據(jù)“靜默吞掉”而是讓整個(gè)總線(xiàn)都知道并恢復(fù)。理解錯(cuò)誤幀不能只看字面意思。錯(cuò)誤幀不是軟件里的“異常拋出”而是 CAN 控制器為了保證總線(xiàn)一致性主動(dòng)發(fā)送的 6 個(gè)顯性位。發(fā)送錯(cuò)誤幀的節(jié)點(diǎn)會(huì)被錯(cuò)誤計(jì)數(shù)器記錄主動(dòng)錯(cuò)誤狀態(tài)和被動(dòng)錯(cuò)誤狀態(tài)的恢復(fù)邏輯也不同。當(dāng)錯(cuò)誤計(jì)數(shù)累計(jì)超過(guò) 255節(jié)點(diǎn)會(huì)進(jìn)入 Bus-Off 狀態(tài)完全脫離總線(xiàn)。這個(gè)機(jī)制在批量設(shè)備調(diào)試時(shí)非常重要后面排查章節(jié)會(huì)專(zhuān)門(mén)講。3. 為什么批量設(shè)備場(chǎng)景更值得選 CAN這一節(jié)用對(duì)比的方式說(shuō)明 CAN 的優(yōu)勢(shì)。下表不是某個(gè)芯片手冊(cè)的全文照搬是工程里常見(jiàn)的參考值具體以實(shí)際器件為準(zhǔn)。對(duì)比項(xiàng)RS-485CAN以太網(wǎng)通信方式主從半雙工多主廣播多節(jié)點(diǎn)交換單總線(xiàn)節(jié)點(diǎn)數(shù)典型 32 個(gè)擴(kuò)展需中繼幾十個(gè)常用受電氣和負(fù)載限制受交換機(jī)端口限制實(shí)時(shí)性依賴(lài)主機(jī)輪詢(xún)硬件優(yōu)先級(jí)仲裁事件驅(qū)動(dòng)依賴(lài)協(xié)議和交換機(jī)抗干擾差分信號(hào)較好差分信號(hào) 嚴(yán)格錯(cuò)誤檢測(cè)變壓器隔離但地環(huán)路風(fēng)險(xiǎn)錯(cuò)誤恢復(fù)需要應(yīng)用層實(shí)現(xiàn)協(xié)議內(nèi)建檢測(cè)和自動(dòng)重發(fā)TCP 可靠 / UDP 需要應(yīng)用層物理成本低中低較高典型傳輸距離約 1200m115kbps約 40m1Mbps距離降低波特率可延長(zhǎng)單段銅纜約 100m3.1 多主通信省掉主機(jī)輪詢(xún)RS-485 最常見(jiàn)的模型是主機(jī)輪詢(xún)從機(jī)從機(jī)不能主動(dòng)上報(bào)。假設(shè)一條總線(xiàn)上掛了 20 個(gè)從機(jī)每個(gè)從機(jī)輪詢(xún)一次需要 5ms全部輪詢(xún)完就是 100ms。一旦某個(gè)從機(jī)通信異常需要重試整條總線(xiàn)的輪詢(xún)周期會(huì)進(jìn)一步拉長(zhǎng)。這對(duì)周期性采集可能夠用但對(duì)突發(fā)報(bào)警不夠。CAN 的多主模型里任何節(jié)點(diǎn)檢測(cè)到事件都可以立刻嘗試發(fā)送由硬件仲裁決定先后。主機(jī)不再承擔(dān)“點(diǎn)名”工作每個(gè)節(jié)點(diǎn)按自己的節(jié)奏工作。批量設(shè)備里最常見(jiàn)的收益是傳感器節(jié)點(diǎn)可以自主上報(bào)異常主控板不需要在一個(gè)周期內(nèi)“照顧”到所有節(jié)點(diǎn)系統(tǒng)整體響應(yīng)更快主機(jī)軟件也更簡(jiǎn)單。3.2 硬件仲裁保證確定性很多工程師第一次聽(tīng)說(shuō) CAN 仲裁時(shí)會(huì)擔(dān)心如果多個(gè)節(jié)點(diǎn)同時(shí)發(fā)送怎么辦CAN 控制器用逐位仲裁解決這個(gè)問(wèn)題優(yōu)先級(jí)高的幀先走優(yōu)先級(jí)低的幀自動(dòng)等待。這個(gè)仲裁過(guò)程完全由硬件完成時(shí)間開(kāi)銷(xiāo)可以忽略也不需要軟件鎖。對(duì)批量設(shè)備來(lái)說(shuō)確定性比絕對(duì)帶寬更重要。報(bào)警幀能不能在 1ms 內(nèi)搶到總線(xiàn)取決于它的 ID 設(shè)計(jì)而不取決于當(dāng)前總線(xiàn)上有多少數(shù)據(jù)。設(shè)計(jì)好 ID 規(guī)劃之后高優(yōu)先級(jí)報(bào)文的延遲上限是可以估算出來(lái)的這對(duì)有安全認(rèn)證要求的設(shè)備非常關(guān)鍵。3.3 抗干擾設(shè)計(jì)與錯(cuò)誤恢復(fù)批量設(shè)備不是實(shí)驗(yàn)室環(huán)境現(xiàn)場(chǎng)有變頻器、繼電器、電機(jī)啟動(dòng)電流任何通信方案都要面對(duì)電磁干擾。CAN 的差分物理層提供了基礎(chǔ)抗擾能力協(xié)議層的錯(cuò)誤檢測(cè)和自動(dòng)重發(fā)又補(bǔ)上了可靠性最后一環(huán)。更重要的是CAN 的錯(cuò)誤處理不是“發(fā)現(xiàn)問(wèn)題就讓?xiě)?yīng)用層處理”。錯(cuò)誤幀、錯(cuò)誤計(jì)數(shù)器、Bus-Off 這些機(jī)制讓故障能夠被識(shí)別、記錄、恢復(fù)。在批量設(shè)備里這意味著售后人員可以通過(guò)總線(xiàn)統(tǒng)計(jì)數(shù)據(jù)判斷是哪個(gè)節(jié)點(diǎn)出了問(wèn)題而不是靠換板子碰運(yùn)氣。3.4 成本可控且生態(tài)成熟CAN 控制器幾乎是所有主流 MCU 的標(biāo)配外設(shè)很多單片機(jī)內(nèi)部已經(jīng)集成硬件上只需要一個(gè) CAN 收發(fā)器和兩個(gè)終端電阻。線(xiàn)束方面一條雙絞線(xiàn)就能掛幾十個(gè)節(jié)點(diǎn)比點(diǎn)對(duì)點(diǎn)的通信方式省線(xiàn)。協(xié)議棧方面SocketCAN、CANopen、J1939 都有大量現(xiàn)成工具鏈開(kāi)發(fā)調(diào)試成本并不高。在批量設(shè)備里成本不是只看一顆芯片多少錢(qián)還要看裝配工時(shí)、售后排障成本和整機(jī)線(xiàn)束。CAN 在這幾項(xiàng)里都有優(yōu)勢(shì)尤其是在節(jié)點(diǎn)多的設(shè)備上省下來(lái)的線(xiàn)束和接插件往往比通信芯片本身更可觀。3.5 哪些場(chǎng)景不適合 CAN任何一個(gè)技術(shù)都有自己的邊界。CAN 的帶寬上限是 1Mbps多數(shù)工業(yè)設(shè)備常用 125k/250k/500k傳輸大量日志或固件升級(jí)包時(shí)會(huì)明顯吃力。距離方面雖然降低波特率可以延長(zhǎng)到幾百米甚至上公里但超過(guò)這個(gè)范圍光纖或無(wú)線(xiàn)更合適。另外CAN 總線(xiàn)是共享介質(zhì)如果設(shè)備節(jié)點(diǎn)數(shù)超過(guò)收發(fā)器驅(qū)動(dòng)能力或者總線(xiàn)負(fù)載率太高也會(huì)出問(wèn)題。所以“CAN 總線(xiàn)適合批量設(shè)備”更準(zhǔn)確的說(shuō)法是對(duì)于節(jié)點(diǎn)多、距離中等、可靠性要求高、成本敏感的批量設(shè)備CAN 在工程上往往是綜合代價(jià)最小的選擇。它解決的不是“能不能通信”而是“在復(fù)雜環(huán)境里能不能穩(wěn)定通信、能不能快速排查、能不能控制住量產(chǎn)成本”。4. CAN 硬件連接要點(diǎn)終端電阻、split 電容與共模干擾很多開(kāi)發(fā)者在開(kāi)發(fā)板上把 CANH、CANL 兩根線(xiàn)一對(duì)程序就能跑通于是以為硬件連接很簡(jiǎn)單。實(shí)際到了批量設(shè)備現(xiàn)場(chǎng)硬件連接問(wèn)題占了 CAN 故障的很大比例。這一節(jié)講最容易丟分的幾個(gè)點(diǎn)。4.1 終端電阻不是可有可無(wú)CAN 總線(xiàn)要求在物理鏈路的兩端各接一個(gè) 120Ω 終端電阻目的是匹配傳輸線(xiàn)阻抗、抑制信號(hào)反射。注意“兩端”指的是總線(xiàn)拓?fù)涞淖钸h(yuǎn)兩端不是每個(gè)節(jié)點(diǎn)都接 120Ω。如果總線(xiàn)上只接了一個(gè)終端電阻或者一個(gè)都沒(méi)接信號(hào)會(huì)在線(xiàn)纜末端反射導(dǎo)致波形畸變波特率越高問(wèn)題越明顯。批量設(shè)備裝配時(shí)最怕這種情況樣機(jī)階段兩塊板子離得近沒(méi)接終端電阻也能跑一到量產(chǎn)機(jī)柜里線(xiàn)纜變長(zhǎng)通信偶發(fā)失敗。排查第一步就應(yīng)該確認(rèn)兩端 120Ω 是否都在位。測(cè)量方法也簡(jiǎn)單設(shè)備斷電后用萬(wàn)用表在總線(xiàn)任意一端量 CANH 和 CANL 之間的電阻正常應(yīng)該接近 60Ω因?yàn)閮蓚€(gè) 120Ω 并聯(lián)。4.2 split 終端與共模電容的作用在電磁干擾比較強(qiáng)的現(xiàn)場(chǎng)只用兩個(gè) 120Ω 終端電阻還不夠。常見(jiàn)做法是用“分裂終端”把每個(gè)終端電阻拆成兩個(gè) 60Ω 串聯(lián)中間抽頭通過(guò)一個(gè)小電容接到外殼或大地形成類(lèi)似這樣的接法CANH ── 60Ω ──┬── 60Ω ── CANL │ 4.7nF 電容 │ 外殼/大地這就是很多工程師搜的“CAN 總線(xiàn) split”和“CAN 總線(xiàn)與外殼加電容”。它的作用是給高頻共模干擾提供一個(gè)低阻抗的回流路徑讓共模噪聲直接泄放到機(jī)殼或大地而不是進(jìn)入收發(fā)器內(nèi)部影響差模判決。電容容值常用 4.7nF 左右耐壓要選得足夠高具體值需要根據(jù)現(xiàn)場(chǎng)的共模噪聲頻率和收發(fā)器要求調(diào)整。需要特別提醒加電容、接外殼、接大地都涉及電氣安全不同現(xiàn)場(chǎng)的接地規(guī)范不一樣。改動(dòng)之前必須斷電確認(rèn)機(jī)殼接地方式和安全要求不能想當(dāng)然直接接。批量設(shè)備做 EMC 整改時(shí)split 終端是一種常見(jiàn)手段但它不是唯一手段也不是所有場(chǎng)景都必須加。4.3 布線(xiàn)細(xì)節(jié)決定量產(chǎn)穩(wěn)定性CAN 總線(xiàn)推薦使用特性阻抗約 120Ω 的雙絞線(xiàn)這樣終端電阻才能起到匹配作用。線(xiàn)纜要盡量避開(kāi)變頻器輸出線(xiàn)和動(dòng)力電纜如果避免不了交叉走線(xiàn)優(yōu)于長(zhǎng)距離平行走線(xiàn)。總線(xiàn)上的分支線(xiàn)要盡量短分支過(guò)長(zhǎng)會(huì)造成阻抗不連續(xù)和反射CAN 協(xié)議允許的“短樁”通常建議在厘米級(jí)到幾十厘米級(jí)具體要按波特率控制。另外批量設(shè)備里多個(gè)節(jié)點(diǎn)如果供電來(lái)自不同電源節(jié)點(diǎn)之間可能存在地電位差。這種情況下更推薦使用帶隔離的 CAN 收發(fā)器避免地環(huán)路電流影響通信。隔離設(shè)計(jì)在成本上會(huì)有增加但相對(duì)售后排障成本通常是值得的。5. 批量設(shè)備 CAN 網(wǎng)絡(luò)協(xié)議設(shè)計(jì)硬件連接穩(wěn)定只是基礎(chǔ)批量設(shè)備能不能可靠工作很大程度上取決于通信協(xié)議設(shè)計(jì)。很多項(xiàng)目失敗不是 CAN 控制器跑不起來(lái)而是幀 ID 隨意分配、報(bào)文格式每塊板子各寫(xiě)各的聯(lián)調(diào)時(shí)才發(fā)現(xiàn)對(duì)不上。這一節(jié)聚焦最核心的三件事幀 ID 分配、數(shù)據(jù)段編碼、心跳與生命周期管理。5.1 幀 ID 分配先定規(guī)則再寫(xiě)代碼批量設(shè)備里幀 ID 就是設(shè)備之間的“接口文檔”不能隨手寫(xiě)。建議按功能組劃分把優(yōu)先級(jí)和功能語(yǔ)義結(jié)合起來(lái)。例如0x001 - 0x01F 急停、報(bào)警、故障信息最高優(yōu)先級(jí) 0x100 - 0x17F 傳感器數(shù)據(jù)上報(bào) 0x200 - 0x27F 控制指令下發(fā) 0x600 - 0x6FF 節(jié)點(diǎn)心跳與狀態(tài) 0x700 - 0x7FF 診斷、參數(shù)讀寫(xiě)為什么急停和報(bào)警要放在最小 ID 段因?yàn)?CAN 仲裁時(shí) ID 越小優(yōu)先級(jí)越高。把最緊急的報(bào)文放在最低段硬件就能保證它搶先占用總線(xiàn)不需要軟件調(diào)度。批量設(shè)備中這張 ID 分配表本身就是團(tuán)隊(duì)協(xié)作的基礎(chǔ)所有開(kāi)發(fā)板必須按同一張表實(shí)施。5.2 數(shù)據(jù)段編碼讓報(bào)文含義可讀CAN 數(shù)據(jù)幀最多攜帶 8 字節(jié)數(shù)據(jù)設(shè)計(jì)時(shí)要明確每個(gè)字節(jié)的含義。下面是一個(gè)批量設(shè)備節(jié)點(diǎn)狀態(tài)報(bào)文的示例字段 長(zhǎng)度 說(shuō)明 設(shè)備類(lèi)型 1字節(jié) 0x01 電機(jī) 0x02 溫度 0x03 IO 設(shè)備編號(hào) 1字節(jié) 0x00-0xFE0xFF 表示廣播 業(yè)務(wù)數(shù)據(jù) 4字節(jié) 按設(shè)備類(lèi)型定義例如溫度值放大 10 倍 狀態(tài)位 1字節(jié) bit0 在線(xiàn) bit1 故障 bit2 告警 預(yù)留 1字節(jié) 默認(rèn) 0x00這樣的編碼規(guī)則清晰也方便用 DBC 文件統(tǒng)一管理。DBC 是 CAN 報(bào)文的行業(yè)標(biāo)準(zhǔn)描述格式批量設(shè)備項(xiàng)目里建議從一開(kāi)始就用 DBC 或類(lèi)似的自動(dòng)化工具管理協(xié)議而不是靠 Excel 和口頭約定。VERSION NS_ : BS_: BU_: Vehicle MotorCtrl BO_ 256 MotorCtrl: 8 MotorCtrl SG_ Speed : 0|161 (1,0) [0|6000] rpm Vehicle SG_ Temp : 16|81 (1,-40) [-40|200] degC Vehicle BO_ 512 Vehicle: 8 Vehicle SG_ Heartbeat : 0|81 (1,0) [0|255] MotorCtrl上面是一個(gè)簡(jiǎn)化示例省略了符號(hào)定義段。實(shí)際項(xiàng)目可以用 CANdb 或開(kāi)源工具編輯 DBC后續(xù)生成解析代碼、做測(cè)試腳本都方便很多。5.3 心跳與上線(xiàn)管理批量設(shè)備最怕節(jié)點(diǎn)“靜默故障”傳感器不報(bào)數(shù)了主控板還以為它正常。解決辦法是每個(gè)節(jié)點(diǎn)周期性發(fā)送心跳幀主機(jī)在一定時(shí)間內(nèi)沒(méi)收到某個(gè)節(jié)點(diǎn)的心跳就判定該節(jié)點(diǎn)離線(xiàn)并告警。心跳周期要按節(jié)點(diǎn)數(shù)量和總線(xiàn)負(fù)載設(shè)計(jì)比如 1 秒一次50 個(gè)節(jié)點(diǎn)就是每秒 50 幀占用很小。除了心跳節(jié)點(diǎn)上線(xiàn)時(shí)最好主動(dòng)上報(bào)一次配置信息包括程序版本、節(jié)點(diǎn)類(lèi)型、節(jié)點(diǎn)編號(hào)。批量設(shè)備產(chǎn)線(xiàn)測(cè)試和售后排查時(shí)這套信息能省很多時(shí)間。如果程序版本不一致調(diào)試人員通過(guò)一幀上報(bào)就能定位不需要逐臺(tái)拆機(jī)看屏幕。5.4 波特率與總線(xiàn)負(fù)載率CAN 總線(xiàn)常用波特率有 125k、250k、500k 等。波特率越高單位時(shí)間能傳的數(shù)據(jù)越多但線(xiàn)纜長(zhǎng)度要求越短對(duì)終端匹配和布線(xiàn)要求也越高。批量設(shè)備要根據(jù)最遠(yuǎn)傳輸距離和通信數(shù)據(jù)量綜合選擇。這里有個(gè)容易忽略的指標(biāo)總線(xiàn)負(fù)載率。負(fù)載率可以簡(jiǎn)單理解為單位時(shí)間內(nèi)總線(xiàn)上實(shí)際傳輸?shù)奈粩?shù)與鏈路容量的比值。工程建議盡量控制總線(xiàn)負(fù)載率在 30% 到 50% 以下否則大量周期報(bào)文擠占總線(xiàn)后突發(fā)的高優(yōu)先級(jí)報(bào)警反而可能延遲。批量設(shè)備里如果發(fā)現(xiàn)總線(xiàn)越來(lái)越忙優(yōu)先優(yōu)化周期上報(bào)頻率而不是盲目升級(jí)波特率。6. 完整示例Linux SocketCAN 批量設(shè)備通信批量設(shè)備量產(chǎn)之后產(chǎn)線(xiàn)測(cè)試工具、售后診斷工具經(jīng)常跑在 Linux 主機(jī)或嵌入式主板上。SocketCAN 是 Linux 內(nèi)核自帶的 CAN 支持方案本文用一套最小可跑通的流程演示發(fā)送、接收、多節(jié)點(diǎn)模擬。6.1 環(huán)境準(zhǔn)備操作系統(tǒng)推薦 Ubuntu 或 Debian 等主流發(fā)行版也可以使用帶 CAN 驅(qū)動(dòng)的嵌入式 Linux。先安裝工具鏈sudo apt-get update sudo apt-get install -y can-utils sudo pip3 install python-cancan-utils 提供cansend、candump等命令行工具python-can 是 Python 操作 CAN 的常用庫(kù)。如果只用 C 語(yǔ)言則不需要 python-can。6.2 啟動(dòng) can0 接口假設(shè)當(dāng)前環(huán)境有 CAN 控制器加載內(nèi)核模塊并啟動(dòng)接口sudo modprobe can sudo modprobe can_raw sudo ip link set can0 up type can bitrate 500000在實(shí)際嵌入式平臺(tái)上CAN 控制器驅(qū)動(dòng)可能已經(jīng)加載modprobe這步可以跳過(guò)。查看接口狀態(tài)ip -details -statistics link show can0如果輸出里能看到can state ERROR-ACTIVE和bitrate 500000說(shuō)明接口啟動(dòng)成功。如果本機(jī)沒(méi)有真實(shí) CAN 硬件可以用虛擬接口驗(yàn)證代碼邏輯sudo modprobe vcan sudo ip link add dev vcan0 type vcan sudo ip link set vcan0 upvcan 是內(nèi)核提供的虛擬 CAN 接口非常適合在沒(méi)有硬件的情況下學(xué)習(xí) SocketCAN 和調(diào)試協(xié)議設(shè)計(jì)但不能驗(yàn)證電平、終端電阻和抗干擾。6.3 Python 發(fā)送與接收示例下面的腳本演示發(fā)送一幀數(shù)據(jù)并持續(xù)接收適用于上位機(jī)測(cè)試工具#!/usr/bin/env python3 # 文件路徑can_send_recv.py import can def main(): bus can.interface.Bus(bustypesocketcan, channelcan0) # 發(fā)送一幀標(biāo)準(zhǔn)幀ID 0x1018字節(jié) msg can.Message( arbitration_id0x101, data[0x02, 0x01, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00], is_extended_idFalse ) bus.send(msg) print(fsend: 0x{msg.arbitration_id:03X} [{msg.dlc}] {msg.data.hex()}) try: with bus: for recv_msg in bus: print(frecv: 0x{recv_msg.arbitration_id:03X} [{recv_msg.dlc}] {recv_msg.data.hex()}) except KeyboardInterrupt: pass if __name__ __main__: main()注意 SocketCAN 的波特率由ip link配置python-can 的Bus參數(shù)里不需要也不建議傳bitrate避免版本差異導(dǎo)致報(bào)錯(cuò)。運(yùn)行前先用cansend can0 101#0201001000010000手動(dòng)發(fā)一幀確認(rèn)接口正常。6.4 用 C 語(yǔ)言寫(xiě)最小收發(fā)程序量產(chǎn)項(xiàng)目里很多嵌入式主控和測(cè)試工裝會(huì)使用 C 語(yǔ)言。下面是一個(gè)最小可編譯的 SocketCAN 發(fā)送程序// 文件路徑can_demo.c #include stdio.h #include string.h #include unistd.h #include sys/socket.h #include net/if.h #include sys/ioctl.h #include linux/can.h #include linux/can/raw.h int main(void) { int s socket(PF_CAN, SOCK_RAW, CAN_RAW); if (s 0) { perror(socket); return 1; } struct ifreq ifr; memset(ifr, 0, sizeof(ifr)); strcpy(ifr.ifr_name, can0); if (ioctl(s, SIOCGIFINDEX, ifr) 0) { perror(ioctl); close(s); return 1; } struct sockaddr_can addr; memset(addr, 0, sizeof(addr)); addr.can_family AF_CAN; addr.can_ifindex ifr.ifr_ifindex; if (bind(s, (struct sockaddr *)addr, sizeof(addr)) 0) { perror(bind); close(s); return 1; } struct can_frame frame; memset(frame, 0, sizeof(frame)); frame.can_id 0x101; frame.can_dlc 4; frame.data[0] 0x02; frame.data[1] 0x01; frame.data[2] 0x10; frame.data[3] 0x01; if (write(s, frame, sizeof(frame)) ! sizeof(frame)) { perror(write); close(s); return 1; } printf(send ok: 0x%03X\n, frame.can_id); close(s); return 0; }編譯運(yùn)行g(shù)cc -o can_demo can_demo.c ./can_demo在另一終端用candump can0抓包可以看到這幀數(shù)據(jù)。要注意代碼里frame.can_id 0x101默認(rèn)是標(biāo)準(zhǔn)幀如果要用擴(kuò)展幀需要設(shè)置frame.can_id | CAN_EFF_FLAG這里不展開(kāi)。6.5 模擬多個(gè)批量節(jié)點(diǎn)上報(bào)批量設(shè)備的特點(diǎn)是節(jié)點(diǎn)多、周期上報(bào)??梢杂镁€(xiàn)程方式在虛擬接口上模擬多個(gè)節(jié)點(diǎn)驗(yàn)證主機(jī)端的接收邏輯#!/usr/bin/env python3 # 文件路徑simulate_nodes.py import can import time import threading def node_loop(node_id, interval): bus can.interface.Bus(bustypesocketcan, channelvcan0) arb_id 0x100 node_id while True: data [node_id, 0x00, 0x00, int(time.time()) 0xFF, 0x00, 0x00, 0x00, 0x00] bus.send(can.Message(arbitration_idarb_id, datadata)) time.sleep(interval) if __name__ __main__: for i in range(1, 4): threading.Thread(targetnode_loop, args(i, 0.05), daemonTrue).start() time.sleep(10)這個(gè)腳本模擬 3 個(gè)節(jié)點(diǎn)分別用 ID 0x101 到 0x103 每 50ms 上報(bào)一幀。真實(shí)項(xiàng)目中可以把node_loop換成具體業(yè)務(wù)邏輯比如溫度采樣、電機(jī)狀態(tài)上報(bào)。多節(jié)點(diǎn)架構(gòu)跑通之后再把協(xié)議換成 DBC 定義的信號(hào)就能和產(chǎn)線(xiàn)測(cè)試工具聯(lián)調(diào)。7. 運(yùn)行結(jié)果與效果驗(yàn)證通信程序?qū)懲瓴凰憬Y(jié)束要驗(yàn)證數(shù)據(jù)是否正確、總線(xiàn)有沒(méi)有錯(cuò)誤。先看抓包結(jié)果。在接收終端執(zhí)行candump can0預(yù)期輸出類(lèi)似can0 101 [8] 02 01 00 10 00 01 00 00 can0 102 [8] 02 02 00 20 00 01 00 00 can0 103 [8] 02 03 00 30 00 01 00 00第一列是接口名第二列是幀 ID第三列是數(shù)據(jù)長(zhǎng)度后面是數(shù)據(jù)字節(jié)。如果不加參數(shù)candump 默認(rèn)不顯示錯(cuò)誤幀。要查看錯(cuò)誤幀可以加-ecandump -e can0看到錯(cuò)誤幀輸出時(shí)重點(diǎn)看錯(cuò)誤幀來(lái)自哪個(gè)節(jié)點(diǎn)、錯(cuò)誤類(lèi)型是什么。SocketCAN 下可以用ip -details -statistics link show can0查看接口統(tǒng)計(jì)信息ip -details -statistics link show can0輸出里可以看到 CAN 控制器的工作狀態(tài)、波特率、采樣點(diǎn)以及收發(fā)錯(cuò)誤計(jì)數(shù)。正常通信時(shí)錯(cuò)誤計(jì)數(shù)應(yīng)該穩(wěn)定為 0 或非常低。如果發(fā)現(xiàn)RX errors、TX errors或bus error持續(xù)增長(zhǎng)說(shuō)明硬件鏈路或現(xiàn)場(chǎng)電磁環(huán)境有問(wèn)題應(yīng)該按下一節(jié)的排查思路處理。判斷通信是否成功的標(biāo)準(zhǔn)有三條。第一預(yù)期 ID 的報(bào)文能持續(xù)收到數(shù)據(jù)內(nèi)容符合協(xié)議編碼。第二總線(xiàn)錯(cuò)誤計(jì)數(shù)不增長(zhǎng)或保持在低位。第三高優(yōu)先級(jí)幀能夠搶占總線(xiàn)低優(yōu)先級(jí)幀不會(huì)長(zhǎng)期“餓死”。在批量設(shè)備產(chǎn)線(xiàn)測(cè)試中這三條可以做成自動(dòng)化用例每臺(tái)設(shè)備出廠前跑一遍能攔截大部分裝配問(wèn)題。8. 常見(jiàn)問(wèn)題與排查思路批量設(shè)備現(xiàn)場(chǎng)調(diào)試時(shí)CAN 的問(wèn)題往往反復(fù)出現(xiàn)而且很多現(xiàn)象相似但原因不同。下表是典型的排查思路。問(wèn)題現(xiàn)象可能原因排查方式解決方案總線(xiàn)上完全收不到數(shù)據(jù)未接終端電阻、波特率不一致萬(wàn)用表測(cè) CANH/CANL 間電阻確認(rèn)波特率配置兩端接 120Ω統(tǒng)一所有節(jié)點(diǎn)波特率錯(cuò)誤幀持續(xù)增長(zhǎng)線(xiàn)纜過(guò)長(zhǎng)、分支過(guò)多、共模干擾candump -e查看錯(cuò)誤幀示波器測(cè)波形縮短分支、使用 split 終端加電容、優(yōu)化布線(xiàn)節(jié)點(diǎn)偶爾離線(xiàn)又恢復(fù)接插件接觸不良、供電不穩(wěn)檢查錯(cuò)誤計(jì)數(shù)和電源紋波緊固接插件、加強(qiáng)電源濾波、考慮隔離高優(yōu)先級(jí)數(shù)據(jù)發(fā)不出去總線(xiàn)負(fù)載率過(guò)高統(tǒng)計(jì)單位時(shí)間總幀數(shù)估算負(fù)載率降低周期報(bào)文頻率篩掉不必要報(bào)文節(jié)點(diǎn)進(jìn)入 Bus-Off連續(xù)錯(cuò)誤觸發(fā) CAN 控制器離線(xiàn)讀取 REC/TEC 錯(cuò)誤計(jì)數(shù)找到干擾源或硬件故障軟件增加 bus-off 恢復(fù)邏輯第一個(gè)常見(jiàn)問(wèn)題是“完全收不到”。先不要懷疑代碼先用萬(wàn)用表確認(rèn)終端電阻是否正常。第二個(gè)常見(jiàn)問(wèn)題是錯(cuò)誤幀很多。錯(cuò)誤幀本質(zhì)是某個(gè)節(jié)點(diǎn)檢測(cè)到了協(xié)議層錯(cuò)誤比如位錯(cuò)誤、填充錯(cuò)誤、CRC 錯(cuò)誤。原因通常是終端電阻缺失、線(xiàn)纜過(guò)長(zhǎng)、分支過(guò)長(zhǎng)、波特率不一致也可能是共模干擾太強(qiáng)此時(shí) split 終端加共模電容是值得優(yōu)先嘗試的整改手段。第三個(gè)常見(jiàn)問(wèn)題是節(jié)點(diǎn)運(yùn)行時(shí)偶爾離線(xiàn)。這種偶發(fā)故障最難查建議在節(jié)點(diǎn)軟件里通過(guò)錯(cuò)誤計(jì)數(shù)器做本地記錄保留最近一次 bus error 的類(lèi)型和現(xiàn)場(chǎng)