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

基于AUTOSAR DCM架构的UDS 0x22服务读取VIN码详解(附C++代码示例)

0
分享至


一、VIN码简介

车辆识别码(Vehicle Identification Number, VIN)是国际上通用的车辆唯一身份标识,由17位字符(字母+数字)组成,包含制造商、车辆特征、生产年份、装配厂等关键信息。在汽车电子领域,VIN码通常被存储在ECU的EEPROM或Flash中,等待诊断工具通过UDS服务读取。

二、UDS 0x22服务详解

UDS(统一诊断服务)中的ReadDataByIdentifier(0x22)服务,用于通过DID(Data Identifier,数据标识符)读取ECU内部存储的数据。该服务不支持子功能参数,因此其报文格式相对简洁。

1. 请求报文格式(单DID):

字节内容        | 含义
----------------|------------------------
字节: 0x22 | 服务标识符(SID)
字节: DID高字节 | 数据标识符高字节
字节: DID低字节 | 数据标识符低字节

2. 肯定响应报文格式:

字节内容        | 含义
----------------|------------------------
字节: 0x62 | 响应SID (SID + 0x40)
字节: DID高字节 | 数据标识符高字节
字节: DID低字节 | 数据标识符低字节
字节 ~ : | 读取到的数据记录值

3. 否定响应(NRC)常见码:

  • 0x13 :报文长度或格式不正确

  • 0x22 :当前条件不满足读取该DID

  • 0x31 :请求的DID不存在或当前会话不支持

  • 0x33 :安全访问等级不足

三、AUTOSAR DCM三层架构概览

在AUTOSAR BSW诊断通信栈中,ECU的接收端DCM模块由三个子模块构成,各自承担不同的职责,从接收到诊断请求到最终填装并返回响应,服务执行路径在此处完成核心解耦。

3.1 DSL子模块 —— 诊断会话管理层

收到请求时,PduR会调用接口将诊断请求数据从下层Buffer拷贝到DCM的接收Buffer中,并通知DCM模块接收到了新的诊断请求。DSL还负责管理诊断会话和安全等级状态。

3.2 DSD子模块 —— 诊断服务分发层

DSD模块从接收的数据中识别请求的服务类型(0x22、0x10、0x27等),其核心功能如下:当DSL接收到新的诊断请求时,它会通过内部接口通知DSD。随后,DSD依次执行以下检查:

  • 检查当前会话类型是否支持该SID

  • 检查安全访问等级是否满足

  • 验证诊断请求数据的长度是否符合要求

当各项检查通过后,DSD再将请求分发至DSP子模块中对应的服务处理函数进行处理,并在DSP完成处理后,负责汇总响应数据,最终将响应封装后发回DSL层,再由DSL向外发送数据。这三层架构既保障了诊断服务的高可用性,也为内部的函数调用结构提供了清晰的逻辑框架——在完整的VIN码读取过程中,每一步模块访问都沿着明确定义的路径进行。

3.3 DSP子模块 —— 诊断服务处理层

DSP负责执行具体的服务处理逻辑。对于ReadDataByIdentifier服务,DSP会:查找DID对应的处理回调、调用底层模块(如NvM)读取数据、构造响应报文。

四、诊断仪读取VIN码完整流程总结

以DID = 0xF190读取17字节VIN码为例,完整流程如下:

  1. 诊断仪发送请求 :发送CAN帧 02 22 F1 90 (PCI=0x00表示单帧,长度=2)

  2. ECU接收 :CanIf接收,PduR路由到CanTp重组,交给DCM,得到数据 22 F1 90 。

  3. DCM DSL接收请求 :DSL接收诊断请求并传递。

  4. DCM DSD服务分发 :DSD检查会话/安全条件,根据SID=0x22查找处理函数。

  5. DSP执行22服务 :提取DID=0xF190,查找DID级回调。该回调配置为通过NvM读取数据,进而调用 NvM_ReadBlock() 从EEPROM读取17字节VIN码。

  6. DCM DSD/DSP构造响应 :DID回调构造响应报文 62 F1 90 + VIN(17字节) 。

  7. DSP逐层返回并最终发送响应 :回调生成的响应自DSP返回上一级,经DSD统一数据再到DSL,DSL调用PduR接口将响应发送出去。

  8. 发送响应 :DCM将响应通过PduR交给CanTp。17字节VIN加上响应头共20字节,CanTp将其分包为多帧,通过CanIf逐帧发送到CAN总线上。

