网易首页 > 网易号 > 正文 申请入驻

UDS诊断协议:按标识符读取数据(0x22)与按标识符写数据(0x2E)服务详解及C++代码实现

0
分享至


1. 引言

在UDS(统一诊断服务)协议中,数据传输功能单元负责客户端(测试仪)与服务器(ECU)之间的数据交换。其中,ReadDataByIdentifier(0x22)和WriteDataByIdentifier(0x2E)是最核心的两个服务:前者允许读取ECU内部某个或多个数据标识符(DID)对应的当前值(如软件版本、序列号、标定参数等),后者则允许改写这些标识符的值。本文将对这两个服务的报文格式、支持的否定响应码(NRC)以及ECU的典型处理流程进行详细讲解,并提供完整的C++实现示例。

2. 服务详解 2.1 按标识符读取数据(0x22) 2.1.1 请求报文

字节位置

参数名称

取值(hex)

服务ID

0x22

数据标识符(DID)1

高字节+低字节

第一个要读取的DID,如0xF190

数据标识符(DID)2

高字节+低字节

可选,第二个要读取的DID

最多可连续请求多个DID(厂商自定义)

最小长度:3字节(服务ID + 1个DID)
最大长度:1 + 2 × N 字节(N为DID个数,由具体实现限制)

2.1.2 肯定响应报文

字节位置

参数名称

取值(hex)

响应服务ID

0x62

请求服务ID + 0x40

数据标识符(DID)1

高字节+低字节

与请求中相同的DID

– +L1-1

DID1的数据值

厂商定义

长度L1由DID的实际数据长度决定

+L1 – +L1+1

DID2

高字节+低字节

第二个DID(若请求中存在)

每个DID及其数据依次排列

注意:ECU返回的DID的顺序必须与请求顺序严格一致。

2.1.3 否定响应

否定响应格式:0x7F 0x22 NRC

NRC

名称

0x13

IncorrectMessageLength

请求报文长度无效(过短或过长)

0x22

ConditionsNotCorrect

当前环境条件(如发动机转速、车速等)不满足读取该DID的要求

0x31

RequestOutOfRange

请求中的所有DID均不被当前会话支持,或某个DID的有效性检查失败

0x33

SecurityAccessDenied

读取该DID需要安全访问,且当前未解锁

0x14

ResponseTooLong

响应数据长度超过ECU物理层(如CAN)所能承载的最大值


2.1.4 ECU处理流程

  1. 检查请求报文长度是否在 [3, 1+2N_max] 范围内;
    → 失败则回复0x7F 0x22 0x13。

  2. 遍历请求中的每一个DID:

  • 检查DID是否在当前诊断会话下支持0x22服务;
    → 若所有DID都不支持,回复0x31。

  • 检查读取该DID是否需要安全解锁,若需要则检查当前状态;
    → 未解锁回复0x33。

  • 检查当前环境条件(如车速、电压等)是否满足;
    → 不满足回复0x22。

计算所有待返回数据的累加总长度(每个DID的长度 + 2字节DID自身)。如果总长度超过ECU发送缓冲区限制,回复0x14。

全部检查通过后,组装肯定响应0x62。

2.2 按标识符写数据(0x2E) 2.2.1 请求报文

字节位置

参数名称

取值(hex)

服务ID

0x2E

数据标识符(DID)

高字节+低字节

待写入的DID

– +L-1

待写入的数据值

厂商定义

数据长度L由DID的规格决定

最小长度:4字节(服务ID + 2字节DID + 至少1字节数据)
实际长度:1 + 2 + L 字节,L由具体DID的数据长度决定。

2.2.2 肯定响应报文

字节位置

参数名称

取值(hex)

响应服务ID

0x6E

0x2E + 0x40

数据标识符(DID)

高字节+低字节

与请求相同

注意:肯定响应中不包含写入的数据内容。

2.2.3 否定响应

否定响应格式:0x7F 0x2E NRC

