一台車上有幾十顆電子控制模組(ECU),各管一塊:引擎、煞車、車門、儀表、影音、通訊……它們再用 CAN、LIN、車用乙太網路串在一起。這篇分兩部分:前半是我整理的車內 ECU 圖鑑(38 種模組的位置、功能、別名、維修重點,加上車內網路和故障碼字首);後半是到一座車廠測試台上實作,用 PCAN 和 Python 找出台上實際有哪些 ECU、讀出它們的名稱和版本,最後試著假扮 ESP 讓儀表顯示車速(沒成功,但學到很多)。
說明:測試台是工作上的開發設備,文中不寫車廠、車型、料號與 VIN,也不放實機照片,只寫通用的做法和觀察。
一、車上有哪些 ECU:互動圖鑑
起點是一份介紹 ECU 種類的影片簡報,裡面列了 BCM、PDM、ACM、EVIC、SM、RSM、IC、DDM、HSM、MSM、APM、SKIM、TCM、CAB、PCM 這 15 個縮寫(都是某美系車廠的叫法)。光看縮寫很難記,所以我把它擴充成一個單頁網頁圖鑑:自己畫一台 SUV 線稿,把 38 種模組標在大致的位置上,點編號就在右邊看說明。
顏色依領域分,借用車用線束的顏色慣例當記憶點:安全氣囊的線束和接頭本來就是黃色,電動車高壓線是橘色。上方的按鈕可以只看某一個領域:
| 領域 | 代表模組 | 一句話 |
|---|---|---|
| 動力 | ECM/PCM、TCM | 引擎噴油點火、變速箱換檔 |
| 底盤 | ABS/ESC、EPS、EPB、TPMS、SAS | 煞車、轉向、手煞車、胎壓、轉向角 |
| 車身 | BCM、IC、HVAC、DDM/PDM、MSM、SKIM、ELV、EVIC、RSM、SM | 燈光門鎖、儀表、空調、車門、座椅、防盜、轉向柱鎖、頂置控制台、雨量、天窗 |
| 被動安全 | ACM/ACU、OCS | 氣囊、判斷前乘客座有沒有人 |
| 資訊娛樂 | IVI、AMP、TCU | 影音主機、擴大機、4G/5G 車載通訊 |
| 駕駛輔助 | 前視攝影機、前雷達、盲點雷達、停車輔助、ADAS 域控制器 | 車道維持、主動巡航、AEB |
| 電動高壓 | VCU、BMS、MCU/逆變器、OBC、DC-DC | 整車控制、電池管理、馬達、充電、取代發電機 |
| 網路 | CGW 中央閘道、OBD-II 診斷座 | 網段之間轉送、診斷入口 |
名詞卡片可以搜尋縮寫、英文或中文。同一種模組在不同車廠名字常常不一樣,例如車身控制模組 BCM 在 Benz 叫 SAM、在 BMW 叫 FEM/BDC、在 Volvo 叫 CEM,卡片的「別名」欄都列出來了;另外像 TCU 同時是「變速箱控制」和「車載通訊」的縮寫,也特別註明:
二、模組之間怎麼溝通
模組依領域掛在不同網段上,中央閘道(Gateway)負責在網段之間轉送需要的訊號,也是 OBD-II 診斷的入口。車窗、座椅馬達、雨量感知器這類便宜的小東西,多半掛在 BCM 或車門模組底下的 LIN 子匯流排:
- CAN(ISO 11898):CAN-H/CAN-L 雙絞線,兩端各一顆 120 Ω 終端電阻;在 OBD 座量 pin 6 和 14 之間約 60 Ω 就表示兩顆都在。動力常見 500 kbit/s,車身 125~500 kbit/s。
- CAN FD:資料段可加速到 2~5 Mbit/s、每框 64 位元組,底盤和刷寫韌體用得多。
- LIN:單線 19.2 kbit/s、一主多從,最便宜。
- FlexRay:10 Mbit/s、時間觸發、雙通道備援,高階車的底盤線控;後半段實作會遇到它。
- 車用乙太網路(100/1000BASE-T1):一對雙絞線就能跑,攝影機、ADAS、影音主機和新一代骨幹網路用。
- UDS(ISO 14229):統一診斷服務,修車廠的診斷電腦就是用它跟模組對話;後面掃描用到的是 0x22「讀資料」和 0x19「讀故障碼」。
三、故障碼開頭字母
四、實作:在測試台上找出有哪些 ECU
手邊有一座車廠的測試台:一個鐵架上裝著一部分真車模組(閘道、兩顆車身控制模組、轉向柱鎖、駕駛側車門模組、數位儀表、影音主機、車載通訊盒),背板把每條網段拉成一個 DB9 孔,標著「車身」「儀表」「資訊娛樂」「通訊」「診斷」……還有 FlexRay 和 LIN。電腦端用 PCAN-USB 加 python-can,全部用 SSH 遠端操作。
第 1 步:只聽不送,看匯流排上有誰
PCAN 先插在車身 CAN 的孔,聽 15 秒,統計每個 CAN ID 出現幾次、週期多長:
import can, time, collections
bus = can.Bus(interface="pcan", channel="PCAN_USBBUS1", bitrate=500000)
seen = collections.Counter(); t0 = time.time()
while time.time() - t0 < 15:
m = bus.recv(0.5)
if m is not None and not m.is_error_frame:
seen[(m.arbitration_id, m.is_extended_id)] += 1
bus.shutdown()
for (i, ext), n in sorted(seen.items()):
print(f"{i:08X}" if ext else f"{i:03X}", n, f"{15000 / n:.0f} ms")
結果是 85 個 ID、每秒約 270 框。光看 ID 不知道誰送的,但裡面有一組很有用的訊框:網路管理(NM)訊框。每顆醒著的模組會定期廣播「我在線上」,這個平台的 NM 訊框 ID 是固定前綴加上一個位元組的節點號,所以把前綴相同的挑出來,就知道這條匯流排上有 4 個節點。後來 UDS 掃描證實:節點號正好等於該模組診斷位址的低位元組。
第一個坑:隔了一陣子再跑,2 秒內一框都沒有,送出去的請求也沒人回 ACK,PCAN 報「錯誤計數達到警告值」。原因是測試台睡了:點火(KL15)關掉、車門也沒動之後,網路管理會讓整條 CAN 進入休眠,完全安靜。按一下啟動鈕就醒了。之後的掃描程式都先聽 2 秒,確認有訊框才開始。
第 2 步:UDS 掃描,問每個位址「你是誰」
每顆模組都有一組診斷用的 CAN ID:一個收請求、一個回答案。掃描就是對 11-bit 的 0x700~0x7EF、以及 29-bit 的一段位址,逐一送 22 F1 97(ReadDataByIdentifier,讀「系統名稱」),有回 62 F1 97 … 的就是存在的模組;再對它們讀料號(F187)、軟體版本(F189)、硬體料號與版本(F191、F1A3)、VIN(F190)。只用讀取服務,不切診斷模式、不寫入、不清碼。
名稱通常超過 7 個位元組,回應會用 ISO-TP 分成多框:收到首框(PCI 0x1_)要立刻回一個 Flow Control(30 00 00),模組才會繼續送連續框(0x2_)。另外模組可能先回 7F 22 78(處理中,請等),這時要把等待時間拉長,不能當成失敗:
def read_did(req, ext, did, timeout=0.2):
send(req, ext, [0x03, 0x22, did >> 8, did & 0xFF])
t_end, buf, total, rid = time.time() + timeout, b"", 0, None
while time.time() < t_end:
m = bus.recv(0.02)
if not is_response(m, req, ext):
continue
d = bytes(m.data); pci = d[0] >> 4
if pci == 0: # 單框
p = d[1:1 + (d[0] & 0x0F)]
if p[:1] == b"\x7f" and p[2:3] == b"\x78": # 處理中,多等一下
t_end = time.time() + 2; continue
return m.arbitration_id, p
if pci == 1: # 首框:回 Flow Control
total = ((d[0] & 0x0F) << 8) | d[1]; buf, rid = d[2:], m.arbitration_id
send(req, ext, [0x30, 0x00, 0x00]); t_end = time.time() + 1
elif pci == 2 and m.arbitration_id == rid: # 連續框
buf += d[1:]
if len(buf) >= total:
return rid, buf[:total]
return None, None
每個位址等 150~200 ms,全部掃完約 1~2 分鐘。我把掃描程式丟到測試電腦上用排程工作自己跑、結果寫成文字檔再取回,因為那台筆電的 Wi-Fi 很不穩(螢幕一合上,天線收訊就掉到快斷線),SSH 撐不了整段掃描。
第 3 步:接車身 CAN 和接診斷孔,掃到的不一樣
| 模組 | 接車身 CAN | 接診斷孔 | 原因 |
|---|---|---|---|
| 轉向柱鎖、駕駛側車門模組、第二顆車身控制模組 | 有回 | 有回 | 本來就掛在車身 CAN 上,直接問得到 |
| 中央閘道 | 不回(只看得到它的 NM 訊框) | 有回 | 閘道只在診斷那一側回應診斷請求 |
| 數位儀表、影音主機、車載通訊盒 | 看不到 | 有回 | 在別的網段,要靠閘道從診斷孔轉送過去 |
| 第一顆車身控制模組 | 看不到 | 有回 | 掛在另一條匯流排 |
| 方向盤按鍵、頂置控制台、上下觸控螢幕 | — | 不回 | LIN 從屬節點或掛在主機後面,不直接回診斷 |
所以接車身 CAN 只找到 3 顆,接診斷孔找到 8 顆。結論很實用:要盤點模組、讀版本、讀故障碼,就接診斷孔;要監聽或模擬車身訊號(例如開關車門、點火狀態),就接那條網段本身,因為閘道不會把這類週期訊息從診斷孔轉進去。最後測試台上接了兩支 PCAN,一支固定接診斷孔、一支固定接車身 CAN,用 PCAN_USBBUS1/2 分開,診斷和測試腳本互不干擾。
這個平台的 11-bit 診斷 ID 有固定規律:回應 ID=請求 ID+固定差值,例如請求 0x74A 就從 0x7B4 回來。有趣的是,有兩顆模組在位址 0x700、0x701 也會「順便」回一次,掃描結果要用回應 ID 去重,才不會多算。掃描結果還有一個用途:通訊盒讀到的軟體版本,剛好和刷寫資料夾裡的軟體包版本一致,以後刷寫前後跑一次掃描,就能確認版本有沒有換成功。
第 4 步:驗證 E2E 保護(AUTOSAR CRC8)
和安全相關的 CAN 訊息(點火狀態、車速、煞車……)每一框都帶 E2E 保護:第 0 個位元組是 CRC,第 1 個位元組的低 4 位是 0~15 循環的計數器。接收端只要 CRC 對不上就整框丟掉,所以想自己送這類訊息,一定要先把 CRC 算對。
常見的做法是 AUTOSAR 的 CRC8(多項式 0x2F、初值和最後 XOR 都是 0xFF),對第 1~7 位元組做 CRC,最後再多餵一個「Data ID」位元組,每種訊息的 Data ID 不同。怎麼確定這個平台也是這樣算?拿監聽到的點火狀態訊框來反推:0~255 每個 Data ID 都試一次,看哪一個算出來的 CRC 跟實際的第 0 個位元組一樣:
def crc8_autosar(data):
crc = 0xFF
for b in data:
crc ^= b
for _ in range(8):
crc = ((crc << 1) ^ 0x2F) & 0xFF if crc & 0x80 else (crc << 1) & 0xFF
return crc ^ 0xFF
def e2e_crc(frame, data_id): # frame[0] 是 CRC,算 frame[1:] 加 Data ID
return crc8_autosar(bytes(frame[1:]) + bytes([data_id]))
for f in captured_frames: # 兩框計數器不同的真實訊框
print(f.hex(), [hex(i) for i in range(256) if e2e_crc(f, i) == f[0]])
兩框(計數器 11 和 13)都只有同一個 Data ID 對得上,而且跟開源社群公開的同平台實作一致,表示演算法確定沒錯。這招很好用:只要有一框真實訊息,就能確認 CRC 算法和那個訊息的 Data ID。
第 5 步:假扮 ESP 讓儀表顯示車速(失敗)
測試台上沒有引擎和 ESP,所以儀表一直顯示 0 km/h,下面亮一排煞車、ABS、胎壓、氣囊的故障燈。我把一支 PCAN 改插「儀表」那條網段,先聽:99 個 ID,裡面沒有任何 10~20 ms 的動力或車速訊息,表示沒有人在送車速,我送的話不會跟人搶。接著用開源資料裡同集團另一個平台的車速訊息格式(ID、位元位置、0.01 km/h 單位)加上算好的 CRC 和計數器,每 20 ms 送一框:
- 車速 0→120 km/h 來回兩趟:儀表沒動。
- 車速固定 60 km/h,把「車速有效旗標」從 0 試到 7,每種 5 秒:儀表還是沒動。
每次都要人盯著儀表很沒效率,所以換一個能自動判斷的方法:一邊送車速、一邊用 UDS 0x19 讀儀表自己的故障碼,比對送之前、送的時候、停止後。儀表有 83 筆故障碼,其中 30 筆是作用中的「收不到某某訊號」;如果我送的訊息被收下,對應那筆的「目前失敗」狀態位元應該會在幾秒內清掉,CRC 錯的話反而可能多出一筆「訊號無效」:
base = read_dtcs() # {DTC: 狀態位元組}
start_sending_speed() # 背景執行緒每 20 ms 送一框
time.sleep(8)
during = read_dtcs()
for k in sorted(set(base) | set(during)):
if base.get(k) != during.get(k):
print("變化", k, base.get(k), "→", during.get(k))
結果三次都是 83 筆、作用中 30 筆,一筆都沒變:儀表完全不理這個訊息。推測原因:這個平台的 ESP 和動力系統在實車上走 FlexRay,由閘道整理後再轉成儀表 CAN 上的訊息,轉過來的 ID、格式和 Data ID 都不一定跟別的平台一樣。沒有車廠的通訊矩陣(K-Matrix,DBC/ARXML),只能亂槍打鳥,所以先停在這裡。另外拍照才發現,這台是電動車版儀表,左邊那顆其實是功率表(READY/POWER),本來就沒有轉速可以顯示。
學到的幾件事
- 先聽再送:NM 訊框就能數出網段上有幾顆模組,節點號還能對到診斷位址。
- 匯流排完全安靜、送出去沒人 ACK,先懷疑測試台在休眠,不是線接錯。
- 盤點模組接診斷孔,模擬車身訊號接該網段;兩支 PCAN 分工最省事。
- E2E CRC 可以用一框真實訊息反推驗證;但不同訊息、不同平台的 Data ID 不能套用。
- 想讓儀表顯示數值,沒有 K-Matrix 很難;邊送邊比對故障碼,比盯著儀表有效率。
延伸閱讀:用 PCAN 在 ATS 操作台讀 OBD-II 行車電腦模擬器、把車速餵給 Geotab IOX 車機(更早的 PeakCAN 與 ECU Simulator 通訊 系列)(OBD-II 是標準化的診斷,跟這篇的 UDS 掃描是同一套 ISO-TP 底層)、ATS 操作台的 CAN 介面。




沒有留言:
張貼留言