至此,诊断仪收到所有帧后重组得到完整响应。

五、DCM内部函数调用路径图解

下图展示了在VIN码读取过程中,DCM DSL→DSD→DSP各子模块之间的典型函数调用路径。该路径清晰对应于前文所述的三层结构,可以帮助理解DCM模块内部如何逐层完成请求接收、检查分发、数据读取和响应返回:


六、C++代码示例

以下是简化的C++代码示例,展示在AUTOSAR架构下如何实现VIN码(DID=0xF190)的读取。代码按DSL、DSD、DSP职责分层,并重点放置与上图中DID回调调用NvM读取数据对应的具体实现。

#include  
          
#include
#include
#include
#include

using namespace std;

// ========== 1. CAN通信层模块 ==========
class CanIf {
public:
// 模拟接收CAN帧:诊断仪发送 02 22 F1 90
static void ReceiveCanFrame(const vector& frame, class PduR& pduRouter);
};

class CanTp {
public:
// 接收CAN帧数据,进行单帧/多帧重组
static void ReceiveData(const vector& canData, class PduR& pduRouter);
};

class PduR {
public:
// PDU路由,根据配置将诊断报文路由到DCM
void RouteToDcm(const vector& udsData);
};

// ========== 2. DCM模块 - 诊断通信管理层 ==========
// 前向声明
class Dsd;
class Dsp;
class NvM;

// DCM上下文结构(简化)
struct Dcm_MsgContextType {
vector reqData; // 请求数据
vector resData; // 响应数据
uint8_t sid; // 服务ID
uint16_t did; // 数据标识符
uint8_t nrc; // 否定响应码(0表示无错误)
};

// 2.1 DSL子模块 - 诊断会话层
class Dsl {
private:
Dsd& dsd;
Dcm_MsgContextType msgCtx;
uint8_t currentSession = 0x01; // 默认会话
uint8_t securityLevel = 0x00;

public:
Dsl(Dsd& dsdRef) : dsd(dsdRef) {}

// DSL: PduR调用的诊断接收指示接口
void Dcm_TpRxIndication(const vector& data) {
msgCtx.reqData = data;
cout << "[DSL] Dcm_TpRxIndication: 收到诊断请求,长度=" << data.size() << endl;

// 检查会话状态
if (currentSession != 0x01 && currentSession != 0x03) {
msgCtx.nrc = 0x7F; // 服务不支持会话
SendNegativeResponse();
return;
}

// 判断是否为单帧SF: 首字节0x00表示SF,后续为数据长度
if (data.size() >= 1 && (data[0] & 0xF0) == 0x00) {
// 实际数据从第2字节开始
vector udsData(data.begin() + 1, data.end());
// 将请求传递给DSD处理
dsd.Dsd_RxIndication(udsData, msgCtx);
} else {
// 多帧情况处理...
dsd.Dsd_RxIndication(data, msgCtx);
}
}

// DSL: DCM模块获取安全等级接口
uint8_t Dcm_GetSecurityLevel() const { return securityLevel; }

// DSL: 发送诊断响应
void Dsl_SendResponse(const Dcm_MsgContextType& ctx) {
// 构造CAN帧(单帧格式)进行发送
if (ctx.resData.size() <= 7) {
vector canFrame;
uint8_t pci = (0x00 << 4) | ctx.resData.size(); // 单帧PCI
canFrame.push_back(pci);
canFrame.insert(canFrame.end(), ctx.resData.begin(), ctx.resData.end());
cout << "[DSL] 发送单帧响应: ";
for (auto b : canFrame) cout << hex << (int)b << " ";
cout << dec << endl;
} else {
cout << "[DSL] 触发多帧发送:" << ctx.resData.size() << "字节" << endl;
}
}

private:
void SendNegativeResponse() {
vector negResp = {0x7F, msgCtx.sid, msgCtx.nrc};
Dcm_MsgContextType negCtx;
negCtx.resData = negResp;
Dsl_SendResponse(negCtx);
}
};

