2、OSI七层模型与UDS:UDS在应用层、会话层、网络层的角色划分

聊到UDS诊断,很多人一上来就扎进各种服务ID(SID)和子功能里去了。但我个人习惯,在动手写代码之前,先把这个协议在OSI七层模型里到底扮演什么角色搞清楚。说白了,UDS不是一个孤立的协议,它是寄生在整个通信协议栈之上的。

你想想看,ECU之间怎么传数据?CAN总线、以太网、LIN……这些是物理层和数据链路层的事。而UDS呢?它站在更高的层次上,负责定义“咱们俩怎么对话”、“对话的内容是什么格式”。今天我就带你把这层窗户纸捅破。

2.1 OSI七层模型速览

先快速过一下OSI模型,免得后面聊晕了。七层从下往上分别是:

  • 物理层:比特流传输,电压、引脚定义。CAN总线用的就是差分信号。
  • 数据链路层:帧的封装、错误检测、访问控制。CAN协议里的ID、DLC、CRC都在这一层。
  • 网络层:路由、寻址、报文分段与重组。对于CAN来说,ISO 15765-2(也就是我们常说的CAN TP)就在这层干活。
  • 传输层:端到端的可靠传输。UDS在CAN上通常不单独强调这层,功能合并到网络层了。
  • 会话层:建立、管理、终止会话。UDS的定时参数、诊断会话控制(0x10服务)就在这层。
  • 表示层:数据编码、加密。UDS里AES加密、Checksum校验可以算在这层。
  • 应用层:用户接口,具体业务逻辑。UDS的读写数据(0x22/0x2E)、例程控制(0x31)都在这里。

嗯,这里要注意:在CAN总线实现UDS时,OSI的会话层、表示层、应用层通常被合并处理,统称为“UDS应用层”。但概念上我们必须分清楚,否则后面调试定时器溢出、报文超时的时候,你会一头雾水。

2.2 UDS在应用层的角色

应用层是UDS最“露脸”的地方。所有的诊断服务,比如读取故障码、写入配置参数、执行自检程序,都在这一层定义。

我记得有一次,客户报了一个Bug:用0x22服务读VIN码,返回的数据总是少两个字节。我排查了半天,最后发现是应用层把数据长度字段算错了。你看,应用层直接决定了“数据对不对”。

应用层的主要职责包括:

  • 服务原语定义:比如诊断请求(Request)、诊断响应(Response)、确认(Confirm)等。
  • 服务ID(SID)分配:0x10到0x3E是诊断和通信管理类,0x40到0x7F是数据传输类,等等。
  • 子功能参数解析:比如0x2E服务,子功能是“按地址写”,后面跟着的数据长度、数据内容怎么解析。
  • 否定响应码(NRC)生成:当请求不合法时,应用层要负责返回0x7F + SID + NRC。

核心要点:应用层只关心“做什么”,不关心“怎么传”。它把数据打包好,丢给下层就完事了。

2.3 UDS在会话层的角色

会话层,说白了就是管“对话状态”的。UDS里最重要的概念——诊断会话,就在这一层实现。

为什么需要会话层?你想想看,ECU不能一直处于“完全开放”的状态吧?万一有人恶意刷写怎么办?所以UDS定义了三种默认会话:

  • 默认会话(Default Session):上电后的初始状态,只允许读故障码等基础服务。
  • 扩展会话(Extended Session):允许读写参数、执行例程,但刷写还是不行。
  • 编程会话(Programming Session):允许刷写固件,但会禁用大部分通信。

我曾经在项目里踩过一个坑:ECU在扩展会话下执行了0x31例程控制,结果例程跑了一半,诊断仪突然发了个0x10 03(切换到扩展会话)。你猜怎么着?ECU直接终止了例程,返回了NRC 0x22(条件不满足)。后来我查规范才发现,会话切换时,当前会话下的所有活动都会被终止。这就是会话层的规则。

会话层还负责:

  • 定时参数管理:P2_Server、P2*_Server这些超时时间,不同会话下可能不同。
  • 安全访问(0x27服务):种子与密钥的交换,也属于会话层的范畴。
  • 通信控制(0x28服务):开启或关闭某些报文,也是会话层在管。