NRC

名称

0x13

IncorrectMessageLength

请求报文长度不符合DID规定的数据长度要求

0x22

ConditionsNotCorrect

当前环境条件不满足写入要求(例如只能在特定状态下写入)

0x31

RequestOutOfRange

DID在当前会话下不支持写入,或写入的数据值无效

0x33

SecurityAccessDenied

写入该DID需要安全访问,且当前未解锁

0x72

GeneralProgrammingFailure

ECU内部写入非易失性存储器失败(如Flash编程故障)


2.2.4 ECU处理流程

  1. 检查报文总长度是否等于3 + L_data(L_data为该DID的标准数据长度);
    → 不等则回复0x13。

  2. 检查DID是否支持写入(即是否支持0x2E服务);
    → 不支持回复0x31。

  3. 检查写入该DID是否需要安全解锁;
    → 需要且未解锁则回复0x33。

  4. 检查当前环境条件(如发动机状态、电压等);
    → 不满足回复0x22。

  5. 检查待写入的数据值是否在有效范围内(例如枚举值、校验和等);
    → 无效则回复0x31。

  6. 执行写入(可能涉及RAM变量更新或非易失存储器编程)。若写入过程失败(如校验错误、写入超时),回复0x72。

  7. 全部成功后返回0x6E肯定响应。

3. C++代码示例

下面给出一个模拟ECU处理0x22和0x2E服务的完整C++实现。代码中定义了UdsEcu类,内部维护一个DID数据库,支持:

  • 多DID批量读取

  • 写入校验(长度、安全性、值有效性)

  • 否定响应的多种场景模拟

#include  

#include
#include
#include
#include

// 诊断会话类型(简略)
enum class SessionType {
Default = 0x01,
Programming = 0x02,
Extended = 0x03
};

// DID 属性结构体
struct DidAttribute {
bool supportsRead; // 是否支持0x22服务
bool supportsWrite; // 是否支持0x2E服务
bool needsSecurity; // 是否需要安全访问
uint16_t dataLength; // 数据长度(字节)
std::vector data; // 当前存储的数据值
std::vector min; // 最小值(可选,用于范围检查)
std::vector max; // 最大值
};

class UdsEcu {
public:
UdsEcu() : currentSession(SessionType::Default), securityUnlocked(false) {
// 初始化DID数据库
// DID 0xF190 : VIN码(17字节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 : 软件版本号(4字节,例如 "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字节,可读写,需要安全访问)
DidAttribute speedLimit;
speedLimit.supportsRead = true;
speedLimit.supportsWrite = true;
speedLimit.needsSecurity = true;
speedLimit.dataLength = 1;
speedLimit.data = {120}; // 默认120 km/h
speedLimit.min = {0};
speedLimit.max = {250};
didMap[0x1000] = speedLimit;

// DID 0x1001 : 发动机转速限制(2字节,可读写,需要安全访问且写入时要求环境条件)
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;
}

// 设置诊断会话(影响某些DID的可见性)
void setSession(SessionType session) {
currentSession = session;
}

// 模拟安全解锁
void unlockSecurity(bool unlock) {
securityUnlocked = unlock;
}

// 模拟环境条件(此处简化为一个全局标志,实际可根据DID单独判断)
void setWriteConditionMet(bool met) {
writeConditionOk = met;
}

// 处理 0x22 读取请求
// 输入:原始请求字节数组
// 输出:响应字节数组(肯定或否定)
std::vector handleReadDataByIdentifier(const std::vector& request) {
// 1. 最小长度检查(服务ID + 至少一个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};
}

// 临时存储将返回的数据段(每个DID: 2字节DID + 数据)
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,最后判断是否全部无效
}
// 3. 会话依赖性检查(示例:编程会话下某些DID不可读)
if (currentSession == SessionType::Programming && did == 0xF191) {
continue; // 编程会话中禁止读取软件版本
}
// 4. 安全访问检查
if (it->second.needsSecurity && !securityUnlocked) {
return {0x7F, 0x22, 0x33};
}
// 5. 环境条件检查(示例:读取车速阈值时要求车速为0)
if (did == 0x1000 && !isReadConditionMet(did)) {
return {0x7F, 0x22, 0x22};
}

