全解析:從報(bào)文格式到VIN碼寫(xiě)入實(shí)戰(zhàn))
干過(guò)車(chē)載診斷這塊的兄弟應(yīng)該都有同感不管你是做ECU開(kāi)發(fā)、診斷測(cè)試還是整車(chē)下線的產(chǎn)線標(biāo)定UDS這套協(xié)議繞不過(guò)去而其中2E服務(wù)——WriteDataByIdentifier按標(biāo)識(shí)符寫(xiě)數(shù)據(jù)更是日常打交道最多的幾個(gè)服務(wù)之一。平時(shí)寫(xiě)VIN碼、寫(xiě)序列號(hào)、刷配置字、標(biāo)定參數(shù)十有六七都要走這個(gè)服務(wù)。但很多剛?cè)腴T(mén)的朋友只會(huì)在診斷儀上點(diǎn)一下“寫(xiě)入”看到正響應(yīng)就以為完事了真到產(chǎn)品出了問(wèn)題、DID寫(xiě)不進(jìn)去、負(fù)響應(yīng)代碼看不懂的時(shí)候才發(fā)現(xiàn)自己對(duì)這個(gè)服務(wù)的理解停留在表面。這篇我就把自己在項(xiàng)目里用2E服務(wù)的經(jīng)驗(yàn)梳理一遍從服務(wù)定位、報(bào)文結(jié)構(gòu)、安全機(jī)制到實(shí)際寫(xiě)VIN碼的完整過(guò)程、DID權(quán)限表設(shè)計(jì)再到常見(jiàn)NRC的排查思路一次講透。內(nèi)容對(duì)正在做UDS診斷棧開(kāi)發(fā)的工程師、負(fù)責(zé)診斷腳本編寫(xiě)的測(cè)試人員、以及剛接觸車(chē)載通信協(xié)議的同學(xué)都適用看完至少能讓你少走幾次彎路。1. 2E服務(wù)在UDS診斷體系里的定位1.1 診斷服務(wù)家族里的“寫(xiě)入者”ISO 14229定義的UDS服務(wù)分成好幾個(gè)功能組比如診斷和通信管理10、11、27、28、3E等、數(shù)據(jù)傳輸22、2E、3C、3D、輸入輸出控制2F、31等等等。2E歸在數(shù)據(jù)傳輸組它的任務(wù)簡(jiǎn)單來(lái)說(shuō)就一句話通過(guò)DIDData Identifier數(shù)據(jù)標(biāo)識(shí)符往ECU里寫(xiě)入一段數(shù)據(jù)。注意這里的關(guān)鍵是“通過(guò)DID”。DID就是ECU內(nèi)部數(shù)據(jù)項(xiàng)的門(mén)牌號(hào)比如0xF190代表VIN碼、0xF191代表零部件號(hào)、0xF1A0代表軟件版本相關(guān)信息。診斷儀發(fā)起2E請(qǐng)求時(shí)請(qǐng)求報(bào)文里必須帶著DID告訴ECU你要寫(xiě)哪一門(mén)牌號(hào)下的數(shù)據(jù)。ECU收到后先查這個(gè)門(mén)牌號(hào)是否存在再查當(dāng)前會(huì)話和安全等級(jí)夠不夠最后查數(shù)據(jù)長(zhǎng)度和內(nèi)容是否合法全部通過(guò)才寫(xiě)入并返回正響應(yīng)。這是個(gè)“單一動(dòng)作”服務(wù)跟它的讀對(duì)應(yīng)物——22服務(wù)ReadDataByIdentifier是一對(duì)。在設(shè)計(jì)診斷規(guī)范的時(shí)候通常會(huì)把可讀可寫(xiě)的DID單獨(dú)列一張表標(biāo)注好讀寫(xiě)屬性、數(shù)據(jù)長(zhǎng)度、訪問(wèn)條件2E服務(wù)就負(fù)責(zé)處理其中“可寫(xiě)”的那些。這個(gè)表的合理性直接影響后面診斷功能能不能順利過(guò)審、產(chǎn)線能不能順利跑起來(lái)。1.2 2E與31、34/36/37服務(wù)的邊界很多初學(xué)者會(huì)把2E和31服務(wù)例程控制搞混也會(huì)糾結(jié)“寫(xiě)大量數(shù)據(jù)時(shí)該用2E還是34/36/37”。我個(gè)人的判斷標(biāo)準(zhǔn)是這樣2E適合小數(shù)據(jù)量、結(jié)構(gòu)化的單次寫(xiě)入。比如VIN碼17字節(jié)、配置字幾個(gè)字節(jié)、密鑰幾字節(jié)。一次請(qǐng)求把數(shù)據(jù)和DID一起發(fā)過(guò)去響應(yīng)也簡(jiǎn)單正響應(yīng)就是6E加回顯DID。31服務(wù)適合觸發(fā)一個(gè)動(dòng)作。比如“擦除整塊Flash”“激活某個(gè)校準(zhǔn)頁(yè)”它不是直接寫(xiě)數(shù)據(jù)而是讓ECU去執(zhí)行一段程序。雖然31服務(wù)也可以帶參數(shù)但核心語(yǔ)義是“運(yùn)行例行程序”。34/36/37是下載/上傳數(shù)據(jù)的成套服務(wù)適合幾十KB甚至幾MB的大塊數(shù)據(jù)比如BootLoader刷寫(xiě)、整包Flash更新。用34請(qǐng)求建立下載會(huì)話36循環(huán)發(fā)送數(shù)據(jù)塊37結(jié)束下載這套流程有傳輸層狀態(tài)機(jī)管理不能拿2E去硬扛大塊數(shù)據(jù)。實(shí)際項(xiàng)目里最常見(jiàn)的錯(cuò)誤就是用2E去寫(xiě)Flash區(qū)里的一大段標(biāo)定數(shù)據(jù)。2E從協(xié)議設(shè)計(jì)上就期望“一次請(qǐng)求、一次響應(yīng)、短小精悍”你要是拿它傳幾KB數(shù)據(jù)CAN幀數(shù)會(huì)非??鋸埲菀子|發(fā)各種超時(shí)安全性也不好控制。標(biāo)定大數(shù)據(jù)老老實(shí)實(shí)走34/36/37或者直接用XCP/CCP別難為2E。2. 2E服務(wù)的報(bào)文格式與關(guān)鍵參數(shù)拆解2.1 請(qǐng)求報(bào)文逐字節(jié)分析2E服務(wù)的請(qǐng)求格式固定為字節(jié)位置內(nèi)容說(shuō)明第1字節(jié)0x2E服務(wù)標(biāo)識(shí)SID第2字節(jié)DID高8位如0xF1第3字節(jié)DID低8位如0x90第4字節(jié)起用戶數(shù)據(jù)長(zhǎng)度取決于DID定義理論最多支持到傳輸層上限舉個(gè)例子往DID 0xF190寫(xiě)入VIN碼“WBA12345678901234”17個(gè)字符請(qǐng)求報(bào)文里的數(shù)據(jù)部分就是2E F1 90 57 42 41 31 32 33 34 35 36 37 38 39 30 31 32 33 34算上SID和DID總長(zhǎng)度20字節(jié)。這里要特別提醒0xF1是UDS文檔里劃給OEM自定義區(qū)域的DID起始段實(shí)際項(xiàng)目里DID分配表通常由診斷規(guī)范統(tǒng)一定義不同ECU的0xF190含義可能完全不同別拿一個(gè)項(xiàng)目里的經(jīng)驗(yàn)硬套另一個(gè)產(chǎn)品。2.2 正響應(yīng)與負(fù)響應(yīng)的判定規(guī)則正響應(yīng)的格式很清晰第一個(gè)字節(jié)是SID 0x40即0x6E后面回顯DID不帶用戶數(shù)據(jù)。以剛才寫(xiě)VIN的請(qǐng)求為例正響應(yīng)是02 6E F1 90 00 00 00 00 00等等這里其實(shí)有個(gè)容易理解錯(cuò)的地方。UDS正響應(yīng)報(bào)文最短可以是“SID 回顯DID”共3字節(jié)后面的00是填充還是數(shù)據(jù)以ISO-TP協(xié)議來(lái)看單幀報(bào)文剩余字節(jié)一般有填充具體取決于收發(fā)雙方約定的填充規(guī)則。所以解析正響應(yīng)時(shí)只關(guān)注前3字節(jié)就夠了。負(fù)響應(yīng)稍微復(fù)雜點(diǎn)統(tǒng)一格式是03 7F 2E [NRC]其中NRC是負(fù)響應(yīng)碼。常見(jiàn)幾個(gè)我會(huì)在第五章詳細(xì)講這里先記住ECU返回負(fù)響應(yīng)說(shuō)明這條寫(xiě)入請(qǐng)求被拒了必須看NRC才能定位原因。另外注意別把NRC和正響應(yīng)的6E混在一起0x7F永遠(yuǎn)是負(fù)響應(yīng)標(biāo)識(shí)跟0x6E就是對(duì)立關(guān)系。2.3 時(shí)序參數(shù)P2與S3對(duì)寫(xiě)入操作的影響診斷時(shí)序參數(shù)在2E服務(wù)里容易被忽視但實(shí)際排查時(shí)卻經(jīng)常是元兇。ISO 14229規(guī)定診斷儀請(qǐng)求發(fā)出后要等ECU回響應(yīng)分兩個(gè)時(shí)間窗口P2Server_processing_time默認(rèn)上限50msECU必須在P2內(nèi)響應(yīng)。如果ECU判斷自己處理不完比如寫(xiě)Flash期間耗時(shí)較長(zhǎng)應(yīng)該先回一個(gè)NRC 0x78Response Pending告訴診斷儀“我還活著再等等”然后進(jìn)入P2*處理時(shí)間通??梢匝娱L(zhǎng)到5秒。S3Server_session_time這是會(huì)話保持超時(shí)一般20秒。如果從上一個(gè)請(qǐng)求算起S3超時(shí)沒(méi)有新請(qǐng)求ECU會(huì)從擴(kuò)展會(huì)話自動(dòng)回退到默認(rèn)會(huì)話之后你再發(fā)2E請(qǐng)求就可能收到0x22條件不滿足錯(cuò)誤。項(xiàng)目上最常見(jiàn)的時(shí)序問(wèn)題就是診斷儀端把P2設(shè)置得很短比如10ms而ECU寫(xiě)Flash時(shí)響應(yīng)慢了點(diǎn)直接導(dǎo)致診斷儀那邊先報(bào)了通信超時(shí)。我處理過(guò)的一個(gè)量產(chǎn)項(xiàng)目就是這樣后來(lái)把診斷儀端的P2超時(shí)調(diào)到50ms、P2*調(diào)到5s問(wèn)題就消失了。2.4 會(huì)話切換與安全解鎖的前置條件2E服務(wù)發(fā)起前多數(shù)ECU要求診斷儀處于擴(kuò)展會(huì)話擴(kuò)展診斷模式通過(guò)10 03切換且完成安全訪問(wèn)27服務(wù)解鎖。這倆前置條件缺一個(gè)2E都進(jìn)不了正響應(yīng)流程。有個(gè)細(xì)節(jié)容易踩坑安全訪問(wèn)的解鎖狀態(tài)和會(huì)話狀態(tài)是有依賴(lài)的。正常流程建議先用10 03進(jìn)入擴(kuò)展會(huì)話。用27 01請(qǐng)求種子27 02發(fā)送Key等待解鎖成功正響應(yīng)。然后發(fā)2E寫(xiě)數(shù)據(jù)。如果在默認(rèn)會(huì)話下先解鎖再切會(huì)話解鎖狀態(tài)可能被清掉。協(xié)議里沒(méi)有強(qiáng)制規(guī)定會(huì)話切換必須清解鎖狀態(tài)但很多ECU實(shí)現(xiàn)里是會(huì)清掉的。我在實(shí)際測(cè)試中就遇到過(guò)某車(chē)型的ECU在會(huì)話切換后安全等級(jí)歸零導(dǎo)致后面2E一直返回0x33排查半天才發(fā)現(xiàn)是會(huì)話切換把解鎖狀態(tài)搞丟了。3. 2E服務(wù)在實(shí)車(chē)項(xiàng)目里的典型應(yīng)用場(chǎng)景3.1 產(chǎn)線寫(xiě)VIN碼的完整流程拆解寫(xiě)VIN碼是2E服務(wù)最經(jīng)典的應(yīng)用。整車(chē)下線時(shí)產(chǎn)線設(shè)備通過(guò)診斷口連接車(chē)輛把車(chē)輛唯一標(biāo)識(shí)寫(xiě)入網(wǎng)關(guān)、BCM、TBOX等多個(gè)ECU。以某ECU為例完整流程如下第一步建立通信。通常先發(fā)10 03進(jìn)入擴(kuò)展會(huì)話。發(fā)送: 02 10 03 響應(yīng): 02 50 03第二步安全解鎖。寫(xiě)VIN屬于敏感操作一般需要27服務(wù)SeedKey校驗(yàn)。實(shí)際項(xiàng)目中密鑰算法由OEM掌控產(chǎn)線診斷儀內(nèi)置了對(duì)應(yīng)的算法庫(kù)。發(fā)送: 02 27 01 響應(yīng): 04 67 01 1A 2B 3C 4D 發(fā)送: 06 27 02 5E 9A 17 22 響應(yīng): 02 67 02第三步發(fā)送2E寫(xiě)入VIN。假設(shè)DID是0xF190VIN是17字節(jié)“WBA12345678901234”發(fā)送: 14 2E F1 90 57 42 41 31 32 33 34 35 36 37 38 39 30 31 32 33 34這條請(qǐng)求數(shù)據(jù)部分加上DID共20字節(jié)ISO-TP會(huì)做單幀發(fā)送經(jīng)典CAN單幀有效負(fù)載8字節(jié)超過(guò)7字節(jié)數(shù)據(jù)要組首幀/連續(xù)幀這條請(qǐng)求已經(jīng)超過(guò)單幀容量具體組包我下面細(xì)說(shuō)。正常情況下ECU返回響應(yīng): 03 6E F1 90到這里一次VIN寫(xiě)入就完成了。但我不建議只在流程層面跑通就收工寫(xiě)入之后還要做回讀校驗(yàn)通常發(fā)22服務(wù)讀0xF190把返回的17字節(jié)和寫(xiě)入值比對(duì)?;刈x校驗(yàn)這個(gè)步驟看似多余卻是產(chǎn)線防錯(cuò)的關(guān)鍵。我就見(jiàn)過(guò)寫(xiě)入正響應(yīng)正常返回、實(shí)際存儲(chǔ)卻因?yàn)镕lash寫(xiě)失敗而出錯(cuò)的案例不校驗(yàn)根本發(fā)現(xiàn)不了。3.2 ISO-TP組包2E數(shù)據(jù)太長(zhǎng)時(shí)的傳輸層細(xì)節(jié)前面那條20字節(jié)的請(qǐng)求經(jīng)典CAN單幀根本裝不下這時(shí)候會(huì)走ISO-TPISO 15765-2組包。ISO-TP的機(jī)制就是第一個(gè)包叫單幀SF如果裝不下就發(fā)首幀F(xiàn)F 連續(xù)幀CF。一條20字節(jié)的請(qǐng)求在8字節(jié)DLC的經(jīng)典CAN上首幀會(huì)帶6字節(jié)有效載荷剩余數(shù)據(jù)拆成兩個(gè)連續(xù)幀每個(gè)連續(xù)幀帶7字節(jié)。FF: 10 14 2E F1 90 57 42 41 CF: 21 31 32 33 34 35 36 37 CF: 22 38 39 30 31 32 33 34注意首幀的第1字節(jié)0x10是幀類(lèi)型標(biāo)識(shí)低4位0x04表示長(zhǎng)度4不對(duì)這里得仔細(xì)說(shuō)首幀第1字節(jié)的高4位是幀類(lèi)型0x1低4位是長(zhǎng)度的最高4位第2字節(jié)是長(zhǎng)度低8位。0x10 0x14表示總長(zhǎng)度20字節(jié)。CF的第1字節(jié)低4位是幀序號(hào)依次0x1、0x2遞增。ECU按照?qǐng)?bào)文標(biāo)識(shí)符或接收邏輯判斷當(dāng)前處于接收過(guò)程中必須等全部幀收齊才能組出完整請(qǐng)求任何一幀丟了整個(gè)請(qǐng)求就失敗了。很多人用CAN卡發(fā)送時(shí)圖省事直接讓上位機(jī)把20字節(jié)塞進(jìn)一個(gè)單片數(shù)據(jù)里結(jié)果總線上一看全是截?cái)嗟膩y碼。正確的做法是用支持ISO-TP的診斷庫(kù)或者自己實(shí)現(xiàn)組包函數(shù)別裸發(fā)CAN幀。3.3 配置類(lèi)DID寫(xiě)入與多ECU同步場(chǎng)景除了VIN2E還經(jīng)常用來(lái)寫(xiě)配置字、參數(shù)集編號(hào)、防盜匹配數(shù)據(jù)。比如更換BCM后售后診斷儀要把車(chē)輛的選裝配置碼寫(xiě)進(jìn)去回復(fù)原車(chē)的功能配置不然新?lián)Q的BCM某些功能就是禁用狀態(tài)。這類(lèi)場(chǎng)景有一個(gè)特點(diǎn)DID多、數(shù)據(jù)量小、但邏輯關(guān)系強(qiáng)。比如某個(gè)ECU上有0xF120車(chē)型代碼、0xF121高低配標(biāo)志、0xF122排放標(biāo)準(zhǔn)三個(gè)DID產(chǎn)線投線時(shí)要求三個(gè)一起寫(xiě)而且配置組合必須匹配車(chē)型A配高配標(biāo)志車(chē)型B配低配標(biāo)志亂寫(xiě)會(huì)讓ECU上電自檢報(bào)錯(cuò)。這種情況下2E服務(wù)本身不負(fù)責(zé)檢查組合合法性它只負(fù)責(zé)把數(shù)據(jù)存到對(duì)應(yīng)的DID。組合校驗(yàn)邏輯要寫(xiě)在ECU的應(yīng)用程序里收到2E寫(xiě)入后不是簡(jiǎn)單存Flash還要做合法性判斷不合法時(shí)返回NRC 0x31請(qǐng)求超出范圍或者0x72一般性編程失敗。我在設(shè)計(jì)診斷規(guī)范時(shí)通常會(huì)強(qiáng)制要求凡是跟功能配置相關(guān)的DID寫(xiě)入時(shí)必須做組合校驗(yàn)不能只做單條數(shù)據(jù)的范圍檢查。這一條在質(zhì)量審核時(shí)經(jīng)常被點(diǎn)名務(wù)必要提前考慮。多ECU同步場(chǎng)景也挺常見(jiàn)。新能源車(chē)上網(wǎng)關(guān)、充電控制器、BMS都要寫(xiě)整車(chē)VIN不可能一臺(tái)臺(tái)手動(dòng)寫(xiě)產(chǎn)線上會(huì)通過(guò)網(wǎng)關(guān)做路由或者用診斷儀循環(huán)訪問(wèn)不同地址。此時(shí)要關(guān)注ECU的物理尋址和功能尋址差異。2E屬于安全敏感操作強(qiáng)烈不建議用功能尋址0x7DF這類(lèi)廣播地址去寫(xiě)每個(gè)ECU一旦某個(gè)ECU沒(méi)做好安全校驗(yàn)廣播寫(xiě)操作的風(fēng)險(xiǎn)沒(méi)法控制。我見(jiàn)過(guò)一個(gè)項(xiàng)目最開(kāi)始圖省事用功能尋址發(fā)2E結(jié)果網(wǎng)關(guān)刷寫(xiě)時(shí)把全部ECU的VIN都改了最后還是老老實(shí)實(shí)改回物理尋址輪詢(xún)。4. 2E服務(wù)實(shí)現(xiàn)階段的核心設(shè)計(jì)思路4.1 DID權(quán)限表讀寫(xiě)屬性、訪問(wèn)條件、長(zhǎng)度約束如果把2E服務(wù)比作一個(gè)門(mén)禁系統(tǒng)DID權(quán)限表就是門(mén)牌號(hào)的登記冊(cè)。ECU的診斷棧實(shí)現(xiàn)里至少要有一張靜態(tài)表每個(gè)條目記錄DID編號(hào)、讀寫(xiě)屬性RW/RO/WO、安全等級(jí)要求、有效會(huì)話、數(shù)據(jù)長(zhǎng)度、數(shù)據(jù)范圍約束。實(shí)際項(xiàng)目里我推薦的做法是把這張表做成代碼里的配置數(shù)組類(lèi)似typedef struct { uint16_t did; uint8_t read_perm; // 是否允許22讀 uint8_t write_perm; // 是否允許2E寫(xiě) uint8_t security_lvl; // 需要安全等級(jí) uint8_t session_mask; // 允許的會(huì)話掩碼 uint8_t data_len; // 固定長(zhǎng)度0表示可變 uint16_t min_val; // 范圍約束 uint16_t max_val; } DidAttributeType;DID權(quán)限表的好處是診斷邏輯和業(yè)務(wù)邏輯解耦。診斷棧只需要查表判斷“能不能寫(xiě)、夠不夠安全、長(zhǎng)度對(duì)不對(duì)”至于數(shù)據(jù)寫(xiě)進(jìn)哪個(gè)Flash地址、要不要觸發(fā)校驗(yàn)由對(duì)應(yīng)的業(yè)務(wù)處理函數(shù)掛鉤。我在代碼審查時(shí)發(fā)現(xiàn)過(guò)一種壞味道有的開(kāi)發(fā)為了方便把DID權(quán)限判斷直接寫(xiě)死在2E服務(wù)處理函數(shù)里來(lái)一個(gè)新DID就加一個(gè)if加了幾十個(gè)分支后代碼根本沒(méi)法維護(hù)。用表驅(qū)動(dòng)新增一個(gè)DID只需要加一條記錄掛一個(gè)回調(diào)函數(shù)清爽很多。注意長(zhǎng)度校驗(yàn)的分寸。固定長(zhǎng)度DID比如VIN固定17字節(jié)長(zhǎng)度不對(duì)直接返回0x13IncorrectMessageLengthOrInvalidFormat。但有些DID允許變長(zhǎng)比如存儲(chǔ)診斷故障碼擴(kuò)展信息的DID這時(shí)長(zhǎng)度校驗(yàn)就只做上下限檢查。別把長(zhǎng)度約死否則以后DID數(shù)據(jù)格式升級(jí)還得改診斷棧代碼。4.2 條件校驗(yàn)寫(xiě)入不是“存數(shù)據(jù)”那么簡(jiǎn)單很多做應(yīng)用層開(kāi)發(fā)的同事對(duì)2E服務(wù)有個(gè)誤解覺(jué)得2E就是把數(shù)據(jù)存進(jìn)內(nèi)存或者Flash存完返回正響應(yīng)就完事了。實(shí)際上真正量產(chǎn)ECU里2E寫(xiě)入之后往往要觸發(fā)一串后續(xù)動(dòng)作。我舉幾個(gè)常見(jiàn)例子寫(xiě)VIN碼后ECU需要更新VIN相關(guān)的校驗(yàn)和并且可能要在Flash里做雙備份存儲(chǔ)。寫(xiě)配置字后ECU需要重新加載該配置對(duì)應(yīng)的功能模塊參數(shù)。寫(xiě)防盜相關(guān)DID會(huì)觸發(fā)防盜算法狀態(tài)的更新甚至要求重新上電才能生效。寫(xiě)標(biāo)定參數(shù)時(shí)可能要求確認(rèn)當(dāng)前車(chē)速為零、發(fā)動(dòng)機(jī)停機(jī)等安全條件。這些條件如果都不滿足應(yīng)該在2E處理函數(shù)里返回0x22ConditionNotCorrect而不是照常寫(xiě)入。條件校驗(yàn)的順序也重要。按照UDS推薦的負(fù)響應(yīng)碼優(yōu)先級(jí)通常順序是先查DID存在性不存在返回0x31、再查會(huì)話和訪問(wèn)權(quán)限0x7F或0x22/0x33、再查長(zhǎng)度0x13、再查應(yīng)用條件0x22、最后嘗試寫(xiě)入寫(xiě)入失敗0x72。如果順序搞亂比如先查應(yīng)用條件再查安全解鎖會(huì)導(dǎo)致沒(méi)解鎖的請(qǐng)求返回0x22而不是0x33給上游診斷儀的解析邏輯增加不必要的困難。4.3 大塊數(shù)據(jù)寫(xiě)入的邊界2E該管多大前面說(shuō)過(guò)2E面向短數(shù)據(jù)但“短”到底是多少項(xiàng)目里要有明確約定。按經(jīng)典CAN和ISO-TP的組合單幀能承載的最多用戶數(shù)據(jù)是7字節(jié)DLC8去掉PCI占用1字節(jié)。再往上就要組首幀連續(xù)幀。ISO-TP協(xié)議本身支持最大4095字節(jié)但這不代表2E就該持續(xù)寫(xiě)幾KB數(shù)據(jù)。我給項(xiàng)目定規(guī)范時(shí)一般這么寫(xiě)2E服務(wù)單條寫(xiě)入數(shù)據(jù)長(zhǎng)度不超過(guò)256字節(jié)。超過(guò)這個(gè)數(shù)一律走34/36/37服務(wù)。為什么是256因?yàn)楹芏郋CU內(nèi)部定義DID數(shù)據(jù)緩沖區(qū)的上限就是256再大就超過(guò)連續(xù)內(nèi)存分配能力了尤其是那些跑在MCU上的小內(nèi)核RAM緊張硬塞一個(gè)幾KB的臨時(shí)緩沖區(qū)既浪費(fèi)又不安全。另外要提醒的是寫(xiě)操作的原子性。2E寫(xiě)入應(yīng)該整包數(shù)據(jù)全部到位后再執(zhí)行寫(xiě)入動(dòng)作中間缺幀錯(cuò)幀都不能觸發(fā)實(shí)際存儲(chǔ)。實(shí)現(xiàn)上最好是收完完整請(qǐng)求、完成全部校驗(yàn)后一次性寫(xiě)入Flash或者RAM。如果邊收邊寫(xiě)傳輸中斷時(shí)Flash里就可能留了半截?cái)?shù)據(jù)那就不叫原子操作了后患無(wú)窮。4.4 掉電保護(hù)與寫(xiě)入確認(rèn)機(jī)制2E寫(xiě)Flash最大的風(fēng)險(xiǎn)是寫(xiě)入過(guò)程中掉電。Flash編程中途掉電輕則數(shù)據(jù)沒(méi)寫(xiě)進(jìn)去重則整塊Flash狀態(tài)異常下次上電ECU引導(dǎo)失敗。這塊是量產(chǎn)項(xiàng)目最容易忽略的。成熟的做法是至少在數(shù)據(jù)層做雙備份Double Bank或者臟數(shù)據(jù)標(biāo)記。簡(jiǎn)單說(shuō)就是Flash里存兩個(gè)區(qū)先寫(xiě)備份區(qū)寫(xiě)成功后置標(biāo)志再寫(xiě)主區(qū)最后把標(biāo)志位更新為“當(dāng)前生效區(qū)是主區(qū)”。下次上電先檢查標(biāo)志位發(fā)現(xiàn)主區(qū)不完整就自動(dòng)切到備份區(qū)保證ECU還能正常工作。2E服務(wù)的正響應(yīng)應(yīng)該什么時(shí)候回穩(wěn)妥是等數(shù)據(jù)真正落Flash成功之后再回正響應(yīng)而不是數(shù)據(jù)暫存RAM就回。這樣診斷儀端收到正響應(yīng)就可以認(rèn)為數(shù)據(jù)已經(jīng)持久化完成產(chǎn)線上隨后斷電也不會(huì)丟數(shù)據(jù)。代價(jià)是響應(yīng)時(shí)間變長(zhǎng)可能需要先回0x78Response Pending再回最終結(jié)果。很多OEM的診斷規(guī)范都會(huì)明確要求涉及Flash寫(xiě)入的DID允許ECU回一次0x78最終正響應(yīng)必須在寫(xiě)操作全部完成后發(fā)出。5. 2E服務(wù)實(shí)戰(zhàn)中的常見(jiàn)問(wèn)題與排查實(shí)錄5.1 負(fù)響應(yīng)碼速查表與排查思路實(shí)戰(zhàn)中2E服務(wù)絕大多數(shù)異常都能通過(guò)NRC直接定位。我把常見(jiàn)情況整理成表方便現(xiàn)場(chǎng)排查時(shí)一眼定方向NRC含義常見(jiàn)觸發(fā)原因排查方向0x13長(zhǎng)度或格式錯(cuò)誤寫(xiě)入數(shù)據(jù)字節(jié)數(shù)與DID定義不一致核對(duì)DID表長(zhǎng)度定義檢查發(fā)送數(shù)據(jù)是否有刪減0x22條件不滿足未進(jìn)擴(kuò)展會(huì)話、操作時(shí)序不對(duì)、條件標(biāo)志未滿足檢查10 03會(huì)話狀態(tài)查條件校驗(yàn)邏輯0x31請(qǐng)求超出范圍DID不存在、DID不可寫(xiě)、寫(xiě)入值非法查DID表查范圍校驗(yàn)邏輯0x33安全訪問(wèn)拒絕未解鎖、解鎖過(guò)期、密鑰錯(cuò)誤重新執(zhí)行27服務(wù)完整流程檢查解鎖狀態(tài)0x72一般性編程失敗Flash寫(xiě)入失敗、校驗(yàn)失敗、物理存儲(chǔ)異常查Flash驅(qū)動(dòng)查硬件查供電0x7E子功能不支持2E請(qǐng)求帶了錯(cuò)誤的子功能字節(jié)查請(qǐng)求報(bào)文是否多夾了字節(jié)0x7F服務(wù)不支持診斷儀根本不該發(fā)2E查ECU診斷配置確認(rèn)2E服務(wù)是否使能這里有個(gè)關(guān)鍵技巧診斷儀把DID和NRC組合起來(lái)看基本能縮小到具體DID。比如2E訪問(wèn)0xF190返回0x33那基本確定是這個(gè)DID要求安全等級(jí)當(dāng)前State沒(méi)達(dá)到。如果返回0x31很可能DID根本不在可寫(xiě)集合里。帶著NRC再去看DID表問(wèn)題一般很快能浮出水面。5.2 總線工具實(shí)操用CAN卡復(fù)現(xiàn)2E寫(xiě)入過(guò)程現(xiàn)場(chǎng)排查時(shí)不能只依賴(lài)診斷儀的黑盒界面最好直接用CAN工具抓總線報(bào)文。以常見(jiàn)的CAN分析工具為例我用CANoe/CANalyzer比較多操作流程大致這樣新建工程正確配置CAN通道的波特率大部分車(chē)載網(wǎng)絡(luò)500kbps也有2Mbps的CAN FD。使用診斷發(fā)送功能配置一條2E請(qǐng)求填入DID和數(shù)據(jù)。手動(dòng)觸發(fā)發(fā)送同時(shí)啟動(dòng)Trace記錄觀察ECU的響應(yīng)報(bào)文和響應(yīng)時(shí)間。要注意的是Trace里的報(bào)文方向和時(shí)間戳。請(qǐng)求發(fā)出和響應(yīng)返回之間的時(shí)間差就是ECU處理時(shí)間如果這個(gè)時(shí)間超過(guò)P2而ECU又沒(méi)回0x78那就是ECU端時(shí)序出了問(wèn)題。建議在總線工具里打開(kāi)時(shí)間戳顯示精度至少要毫秒級(jí)。如果手上沒(méi)有CANoe這種商業(yè)工具也可以用Python加USB-CAN適配器實(shí)現(xiàn)類(lèi)似功能。核心就是自己拼接ISO-TP幀并發(fā)送。比如用python-can庫(kù)自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的ISO-TP發(fā)送函數(shù)把2E請(qǐng)求拆成單幀或首幀連續(xù)幀。實(shí)測(cè)下來(lái)腳本方式做單條診斷測(cè)試完全夠用但要做完整診斷仿真還是建議用專(zhuān)業(yè)工具。5.3 實(shí)際項(xiàng)目里踩過(guò)的坑第一個(gè)坑是“沒(méi)解鎖就寫(xiě)”。有一陣子做售后診斷儀聯(lián)調(diào)反復(fù)出現(xiàn)2E返回0x33。查日志發(fā)現(xiàn)診斷儀在發(fā)2E之前確實(shí)發(fā)了27解鎖但解鎖用的Key算法和ECU端不一致ECU校驗(yàn)失敗解鎖根本沒(méi)成功。這個(gè)坑的隱蔽點(diǎn)在于診斷儀端的解鎖成功提示是“請(qǐng)求已發(fā)出”不是“校驗(yàn)已通過(guò)”。正確的做法是看27服務(wù)02子功能是不是返回了0x67正響應(yīng)同時(shí)最好在ECU端打印安全狀態(tài)日志。第二個(gè)坑是“寫(xiě)完沒(méi)校驗(yàn)”。某項(xiàng)目上線后發(fā)現(xiàn)部分ECU的VIN明顯不對(duì)但產(chǎn)線測(cè)試報(bào)告全是Pass。一查才知道產(chǎn)線流程里寫(xiě)完2E就沒(méi)做22回讀而某個(gè)批次的ECU Flash寫(xiě)入存在偶發(fā)性的半字節(jié)翻轉(zhuǎn)。后來(lái)在產(chǎn)線流程里加了自動(dòng)回讀問(wèn)題立刻暴露再配合Flash驅(qū)動(dòng)層加校驗(yàn)才根治。從那以后我對(duì)寫(xiě)類(lèi)服務(wù)基本養(yǎng)成一個(gè)習(xí)慣寫(xiě)后必讀這是成本最低的可靠性保障。第三個(gè)坑是“單幀/連續(xù)幀錯(cuò)位”。用第三方診斷儀寫(xiě)一個(gè)很長(zhǎng)的DID時(shí)抓報(bào)文發(fā)現(xiàn)請(qǐng)求只發(fā)了一半對(duì)方就超時(shí)了。排查后發(fā)現(xiàn)是診斷儀配置里CAN幀類(lèi)型被設(shè)成了單幀超長(zhǎng)數(shù)據(jù)直接截?cái)喟l(fā)送。換成自動(dòng)組包模式后恢復(fù)正常。這種問(wèn)題在現(xiàn)場(chǎng)很容易被誤判為ECU故障實(shí)際上就是工具配置問(wèn)題先抓總線報(bào)文是最快的破案方式。第四個(gè)坑是關(guān)于0x78的響應(yīng)處理。有一個(gè)ECU寫(xiě)Flash數(shù)據(jù)時(shí)耗時(shí)較長(zhǎng)會(huì)先回0x78再在幾百毫秒后回最終正響應(yīng)。但診斷儀端在收到0x78后沒(méi)有正確處理直接判定為負(fù)響應(yīng)導(dǎo)致流程中斷。這個(gè)問(wèn)題的根因是診斷儀側(cè)的狀態(tài)機(jī)沒(méi)實(shí)現(xiàn)Response Pending處理機(jī)制收到0x78不知道要等待后續(xù)響應(yīng)。排查時(shí)看Trace里其實(shí)有響應(yīng)只是診斷儀沒(méi)認(rèn)容易被誤導(dǎo)成ECU不回消息。5.4 2E服務(wù)測(cè)試的自動(dòng)化設(shè)計(jì)思路如果你負(fù)責(zé)診斷測(cè)試建議把2E服務(wù)用例自動(dòng)化。我搭建過(guò)一套基于CAPL的UDS自動(dòng)化測(cè)試腳本大致用例覆蓋這些點(diǎn)會(huì)話切換前置條件測(cè)試分別在默認(rèn)會(huì)話、擴(kuò)展會(huì)話、編程會(huì)話下發(fā)2E預(yù)期只有合法會(huì)話能成功。安全解鎖前置條件測(cè)試未解鎖、解鎖成功、解鎖后又切會(huì)話分別下發(fā)2E預(yù)期結(jié)果各不相同。長(zhǎng)度邊界測(cè)試DID固定長(zhǎng)度17字節(jié)分別發(fā)16、17、18字節(jié)驗(yàn)證0x13負(fù)響應(yīng)邏輯。數(shù)值邊界測(cè)試配置DID允許范圍0~100寫(xiě)入101應(yīng)該返回0x31。斷電測(cè)試寫(xiě)入過(guò)程中模擬掉電重啟后檢查數(shù)據(jù)完整性這個(gè)需要臺(tái)架配合。寫(xiě)后回讀測(cè)試每次寫(xiě)入成功后自動(dòng)發(fā)22讀回比對(duì)數(shù)據(jù)一致性。自動(dòng)化用例的關(guān)鍵是斷言寫(xiě)在后置條件里比如解鎖流程要完整執(zhí)行并通過(guò)不能因?yàn)榘踩詸z查不通過(guò)就把整個(gè)用例跳過(guò)。測(cè)試報(bào)告里要把每條請(qǐng)求報(bào)文、響應(yīng)報(bào)文、NRC、處理時(shí)間全部記錄下來(lái)這樣后續(xù)排查問(wèn)題才有據(jù)可依。寫(xiě)在最后的一點(diǎn)個(gè)人體會(huì)2E服務(wù)從協(xié)議角度看并不復(fù)雜真正讓它變得復(fù)雜的是藏在DID背后的業(yè)務(wù)邏輯安全等級(jí)、會(huì)話管理、條件校驗(yàn)、Flash可靠性、多ECU協(xié)同。我這些年做下來(lái)最大的感受是——設(shè)計(jì)診斷服務(wù)時(shí)多花一小時(shí)做權(quán)限表和條件梳理能省下后續(xù)幾個(gè)月在生產(chǎn)現(xiàn)場(chǎng)和售后排查上的大量精力。2E服務(wù)寫(xiě)壞了數(shù)據(jù)不是改個(gè)報(bào)文就能恢復(fù)的輕則重新寫(xiě)重則得返工更換控制器。如果你現(xiàn)在正準(zhǔn)備設(shè)計(jì)診斷規(guī)范或者實(shí)現(xiàn)診斷棧建議把你計(jì)劃暴露的DID一個(gè)個(gè)過(guò)一遍可讀還是可寫(xiě)什么條件下可寫(xiě)寫(xiě)失敗了會(huì)怎樣。這塊摳得越細(xì)后面越穩(wěn)。