![]()
1. 引言
在UDS(統(tǒng)一診斷服務(wù))協(xié)議中,數(shù)據(jù)傳輸功能單元負(fù)責(zé)客戶端(測(cè)試儀)與服務(wù)器(ECU)之間的數(shù)據(jù)交換。其中,ReadDataByIdentifier(0x22)和WriteDataByIdentifier(0x2E)是最核心的兩個(gè)服務(wù):前者允許讀取ECU內(nèi)部某個(gè)或多個(gè)數(shù)據(jù)標(biāo)識(shí)符(DID)對(duì)應(yīng)的當(dāng)前值(如軟件版本、序列號(hào)、標(biāo)定參數(shù)等),后者則允許改寫這些標(biāo)識(shí)符的值。本文將對(duì)這兩個(gè)服務(wù)的報(bào)文格式、支持的否定響應(yīng)碼(NRC)以及ECU的典型處理流程進(jìn)行詳細(xì)講解,并提供完整的C++實(shí)現(xiàn)示例。
2. 服務(wù)詳解 2.1 按標(biāo)識(shí)符讀取數(shù)據(jù)(0x22) 2.1.1 請(qǐng)求報(bào)文
字節(jié)位置
參數(shù)名稱
取值(hex)
服務(wù)ID
0x22
數(shù)據(jù)標(biāo)識(shí)符(DID)1
高字節(jié)+低字節(jié)
第一個(gè)要讀取的DID,如0xF190
數(shù)據(jù)標(biāo)識(shí)符(DID)2
高字節(jié)+低字節(jié)
可選,第二個(gè)要讀取的DID
最多可連續(xù)請(qǐng)求多個(gè)DID(廠商自定義)
最小長(zhǎng)度:3字節(jié)(服務(wù)ID + 1個(gè)DID)
最大長(zhǎng)度:1 + 2 × N 字節(jié)(N為DID個(gè)數(shù),由具體實(shí)現(xiàn)限制)
2.1.2 肯定響應(yīng)報(bào)文
字節(jié)位置
參數(shù)名稱
取值(hex)
響應(yīng)服務(wù)ID
0x62
請(qǐng)求服務(wù)ID + 0x40
數(shù)據(jù)標(biāo)識(shí)符(DID)1
高字節(jié)+低字節(jié)
與請(qǐng)求中相同的DID
– +L1-1
DID1的數(shù)據(jù)值
廠商定義
長(zhǎng)度L1由DID的實(shí)際數(shù)據(jù)長(zhǎng)度決定
+L1 – +L1+1
DID2
高字節(jié)+低字節(jié)
第二個(gè)DID(若請(qǐng)求中存在)
每個(gè)DID及其數(shù)據(jù)依次排列
注意:ECU返回的DID的順序必須與請(qǐng)求順序嚴(yán)格一致。
2.1.3 否定響應(yīng)
否定響應(yīng)格式:0x7F 0x22 NRC
NRC
名稱
0x13
IncorrectMessageLength
請(qǐng)求報(bào)文長(zhǎng)度無(wú)效(過短或過長(zhǎng))
0x22
ConditionsNotCorrect
當(dāng)前環(huán)境條件(如發(fā)動(dòng)機(jī)轉(zhuǎn)速、車速等)不滿足讀取該DID的要求
0x31
RequestOutOfRange
請(qǐng)求中的所有DID均不被當(dāng)前會(huì)話支持,或某個(gè)DID的有效性檢查失敗
0x33
SecurityAccessDenied
讀取該DID需要安全訪問,且當(dāng)前未解鎖
0x14
ResponseTooLong
響應(yīng)數(shù)據(jù)長(zhǎng)度超過ECU物理層(如CAN)所能承載的最大值
2.1.4 ECU處理流程
檢查請(qǐng)求報(bào)文長(zhǎng)度是否在 [3, 1+2N_max] 范圍內(nèi);
→ 失敗則回復(fù)0x7F 0x22 0x13。遍歷請(qǐng)求中的每一個(gè)DID:
檢查DID是否在當(dāng)前診斷會(huì)話下支持0x22服務(wù);
→ 若所有DID都不支持,回復(fù)0x31。檢查讀取該DID是否需要安全解鎖,若需要?jiǎng)t檢查當(dāng)前狀態(tài);
→ 未解鎖回復(fù)0x33。檢查當(dāng)前環(huán)境條件(如車速、電壓等)是否滿足;
→ 不滿足回復(fù)0x22。
計(jì)算所有待返回?cái)?shù)據(jù)的累加總長(zhǎng)度(每個(gè)DID的長(zhǎng)度 + 2字節(jié)DID自身)。如果總長(zhǎng)度超過ECU發(fā)送緩沖區(qū)限制,回復(fù)0x14。
全部檢查通過后,組裝肯定響應(yīng)0x62。
2.2 按標(biāo)識(shí)符寫數(shù)據(jù)(0x2E) 2.2.1 請(qǐng)求報(bào)文
字節(jié)位置
參數(shù)名稱
取值(hex)
服務(wù)ID
0x2E
數(shù)據(jù)標(biāo)識(shí)符(DID)
高字節(jié)+低字節(jié)
待寫入的DID
– +L-1
待寫入的數(shù)據(jù)值
廠商定義
數(shù)據(jù)長(zhǎng)度L由DID的規(guī)格決定
最小長(zhǎng)度:4字節(jié)(服務(wù)ID + 2字節(jié)DID + 至少1字節(jié)數(shù)據(jù))
實(shí)際長(zhǎng)度:1 + 2 + L 字節(jié),L由具體DID的數(shù)據(jù)長(zhǎng)度決定。
2.2.2 肯定響應(yīng)報(bào)文
字節(jié)位置
參數(shù)名稱
取值(hex)
響應(yīng)服務(wù)ID
0x6E
0x2E + 0x40
數(shù)據(jù)標(biāo)識(shí)符(DID)
高字節(jié)+低字節(jié)
與請(qǐng)求相同
注意:肯定響應(yīng)中不包含寫入的數(shù)據(jù)內(nèi)容。
2.2.3 否定響應(yīng)
否定響應(yīng)格式:0x7F 0x2E NRC
NRC
名稱
0x13
IncorrectMessageLength
請(qǐng)求報(bào)文長(zhǎng)度不符合DID規(guī)定的數(shù)據(jù)長(zhǎng)度要求
0x22
ConditionsNotCorrect
當(dāng)前環(huán)境條件不滿足寫入要求(例如只能在特定狀態(tài)下寫入)
0x31
RequestOutOfRange
DID在當(dāng)前會(huì)話下不支持寫入,或?qū)懭氲臄?shù)據(jù)值無(wú)效
0x33
SecurityAccessDenied
寫入該DID需要安全訪問,且當(dāng)前未解鎖
0x72
GeneralProgrammingFailure
ECU內(nèi)部寫入非易失性存儲(chǔ)器失敗(如Flash編程故障)
2.2.4 ECU處理流程
檢查報(bào)文總長(zhǎng)度是否等于
3 + L_data(L_data為該DID的標(biāo)準(zhǔn)數(shù)據(jù)長(zhǎng)度);
→ 不等則回復(fù)0x13。檢查DID是否支持寫入(即是否支持0x2E服務(wù));
→ 不支持回復(fù)0x31。檢查寫入該DID是否需要安全解鎖;
→ 需要且未解鎖則回復(fù)0x33。檢查當(dāng)前環(huán)境條件(如發(fā)動(dòng)機(jī)狀態(tài)、電壓等);
→ 不滿足回復(fù)0x22。檢查待寫入的數(shù)據(jù)值是否在有效范圍內(nèi)(例如枚舉值、校驗(yàn)和等);
→ 無(wú)效則回復(fù)0x31。執(zhí)行寫入(可能涉及RAM變量更新或非易失存儲(chǔ)器編程)。若寫入過程失敗(如校驗(yàn)錯(cuò)誤、寫入超時(shí)),回復(fù)
0x72。全部成功后返回
0x6E肯定響應(yīng)。
下面給出一個(gè)模擬ECU處理0x22和0x2E服務(wù)的完整C++實(shí)現(xiàn)。代碼中定義了UdsEcu類,內(nèi)部維護(hù)一個(gè)DID數(shù)據(jù)庫(kù),支持:
多DID批量讀取
寫入校驗(yàn)(長(zhǎng)度、安全性、值有效性)
否定響應(yīng)的多種場(chǎng)景模擬
4. 代碼說(shuō)明#include
#include
#include
#include
#include
// 診斷會(huì)話類型(簡(jiǎn)略)
enum class SessionType {
Default = 0x01,
Programming = 0x02,
Extended = 0x03
};
// DID 屬性結(jié)構(gòu)體
struct DidAttribute {
bool supportsRead; // 是否支持0x22服務(wù)
bool supportsWrite; // 是否支持0x2E服務(wù)
bool needsSecurity; // 是否需要安全訪問
uint16_t dataLength; // 數(shù)據(jù)長(zhǎng)度(字節(jié))
std::vector data; // 當(dāng)前存儲(chǔ)的數(shù)據(jù)值
std::vector min; // 最小值(可選,用于范圍檢查)
std::vector max; // 最大值
};
class UdsEcu {
public:
UdsEcu() : currentSession(SessionType::Default), securityUnlocked(false) {
// 初始化DID數(shù)據(jù)庫(kù)
// DID 0xF190 : VIN碼(17字節(jié)ASCII)
DidAttribute vin;
vin.supportsRead = true;
vin.supportsWrite = false; // VIN通常只讀
vin.needsSecurity = false;
vin.dataLength = 17;
vin.data = {'1', 'G', 'N', 'V', 'K', 'L', 'A', '6', '8', 'U', '1', '1', '2', '3', '4', '5', '\0'};
didMap[0xF190] = vin;
// DID 0xF191 : 軟件版本號(hào)(4字節(jié),例如 "1.0.0")
DidAttribute swVer;
swVer.supportsRead = true;
swVer.supportsWrite = false;
swVer.needsSecurity = false;
swVer.dataLength = 4;
swVer.data = {'1', '.', '0', '0'};
didMap[0xF191] = swVer;
// DID 0x1000 : 車速閾值(1字節(jié),可讀寫,需要安全訪問)
DidAttribute speedLimit;
speedLimit.supportsRead = true;
speedLimit.supportsWrite = true;
speedLimit.needsSecurity = true;
speedLimit.dataLength = 1;
speedLimit.data = {120}; // 默認(rèn)120 km/h
speedLimit.min = {0};
speedLimit.max = {250};
didMap[0x1000] = speedLimit;
// DID 0x1001 : 發(fā)動(dòng)機(jī)轉(zhuǎn)速限制(2字節(jié),可讀寫,需要安全訪問且寫入時(shí)要求環(huán)境條件)
DidAttribute rpmLimit;
rpmLimit.supportsRead = true;
rpmLimit.supportsWrite = true;
rpmLimit.needsSecurity = true;
rpmLimit.dataLength = 2;
rpmLimit.data = {0x13, 0x88}; // 5000 rpm (0x1388)
rpmLimit.min = {0x00, 0x00};
rpmLimit.max = {0x27, 0x10}; // 10000 rpm
didMap[0x1001] = rpmLimit;
}
// 設(shè)置診斷會(huì)話(影響某些DID的可見性)
void setSession(SessionType session) {
currentSession = session;
}
// 模擬安全解鎖
void unlockSecurity(bool unlock) {
securityUnlocked = unlock;
}
// 模擬環(huán)境條件(此處簡(jiǎn)化為一個(gè)全局標(biāo)志,實(shí)際可根據(jù)DID單獨(dú)判斷)
void setWriteConditionMet(bool met) {
writeConditionOk = met;
}
// 處理 0x22 讀取請(qǐng)求
// 輸入:原始請(qǐng)求字節(jié)數(shù)組
// 輸出:響應(yīng)字節(jié)數(shù)組(肯定或否定)
std::vector handleReadDataByIdentifier(const std::vector& request) {
// 1. 最小長(zhǎng)度檢查(服務(wù)ID + 至少一個(gè)DID)
if (request.size() < 3 || request[0] != 0x22) {
return {0x7F, 0x22, 0x13}; // IncorrectMessageLength
}
// 提取DID列表
std::vector didList;
for (size_t i = 1; i + 1 < request.size(); i += 2) {
uint16_t did = (request[i] << 8) | request[i+1];
didList.push_back(did);
}
if (didList.empty()) {
return {0x7F, 0x22, 0x13};
}
// 臨時(shí)存儲(chǔ)將返回的數(shù)據(jù)段(每個(gè)DID: 2字節(jié)DID + 數(shù)據(jù))
std::vector responseData;
bool anyValid = false;
for (uint16_t did : didList) {
auto it = didMap.find(did);
// 2. 檢查DID是否存在且支持讀取
if (it == didMap.end() || !it->second.supportsRead) {
continue; // 跳過不支持的DID,最后判斷是否全部無(wú)效
}
// 3. 會(huì)話依賴性檢查(示例:編程會(huì)話下某些DID不可讀)
if (currentSession == SessionType::Programming && did == 0xF191) {
continue; // 編程會(huì)話中禁止讀取軟件版本
}
// 4. 安全訪問檢查
if (it->second.needsSecurity && !securityUnlocked) {
return {0x7F, 0x22, 0x33};
}
// 5. 環(huán)境條件檢查(示例:讀取車速閾值時(shí)要求車速為0)
if (did == 0x1000 && !isReadConditionMet(did)) {
return {0x7F, 0x22, 0x22};
}
// 通過檢查,加入響應(yīng)數(shù)據(jù)
anyValid = true;
responseData.push_back((did >> 8) & 0xFF);
responseData.push_back(did & 0xFF);
const auto& data = it->second.data;
responseData.insert(responseData.end(), data.begin(), data.end());
}
if (!anyValid) {
return {0x7F, 0x22, 0x31}; // RequestOutOfRange
}
// 6. 響應(yīng)長(zhǎng)度限制檢查(此處假設(shè)CAN單幀最大8字節(jié),實(shí)際UDS多幀傳輸,簡(jiǎn)化為例)
if (responseData.size() + 1 > 8) {
return {0x7F, 0x22, 0x14}; // ResponseTooLong
}
// 7. 組裝肯定響應(yīng)
std::vector response;
response.push_back(0x62); // 肯定響應(yīng)ID
response.insert(response.end(), responseData.begin(), responseData.end());
return response;
}
// 處理 0x2E 寫入請(qǐng)求
std::vector handleWriteDataByIdentifier(const std::vector& request) {
// 1. 最小長(zhǎng)度檢查(服務(wù)ID + DID + 至少1字節(jié)數(shù)據(jù))
if (request.size() < 4 || request[0] != 0x2E) {
return {0x7F, 0x2E, 0x13};
}
uint16_t did = (request[1] << 8) | request[2];
auto it = didMap.find(did);
if (it == didMap.end()) {
return {0x7F, 0x2E, 0x31};
}
DidAttribute& attr = it->second;
// 2. 檢查是否支持寫入
if (!attr.supportsWrite) {
return {0x7F, 0x2E, 0x31};
}
// 3. 數(shù)據(jù)長(zhǎng)度匹配檢查
size_t dataLen = request.size() - 3; // 實(shí)際待寫入數(shù)據(jù)長(zhǎng)度
if (dataLen != attr.dataLength) {
return {0x7F, 0x2E, 0x13}; // IncorrectMessageLength
}
// 4. 安全訪問檢查
if (attr.needsSecurity && !securityUnlocked) {
return {0x7F, 0x2E, 0x33};
}
// 5. 環(huán)境條件檢查(示例:寫入轉(zhuǎn)速限制需要在車輛靜止時(shí))
if (did == 0x1001 && !writeConditionOk) {
return {0x7F, 0x2E, 0x22};
}
// 6. 數(shù)據(jù)有效性檢查(范圍檢查)
std::vector newData(request.begin() + 3, request.end());
if (!isValueValid(attr, newData)) {
return {0x7F, 0x2E, 0x31};
}
// 7. 模擬寫入過程(此處假設(shè)總是成功,可擴(kuò)展為寫入失敗返回0x72)
attr.data = newData;
// 如果是非易失性參數(shù),可在此處觸發(fā)存儲(chǔ)編程
// 8. 肯定響應(yīng)
return {0x6E, (uint8_t)((did >> 8) & 0xFF), (uint8_t)(did & 0xFF)};
}
private:
std::unordered_map didMap;
SessionType currentSession;
bool securityUnlocked;
bool writeConditionOk = true; // 模擬環(huán)境條件,實(shí)際需要更精細(xì)的判斷
// 模擬讀取條件(例如車速閾值只能在車輛靜止時(shí)讀取)
bool isReadConditionMet(uint16_t did) {
if (did == 0x1000) {
// 假設(shè)靜態(tài)變量表示車速為0(演示用)
static bool vehicleStationary = true;
return vehicleStationary;
}
return true;
}
// 檢查寫入數(shù)據(jù)是否在有效范圍內(nèi)
bool isValueValid(const DidAttribute& attr, const std::vector& value) {
if (attr.min.empty() || attr.max.empty()) return true; // 無(wú)范圍限制
// 簡(jiǎn)單比較:數(shù)值類型的字節(jié)序這里假設(shè)大端或單字節(jié)
if (value.size() == 1) {
return (value[0] >= attr.min[0] && value[0] <= attr.max[0]);
} else if (value.size() == 2) {
uint16_t val = (value[0] << 8) | value[1];
uint16_t minv = (attr.min[0] << 8) | attr.min[1];
uint16_t maxv = (attr.max[0] << 8) | attr.max[1];
return (val >= minv && val <= maxv);
}
// 字符串等其他類型可使用自定義校驗(yàn)
return true;
}
};
// ------------------ 測(cè)試示例 ------------------
int main() {
UdsEcu ecu;
// 初始狀態(tài):默認(rèn)會(huì)話,未解鎖安全訪問,環(huán)境條件滿足
ecu.unlockSecurity(false);
ecu.setWriteConditionMet(true);
// 測(cè)試1:讀取單個(gè)DID (0xF190 VIN)
std::vector reqRead1 = {0x22, 0xF1, 0x90};
auto resp1 = ecu.handleReadDataByIdentifier(reqRead1);
std::cout << "Read VIN: ";
for (auto b : resp1) printf("%02X ", b);
std::cout << std::endl; // 預(yù)期 62 F1 90 31 47 4E ... (肯定響應(yīng))
// 測(cè)試2:讀取需要安全訪問的DID (0x1000) 且未解鎖 -> 應(yīng)返回NRC 0x33
std::vector reqRead2 = {0x22, 0x10, 0x00};
auto resp2 = ecu.handleReadDataByIdentifier(reqRead2);
std::cout << "Read speed limit (no security): ";
for (auto b : resp2) printf("%02X ", b);
std::cout << std::endl; // 預(yù)期 7F 22 33
// 解鎖安全訪問
ecu.unlockSecurity(true);
auto resp3 = ecu.handleReadDataByIdentifier(reqRead2);
std::cout << "Read speed limit (unlocked): ";
for (auto b : resp3) printf("%02X ", b);
std::cout << std::endl; // 預(yù)期 62 10 00 78 (0x78 = 120)
// 測(cè)試3:寫入DID (0x1000 車速閾值) 新值 = 150
std::vector reqWrite = {0x2E, 0x10, 0x00, 150};
auto resp4 = ecu.handleWriteDataByIdentifier(reqWrite);
std::cout << "Write speed limit to 150: ";
for (auto b : resp4) printf("%02X ", b);
std::cout << std::endl; // 預(yù)期 6E 10 00
// 驗(yàn)證寫入是否成功
auto resp5 = ecu.handleReadDataByIdentifier(reqRead2);
std::cout << "Read speed limit after write: ";
for (auto b : resp5) printf("%02X ", b);
std::cout << std::endl; // 應(yīng)顯示 150
// 測(cè)試4:寫入長(zhǎng)度錯(cuò)誤 (0x1000 只需要1字節(jié),但提供2字節(jié))
std::vector reqWriteBadLen = {0x2E, 0x10, 0x00, 0x12, 0x34};
auto resp6 = ecu.handleWriteDataByIdentifier(reqWriteBadLen);
std::cout << "Write with bad length: ";
for (auto b : resp6) printf("%02X ", b);
std::cout << std::endl; // 預(yù)期 7F 2E 13
// 測(cè)試5:多DID讀取 (0xF190 + 0xF191)
std::vector reqMultiRead = {0x22, 0xF1, 0x90, 0xF1, 0x91};
auto resp7 = ecu.handleReadDataByIdentifier(reqMultiRead);
std::cout << "Multi-read VIN+SWVer: ";
for (auto b : resp7) printf("%02X ", b);
std::cout << std::endl;return 0;
}
UdsEcu類:模擬ECU的核心邏輯,內(nèi)部維護(hù)一個(gè)DID屬性映射表(
unordered_map)。每個(gè)屬性包含讀寫支持標(biāo)志、安全訪問需求、數(shù)據(jù)長(zhǎng)度、當(dāng)前值以及可選的有效范圍。handleReadDataByIdentifier:
逐個(gè)處理請(qǐng)求中的DID,跳過不支持或會(huì)話不可見的DID,最后檢查是否至少有一個(gè)有效DID。
對(duì)每一個(gè)通過的DID進(jìn)行安全訪問和環(huán)境條件檢查,任意一項(xiàng)失敗即返回否定響應(yīng)(符合UDS規(guī)范:遇到第一個(gè)失敗條件就終止)。
最終計(jì)算響應(yīng)總長(zhǎng)度,若超過8字節(jié)(CAN單幀載荷)則返回NRC 0x14(實(shí)際UDS可通過多幀傳輸,本例為簡(jiǎn)化演示)。
handleWriteDataByIdentifier:
嚴(yán)格按照服務(wù)流程:長(zhǎng)度檢查 → 支持性 → 數(shù)據(jù)長(zhǎng)度匹配 → 安全解鎖 → 環(huán)境條件 → 數(shù)值有效性 → 寫入。
寫入成功后僅返回DID,不返回?cái)?shù)據(jù)。
測(cè)試代碼展示了典型場(chǎng)景:讀取VIN、安全訪問保護(hù)、寫入數(shù)據(jù)、長(zhǎng)度錯(cuò)誤處理、多DID讀取等。
0x22和0x2E服務(wù)是UDS診斷中訪問ECU內(nèi)部數(shù)據(jù)的關(guān)鍵接口。0x22用于讀取,支持批量請(qǐng)求;0x2E用于寫入,需嚴(yán)格遵循預(yù)定義的數(shù)據(jù)長(zhǎng)度和有效性規(guī)則。在實(shí)際工程中,開發(fā)者還需考慮:
多幀傳輸(當(dāng)響應(yīng)數(shù)據(jù)超過單幀大小時(shí),需使用ISO 15765-2的流控機(jī)制)
更精細(xì)的安全訪問(種子-密鑰算法)
非易失性存儲(chǔ)的原子性和錯(cuò)誤恢復(fù)(如Flash寫入失敗時(shí)的回滾策略)
本文提供的C++示例給出了一個(gè)可擴(kuò)展的框架,讀者可據(jù)此集成到實(shí)際的診斷棧中。
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。
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.