第2章:UDS协议入门:协议栈架构、服务标识符(SID)分类、诊断会话控制
好,咱们开始聊UDS协议。说实话,很多做Bootloader的工程师,一听到「协议栈」三个字就头大。我当年也一样,觉得这东西太抽象了。但后来我发现,只要你把UDS当成「汽车ECU之间的对话规则」来理解,事情就简单多了。
这一章,我带你从三个角度切入:协议栈长什么样、SID怎么分类、会话控制是怎么回事。嗯,都是基础,但基础不牢,后面调试会哭的。
2.1 UDS协议栈架构:别被「七层模型」吓到
UDS协议栈,说白了就是一套分层结构。它基于OSI七层模型,但咱们做嵌入式开发的,不用管那么多层。你只需要关注三层:应用层、会话层、传输层。
- 应用层:你写的诊断服务代码就在这层。比如你要读ECU的版本号,就发个0x22(读数据)请求。
- 会话层:负责管理诊断会话的状态。比如从默认会话切到编程会话,就是这层干的活。
- 传输层:处理数据分包、重组、流控制。CAN总线上一次只能发8个字节,数据长了就得拆。
我个人习惯:画图时把这三层竖着画,最上面是应用层,中间是会话层,最下面是传输层。调试时遇到问题,先定位是哪一层出的错。比如请求发出去没响应,先查传输层有没有丢包,再查会话层状态对不对。
我记得有一次,客户说ECU不响应诊断请求。我查了半天,发现是会话层卡在「扩展会话」里没切回来。嗯,这种坑踩过一次就记住了。
2.2 服务标识符(SID)分类:0x10到0x3E的秘密
SID是UDS协议的核心。每个SID代表一个服务,比如0x10是诊断会话控制,0x22是读数据,0x2E是写数据。SID一共就那么多,但分类方式很重要。
我习惯把SID分成四类:
| 分类 | SID范围 | 典型服务 | 说明 |
|---|---|---|---|
| 诊断管理类 | 0x10 - 0x3E | 0x10(诊断会话控制)、0x11(ECU复位)、0x3E(测试仪在线) | 控制诊断会话、复位ECU、保持连接 |
| 数据传输类 | 0x22 - 0x2E | 0x22(读数据)、0x2E(写数据) | 读写ECU内部数据,比如DTC、版本号 |
| 存储管理类 | 0x14 - 0x19 | 0x14(清除诊断信息)、0x19(读取DTC信息) | 管理故障码(DTC) |
| 上传下载类 | 0x34 - 0x37 | 0x34(请求下载)、0x36(传输数据)、0x37(请求退出传输) | Bootloader刷写时最常用 |
避坑指南:我曾经在项目中把0x22和0x2E搞混了。0x22是读数据,0x2E是写数据。你想想看,如果刷写时用错了SID,ECU可能直接拒绝服务。所以,写代码前先把SID表打印出来贴在工位上。
另外,SID的响应格式也有讲究。请求是单字节SID,响应是SID+0x40。比如你发0x10,正常响应是0x50。如果收到0x7F,那就是否定响应,后面跟着一个NRC(否定响应码)。
2.3 诊断会话控制:0x10服务的核心逻辑
0x10服务,全称是「诊断会话控制」。它决定了ECU当前处于哪种诊断模式。UDS定义了三种标准会话:
- 默认会话(Default Session):ECU上电后的初始状态。只能执行少量诊断服务,比如读数据。不能刷写。
- 编程会话(Programming Session):刷写Bootloader或应用程序时用的会话。支持上传下载类服务。
- 扩展会话(Extended Session):比默认会话权限高,但比编程会话低。常用于标定或特殊测试。
会话切换的流程很简单:Tester发0x10 + 子功能,ECU回复0x50 + 子功能。比如:
// 请求:切换到编程会话
Tester -> ECU: 0x10 0x02
// 响应:成功切换
ECU -> Tester: 0x50 0x02
但这里有个关键点:会话切换后,ECU会启动一个超时定时器(S3定时器)。如果Tester在超时时间内没有发任何诊断请求,ECU会自动切回默认会话。这个超时时间通常是5秒。
注意:我曾经遇到一个Bug,刷写过程中Tester发了0x3E(测试仪在线)来保持会话,但0x3E的发送间隔超过了S3超时时间。结果ECU切回默认会话,刷写中断。后来我把0x3E的发送间隔改成2秒,问题解决。
还有一个细节:不同会话下,ECU支持的服务列表不一样。比如在默认会话下,你发0x34(请求下载)会收到否定响应(NRC=0x78,请求超出范围)。只有在编程会话下,0x34才能正常执行。
所以,写Bootloader时,第一步一定是先切到编程会话。我习惯在初始化函数里加一个循环,确保会话切换成功后再往下走:
// 伪代码:确保切换到编程会话
while (1) {
send_diagnostic_request(0x10, 0x02);
response = receive_response();
if (response == 0x50) {
break; // 切换成功
} else if (response == 0x7F) {
// 处理否定响应
handle_negative_response();
}
delay(100); // 重试间隔
}
嗯,这里要注意,重试次数不能太多,否则会卡死。我一般设3次重试,失败就报错。
2.4 小结:这一章你该记住什么?
这一章的内容,说白了就是三件事:
- 协议栈架构:应用层、会话层、传输层。调试时逐层排查。
- SID分类:记住0x10、0x22、0x2E、0x34这几个关键SID。刷写时最常用的是0x10和0x34-0x37。
- 会话控制:刷写前必须切到编程会话,别忘了发0x3E保活。
下一章,我会带你深入0x34-0x37服务,也就是Bootloader刷写的核心流程。到时候咱们手写一个刷写Demo,你就能感受到UDS协议在Bootloader中的实际应用了。
好,这一章就到这。有问题随时问我,咱们下章见。