// 通过检查,加入响应数据
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. 响应长度限制检查(此处假设CAN单帧最大8字节,实际UDS多帧传输,简化为例)
if (responseData.size() + 1 > 8) {
return {0x7F, 0x22, 0x14}; // ResponseTooLong
}

// 7. 组装肯定响应
std::vector response;
response.push_back(0x62); // 肯定响应ID
response.insert(response.end(), responseData.begin(), responseData.end());
return response;
}

// 处理 0x2E 写入请求
std::vector handleWriteDataByIdentifier(const std::vector& request) {
// 1. 最小长度检查(服务ID + DID + 至少1字节数据)
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. 数据长度匹配检查
size_t dataLen = request.size() - 3; // 实际待写入数据长度
if (dataLen != attr.dataLength) {
return {0x7F, 0x2E, 0x13}; // IncorrectMessageLength
}

// 4. 安全访问检查
if (attr.needsSecurity && !securityUnlocked) {
return {0x7F, 0x2E, 0x33};
}

// 5. 环境条件检查(示例:写入转速限制需要在车辆静止时)
if (did == 0x1001 && !writeConditionOk) {
return {0x7F, 0x2E, 0x22};
}

// 6. 数据有效性检查(范围检查)
std::vector newData(request.begin() + 3, request.end());
if (!isValueValid(attr, newData)) {
return {0x7F, 0x2E, 0x31};
}

// 7. 模拟写入过程(此处假设总是成功,可扩展为写入失败返回0x72)
attr.data = newData;
// 如果是非易失性参数,可在此处触发存储编程

// 8. 肯定响应
return {0x6E, (uint8_t)((did >> 8) & 0xFF), (uint8_t)(did & 0xFF)};
}

private:
std::unordered_map didMap;
SessionType currentSession;
bool securityUnlocked;
bool writeConditionOk = true; // 模拟环境条件,实际需要更精细的判断

// 模拟读取条件(例如车速阈值只能在车辆静止时读取)
bool isReadConditionMet(uint16_t did) {
if (did == 0x1000) {
// 假设静态变量表示车速为0(演示用)
static bool vehicleStationary = true;
return vehicleStationary;
}
return true;
}

// 检查写入数据是否在有效范围内
bool isValueValid(const DidAttribute& attr, const std::vector& value) {
if (attr.min.empty() || attr.max.empty()) return true; // 无范围限制
// 简单比较:数值类型的字节序这里假设大端或单字节
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);
}
// 字符串等其他类型可使用自定义校验
return true;
}
};

// ------------------ 测试示例 ------------------
int main() {
UdsEcu ecu;

// 初始状态:默认会话,未解锁安全访问,环境条件满足
ecu.unlockSecurity(false);
ecu.setWriteConditionMet(true);

// 测试1:读取单个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; // 预期 62 F1 90 31 47 4E ... (肯定响应)

// 测试2:读取需要安全访问的DID (0x1000) 且未解锁 -> 应返回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; // 预期 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; // 预期 62 10 00 78 (0x78 = 120)

// 测试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; // 预期 6E 10 00

// 验证写入是否成功
auto resp5 = ecu.handleReadDataByIdentifier(reqRead2);
std::cout << "Read speed limit after write: ";
for (auto b : resp5) printf("%02X ", b);
std::cout << std::endl; // 应显示 150

// 测试4:写入长度错误 (0x1000 只需要1字节,但提供2字节)
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; // 预期 7F 2E 13

// 测试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;
}
4. 代码说明
  • UdsEcu类:模拟ECU的核心逻辑,内部维护一个DID属性映射表(unordered_map )。每个属性包含读写支持标志、安全访问需求、数据长度、当前值以及可选的有效范围。

  • handleReadDataByIdentifier:

    • 逐个处理请求中的DID,跳过不支持或会话不可见的DID,最后检查是否至少有一个有效DID。

    • 对每一个通过的DID进行安全访问和环境条件检查,任意一项失败即返回否定响应(符合UDS规范:遇到第一个失败条件就终止)。

    • 最终计算响应总长度,若超过8字节(CAN单帧载荷)则返回NRC 0x14(实际UDS可通过多帧传输,本例为简化演示)。

  • handleWriteDataByIdentifier:

    • 严格按照服务流程:长度检查 → 支持性 → 数据长度匹配 → 安全解锁 → 环境条件 → 数值有效性 → 写入。

    • 写入成功后仅返回DID,不返回数据。

  • 测试代码展示了典型场景:读取VIN、安全访问保护、写入数据、长度错误处理、多DID读取等。

