第2章:诊断协议栈架构:OSI七层模型与UDS映射、诊断应用层、诊断会话层、诊断传输层

各位工程师朋友,大家好。今天我们聊一个很基础但特别容易搞混的话题——诊断协议栈的架构。

我记得刚入行那会儿,看UDS协议栈的文档,满眼都是OSI七层模型、应用层、会话层、传输层……说实话,当时我脑子里就一个想法:这玩意儿到底怎么分层?每层管什么?跟我写代码有什么关系?

后来踩了不少坑,才慢慢理清楚。今天我就把这些经验掰开了讲给你听。

2.1 OSI七层模型与UDS的映射关系

先说说OSI七层模型。这个模型是国际标准化组织定义的网络通信框架,从下到上分别是:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。

但在汽车诊断领域,我们不会把七层全用上。实际上,UDS协议栈只用了其中几层。我个人习惯把它简化成四层来看:

OSI层 UDS协议栈对应 典型协议/实现
应用层(第7层) 诊断应用层 UDS服务(如0x22读数据、0x2E写数据)
表示层(第6层) (通常合并到应用层) 数据编码格式
会话层(第5层) 诊断会话层 会话状态管理(默认、扩展、编程)
传输层(第4层) 诊断传输层 ISO 15765-2(CAN上的多帧传输)
网络层(第3层) (通常由CAN控制器处理) CAN ID过滤、路由
数据链路层(第2层) CAN数据链路层 CAN帧格式、错误检测
物理层(第1层) CAN物理层 CAN总线电平、收发器

你想想看,为什么UDS不把七层全用上?说白了,汽车诊断通信的场景相对固定,不需要像互联网那样复杂的协议栈。我们更关心的是:怎么把诊断请求发出去,怎么把响应收回来,中间别丢包、别超时。

核心要点:UDS协议栈真正需要开发者关注的,是应用层、会话层和传输层这三层。物理层和数据链路层通常由CAN硬件和驱动搞定。

2.2 诊断应用层——你写的代码都在这里

诊断应用层,说白了就是UDS服务本身。你平时写的那些处理0x10、0x22、0x2E的代码,全在这一层。

这一层负责什么呢?

  • 服务解析:收到一个诊断请求,先看SID(服务标识符)是什么,然后分发给对应的处理函数。
  • 参数校验:检查请求中的子功能、数据长度、格式是否合法。
  • 执行操作:比如读DID、写DID、执行例程、上传下载数据。
  • 响应组装:把执行结果打包成响应报文,包括肯定响应和否定响应。

我在项目中遇到过一个问题:某ECU在收到0x22读数据请求时,总是返回否定响应0x7F。查了半天,发现是应用层代码里把DID的地址范围写错了。嗯,这种低级错误其实挺常见的,尤其是当DID数量超过100个的时候。

我的建议:在应用层实现时,一定要把DID的合法性检查放在最前面。先判断DID是否支持,再判断当前会话是否允许访问。顺序搞反了,可能会漏掉一些安全漏洞。

2.3 诊断会话层——状态机是核心

诊断会话层,很多人容易忽略它。但说实话,这一层要是没写好,整个诊断系统都会出问题。

会话层管理的是ECU的诊断会话状态。UDS定义了三种标准会话:

  • 默认会话(Default Session):上电后的初始状态,只允许执行一些基础服务,比如读VIN码、读故障码。
  • 扩展会话(Extended Session):允许执行更多服务,比如写数据、执行例程。通常需要先通过0x10服务切换过来。
  • 编程会话(Programming Session):用于刷写固件,权限最高。一般需要安全访问认证后才能进入。

会话层的工作就是维护这个状态机。每次收到诊断请求,先检查当前会话是否允许执行这个服务。如果不允许,直接返回0x7F(服务不支持或会话不允许)。

我曾经踩过一个坑:某项目在编程会话下执行了写DID操作,结果把校准数据写坏了。后来分析发现,是会话层没有限制写DID只能在扩展会话下执行。从那以后,我要求团队在会话层做严格的权限矩阵检查。

