![]()
1. 服务概述
在统一诊断服务(UDS,ISO 14229)中,0x22 – ReadDataByIdentifier是一个最常用的读取服务。它允许诊断工具(Tester)向电子控制单元(ECU)请求一个或几个特定标识符(DID,Data Identifier)对应的数据。这些数据可以是 ECU 的硬/软件版本、车辆识别码(VIN)、序列号、当前会话类型、标定参数,甚至是实时采集的里程、电压、车窗位置等动态信号。
主要特点:
支持读取一个或多个 DID(最多由底层传输协议决定)。
DID 占 2 个字节(0x0000 – 0xFFFF),由 ECU 供应商定义。
响应返回 DID 及其对应的原始字节数据。
字节偏移
参数名
值(hex)
描述
0
Service ID
0x22
请求服务标识符
1
DID high byte
0xF1
DID 高字节
2
DID low byte
0x8C
DID 低字节
更多 DID(可选)
0xF1 0x90
连续提供多个 DID(2字节/个)
示例请求:22 F1 8C
含义:读取 DID = 0xF18C(ECU 序列号)。
2.2 肯定响应
字节偏移
参数名
值(hex)
描述
0
Response ID
0x62
肯定响应标识符(=0x22+0x40)
1
DID high byte
0xF1
原请求中的 DID 高字节
2
DID low byte
0x8C
原请求中的 DID 低字节
3..n
Data Record
12 34 56 78
该 DID 对应的数据(长度可变)
示例响应:62 F1 8C 12 34 56 78
含义:DID 0xF18C 的数据是 4 个字节0x12,0x34,0x56,0x78。
2.3 否定响应(NRC)
当请求不符合规范或 ECU 无法处理时,返回7F+ SID + NRC:
NRC 代码
含义
0x13
消息长度/格式错误
0x14
数据太长
0x22
条件不满足(如安全等级不足)
0x31
请求超出范围(DID 不支持)
3. 常用 DID 示例(非标准,厂家自定)
DID
描述
数据长度示例
数据内容示意
0xF18C
ECU 序列号
8 字节
"SN123456"
0xF190
VIN码
17 字节
"LFV3A23K9P3123456"
0xF186
当前会话
1 字节
0x01
(默认会话)
0xF187
软件版本
4 字节
0x01 0x02 0x03 0x00
0x1000
当前里程(km)
4 字节
0x00 0x00 0x12 0x34
(4660km)
0x1001
蓄电池电压(mV)
2 字节
0x0D 0x48
(3400mV)
注意:DID 及其含义完全由 ECU 供应商决定。ISO 14229 中仅保留 0x0000–0x00FF 为标准 DID(如 0xF190 常用作 VIN,但不是强制标准)。4. C++ 代码实现举例
下面给出一个简化但完整的 C++ 示例,模拟 ECU 中的0x22 服务处理逻辑。
代码包含:
一个 DID 数据表(
std::map存储 DID 与其字节数据)解析请求、查找 DID、构造响应
支持单个 DID 读取
处理不支持 DID 的错误(返回 NRC 0x31)
C++17 及以上(使用
std::vector、std::map)假设底层 CAN/DoCAN 已剥离传输协议,仅处理应用层消息(完整 UDS 长度 ≤ 8 字节)
4.3 代码运行结果#include
#include
#include
#include
#include
// 模拟 ECU 内部的 DID 数据存储器
class DidStorage {
public:
DidStorage() {
// 初始化一些示例 DID 数据
// DID 0xF18C : ECU 序列号 (8字节)
store[0xF18C] = {0x53, 0x4E, 0x30, 0x30, 0x31, 0x32, 0x33, 0x34}; // "SN001234"
// DID 0xF190 : VIN码 (17字节)
store[0xF190] = {0x4C, 0x46, 0x56, 0x33, 0x41, 0x32, 0x33, 0x4B,
0x39, 0x50, 0x33, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36}; // "LFV3A23K9P3123456"
// DID 0xF186 : 当前会话 (1字节)
store[0xF186] = {0x01}; // 默认会话
// DID 0x1000 : 里程,单位 km (4字节,小端)
store[0x1000] = {0x34, 0x12, 0x00, 0x00}; // 0x1234 = 4660 km
}
// 根据 DID 获取数据,若不存在返回 nullptr
const std::vector* getData(uint16_t did) const {
auto it = store.find(did);
if (it != store.end()) {
return &(it->second);
}
return nullptr;
}
private:
std::map> store;
};
// 处理 0x22 服务请求
// 输入: 完整的 UDS 请求报文 (已剥离传输层,仅应用层数据)
// 输出: 响应报文 (肯定或否定)
std::vector handleReadDataByIdentifier(const std::vector& request, const DidStorage& storage) {
// 最小请求长度: 1字节SID + 2字节DID = 3
if (request.size() < 3 || request[0] != 0x22) {
// 格式错误或非0x22服务 -> 返回 NRC 0x13 (消息长度/格式错误)
return {0x7F, 0x22, 0x13};
}
// 目前仅实现单个 DID 读取 (多DID处理原理相同,可循环)
if (request.size() == 3) {
uint16_t did = (static_cast(request[1]) << 8) | request[2];
const auto* data = storage.getData(did);
if (data == nullptr) {
// DID 不支持 -> NRC 0x31 (请求超出范围)
return {0x7F, 0x22, 0x31};
}
// 构造肯定响应: 0x62 + DID(2B) + data
std::vector response;
response.reserve(3 + data->size());
response.push_back(0x62);
response.push_back(static_cast((did >> 8) & 0xFF));
response.push_back(static_cast(did & 0xFF));
response.insert(response.end(), data->begin(), data->end());
return response;
}
else {
// 请求报文长度 >3 意味着多个 DID,本例简化处理,返回 NRC 0x14 (数据太长)
// 实际产品应解析多个 DID 并按顺序拼接数据
return {0x7F, 0x22, 0x14};
}
}
// 辅助打印函数
void printHex(const std::vector& data, const std::string& desc) {
std::cout << desc << " [";
for (size_t i = 0; i < data.size(); ++i) {
std::cout << std::hex << std::uppercase << static_cast(data[i]);
if (i != data.size() - 1) std::cout << " ";
}
std::cout << std::dec << "]" << std::endl;
}
int main() {
// 初始化 ECU 数据存储区
DidStorage ecuStorage;
// 测试场景1: 有效单个 DID (0xF18C)
std::vector request1 = {0x22, 0xF1, 0x8C};
auto response1 = handleReadDataByIdentifier(request1, ecuStorage);
printHex(response1, "Request: 22 F1 8C -> Response");
// 测试场景2: 有效单个 DID (0x1000 里程)
std::vector request2 = {0x22, 0x10, 0x00};
auto response2 = handleReadDataByIdentifier(request2, ecuStorage);
printHex(response2, "Request: 22 10 00 -> Response");
// 测试场景3: 不支持的 DID (0x9999)
std::vector request3 = {0x22, 0x99, 0x99};
auto response3 = handleReadDataByIdentifier(request3, ecuStorage);
printHex(response3, "Request: 22 99 99 -> Response");
// 测试场景4: 格式错误 (长度不足)
std::vector request4 = {0x22, 0xF1};
auto response4 = handleReadDataByIdentifier(request4, ecuStorage);
printHex(response4, "Request: 22 F1 (too short) -> Response");return 0;
}
Request: 22 F1 8C -> Response [62 F1 8C 53 4E 30 30 31 32 33 34]
Request: 22 10 00 -> Response [62 10 00 34 12 0 0]
Request: 22 99 99 -> Response [7F 22 31]
Request: 22 F1 (too short) -> Response [7F 22 13]
4.4 代码说明DidStorage 类:模拟 ECU 内部的非易失/易失数据字典,使用
std::map >存储。handleReadDataByIdentifier:
首先校验请求长度及 SID。
提取第一个 DID(本例为简化,只处理单 DID)。
调用
storage.getData查询数据。若找到则构造
0x62 + DID + Data的肯定响应。若找不到返回 NRC 0x31。
多 DID 支持:标准 UDS 允许一次请求多个 DID,响应应顺序返回每个 DID 及其数据。本示例为简洁未实现,但可以扩展:循环解析每个 DID,依次查表,将结果拼接进响应;若任何一个 DID 无效,则中止并返回 NRC 0x31。
传输层:实际车辆中,UDS 报文可能大于 8 字节(如 VIN 的 17 字节),需要底层协议如 ISO 15765-2 (CAN-TP) 进行拆包。本例假设请求/响应长度均 ≤ 8 字节,聚焦服务逻辑。
5. 实际开发注意事项
安全性:某些 DID 读取需要先通过安全访问(0x27 服务),否则应返回 NRC 0x22。
动态数据:里程、电压等 DID 应在读取时实时从硬件获取,而不是返回静态缓存值。
字节序:多字节数值(如 int32)在 CAN 总线通常采用大端(Motorola)或小端(Intel)。需与 OEM 保持一致,本示例中里程按小端存储。
扩展多 DID:请求
22 F1 8C F1 90,响应应为62 F1 8C F1 90。NRC 优先级:若同时存在多个错误(如长度错误且 DID 不支持),应优先返回格式类 NRC(0x13)。
UDS 0x22 服务是诊断交互中最基础也最实用的服务之一,让外部工具轻松读取出 ECU 内部的各种标识数据。通过合理设计 DID 表,可以覆盖从静态标定信息到实时物理量的全部读取需求。开发者实现时需注意:
正确解析 DID
处理不支持 DID 情况
考虑数据长度和传输协议
必要时加入安全访问保护敏感数据
以上代码示例可以直接集成到 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.