避坑指南:我曾经在调试一个项目时,发现ECU在编程会话下总是超时。查了半天,原来是P2*_Server定时器配置成了默认会话的值,太短了。记住:不同会话的定时参数要独立配置!

2.4 UDS在网络层的角色

网络层,对于CAN总线来说,就是ISO 15765-2(CAN TP)。它的核心任务只有一个:把应用层的大数据包,切成CAN帧能装下的小块,再在接收端拼回去

CAN帧的数据场最多8个字节(CAN FD可以到64字节,但传统CAN还是8字节)。而UDS的一个诊断请求,比如0x22服务读VIN码,VIN码有17个字节,加上服务ID和长度,总共20多个字节。一个CAN帧根本装不下。

这时候网络层就上场了:

  • 单帧(SF):数据长度 ≤ 7字节(CAN)或 ≤ 63字节(CAN FD),一帧搞定。
  • 首帧(FF):数据长度 > 7字节,第一帧告诉接收方“我要发多少数据”。
  • 连续帧(CF):后续的数据块,每帧带一个序列号,防止丢包乱序。
  • 流控帧(FC):接收方告诉发送方“慢点发,我处理不过来”或者“继续发”。

我记得有一次,诊断仪发了一个很大的下载请求(0x34服务),ECU接收完首帧后,连续发了几个流控帧要求“暂停”。结果诊断仪没理它,继续狂发连续帧。ECU的接收缓冲区直接爆了,丢了一堆数据。最后我加了一个流控状态机,严格按规范处理BS(Block Size)和STmin参数,问题才解决。

警告:网络层的流控机制不是摆设!很多工程师觉得“反正CAN总线快,不用流控”,结果在高速刷写时频繁丢包。记住:ECU的处理速度可能远低于总线速度,流控是保命用的。

2.5 三层之间的协作关系

这三层不是各干各的,它们是一条流水线。我画个简单的流程给你看:

  1. 应用层:诊断仪发起0x22服务请求,读取DID 0xF190(VIN码)。应用层打包成:SID=0x22,DID=0xF190。
  2. 会话层:检查当前会话是否允许0x22服务。如果是默认会话,允许;如果是编程会话,也允许。然后检查定时器是否超时。
  3. 网络层:应用层的数据包有4个字节(0x22 + 0xF1 + 0x90),小于7字节,所以封装成单帧(SF),通过CAN总线发出去。
  4. ECU端:网络层收到单帧,拆包后交给会话层;会话层检查会话状态,再交给应用层;应用层解析DID,读取VIN码,生成响应。
  5. 响应返回:VIN码17个字节,加上响应SID(0x62)和长度,总共20字节。网络层切成首帧+连续帧,发回诊断仪。

你看,每一层各司其职,缺一不可。如果网络层切包切错了,应用层收到乱序数据,解析出来就是错的。如果会话层没检查权限,应用层可能被非法访问。

总结一下:应用层管“内容”,会话层管“状态”,网络层管“传输”。这三层配合好了,UDS诊断栈才能稳定运行。

2.6 实际项目中的分层建议

最后,我分享一下我在实际项目中搭建UDS栈时的分层习惯:

层次 代码模块 典型文件
应用层 UDS服务处理 uds_app.c / uds_app.h
会话层 会话管理、安全访问 uds_session.c / uds_session.h
网络层 CAN TP协议 can_tp.c / can_tp.h
接口层 CAN驱动抽象 can_drv.c / can_drv.h

我个人习惯把会话层单独抽出来,不跟应用层混在一起。为什么?因为会话层的状态机(默认、扩展、编程)切换逻辑很复杂,混在一起容易出Bug。我曾经在一个项目里,把会话切换和0x22服务写在同一个文件里,结果改了一个定时器参数,把服务处理逻辑也带偏了。从那以后,我坚持分层隔离。

好了,这一章的内容就到这里。下一章我会带你手写一个最简单的UDS应用层框架,从0x10服务开始。到时候你就知道,分层设计到底有多香了。