注意:会话层还有一个重要功能——会话超时管理。如果ECU在某个非默认会话下长时间没有收到诊断请求,应该自动切回默认会话。这个超时时间通常是5秒,但OEM可以自定义。我建议设成3~10秒之间,太短了容易误切,太长了不安全。

2.4 诊断传输层——数据怎么安全到达

诊断传输层,在CAN总线上对应的是ISO 15765-2协议。这一层解决的核心问题是:当诊断数据超过单帧CAN报文容量(8字节)时,怎么拆包、怎么重组。

传输层的主要工作包括:

  • 单帧/多帧判断:如果诊断数据长度 ≤ 7字节(CAN标准帧),直接用单帧发送。如果超过7字节,就需要拆成多帧。
  • 流控制管理:发送方发完第一帧后,接收方会回复流控制帧,告诉发送方「你可以继续发了」或者「等一下,我还没准备好」。
  • 超时重传:如果发送方没收到流控制帧,或者接收方没收到后续帧,都需要超时重传机制。
  • 顺序重组:接收方把收到的多帧数据按顺序拼起来,还原成完整的诊断请求或响应。

我记得有一次调试,发现ECU总是收不到完整的诊断请求。用CAN分析仪抓包一看,原来是发送方发的连续帧太快了,接收方的缓冲区溢出了。嗯,这就是典型的流控制没做好。

避坑指南:我曾经在传输层实现中忘记处理「连续帧之间的间隔时间」。结果某些CAN卡发送速度太快,ECU根本来不及处理。后来我加了一个最小帧间隔检查,问题就解决了。这个间隔一般建议设成0.5ms以上。

2.5 三层之间的协作关系

这三层不是各自为政的,它们之间有明确的调用关系。我画个简单的流程给你看:

诊断请求到达(CAN报文)
    ↓
传输层:接收CAN帧,判断单帧/多帧
    ↓  (重组完整报文)
会话层:检查当前会话状态,判断服务是否允许
    ↓  (通过检查)
应用层:解析SID,执行具体服务逻辑
    ↓  (生成响应)
会话层:检查响应是否允许在当前会话下发送
    ↓  (通过检查)
传输层:拆包(如果需要),发送CAN帧
    ↓
诊断响应发出

你想想看,如果会话层和应用层的顺序搞反了,会怎么样?比如先执行了服务,再检查会话权限。那万一服务执行过程中修改了关键数据,然后会话层说「不行,这个会话不允许」,这时候数据已经改了,回不去了。

所以,我个人习惯的检查顺序是:传输层完整性检查 → 会话层权限检查 → 应用层服务执行。这个顺序不能乱。

2.6 实际项目中的分层建议

最后,我结合项目经验,给你几个分层实现的建议:

  • 代码分层要清晰:传输层、会话层、应用层的代码要放在不同的模块里,不要混在一起。我见过有人把会话状态检查写在应用层的处理函数里,结果后面要改会话权限,得翻遍所有服务代码。
  • 接口要标准化:每一层对外暴露的接口要固定。比如传输层提供SendDiagnosticMessage和ReceiveDiagnosticMessage两个接口,会话层提供CheckSessionAccess接口,应用层提供ProcessService接口。
  • 日志要分层打:调试的时候,每一层都要打日志。传输层打CAN帧的原始数据,会话层打会话状态切换,应用层打服务执行结果。这样出问题的时候,一眼就能看出是哪一层的问题。
  • 单元测试要覆盖:每一层都要单独做单元测试。传输层测试多帧拆包重组,会话层测试状态机切换,应用层测试每个服务的正反逻辑。

一个小技巧:在开发初期,可以先实现一个「透传模式」——传输层收到什么,直接丢给应用层,应用层原样返回。这样能快速验证传输层和CAN通信是否正常。等传输层稳定了,再逐步加入会话层和应用层的逻辑。

好了,关于诊断协议栈的架构,今天就聊到这里。下一章我们会深入讲解诊断应用层的具体服务实现,包括0x10、0x22、0x2E这些常用服务的细节。到时候我会结合代码示例,带你一步步实现。

记住一句话:协议栈分层不是给你添麻烦的,是帮你把复杂问题拆成小块,每个小块都容易搞定。你只要把每一层做好,整个系统就不会差到哪里去。