J1939診斷應用層協議(SAE J1939-73)是重型車輛診斷的核心標準,它規范了故障碼(DTC)的格式、診斷報文的定義以及多包傳輸機制。本文結合協議原理與C++代碼示例,深入解析DM1報文的解析與發送過程。
![]()
1. J1939診斷應用層協議簡介
J1939的診斷應用層(對應SAE J1939-73標準)不僅覆蓋基礎的故障檢測功能,還涵蓋了監視系統、內存存取、數據轉換、引導載入、標定等一系列復雜的交互功能。其核心目的之一是標準化不同廠商、不同車型的診斷信息交互方式,避免重復開發。
這些功能通過不同類型的診斷報文(Diagnostic Message, DM) 實現,例如:
DM1 :當前激活故障碼
DM2 :歷史故障碼
DM3 :歷史故障碼清除/復位
DM11 :凍結幀數據
在深入代碼之前,需要理解J1939診斷的幾個核心術語。
2.1 SPN (可疑參數編號)
SPN用于標識具體的子系統、部件或故障對象(如發動機、傳感器)。它是一個19位的編號,前511個與SAE J1587兼容,之后的由SAE定義或廠商自定義。
2.2 DTC (診斷故障碼) 的構成
J1939的DTC結構與常見的UDS(3字節)不同,它由4個字段組成,共占4個字節:
字段
位數
描述
SPN
19位
指示故障發生在哪個部件/子系統
FMI
5位
故障模式指示器,描述故障類型(如電壓高、數據不穩定)
OC
7位
故障發生次數
CM
1位
SPN轉換方式(通常設為0,表示使用最新版本轉換)
2.3 PGN (參數組編號)
診斷報文在總線上通過PGN進行識別。例如,**DM1報文的PGN是65226 (0x00FECA)**。
3. 診斷應用示例:DM1報文(當前故障碼)
DM1是診斷中最核心的報文,用于周期性廣播當前處于激活狀態的故障碼。根據SAE J1939-73規范,當存在激活故障時,ECU必須每秒發送一次DM1報文;如果故障狀態發生變化,必須立即發送。
3.1 DM1報文格式
DM1報文數據域格式如下(Intel格式,小端模式):
Byte
參數
描述
1
指示燈狀態
Bit 7-6: MIL, Bit 5-4: 紅色停止燈, Bit 3-2: 琥珀色警告燈, Bit 1-0: 保護燈
2
預留
通常為0xFF
3-6
DTC
SPN (19位) + FMI (5位) + OC (7位) + CM (1位)
7-8
DTC
如果存在多個故障,繼續排列
3.2 C++ 代碼示例:解析DM1報文
以下代碼演示如何從CAN原始數據中提取DM1中的第一個DTC,并解析相關的指示燈狀態。
3.3 C++ 代碼示例:發送DM1報文#include
#include
// 定義DTC結構體
struct J1939_DTC {
uint32_t spn; // 可疑參數編號 (0 - 524287)
uint8_t fmi; // 故障模式標識 (0 - 31)
uint8_t oc; // 發生次數 (0 - 126)
bool cm; // 轉換方式 (通常為0)
};
// 解析DM1報文的函數
// data: 指向CAN數據域8字節的指針
void Parse_DM1(const uint8_t* data) {
// 1. 解析指示燈狀態 (Byte 1)
uint8_t lampStatus = data[0];
bool mil_on = (lampStatus >> 7) & 0x01; // Malfunction Indicator Lamp
bool red_stop_on = (lampStatus >> 5) & 0x01; // Red Stop Lamp
bool amber_warning_on = (lampStatus >> 3) & 0x01; // Amber Warning Lamp
bool protect_on = (lampStatus >> 1) & 0x01; // Protection Lamp
std::cout << "=== DM1 解析結果 ===" << std::endl;
std::cout << "MIL燈: " << (mil_on ? "點亮" : "熄滅") << std::endl;
std::cout << "紅色停止燈: " << (red_stop_on ? "點亮" : "熄滅") << std::endl;
std::cout << "琥珀色警告燈: " << (amber_warning_on ? "點亮" : "熄滅") << std::endl;
// 2. 解析第一個DTC (Byte 3-6)
// 注:J1939通常使用Intel格式,即低字節在前
uint32_t raw_spn_fmi = data[3] | (data[4] << 8) | (data[5] << 16) | (data[6] << 24);
J1939_DTC dtc;
// 提取SPN (低19位: bits 0-18)
dtc.spn = raw_spn_fmi & 0x7FFFF;
// 提取FMI (bits 19-23) -> 右移19位,取低5位
dtc.fmi = (raw_spn_fmi >> 19) & 0x1F;
// 提取CM (bit 24) -> 右移24位,取低1位
dtc.cm = (raw_spn_fmi >> 24) & 0x01;
// 提取OC (bits 25-31) -> 右移25位,取低7位
dtc.oc = (raw_spn_fmi >> 25) & 0x7F;
std::cout << "\n=== 當前激活故障碼 (DTC ) ===" << std::endl;
std::cout << "SPN: " << dtc.spn << " (部件標識)" << std::endl;
std::cout << "FMI: " << (int)dtc.fmi << " (故障模式)" << std::endl;
std::cout << "OC: " << (int)dtc.oc << " (發生次數)" << std::endl;
std::cout << "CM: " << dtc.cm << " (轉換方式)" << std::endl;
}int main() {
// 模擬總線接收到的一幀DM1報文
// 場景:發動機進氣歧管壓力(SPN 102)電壓低于正常值(FMI 4),發生2次
// 數據排列 (Intel 格式):
// Byte1: 指示燈(0x04) -> AMBER燈亮
// Byte2: 0xFF
// Byte3-6: SPN=102, FMI=4, OC=2, CM=0
// 計算 Raw Value: SPN(102) | (FMI(4)<<19) | (CM(0)<<24) | (OC(2)<<25)
// 結果 = 0x00040066 (小端存儲在內存中: 0x66, 0x00, 0x04, 0x00)
uint8_t simulated_dm1[8] = {0x04, 0xFF, 0x66, 0x00, 0x04, 0x00, 0xFF, 0xFF};
Parse_DM1(simulated_dm1);
return 0;
}
在實際ECU開發中,需要構造并發送DM1。以下示例展示如何打包一個DTC并發送(此處模擬傳輸層發送函數)。
4. 多包傳輸機制#include
#include
#include
// 模擬CAN發送函數(實際開發中替換為驅動層函數)
bool CAN_Send(uint32_t id, const uint8_t* data, uint8_t len) {
std::cout << "發送CAN ID: 0x" << std::hex << id << std::endl;
std::cout << "數據: ";
for (int i = 0; i < len; i++) {
std::cout << std::hex << (int)data[i] << " ";
}
std::cout << std::dec << std::endl;
return true;
}
// 發送DM1報文的函數
void Send_DM1(uint8_t lamp_status, const J1939_DTC* dtc_list, uint8_t count) {
uint8_t data[8];
memset(data, 0xFF, sizeof(data)); // 初始化填充0xFF
// 設置Byte 1: 指示燈狀態
data[0] = lamp_status;
// 設置Byte 2: 預留
data[1] = 0xFF;
if (count > 0 && dtc_list != nullptr) {
// 打包第一個DTC到 Bytes 3-6
// 構造32位原始DTC值: Bytes = SPN + (FMI<<19) + (CM<<24) + (OC<<25)
uint32_t raw_dtc = dtc_list[0].spn;
raw_dtc |= (dtc_list[0].fmi << 19);
raw_dtc |= ((dtc_list[0].cm ? 1 : 0) << 24);
raw_dtc |= (dtc_list[0].oc << 25);
// 寫入數組 (Intel格式: 低字節在低地址)
data[3] = raw_dtc & 0xFF;
data[4] = (raw_dtc >> 8) & 0xFF;
data[5] = (raw_dtc >> 16) & 0xFF;
data[6] = (raw_dtc >> 24) & 0xFF;
// 注意:若有多個DTC,需要利用傳輸層協議(TP)發送多包,此處僅演示單包
}
// DM1的PGN是65226,對應CAN ID通常為 0x18FECA00 + Source Address
uint32_t can_id = 0x18FECA00; // 假設源地址為0
CAN_Send(can_id, data, 8);
}int main() {
J1939_DTC active_dtc;
active_dtc.spn = 100; // 發動機機油壓力
active_dtc.fmi = 1; // 數據低于正常范圍
active_dtc.oc = 3; // 發生3次
active_dtc.cm = 0; // 標準轉換
// 點亮琥珀色警告燈
uint8_t lamp = 0x04; // Amber燈亮
std::cout << "發送激活故障碼 DM1..." << std::endl;
Send_DM1(lamp, &active_dtc, 1);
return 0;
}
當DM1報文包含的激活故障碼超過1個時(即數據超過8字節),必須使用J1939傳輸層協議(TP) 進行多包傳輸。
**BAM (廣播公告報文)**:發送方先發出PGN 60416的報文,聲明即將發送消息的字節總數、分包數量及目標PGN(如65226)。
**DT (數據傳輸報文)**:隨后發送一系列PGN 60160的報文,每一包包含7字節的有效載荷,順序拼接成完整的DM1報文。
下圖展示了多包DM1在總線上的邏輯結構:
[ BAM Packet ] --> 告知: 我要發送22字節數據,分4包發,內容屬于DM1
[ DT Packet ] --> 序列號1 + Data(Byte 0-6)
[ DT Packet ] --> 序列號2 + Data(Byte 7-13)
[ DT Packet ] --> 序列號3 + Data(Byte 14-20)
[ DT Packet ] --> 序列號4 + Data(Byte 21-22 + 填充)
5. 總結J1939診斷應用層協議(J1939-73)通過標準化的SPN、FMI和DM1報文,為重型車輛提供了高效的故障自診斷方案。理解其DTC的位/字節布局以及多包傳輸(BAM/DT) 機制,是進行商用車控制器(ECU)開發和故障診斷的基礎。
與乘用車常用的UDS協議不同,J1939診斷具有更強的周期性廣播特性(每秒一次的固定心跳),這要求開發者在設計ECU軟件時,需嚴格遵循其發送時序,以防止因報文速率過高導致總線負載異常。
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.