UDS Reflash 序列:0x10 → 0x27 → 0x31 → 0x34 / 0x36 / 0x37 完整實作
詳解 UDS (ISO 14229) 燒錄完整序列:0x10 session 切換、0x27 Security Access、0x31 erase routine、0x34/0x36/0x37 區塊下載與 0x11 ECU Reset 的實作細節與常見 NRC。
為什麼 reflash 需要這條完整序列
ECU reflash 不是「把一段 binary 寫進 flash」這麼簡單。bootloader 必須先確認:對方是合法工具、應用程式已被妥善停用、flash 已被抹除、待寫入的位址範圍與大小被正確協商、每一段 payload 的順序沒有遺漏,最後資料完整性也經過 CRC 驗證。UDS (ISO 14229) 把這整個流程以服務 ID 的方式標準化,跑在 ISO-TP (ISO 15765-2) 之上,讓不同 OEM、不同工具、不同 ECU 都能以同一套流程互通。本篇按照 KITE Reflasher 在實機上實際執行的順序,逐步拆解 0x10 → 0x27 → 0x31 → 0x34 / 0x36 / 0x37 → 0x31 → 0x11 這條完整序列。
0x10 Diagnostic Session Control:從 default 到 programming
序列的第一步是切換到具備燒錄權限的 session。一般作法是先進入 extended diagnostic session (0x03) 做前置準備(如關閉非診斷通訊),再切到 programming session (0x02),這時 ECU 會跳到 bootloader 端執行。
# 進 extended session
Tx: 02 10 03 00 00 00 00 00
Rx: 06 50 03 00 32 01 F4 00 ; P2=50ms, P2*=5000ms
# 進 programming session
Tx: 02 10 02 00 00 00 00 00
Rx: 06 50 02 00 32 01 F4 00
切換完成後,KITE Reflasher 會啟動 TesterPresent (0x3E) 週期性回報,以避免 P2 timeout。
0x27 Security Access:seed → DLL → key
進入 programming session 後,bootloader 不會接受任何寫入要求,直到 Security Access 完成。流程是:工具向 ECU 要 seed (0x27 01),拿到 seed 後交給外部 DLL 計算對應的 key,再以 0x27 02 送回。
# 要 seed
Tx: 02 27 01 00 00 00 00 00
Rx: 06 67 01 11 22 33 44 00 ; seed = 11 22 33 44
# (本機) 呼叫 compute_key_from_dll(seed) → key
# 送 key
Tx: 06 27 02 AA BB CC DD 00
Rx: 02 67 02 00 00 00 00 00
KITE Reflasher 透過 libloading 載入 DLL,並支援兩種匯出慣例:KopherBit 的 computeKeyFromSeed 與 Vector CANoe 相容的 GenerateKeyEx。多數 OEM 提供其中一種。
0x31 Routine Control:pre-programming、erase、check、CRC
0x31 是一個服務 ID 但用途多元,bootloader 通常會註冊四到六個 routine identifier (RID),例如:
Pre-programming— 應用程式先做關機/saveNV 動作。Dependency check— 確認硬體版本、Bootloader 版本相容。Erase memory— 由工具指定起始位址與長度進行抹除。Check programming dependencies— 寫完後檢查映像完整性。CRC check— 對下載完的區段做 CRC 比對。
以 erase 為例(子功能 01 = startRoutine,RID FF 00,參數為 address[4] | size[4]):
Tx: 0F 31 01 FF 00 00 02 00 00 00 00 80 00 00 00 00 00
Rx: 04 71 01 FF 00 00 00 00 ; routine 完成,no data
抹除耗時較長,bootloader 可能回 0x78 (RequestCorrectlyReceived-ResponsePending) 暫拖時間,此時工具不可放棄、必須等待最終的 positive response。
0x34 RequestDownload:位址、大小、format identifier
接著要協商每一段 download。0x34 帶有:
- dataFormatIdentifier:高 nibble = 壓縮、低 nibble = 加密(通常是
0x00)。 - addressAndLengthFormatIdentifier:高 nibble = size 長度、低 nibble = address 長度,常見
0x44表示 4 + 4 bytes。 - memoryAddress(4 bytes)+ memorySize(4 bytes)。
# 在 0x00020000 寫入 0x00010000 bytes
Tx: 0B 34 00 44 00 02 00 00 00 01 00 00
Rx: 04 74 20 04 02 00 ; lengthFormatId=0x20, maxBlockLen=0x0402
ECU 回覆中的 maxNumberOfBlockLength 告訴工具「下一階段每一段 TransferData 最多能裝多少 bytes(含 SID 與 BSC)」。KITE Reflasher 會以此為上限切割 payload。
0x36 TransferData:block sequence counter 與分段
每一段資料都用 0x36 送出,第一個 byte 是 block sequence counter (BSC),從 01 開始,每次 +1,溢位後回到 00。
Tx: ... 36 01 <payload bytes>
Rx: 02 76 01 00
Tx: ... 36 02 <payload bytes>
Rx: 02 76 02 00
...
如果 ECU 回覆 BSC 與工具預期不一致,代表中間掉包;此時應以原 BSC 重送。一個 block 必然由多個 ISO-TP CF (Consecutive Frame) 組成,工具端的 ISO-TP 層會自動處理 flow control(stmin / BS)。
0x37 RequestTransferExit:結束本段下載(可帶 CRC)
當這一段的全部資料送畢,送 0x37 結束。0x37 後面可以選擇性附帶 transferRequestParameter,常見作法是傳入工具端算好的 CRC-32,讓 bootloader 確認接收到的 binary 正確。
# 不帶參數
Tx: 01 37 00 00 00 00 00 00
Rx: 01 77 00 00 00 00 00 00
# 帶 CRC-32
Tx: 05 37 12 34 56 78 00 00 00
Rx: 01 77 00 00 00 00 00 00
如要寫多個 sector,則回到 0x34 重新協商下一段。
0x11 ECU Reset:套用新韌體
所有 segment 寫完、最後再以 0x31 執行 CRC check 與 dependency check 通過後,送 0x11 01 觸發 hard reset。bootloader 會跳回應用程式,reflash 完成。
Tx: 02 11 01 00 00 00 00 00
Rx: 02 51 01 00 00 00 00 00
HEX / S-record / VBF 在這條序列中的角色
工具端拿到的 HEX、S-record、VBF 並不是「一條線性的 binary」,而是多段帶位址的區塊(records)。KITE Reflasher 在進入 0x34 階段之前,會先做 memory-aware merge:
- 解析 HEX 的 ULBA + data record,或 S-record 的 S1/S2/S3,或 VBF 的 erase regions + data blocks。
- 依照使用者在
MemoryConfigPanel設定的 sector layout,把連續的 record 合併成單一 block。 - 空隙以
0xFF填充(Pad 旗標),避免下載階段過度分段。 - 合併後的 binary 同時以 Intel HEX 落地,作為 audit artifact。
這樣每個 0x34 對應一個合併後的 sector;0x36 則只是把該 sector 的 bytes 依 maxBlockLen 切片送出。
在 KITE Reflasher 上的實作:序列器逐步組合
在 UdsSequencerPanel 上,工程師逐項插入步驟:
Diagnostic Session Control(0x03)Diagnostic Session Control(0x02)Security Access(0x27 level=01)— 此步骤会自动呼叫compute_key_from_dllRoutine Control(0x31 startRoutine, RID=Pre-programming)Routine Control(0x31 startRoutine, RID=EraseMemory, params=addr|size)Download Step— 載入 .hex/.vbf,選取要寫入的區塊,工具自動展開為0x34 → 0x36×N → 0x37Routine Control(0x31 startRoutine, RID=CheckProgrammingDependencies)Routine Control(0x31 startRoutine, RID=CRCCheck)ECU Reset(0x11 01)
每一步驟在 UI 顯示 idle / running / success / error,執行迴圈可中止。整段流程儲存為 .udss,EOL 工裝可重播相同序列。
常見 NRC
只要哪一步出錯,bootloader 會以 7F <SID> <NRC> 回覆。最常見的是:
| NRC | 名稱 | 多半發生在 |
|---|---|---|
| 0x10 | generalReject | 服務組合錯誤 |
| 0x11 | serviceNotSupported | 工具發了 ECU 沒實作的 SID |
| 0x12 | subFunctionNotSupported | session 或 routine subfunction 不存在 |
| 0x22 | conditionsNotCorrect | 沒進 programming session 就送 0x34 |
| 0x33 | securityAccessDenied | DLL 算錯 key,或 level 不對 |
| 0x35 | invalidKey | seed 對應的 key 計算錯誤 |
| 0x37 | requiredTimeDelayNotExpired | Security Access 連錯後的延遲未到 |
| 0x71 | transferDataSuspended | 上一筆 0x36 還沒收齊 |
| 0x72 | generalProgrammingFailure | flash 寫入硬體錯誤 |
| 0x78 | requestCorrectlyReceivedResponsePending | erase / CRC 等耗時操作中,工具須繼續等待 |
0x78 不是錯誤 — 它是「請等我」的意思,工具要 reset P2 timer 並繼續等待最終的 positive response。在 KITE Reflasher 中,這由 ISO-TP 層自動處理,不會中斷整段序列。
結語
理解這條序列的關鍵不是任何單一 SID,而是步驟之間的依賴關係:沒有 0x10 進不到 0x27、沒有 0x27 進不到 0x31、沒有 0x31 erase 就直接 0x34 會被 NRC 0x22 擋下。把序列以可重現的方式留在 .udss 與 ASC 紀錄裡,bring-up 與量產驗證都能基於同一份 ground truth 進行。