// 2.2 DSD子模块 - 诊断服务分发层
class Dsd {
private:
Dsp& dsp;

// 服务表:SID -> 服务处理函数指针
using Dcm_SidTable_Fnc = bool (*)(const Dcm_MsgContextType&, Dcm_MsgContextType&);
struct DcmDsdServiceTable {
uint8_t sid;
Dcm_SidTable_Fnc serviceFunc;
};

static constexpr DcmDsdServiceTable serviceTables[] = {
{0x22, Dsp::Service_ReadDataByIdentifier}
};

public:
Dsd(Dsp& dspRef) : dsp(dspRef) {}

// DSD: 分发诊断请求到具体服务处理函数
void Dsd_RxIndication(const vector& udsData, Dcm_MsgContextType& msgCtx) {
if (udsData.empty()) return;
msgCtx.sid = udsData[0]; // 提取SID

cout << "[DSD] 分发诊断请求: SID=0x" << hex << (int)msgCtx.sid << dec << endl;

// 安全检查: 获取当前安全等级, 若等级为0且此SID需要安全访问则拒绝
if (msgCtx.sid == 0x22 && msgCtx.did == 0xF190) {
// 假设0xF190 DID需要安全等级至少1级
}

// 查找服务表
for (const auto& entry : serviceTables) {
if (entry.sid == msgCtx.sid) {
// 调用服务处理函数,并将响应结果存回msgCtx.resData
if (entry.serviceFunc(msgCtx, msgCtx)) {
// 发送正响应(由上层触发)
Dsl_SendResponse(msgCtx);
} else {
// 发送负响应(NRC已在处理函数中设置)
Dsl_SendNegativeResponse(msgCtx);
}
return;
}
}

// 服务未找到:发送负响应NRC=0x11(服务不支持)
msgCtx.nrc = 0x11;
Dsl_SendNegativeResponse(msgCtx);
}

void Dsl_SendResponse(Dcm_MsgContextType& ctx) {
ctx.resData.insert(ctx.resData.begin(), ctx.sid + 0x40); // 将SID转换为响应ID(+0x40)
// 调用DSL发送
}

void Dsl_SendNegativeResponse(Dcm_MsgContextType& ctx) {
vector negRes = {0x7F, ctx.sid, ctx.nrc};
ctx.resData = negRes;
// 调用DSL发送
}
};

// 2.3 DSP子模块 - 诊断服务处理层
class Dsp {
private:
// DID 0xF190对应的NvM块ID (VIN码存储块)
static constexpr uint32_t NVM_BLOCK_ID_VIN = 0x0100;

public:
// 22服务处理函数(静态入口,供服务表调用)
static bool Service_ReadDataByIdentifier(const Dcm_MsgContextType& reqCtx, Dcm_MsgContextType& resCtx) {
// 解析DID
if (reqCtx.reqData.size() < 3) {
resCtx.nrc = 0x13; // 报文长度不正确
return false;
}
uint16_t did = (reqCtx.reqData[1] << 8) | reqCtx.reqData[2];
cout << "[DSP] Service_ReadDataByIdentifier: DID=0x" << hex << did << dec << endl;

// 查找对应DID的处理函数(DID级回调)
return DID_ReadHandler_0xF190(reqCtx, resCtx);
}

// DID级处理回调:DID 0xF190 (VIN码)
static bool DID_ReadHandler_0xF190(const Dcm_MsgContextType& reqCtx, Dcm_MsgContextType& resCtx) {
cout << "[DSP] DID_ReadHandler_0xF190: 读取VIN码" << endl;

// 从NvM读取17字节VIN码
vector vinData(17);
NvM& nvm = NvM::GetInstance();
if (!nvm.NvM_ReadBlock(NVM_BLOCK_ID_VIN, vinData.data(), vinData.size())) {
resCtx.nrc = 0x22; // 条件不满足
return false;
}

// 构造肯定响应数据:DID + 数据值
vector responseData;
responseData.push_back((reqCtx.reqData[1])); // DID高字节
responseData.push_back((reqCtx.reqData[2])); // DID低字节
responseData.insert(responseData.end(), vinData.begin(), vinData.end());
resCtx.resData = responseData;

cout << "[DSP] VIN读取成功,17字节数据已加载" << endl;
return true; // 处理成功
}
};

