動(dòng)包與UDS協(xié)議棧實(shí)戰(zhàn):ECU診斷集成與刷寫流程解析)
簡(jiǎn)介面向汽車電子診斷開發(fā)者的DCM驅(qū)動(dòng)包內(nèi)含完整UDS協(xié)議棧實(shí)現(xiàn)可直接應(yīng)用于車載ECU故障檢測(cè)、軟件升級(jí)、數(shù)據(jù)讀取與清除等診斷任務(wù)。壓縮包共30個(gè)文件由22個(gè)h頭文件、7個(gè)c源文件和1個(gè)txt說明文件組成整體僅97KB目錄覆蓋Dcm、CanIf、CanTp、J1939Tp等模塊結(jié)構(gòu)清晰便于按需查閱。源碼實(shí)現(xiàn)了從CAN物理層幀編解碼、傳輸層數(shù)據(jù)分段重組到UDS服務(wù)層請(qǐng)求響應(yīng)的完整鏈路其中CANIF負(fù)責(zé)控制器交互與錯(cuò)誤恢復(fù)機(jī)制CANTP管理分段、重傳等傳輸細(xì)節(jié)J1939TP則面向卡車、巴士等重型車輛的多源多目的通信場(chǎng)景擴(kuò)展了診斷覆蓋范圍。開發(fā)者可借此理解UDS協(xié)議棧的模塊劃分、狀態(tài)管理以及診斷報(bào)文交互時(shí)序并針對(duì)實(shí)際項(xiàng)目裁剪、優(yōu)化或增加自定義服務(wù)。已有1262人學(xué)習(xí)下載適合嵌入式工程師、車輛網(wǎng)絡(luò)專家以及汽車電子愛好者深入學(xué)習(xí)與二次開發(fā)。 拿到一個(gè)dcm驅(qū)動(dòng)包(內(nèi)含uds協(xié)議棧).zip的時(shí)候我第一反應(yīng)是這年頭還能在網(wǎng)上搜到“dcm轉(zhuǎn)png”“華為matebook驅(qū)動(dòng)包”這種結(jié)果真的是同名不同世界。但在車載電子圈子里DCM 是 Diagnostic Communication Manager也就是診斷通信管理器它配合 UDSUnified Diagnostic Services統(tǒng)一診斷服務(wù)協(xié)議棧解決一個(gè)很實(shí)際的問題讓 ECU 聽懂診斷儀發(fā)過來的幀、正確回復(fù)、按狀態(tài)機(jī)走完安全解鎖和刷寫流程。這個(gè)包適合三類人剛接手 ECU 診斷功能的嵌入式工程師、要把診斷功能快速落地到 MCU 項(xiàng)目的軟件負(fù)責(zé)人、以及準(zhǔn)備做 Bootloader 刷寫方案但又不想從零寫協(xié)議的開發(fā)者。我最近正好在項(xiàng)目里集成了一版類似的驅(qū)動(dòng)包踩了不少坑也把協(xié)議棧和底層 CAN 驅(qū)動(dòng)之間的配合方式摸了一遍這篇就把我的實(shí)際操作過程、配置思路和排查經(jīng)驗(yàn)寫出來給后面接包的人省點(diǎn)時(shí)間。1. 包里裝的到底是什么DCM 與 UDS 的關(guān)系很多人把 DCM 和 UDS 當(dāng)成一個(gè)東西其實(shí)不對(duì)。UDS 是 ISO 14229 定義的診斷服務(wù)規(guī)范規(guī)定了 0x10、0x22、0x27 這些服務(wù)怎么發(fā)、怎么回、NRC 怎么填。而 DCM 是 AUTOSAR 架構(gòu)里的一個(gè)具體模塊通俗講就是“UDS 協(xié)議的軟件實(shí)現(xiàn)容器”。這個(gè) zip 包叫“dcm 驅(qū)動(dòng)包內(nèi)含 uds 協(xié)議?!币馑季褪墙桓段锢锛劝?DCM 這個(gè)模塊的源碼/庫也包括了從 CAN 報(bào)文到診斷服務(wù)的完整解析鏈路。1.1 DCM 在軟件架構(gòu)里的位置先看一條典型的診斷數(shù)據(jù)鏈路診斷儀Tester通過 CAN/CAN FD 發(fā)請(qǐng)求幀 → ECU 的 CAN 控制器接收 → CanIfCAN 接口層把幀交給 PduRPDU 路由器→ PduR 路由給 Dcm → Dcm 解析 SID 和子功能 → 分發(fā)到應(yīng)用層處理 → 應(yīng)用層返回響應(yīng)數(shù)據(jù) → Dcm 組幀 → 原路返回給診斷儀。這個(gè)包里的“驅(qū)動(dòng)”二字指的就是這條鏈路上 DCM 這一層一般會(huì)包含DCM 核心狀態(tài)機(jī)接收、處理、發(fā)送、超時(shí)管理UDS 服務(wù)分發(fā)表SID → 處理函數(shù)NRC 錯(cuò)誤碼生成邏輯會(huì)話管理、安全等級(jí)管理底層 CAN 收發(fā)適配接口應(yīng)用層回調(diào)接口DID 讀寫、例程控制、刷寫等如果你是裸機(jī)項(xiàng)目沒有 AUTOSAR 基礎(chǔ)軟件那這個(gè)包通常還帶一個(gè)簡(jiǎn)易的 CanIf/PduR 抽象層或者直接要求你自己把 CAN 收發(fā)的回調(diào)函數(shù)接進(jìn)來。1.2 UDS 服務(wù)與 DCM 的分發(fā)機(jī)制DCM 內(nèi)部最關(guān)鍵的部分就是服務(wù)分發(fā)表。收到一幀診斷請(qǐng)求后它先看第一個(gè)字節(jié)是不是合法 SID不是就回 0x7F SID 0x11服務(wù)不支持再看當(dāng)前會(huì)話模式允不允許這個(gè)服務(wù)不允許就回 0x7F SID 0x7F服務(wù)在當(dāng)前會(huì)話下不支持最后檢查子功能和消息長(zhǎng)度有問題就回對(duì)應(yīng)的 NRC。常用服務(wù)我整理了一張表方便你對(duì)照理解SID服務(wù)名稱主要用途典型場(chǎng)景0x10DiagnosticSessionControl切換診斷會(huì)話進(jìn)入編程/擴(kuò)展會(huì)話0x11ECUReset復(fù)位 ECU刷寫完成后重啟0x27SecurityAccess安全解鎖進(jìn)入寫/刷寫前的鑒權(quán)0x22ReadDataByIdentifier按 DID 讀數(shù)據(jù)讀 VIN、版本號(hào)、電壓0x2EWriteDataByIdentifier按 DID 寫數(shù)據(jù)寫配置參數(shù)、標(biāo)定值0x19ReadDTCInformation讀故障碼讀取 DTC 狀態(tài)/快照0x14ClearDiagnosticInformation清故障碼維修后清除 DTC0x31RoutineControl例程控制自檢、擦除、校驗(yàn)和計(jì)算0x34RequestDownload請(qǐng)求下載刷寫前握手0x36TransferData傳輸數(shù)據(jù)刷寫數(shù)據(jù)塊0x37RequestTransferExit結(jié)束傳輸刷寫收尾0x3ETesterPresent?;罘乐箷?huì)話超時(shí)0x85ControlDTCSetting控制 DTC 記錄產(chǎn)線模式關(guān)閉故障記錄DCM 的服務(wù)分發(fā)表一般是一個(gè)結(jié)構(gòu)體數(shù)組每個(gè)條目包含 SID、是否允許功能尋址、最小/最大長(zhǎng)度、處理函數(shù)指針。你拿到包后第一步不是看源代碼邏輯而是先找到這個(gè)表確認(rèn)你要用到的服務(wù)已經(jīng)使能。1.3 為什么不建議自己從零寫我見過不少團(tuán)隊(duì)想自己寫一個(gè) UDS 協(xié)議棧最后基本都會(huì)在幾個(gè)地方翻車P2/P2* 定時(shí)器的精度控制、0x36 連續(xù)幀接收時(shí)的序列號(hào)管理、多會(huì)話/多安全等級(jí)的狀態(tài)切換、NRC 優(yōu)先級(jí)順序。ISO 14229 本身是一個(gè)很大的文檔光是一個(gè) 0x19 服務(wù)就能拆出十幾種子功能自己慢慢啃的周期遠(yuǎn)比想象中長(zhǎng)。直接用現(xiàn)成的驅(qū)動(dòng)包相當(dāng)于把協(xié)議層風(fēng)險(xiǎn)轉(zhuǎn)移給了交付方你要做的是把底層接口調(diào)通、把應(yīng)用回調(diào)填好、把關(guān)重測(cè)試跑明白。這不是偷懶而是汽車軟件開發(fā)里很常見的“買協(xié)議棧自己寫應(yīng)用”策略。2. 核心模塊與關(guān)鍵機(jī)制拆解這個(gè)包真正讓我覺得有價(jià)值的地方不是它實(shí)現(xiàn)了多少服務(wù)而是把幾個(gè)容易出問題的機(jī)制都封裝好了。我挑幾個(gè)實(shí)用的展開講。2.1 會(huì)話管理和安全等級(jí)機(jī)制診斷儀和 ECU 之間不是一上來就能干任何事的。DCM 里有一張“會(huì)話 × 服務(wù) × 安全等級(jí)”的訪問矩陣。常見的三種會(huì)話是默認(rèn)會(huì)話0x01、編程會(huì)話0x02、擴(kuò)展會(huì)話0x03。默認(rèn)會(huì)話下一般只允許讀 DTC、讀數(shù)據(jù)、TesterPresent想要刷寫或者寫配置必須切到擴(kuò)展或編程會(huì)話。切換會(huì)話的流程是診斷儀發(fā)10 02ECU 回復(fù)50 02然后 DCM 內(nèi)部把當(dāng)前會(huì)話狀態(tài)切為目標(biāo)會(huì)話同時(shí)啟動(dòng) S3 服務(wù)器定時(shí)器。如果 S3 超時(shí)典型值 5000ms沒有收到任何診斷請(qǐng)求就會(huì)自動(dòng)回到默認(rèn)會(huì)話。安全訪問是另一個(gè)門檻。很多診斷服務(wù)比如 0x2E 寫數(shù)據(jù)、0x34 刷寫在訪問矩陣?yán)镆蟆鞍踩燃?jí)已解鎖”。0x27 服務(wù)分兩步診斷儀發(fā)27 01請(qǐng)求種子SeedECU 根據(jù)內(nèi)部算法回復(fù)67 01 Seed診斷儀把 Seed 丟進(jìn) Key 算法算出密鑰再發(fā)27 02 KeyECU 校驗(yàn)通過回復(fù)67 02否則回7F 27 35Key 錯(cuò)誤或7F 27 36嘗試次數(shù)超限。提示Key 算法是整車廠/供應(yīng)商私有的通常不會(huì)放在驅(qū)動(dòng)包里。這個(gè) zip 里的安全訪問模塊一般會(huì)留一個(gè)函數(shù)指針比如Dcm_SecurityKeyAlgorithm(seed, len, out_key)你得自己把算法實(shí)現(xiàn)掛上去。改包的時(shí)候不要?jiǎng)訁f(xié)議棧主體只要在這個(gè)回調(diào)里做你的邏輯。2.2 DID 數(shù)據(jù)標(biāo)識(shí)符和例程控制DIDData Identifier是 UDS 里最常用的數(shù)據(jù)入口。0x22 讀 DID 的格式是22 DID_H DID_L比如22 F1 90讀 VIN。0x2E 寫 DID 的格式是2E DID 數(shù)據(jù)。DCM 收到后不會(huì)自己去存取數(shù)據(jù)而是把請(qǐng)求轉(zhuǎn)給應(yīng)用層通過回調(diào)函數(shù)拿到實(shí)際結(jié)果。這個(gè)包的 DID 管理通常是一個(gè)配置表每條 DID 包括DID 值、訪問權(quán)限會(huì)話/安全等級(jí)、數(shù)據(jù)長(zhǎng)度、讀寫回調(diào)函數(shù)。配置示意見const Dcm_DidConfigType Dcm_DidTable[] { { 0xF190, DCM_SESSION_EXTENDED, DCM_SECURITY_LOCKED, Dcm_ReadVin, NULL }, { 0xF191, DCM_SESSION_EXTENDED, DCM_SECURITY_UNLOCK, Dcm_ReadSwVer, Dcm_WriteSn }, };0x31 例程控制是另一個(gè)大頭。它支持三個(gè)子功能啟動(dòng)例程0x01、停止例程0x02、請(qǐng)求例程結(jié)果0x03。常見應(yīng)用是 Flash 擦除、自檢、校驗(yàn)和計(jì)算。例程和普通讀寫的最大區(qū)別在于它是“耗時(shí)操作”驅(qū)動(dòng)包里通常會(huì)把它設(shè)計(jì)成異步流程先返回一個(gè)“正在執(zhí)行”的狀態(tài)執(zhí)行完后再通過Dcm_ReportRoutineResult把結(jié)果告訴診斷儀。如果你在回調(diào)里直接做耗時(shí)同步操作很容易把整個(gè) DCM 狀態(tài)機(jī)卡死。2.3 刷寫流程和 DTC 響應(yīng)邏輯刷寫編程是 UDS 里最復(fù)雜的場(chǎng)景。標(biāo)準(zhǔn)流程是10 02進(jìn)入編程會(huì)話 →27 01/02安全解鎖 →31 01 FF 00擦除應(yīng)用區(qū) →34 數(shù)據(jù)格式 地址 長(zhǎng)度請(qǐng)求下載 →36 塊序列號(hào) 數(shù)據(jù)一幀一幀傳 →37結(jié)束傳輸 → 例程執(zhí)行校驗(yàn)和 →11 01復(fù)位。這個(gè)流程里最容易錯(cuò)的是 0x36 的塊序列號(hào)Block Sequence Counter它從 0x01 開始每發(fā)一幀加一DCM 收到后必須校驗(yàn)連續(xù)性不連續(xù)就回7F 36 73校驗(yàn)錯(cuò)誤。驅(qū)動(dòng)包一般會(huì)把這個(gè)計(jì)數(shù)器放在內(nèi)部狀態(tài)里你只需要保證底層 CAN 接收順序別亂就可以。DTCDiagnostic Trouble Code部分0x19 讀故障碼、0x14 清故障碼表面上簡(jiǎn)單實(shí)際 DTC 的狀態(tài)位當(dāng)前故障、已確認(rèn)、歷史故障等是 DEMDiagnostic Event Manager模塊維護(hù)的。驅(qū)動(dòng)包只負(fù)責(zé)解析協(xié)議和搬運(yùn)數(shù)據(jù)真正的 DTC 狀態(tài)管理還得靠應(yīng)用層或者另一個(gè)組件。如果你拿到的包里沒帶 DEM那就只能自己做 DTC 狀態(tài)位。2.4 P2/P2* 定時(shí)與 0x78 響應(yīng)待定診斷儀和 ECU 之間有一個(gè)時(shí)序約定ECU 收到請(qǐng)求后要在 P2 時(shí)間內(nèi)給出響應(yīng)典型值是 50ms。如果某個(gè)服務(wù)處理時(shí)間超過 P2ECU 必須在 P2 超時(shí)前先回復(fù)7F SID 78響應(yīng)待定告訴診斷儀“我還在處理”然后繼續(xù)干活。最終響應(yīng)必須在 P2*典型值 5000ms內(nèi)給出。這個(gè)機(jī)制是幾乎所有協(xié)議棧實(shí)現(xiàn)里最容易出 Bug 的地方。比如你的 Flash 擦除要 300msDCM 在這 300ms 里一直阻塞那它根本沒機(jī)會(huì)發(fā) 0x78。好的驅(qū)動(dòng)包會(huì)要求應(yīng)用層不要阻塞住協(xié)議?;蛘咛峁┮粋€(gè)獨(dú)立接口讓應(yīng)用層主動(dòng)發(fā)送 0x78。實(shí)際配置時(shí)P2 和 P2* 不是隨便填的要看整車診斷規(guī)范很多 OEM 對(duì)這兩個(gè)時(shí)間有硬性要求。3. 實(shí)際接入把協(xié)議棧跑起來的完整流程說了這么多機(jī)制接下來講我怎么把這個(gè)包接到一個(gè) MCU 項(xiàng)目里的。這里只說通用流程適配具體 MCU 時(shí)接口名可能有差異但思路完全一致。3.1 解包后先看什么拿到 zip 后不要急著往工程里塞文件。先看目錄結(jié)構(gòu)一個(gè)規(guī)范的驅(qū)動(dòng)包大概長(zhǎng)這樣dcm_driver/ ├── doc/ # 集成手冊(cè)、配置指南 ├── src/ │ ├── dcm_core.c # DCM 核心狀態(tài)機(jī) │ ├── dcm_uds.c # UDS 服務(wù)分發(fā) │ ├── dcm_cfg.c # 配置表服務(wù)表、DID表、會(huì)話矩陣 │ ├── dcm_cbk.c # 應(yīng)用回調(diào)默認(rèn)空實(shí)現(xiàn) │ └── dcm_adapter.c # 底層適配CAN 收發(fā)、定時(shí)器、存儲(chǔ) ├── inc/ │ ├── dcm_cfg.h # 宏定義配置 │ └── dcm_api.h # 對(duì)外接口聲明 └── test/ └── dcm_test.c # 自測(cè)用例我一般先打開dcm_api.h看接口清單里有沒有這幾個(gè)關(guān)鍵函數(shù)Dcm_Init、Dcm_MainFunction、Dcm_RxIndication、Dcm_TxConfirmation、Dcm_TriggerTransmit。這五個(gè)是協(xié)議棧和外部世界打交道的“命門”。如果缺某個(gè)函數(shù)說明這個(gè)包的底層適配方式和我理解的不一樣得看集成手冊(cè)確認(rèn)。3.2 底層對(duì)接三板斧第一板斧是 CAN 接收。你的 CAN 驅(qū)動(dòng)收到一幀診斷報(bào)文后要調(diào)用Dcm_RxIndication把數(shù)據(jù)交給協(xié)議棧。如果你用的是 AUTOSAR CanIf PduR那這個(gè)調(diào)用鏈已經(jīng)現(xiàn)成了如果是裸機(jī) CAN 中斷你需要在中斷服務(wù)程序里把報(bào)文拷出來再調(diào)Dcm_RxIndication。第二板斧是周期調(diào)度。DCM 不是完全事件驅(qū)動(dòng)的它需要一個(gè)周期任務(wù)來跑超時(shí)管理和 P2 計(jì)時(shí)。一般在 1ms 或 5ms 的定時(shí)中斷里調(diào)用Dcm_MainFunction。注意這個(gè)調(diào)用的抖動(dòng)不能太大直接影響 P2 的精度。第三板斧是發(fā)送。協(xié)議棧組好響應(yīng)幀后會(huì)通過適配層的發(fā)送接口通知你發(fā)出去。有些包是回調(diào)方式DCM 調(diào)用Dcm_TriggerTransmit讓你從內(nèi)部緩沖區(qū)讀數(shù)據(jù)然后你自己調(diào)用 CAN 發(fā)送函數(shù)有些包是直接給一個(gè)函數(shù)指針讓你填 CAN 發(fā)送函數(shù)。發(fā)送完成后你還要調(diào)Dcm_TxConfirmation告知 DCM“這幀已經(jīng)發(fā)完了”否則它內(nèi)部的發(fā)送狀態(tài)機(jī)可能卡住。3.3 配置項(xiàng)逐條實(shí)戰(zhàn)以我接入的一個(gè)項(xiàng)目為例dcm_cfg.h里的關(guān)鍵配置長(zhǎng)這樣#define DCM_CAN_REQUEST_ID 0x7E0u /* 物理請(qǐng)求 ID */ #define DCM_CAN_RESPONSE_ID 0x7E8u /* 物理響應(yīng) ID */ #define DCM_CAN_FUNC_REQUEST_ID 0x7DFu /* 功能尋址請(qǐng)求 ID */ #define DCM_CAN_ID_TYPE DCM_CAN_ID_STANDARD #define DCM_P2_TIMEOUT_MS 50u /* P2 超時(shí) */ #define DCM_P2_STAR_TIMEOUT_MS 5000u /* P2* 超時(shí) */ #define DCM_S3_TIMEOUT_MS 5000u /* 會(huì)話超時(shí) */ #define DCM_MAX_DIAG_LEN 4096u /* 刷寫緩沖區(qū)長(zhǎng)度 */重點(diǎn)說兩個(gè)P2 和 P2* 的取值我上面寫的是常見值但實(shí)際項(xiàng)目里客戶規(guī)范可能寫 25ms 2000ms或者 100ms 5000ms一定要以診斷規(guī)范為準(zhǔn)。DCM_MAX_DIAG_LEN這個(gè)值決定了 0x34 刷寫時(shí)一個(gè)邏輯塊能有多大。它至少比你單包發(fā)送的數(shù)據(jù)量大一個(gè)數(shù)量級(jí)否則 0x36 連續(xù)傳幾包就可能緩沖區(qū)溢出。還有一個(gè)容易被忽略的配置ECU 地址和診斷儀地址。有些規(guī)范里響應(yīng) ID 不是請(qǐng)求 ID 8而是固定的另一組 ID。你必須在dcm_cfg.c里確認(rèn)請(qǐng)求/響應(yīng) ID 和 OEM 給的診斷矩陣一致。3.4 用 CAN 工具聯(lián)調(diào)從報(bào)文到應(yīng)用接入完成后我用 CANoe 或 PCAN 發(fā)診斷報(bào)文驗(yàn)證。最簡(jiǎn)單的一個(gè)測(cè)試是發(fā)10 02進(jìn)擴(kuò)展會(huì)話期望回復(fù)50 02。如果你看到的是7F 10 12說明 0x10 服務(wù)里 0x02 子功能沒使能去dcm_cfg.c的服務(wù)表里確認(rèn)。一個(gè)真實(shí)的請(qǐng)求/響應(yīng)報(bào)文示例發(fā)送: 02 10 02 00 00 00 00 00 接收: 02 50 02 00 00 00 00 00解析一下第一個(gè)字節(jié)0x02是 PCI 類型 長(zhǎng)度表示這是一個(gè)單幀后續(xù)診斷數(shù)據(jù)長(zhǎng)度為 2 字節(jié)。接著是0x10SID0x02是子功能。響應(yīng)同理0x50是請(qǐng)求 SID 加 0x40 的“正響應(yīng)”標(biāo)志。如果發(fā)22 F1 90讀 VIN而你的 DID 回調(diào)還沒實(shí)現(xiàn)你會(huì)收到7F 22 31請(qǐng)求超出范圍或者直接超時(shí)——超時(shí)通常是因?yàn)檎?qǐng)求已進(jìn)入 DCM 但應(yīng)用回調(diào)沒返回結(jié)果和協(xié)議棧本身無關(guān)。這一步的調(diào)試經(jīng)驗(yàn)是先把所有服務(wù)的回調(diào)函數(shù)都做成“至少回一個(gè)固定的正響應(yīng)數(shù)據(jù)”這樣新接一個(gè)包時(shí)可以快速區(qū)分問題在協(xié)議棧還是應(yīng)用層。4. 常見問題與排查技巧實(shí)錄接入過程中我積累了一些排查套路這里整理成問題速查很多是常規(guī)文檔里不會(huì)寫的細(xì)節(jié)。4.1 無響應(yīng)或回環(huán)失敗先別懷疑協(xié)議棧診斷儀發(fā)10 03ECU 完全沒反應(yīng)。這個(gè)現(xiàn)象我遇到太多了80% 不是協(xié)議棧的問題而是底層鏈路。優(yōu)先檢查三件事CAN 收發(fā)器是否進(jìn)了正常模式、CAN 濾波器是否放行了診斷 ID、接收中斷是否正確調(diào)用了Dcm_RxIndication。CAN 濾波器是很隱蔽的坑。很多芯片默認(rèn)過濾器只允許特定 ID如果你忘了把0x7E0和0x7DF配進(jìn)過濾器DCM 根本收不到請(qǐng)求。我建議第一步在 CAN 接收中斷里加一個(gè)斷點(diǎn)或計(jì)數(shù)變量確認(rèn)物理層有沒有收到幀。如果收到幀但協(xié)議棧不回包再查Dcm_RxIndication調(diào)用路徑。4.2 0x27 安全訪問反復(fù)失敗現(xiàn)象是診斷儀發(fā)27 01能收到種子但發(fā)27 02一直回7F 27 35。三個(gè)排查點(diǎn)Key 算法是不是真的掛到回調(diào)上了。有些包默認(rèn)回調(diào)返回全 0你發(fā)什么 Key 都錯(cuò)。種子字節(jié)序。MCU 是小端診斷儀和協(xié)議規(guī)范可能是大端你拿到的算法可能要求反轉(zhuǎn)。這個(gè)坑非常常見種子從Dcm_GetSeed出來是原始數(shù)組Key 算法內(nèi)部如果做了大小端轉(zhuǎn)換一定要和規(guī)范對(duì)齊。嘗試次數(shù)限制。失敗次數(shù)達(dá)到上限常見 3 次后ECU 會(huì)在一段時(shí)間內(nèi)拒絕任何27 02哪怕 Key 對(duì)的也回7F 27 36。調(diào)試時(shí)把這個(gè)限制先放寬或者延時(shí)等待計(jì)數(shù)器清零。4.3 刷寫到一半失敗0x36 的序列號(hào)和地址對(duì)齊刷寫時(shí)最容易看到7F 36 72一般編程失敗或7F 36 73校驗(yàn)錯(cuò)誤。我遇到過的原因排序0x34 請(qǐng)求的地址不是 Flash 扇區(qū)對(duì)齊地址、0x36 的總字節(jié)數(shù)和 0x34 聲明的長(zhǎng)度不一致、Flash 驅(qū)動(dòng)在寫入時(shí)發(fā)生了 ECC 錯(cuò)誤。排查方法是把每幀 0x36 的塊序列號(hào)、數(shù)據(jù)長(zhǎng)度、當(dāng)前累計(jì)長(zhǎng)度打日志和診斷儀發(fā)出來的對(duì)比。另外注意 0x34 里的地址是 4 字節(jié)高位字節(jié)序要和 MCU 端解析一致否則 DCM 把邏輯地址解出來就是錯(cuò)的。還有一個(gè)經(jīng)驗(yàn)刷寫數(shù)據(jù)緩沖區(qū)如果是靜態(tài)分配的要考慮 CAN FD 和經(jīng)典 CAN 的幀長(zhǎng)差異。經(jīng)典 CAN 單幀最多 8 字節(jié)UDS 單幀帶 PCI 后有效數(shù)據(jù)只有 6 字節(jié)CAN FD 能到 64 字節(jié)有效載荷 62 字節(jié)。同一個(gè)包如果同時(shí)支持兩種幀類型緩沖區(qū)最小長(zhǎng)度和分幀狀態(tài)機(jī)都要按 CAN FD 來配否則高負(fù)載時(shí)直接 buffer overflow。4.4 0x78 響應(yīng)待定發(fā)不出去看門狗和任務(wù)優(yōu)先級(jí)有些耗時(shí)服務(wù)需要在代碼里顯式調(diào)用發(fā)送 0x78 的接口然后返回結(jié)果我發(fā)現(xiàn)很多人把耗時(shí)操作放在Dcm_MainFunction的調(diào)用線程里同步執(zhí)行導(dǎo)致觸發(fā)看門狗復(fù)位或者 P2* 都超時(shí)了還沒發(fā)最終響應(yīng)。正確做法是應(yīng)用層回調(diào)里如果是耗時(shí)操作先把結(jié)果狀態(tài)記下來立即返回一個(gè)“處理中”標(biāo)志DCM 發(fā)現(xiàn)這個(gè)標(biāo)志后會(huì)替你在 P2 超時(shí)前發(fā) 0x78然后你把耗時(shí)操作放到后臺(tái)任務(wù)或中斷里慢慢跑跑完了調(diào)用Dcm_RoutineResult之類的接口上報(bào)最終結(jié)果。如果你拿到的包沒有這種異步機(jī)制那就只能在應(yīng)用回調(diào)里自己發(fā)完 0x78 再執(zhí)行耗時(shí)邏輯但這樣要確保發(fā)送函數(shù)不會(huì)被自己的阻塞卡住否則同樣發(fā)不出去。4.5 NRC 速查表最后給一張我在調(diào)試時(shí)經(jīng)常對(duì)照的 NRC 表保留下來能省不少翻規(guī)范的時(shí)間NRC含義常發(fā)原因0x11服務(wù)不支持SID 沒注冊(cè)0x12子功能不支持子功能不在允許列表0x13消息長(zhǎng)度錯(cuò)誤請(qǐng)求長(zhǎng)度與服務(wù)定義不符0x22條件不滿足當(dāng)前會(huì)話/安全等級(jí)不滿足0x31請(qǐng)求超出范圍DID 不存在或參數(shù)超范圍0x33安全訪問被拒絕未解鎖就執(zhí)行受限服務(wù)0x35密鑰錯(cuò)誤0x27 的 Key 不對(duì)0x36嘗試次數(shù)超限安全訪問失敗次數(shù)過多0x72一般編程失敗Flash 擦寫失敗0x73校驗(yàn)錯(cuò)誤0x36 序列號(hào)不連續(xù)或長(zhǎng)度不符0x78響應(yīng)待定處理中不是錯(cuò)誤調(diào)試時(shí)記住一個(gè)原則DCM 給你回 NRC說明請(qǐng)求已經(jīng)正確進(jìn)入?yún)f(xié)議棧了問題在“服務(wù)級(jí)”如果完全沒回包問題在“鏈路級(jí)”。先把鏈路級(jí)問題排查干凈再對(duì)著 NRC 表追服務(wù)級(jí)問題效率會(huì)高很多。我在實(shí)際項(xiàng)目里養(yǎng)成的習(xí)慣是接到這類協(xié)議棧驅(qū)動(dòng)包先建一個(gè)最小測(cè)試工程把 0x10、0x22、0x3E 三個(gè)服務(wù)調(diào)通然后再逐步打開 0x27 和刷寫流程。這樣每一步失敗都能快速定位不會(huì)一堆問題混在一起無從下手。最后再分享一個(gè)小技巧P2、P2*、S3 這些超時(shí)參數(shù)千萬不要只寫在頭文件宏定義里最好做成可通過標(biāo)定修改的變量因?yàn)檎嚶?lián)調(diào)階段 OEM 隨時(shí)可能改時(shí)間參數(shù)到時(shí)候不用改代碼重新燒錄。本文還有配套的精品資源點(diǎn)擊獲取