1、诊断栈概述:汽车诊断协议(UDS/OBD)简介、诊断栈在ECU中的位置、分层架构的价值
1.1 汽车诊断协议:UDS与OBD到底在聊什么?
说到汽车诊断,大家首先想到的可能是4S店插上设备读故障码。但背后的协议,其实就两大主流:OBD和UDS。
OBD(On-Board Diagnostics),说白了就是“强制体检”。它最早是美国加州空气资源委员会搞出来的,目的是监控排放相关的系统。OBD II是它的第二代标准,规定了统一的诊断接口和一组固定的诊断服务。比如读取故障码(Service 01)、清除故障码(Service 04)、读取实时数据(Service 01)等。它的特点是“轻量、固定、只关心排放”。
UDS(Unified Diagnostic Services),则是“全面体检+手术”。它基于ISO 14229标准,覆盖了从读取/写入数据、例程控制、上传下载软件等几乎所有诊断场景。UDS的服务数量远多于OBD,而且支持扩展。比如你可以通过UDS刷写ECU固件,这在OBD里是做不到的。
我个人习惯把OBD比作“体检套餐”,UDS则是“全科门诊+手术室”。你想想看,OBD只能告诉你“发动机排放超标了”,但UDS可以告诉你“是氧传感器老化,还是三元催化器堵了”,甚至能直接帮你更新软件修复问题。
核心区别一览:
| 维度 | OBD II | UDS |
|---|---|---|
| 标准 | ISO 15031 / SAE J1979 | ISO 14229 |
| 服务数量 | 约10个(固定) | 26个(可扩展) |
| 主要用途 | 排放相关诊断 | 全功能诊断(含刷写) |
| 传输层 | CAN(11位ID) | CAN、CAN FD、以太网等 |
| 应用场景 | 法规检测、通用读码 | 产线、售后、远程诊断 |
我在项目中遇到过不少工程师把UDS和OBD混为一谈。其实它们虽然都跑在CAN总线上,但应用层协议完全不同。你如果拿OBD的请求去问UDS的ECU,它根本不会理你——因为服务ID对不上。
1.2 诊断栈在ECU中的位置:它到底住在哪?
诊断栈不是一个独立的硬件模块,它是一套软件组件,嵌入在ECU的应用层与底层驱动之间。我习惯把它画在“中间件”这一层。
你可以把ECU的软件架构想象成三层楼:
- 底层(硬件抽象层):负责CAN/以太网收发器、定时器、存储等硬件操作。
- 中间层(诊断栈):负责解析诊断请求、管理会话、处理安全访问、执行诊断服务。
- 上层(应用层):负责具体的功能逻辑,比如发动机控制、车窗升降等。
诊断栈就像一个“翻译官+调度员”。它从总线上收到一串字节流,解析出这是“读取故障码”还是“写入参数”,然后调用应用层提供的接口去执行,最后把结果打包成响应报文发回去。
我的经验:诊断栈通常不直接操作硬件,也不直接实现业务逻辑。它只负责“协议解析”和“服务路由”。真正的数据读写,比如读取某个传感器的值,是由应用层提供的回调函数完成的。这样设计的好处是——换一个ECU,只要应用层接口不变,诊断栈代码几乎不用改。
嗯,这里要注意:诊断栈在AUTOSAR架构中属于BSW(基础软件)的一部分,具体来说是“通信栈”和“诊断栈”两个模块。但在非AUTOSAR项目中,我们通常自己实现一个轻量级的诊断栈,放在OS之上、应用之下。
3 分层架构的价值:为什么我们要把它拆开?
你可能会问:为什么不把诊断逻辑直接写在应用代码里?反正就几个if-else判断服务ID嘛。
我刚开始做诊断时也这么想。直到有一次,客户要求把诊断协议从UDS换成OBD,我改了一个星期的应用代码,还引入了好几个bug。从那以后,我再也不敢把诊断逻辑和应用逻辑混在一起了。
分层架构的核心价值,我总结为三点:
- 解耦:诊断栈不依赖具体的应用逻辑。你换一个ECU,只要应用层接口不变,诊断栈代码直接复用。我在一个项目中,把同一套诊断栈用在了BCM、GW、VCU三个ECU上,只改了配置文件和回调函数。
- 可测试性:分层后,你可以单独测试诊断栈。比如用CANoe模拟诊断仪,发送各种合法和非法的请求,验证诊断栈的响应是否正确。不用等应用层开发完。
- 可维护性:诊断协议升级时,比如从UDS 2006版升级到2020版,你只需要修改诊断栈内部的服务处理逻辑,应用层代码完全不用动。
避坑指南:我曾经见过一个项目,诊断逻辑散落在各个应用模块中。结果客户要求增加一个“安全访问”功能,需要修改所有涉及诊断写操作的模块。那叫一个痛苦。所以,我建议从一开始就把诊断栈独立出来,哪怕项目很小。
分层架构还有一个隐藏的好处:便于复用。你想想看,现在汽车电子开发越来越平台化。一个诊断栈可以服务于多个项目,只要把硬件相关的部分(比如CAN驱动、NVM驱动)抽象出来。这就是为什么AUTOSAR要定义标准化的诊断栈接口——说白了,就是为了让不同供应商的代码能拼在一起工作。
最后,我用一个简单的代码片段来展示分层后的调用关系:
// 诊断栈层:解析请求
void DiagStack_ProcessMessage(uint8_t* data, uint16_t len) {
uint8_t serviceId = data[0];
switch (serviceId) {
case 0x22: // ReadDataByIdentifier
DiagSrv_ReadDataByIdentifier(&data[1], len-1);
break;
case 0x2E: // WriteDataByIdentifier
DiagSrv_WriteDataByIdentifier(&data[1], len-1);
break;
// ... 其他服务
}
}
// 应用层接口:实际读写数据
// 由应用层实现,诊断栈通过函数指针调用
void App_ReadData(uint16_t did, uint8_t* value, uint16_t* len) {
// 根据DID读取对应的传感器值或标定参数
}
你看,诊断栈只负责“路由”,不负责“干活”。这就是分层架构的精髓。
好了,这一章我们聊了诊断协议的基本概念、诊断栈在ECU中的位置,以及分层架构为什么值得做。下一章,我会带大家深入诊断栈的内部结构,看看它到底由哪些模块组成,以及它们之间如何协作。