// ========== 3. NvM模块 - 非易失性存储管理 ==========
class NvM {
private:
static NvM instance;
// 模拟EEPROM中存储的VIN码数据
uint8_t vinData[17] = {'L','F','V','3','A','2','1','F','8',
'K','5','P','6','0','0','1','2'};

public:
static NvM& GetInstance() { return instance; }

// NvM读取块数据接口
bool NvM_ReadBlock(uint32_t blockId, uint8_t* buffer, uint32_t length) {
if (blockId == 0x0100 && length == 17) {
memcpy(buffer, vinData, 17);
cout << "[NvM] NvM_ReadBlock: 从EEPROM读取VIN码成功" << endl;
return true;
}
return false;
}

// NvM写入块数据接口
bool NvM_WriteBlock(uint32_t blockId, const uint8_t* buffer, uint32_t length) {
if (blockId == 0x0100 && length == 17) {
memcpy(vinData, buffer, 17);
cout << "[NvM] NvM_WriteBlock: 写入VIN码成功" << endl;
return true;
}
return false;
}
};
NvM NvM::instance;

// ========== 4. CAN通信层实现 ==========
class CanIf {
public:
static void ReceiveCanFrame(const vector& frame, class PduR& pduRouter) {
// 解析CAN TP单帧或多帧(简化示例)
if (frame.empty()) return;
uint8_t pci = frame[0];
uint8_t pciType = (pci >> 4) & 0x0F;
if (pciType == 0x00) { // SF单帧
uint8_t dataLen = pci & 0x0F;
vector canTpData(frame.begin() + 1, frame.begin() + 1 + dataLen);
// 交给CanTp进行重组
CanTp::ReceiveData(canTpData, pduRouter);
} else {
// 多帧处理...
}
}
};

class CanTp {
public:
static void ReceiveData(const vector& canData, class PduR& pduRouter) {
// 将重组后的UDS数据路由到PduR
pduRouter.RouteToDcm(canData);
}
};

void PduR::RouteToDcm(const vector& udsData) {
static Dsp dsp;
static Dsd dsd(dsp);
static Dsl dsl(dsd);
// 传递给DCM DSL模块
dsl.Dcm_TpRxIndication(udsData);
}

