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 三层之间的协作关系
这三层不是各干各的,它们是一条流水线。我画个简单的流程给你看:
- 应用层:诊断仪发起0x22服务请求,读取DID 0xF190(VIN码)。应用层打包成:SID=0x22,DID=0xF190。
- 会话层:检查当前会话是否允许0x22服务。如果是默认会话,允许;如果是编程会话,也允许。然后检查定时器是否超时。
- 网络层:应用层的数据包有4个字节(0x22 + 0xF1 + 0x90),小于7字节,所以封装成单帧(SF),通过CAN总线发出去。
- ECU端:网络层收到单帧,拆包后交给会话层;会话层检查会话状态,再交给应用层;应用层解析DID,读取VIN码,生成响应。
- 响应返回: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服务开始。到时候你就知道,分层设计到底有多香了。