5. 总结

0x22和0x2E服务是UDS诊断中访问ECU内部数据的关键接口。0x22用于读取,支持批量请求;0x2E用于写入,需严格遵循预定义的数据长度和有效性规则。在实际工程中,开发者还需考虑:

  • 多帧传输(当响应数据超过单帧大小时,需使用ISO 15765-2的流控机制)

  • 更精细的安全访问(种子-密钥算法)

  • 非易失性存储的原子性和错误恢复(如Flash写入失败时的回滚策略)

本文提供的C++示例给出了一个可扩展的框架,读者可据此集成到实际的诊断栈中。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

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.

相关推荐
热点推荐
韩国教授破大防了:中秋是韩国的,和中国“完全无关”!

韩国教授破大防了:中秋是韩国的,和中国“完全无关”!

乌克兰小静
2026-09-27 06:53:53
9月27日,大家期盼已久的2026退休养老金,到底涨还是不涨?

9月27日,大家期盼已久的2026退休养老金,到底涨还是不涨?

乐天闲聊
2026-09-27 13:51:19
林诗栋胜松岛辉空后跨两道障碍奔向王皓拥抱!拉国旗细节戳人:少年把国家扛在心上

林诗栋胜松岛辉空后跨两道障碍奔向王皓拥抱!拉国旗细节戳人:少年把国家扛在心上

宝哥精彩赛事
2026-09-28 01:00:41
西方媒体骗了全世界!胡塞武装真实身份,压根不是"伊朗代理人"

西方媒体骗了全世界!胡塞武装真实身份,压根不是"伊朗代理人"

兵卒史
2026-09-28 01:31:22
启动近20年、耗资20亿英镑的烂尾球场,下赛季有望承办巴萨、皇马赛事

启动近20年、耗资20亿英镑的烂尾球场,下赛季有望承办巴萨、皇马赛事

福酱的小时光
2026-09-27 17:34:06
再敢绑架任何1名以色列人,加沙城100万人将遭驱赶,城市荡平

再敢绑架任何1名以色列人,加沙城100万人将遭驱赶,城市荡平

移光幻影
2026-09-23 07:42:57
刘欢去世:把音乐看得大一些,把自己看得小一些

刘欢去世:把音乐看得大一些,把自己看得小一些

第一财经资讯
2026-09-26 21:30:09
白米粥立大功!医生发现:糖尿病常喝白米粥,或能降低4种并发症

白米粥立大功!医生发现:糖尿病常喝白米粥,或能降低4种并发症

华庭讲美食
2026-09-27 17:04:47
维尼修斯在巴西和在皇马,是两种表现;维尼修斯在皇马,有消极怠工嫌疑!

维尼修斯在巴西和在皇马,是两种表现;维尼修斯在皇马,有消极怠工嫌疑!

福酱的小时光
2026-09-27 17:20:01
亚运会9月28日中国队赛程+直播:王曼昱/蒯曼冲击女双金牌!王楚钦、林诗栋出战!男子、女子4×100米接力决赛