// ========== 5. 主函数测试示例 ==========
int main() {
PduR pduRouter;

// 模拟诊断仪发送:单帧 CAN message = 0x02 (PCI: 0x00=SF, 长度=2) | 0x22 | 0xF1 | 0x90
vector canFrame = {0x02, 0x22, 0xF1, 0x90};
cout << "========================================" << endl;
cout << "模拟诊断仪发送诊断请求:" << endl;
cout << "CAN帧数据: ";
for (auto b : canFrame) cout << hex << (int)b << " ";
cout << dec << endl << endl;

// 调用CanIf接收(模拟ECU接收)
CanIf::ReceiveCanFrame(canFrame, pduRouter);

cout << "========================================" << endl;
return 0;
}
七、代码说明
  1. DCM三层结构:代码将DCM划分为Dsl(诊断会话层)、Dsd(诊断服务分发层)、Dsp(诊断服务处理层),与AUTOSAR标准概念对应,并分别实现了上图中所示的DSL接收请求、DSD服务分发和DID级回调处理三大环节。

  2. 服务表机制:Dsd模块维护一个服务表,将SID映射到对应的处理函数。Service_ReadDataByIdentifier入口函数先提取DID,再通过手工判断找到DID_0xF190对应的处理逻辑;实际量产代码中会通过脚本或自动工具为每个DID生成独立配置结构,以提升可维护性和表格驱动能力。

  3. NvM读取:DSP的DID回调函数调用NvM_ReadBlock()从EEPROM读取17字节VIN码,对应图中“DID回调调用NvM_ReadBlock”的具体实现。

  4. CAN TP处理:严格遵循ISO 15765-2,识别单帧(SF)和多帧(含首帧、流控帧、连续帧)两种传输模式,单帧直接重组,多帧则通过接收状态机完成完整报文重建。代码中CanIf::ReceiveCanFrame对PCI进行初步解析后交给CanTp,再由PduR单一出口路由至DCM,整体路径与CAN→CanIf→CanTp→PduR→DCM的AUTOSAR标准分层完全一致。

  5. 代码执行流程:诊断仪发送诊断请求报文(02 22 F1 90)→ECU端的CanIf接收到CAN帧→CanTp重组获得UDS数据(22 F1 90)→PduR路由至DCM DSL→DSD检查会话和安全条件→查找到22服务的处理函数入口→DSP提取DID调用相应处理回调读取NvM→构造肯定响应(62 F1 90 VIN[17])→DSL发送响应至诊断仪。该流程每一步都可以在前文“DCM内部函数调用路径图解”中找到函数层面的映射关系,实现了分层理论与代码实现的一致闭环。

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

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-28 12:32:49
刘青云携妻子郭蔼明现身卡定沟游玩,模样越来越像萨达姆,一个黑脸一个笑脸对比鲜明

刘青云携妻子郭蔼明现身卡定沟游玩,模样越来越像萨达姆,一个黑脸一个笑脸对比鲜明

裕丰娱间说
2026-09-27 13:57:53
涉案金额过亿 刘应成(原法名释永信)一审被判有期徒刑24年

涉案金额过亿 刘应成(原法名释永信)一审被判有期徒刑24年

每日经济新闻
2026-05-30 00:42:59
权贵们绕开高考走捷径:北理工裴轶和协会 4+4 一个路数

权贵们绕开高考走捷径:北理工裴轶和协会 4+4 一个路数

放开他让wo来
2026-09-27 13:45:46
亚运会最新奖牌榜:日本157枚,韩国83枚,中国健儿太让国人自豪

亚运会最新奖牌榜:日本157枚,韩国83枚,中国健儿太让国人自豪

锅锅爱历史
2026-09-28 06:23:47
被质疑放水输球!混双丢冠,孙颖莎王楚钦开心互动,谁注意王楚钦表态

被质疑放水输球!混双丢冠,孙颖莎王楚钦开心互动,谁注意王楚钦表态

懂球社
2026-09-27 11:25:19
日本:引进中国人,韩国人,甚至越南人都可以,但绝对不引进印度人和穆斯林!

日本:引进中国人,韩国人,甚至越南人都可以,但绝对不引进印度人和穆斯林!

步论天下事
2026-09-28 10:50:23
“没想到一枪就‘下班’了”,18岁严子怡只投了一枪,拿下金牌,大比分碾压日本名将!她曾放话:未来,开启一个属于我的新时代吧

“没想到一枪就‘下班’了”,18岁严子怡只投了一枪,拿下金牌,大比分碾压日本名将!她曾放话:未来,开启一个属于我的新时代吧

南方都市报
2026-09-28 11:58:41
天彻底塌了!亚运全线溃败零金收官,中国篮球已跌至谷底!

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

田先生篮球
2026-09-25 17:24:52
女友在亚马尔社媒评论:我还是觉得你也太帅了,太不真实了

女友在亚马尔社媒评论:我还是觉得你也太帅了,太不真实了

懂球帝
2026-09-27 21:29:20
金卡戴珊全家逛上海野生动物园,紧身裤配高跟鞋,这打扮真够精致

金卡戴珊全家逛上海野生动物园,紧身裤配高跟鞋,这打扮真够精致

悦君兮君不知
2026-09-28 11:21:46
三毛说我去非洲是为了帮助黑人,李敖反问:你为什么不帮助黄人?

三毛说我去非洲是为了帮助黑人,李敖反问:你为什么不帮助黄人?

言过于诚
2026-09-27 22:31:29
骗走50亿!用小鲜肉的血抗衰,被央视曝光的“捞金女王”,真栽了

骗走50亿!用小鲜肉的血抗衰,被央视曝光的“捞金女王”,真栽了

深析古今
2026-04-08 10:09:38
张本智和:阿拉米扬以往发球就算不上完全合规,现在又肆无忌惮

张本智和:阿拉米扬以往发球就算不上完全合规,现在又肆无忌惮

懂球帝
2026-09-27 15:40:00
“反华妖女”许可馨,回国后曾叫嚣没人敢动她,六年后下场如何?

“反华妖女”许可馨,回国后曾叫嚣没人敢动她,六年后下场如何?

锅锅爱历史
2026-07-26 08:50:12
你做过最龌龊,最下流的事是什么?

你做过最龌龊,最下流的事是什么?

那年秋天
2026-08-16 11:50:09
仔细看,这张拍于2016年的合影照,当时樊振东19岁,那时的樊振东确实还满是青涩感!就不知左边的美女是谁?

仔细看,这张拍于2016年的合影照,当时樊振东19岁,那时的樊振东确实还满是青涩感!就不知左边的美女是谁?

小琦聊生活
2026-09-25 13:24:02
《繁花》里最狠的一条生意经:嘴甜是下等人的本事,忠心是中等人的筹码,能被大人物拉上桌的分蛋糕者,手里攥着这两种教人离不开的硬通货

《繁花》里最狠的一条生意经:嘴甜是下等人的本事,忠心是中等人的筹码,能被大人物拉上桌的分蛋糕者,手里攥着这两种教人离不开的硬通货

心理观察局
2026-08-04 06:38:04
河南一技校101名毕业生入职北大,所涉专业系全国唯一,该校曾入职北大的毕业生:有和北大人才中心签约的,也有劳务派遣

河南一技校101名毕业生入职北大,所涉专业系全国唯一,该校曾入职北大的毕业生:有和北大人才中心签约的,也有劳务派遣

极目新闻
2026-09-27 19:00:11
微信上基本不发朋友圈的人,未必低调,十有八九是这三种人:1、生活过于丰富,不便展示;2、看透人性,藏锋守拙;3、自给自足,太充实

微信上基本不发朋友圈的人,未必低调,十有八九是这三种人:1、生活过于丰富,不便展示;2、看透人性,藏锋守拙;3、自给自足,太充实

品读时刻
2026-09-12 09:06:58
2026-09-28 13:11:00
新能源自动驾驶 incentive-icons
新能源自动驾驶
专注于半导体行业资讯
1015文章数 348关注度
往期回顾 全部

科技要闻

智能体惹了多少祸,OpenAI自己没查清!

头条要闻

供销社职工以46600元参与拐卖外籍妇女 获刑5年9个月

头条要闻

供销社职工以46600元参与拐卖外籍妇女 获刑5年9个月

体育要闻

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

娱乐要闻

好汉刘欢:一副好嗓子,一颗赤子心

财经要闻

标价298元核桃油, 实为大豆油冒充

汽车要闻

神龙科技与Momenta达成全球战略合作 2027年起推新车

态度原创

旅游
艺术
亲子
本地
公开课

旅游要闻

广州南沙中秋接待游客超104万人次,多元演艺点燃文旅消费

艺术要闻

12张效果图,抖音深圳第二总部显真容

亲子要闻

世界上最危险的十大玩具,为何成为了可怕的伤人利器?

本地新闻

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

公开课

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

无障碍浏览 进入关怀版