亚运会9月28日中国队赛程+直播:王曼昱/蒯曼冲击女双金牌!王楚钦、林诗栋出战!男子、女子4×100米接力决赛

开成运动会
2026-09-28 00:22:07
天彻底塌了!亚运全线溃败零金收官,中国篮球已跌至谷底!

天彻底塌了!亚运全线溃败零金收官,中国篮球已跌至谷底!

田先生篮球
2026-09-25 17:24:52
中国驻菲律宾使馆发言人就菲防长特奥多罗涉华消极言论表明立场

中国驻菲律宾使馆发言人就菲防长特奥多罗涉华消极言论表明立场

新京报
2026-09-27 20:15:10
随着西班牙3-2战胜英格兰,金球奖投票明天截止,前3排名基本如下

随着西班牙3-2战胜英格兰,金球奖投票明天截止,前3排名基本如下

球场没跑道
2026-09-27 10:40:16
高铁女孩熟睡男生盯死手机不敢转头,两百万外网围观无声的人机对峙,藏着成年人不敢破冰的社交尴尬防线

高铁女孩熟睡男生盯死手机不敢转头,两百万外网围观无声的人机对峙,藏着成年人不敢破冰的社交尴尬防线

明话直说
2026-08-25 18:07:55
昔日国乒名将陈龙灿发声!张本宇留日另有原因,当年选择不简单

昔日国乒名将陈龙灿发声!张本宇留日另有原因,当年选择不简单

潋滟晴方DAY
2026-08-13 13:06:45
仅差6500万,沈腾《欢迎来龙餐馆》票房将超越周星驰《功夫女足》

仅差6500万,沈腾《欢迎来龙餐馆》票房将超越周星驰《功夫女足》

可乐谈情感
2026-09-27 18:11:20
张本智和爆冷出局!日本网友怒批:赢球就大喊大叫,输球就心态崩盘

张本智和爆冷出局!日本网友怒批:赢球就大喊大叫,输球就心态崩盘

乒乓乐园
2026-09-28 00:05:07
2-1!世界第9征服客场,新帅哈维收获首胜,23岁天才梅开二度

2-1!世界第9征服客场,新帅哈维收获首胜,23岁天才梅开二度

足球狗说
2026-09-28 01:52:53
重回110米栏亚洲之巅!上海小伙陈圆将用小情绪打脸日本选手

重回110米栏亚洲之巅!上海小伙陈圆将用小情绪打脸日本选手

上观新闻
2026-09-27 22:01:06
彭博社再爆料:运往香港的F35零部件,除了座舱盖,还有弹仓构件

彭博社再爆料:运往香港的F35零部件,除了座舱盖,还有弹仓构件

军武吐槽君
2026-09-24 18:28:09
2026-09-28 04:15:00
新能源自动驾驶 incentive-icons
新能源自动驾驶
专注于半导体行业资讯
1015文章数 348关注度
往期回顾 全部

科技要闻

星舰第14飞,为什么准备绕地球六圈?

头条要闻

孙颖莎2比4不敌王曼昱摘银 称可能等不到下届亚运会了

头条要闻

孙颖莎2比4不敌王曼昱摘银 称可能等不到下届亚运会了

体育要闻

114项指控成立,曼城已经完蛋了吗?

娱乐要闻

刘欢去世后谣言四起,韩磊被翻旧账

财经要闻

“为了看短剧,我妈两年花了22万 ”

汽车要闻

MAZDA EZ-60激光版上市 焕新全系11.39万元起

态度原创

本地
教育
手机
公开课
军事航空

本地新闻

中秋逛白塔寺,体验国医妙荟雅集

教育要闻

成绩靠后的老师,就该“强制”自费培训20周吗?

手机要闻

iQOO16影像细节再剧透,主摄样张公布

公开课

李玫瑾:为什么性格比能力更重要?

军事要闻

特朗普"罕见"提二战中美同盟 日本慌了

无障碍浏览 进入关怀版