1. 诊断会话控制(0x10)到底是什么?能用来干嘛?
如果你接触过汽车电子,尤其是ECU(电子控制单元)的开发或测试,那你大概率听说过UDS诊断。而在这个庞大的诊断协议家族里,诊断会话控制服务(Service 0x10) 绝对算得上是“守门人”和“大管家”。听起来有点玄乎?别急,我用大白话给你解释一下。
你可以把ECU想象成一个戒备森严的办公楼。平时,它只开放一个公共大厅(默认会话),访客(诊断仪)在这里只能进行一些基本的问询,比如看看大楼的灯亮不亮(读故障码0x19)、问问物业电话(读数据0x22)。但是,如果你想进入核心的研发实验室(编程会话)或者财务室(扩展诊断会话)去做更深入的操作,比如刷写新程序(0x31)、或者修改核心参数,那你就必须先在公共大厅的前台,出示特殊的通行证并申请进入特定区域——这个“申请进入特定区域”的动作,就是0x10服务干的事情。
所以,0x10服务的核心作用就两个:切换会话和管理会话状态。它决定了诊断仪接下来能在ECU上“干什么”以及“能干多久”。没有正确的会话,很多高级诊断服务都会对你亮起红灯(返回否定响应)。我在实际项目中就遇到过,测试同事抱怨刷写功能总是失败,一查日志,发现诊断仪压根没发0x10切到编程会话,直接在默认会话里发刷写请求,ECU当然会拒绝。理解0x10,是玩转UDS诊断的第一步。
2. 深入原理:会话、定时器与状态转换
知道了0x10是“守门人”,我们得搞清楚这个门卫的工作手册,也就是它的状态机。这是整个服务的灵魂,也是开发时最容易出bug的地方。
2.1 三种核心会话模式
UDS标准定义了多种会话,但最常用、最核心的是这三种:
- 默认会话(Default Session, 0x01):ECU上电后的初始状态。功能受限,通常只支持读取信息、清除故障码等基础服务。功耗低,资源占用少。
- 编程会话(Programming Session, 0x02):用于ECU软件刷写(Bootloader)。在此会话下,ECU会准备好内存、通信等资源,专用于接收和烧录新的程序数据。安全性要求最高。
- 扩展诊断会话(Extended Diagnostic Session, 0x03):用于执行深度诊断、标定、输入输出控制等高级功能。比默认会话开放更多权限,但通常也需要通过安全访问(0x27) 服务解锁。
当你用诊断仪发送 10 02(切换到编程会话)时,ECU内部的状态机就开始运转了。
2.2 生命线:S3定时器与TesterPresent保活
这是实战中最关键的一个机制,我称之为“会话生命线”。想象一下,你申请进入了核心实验室(非默认会话),但门卫(ECU)有个规定:如果你进去后连续一段时间(比如5000ms)没有任何动静,门卫就会认为你已经离开,为了安全起见,他会自动把实验室锁上,并把所有人赶回公共大厅(默认会话)。
这个“连续一段时间”,就是 S3定时器(S3server)。它的值由整车厂定义,常见的有5000ms或10000ms。
那么,如何告诉门卫“我还在里面,别锁门”呢?有两个方法:
- 发送任何有效的诊断请求:比如你读个数据(0x22)、写个参数(0x2E),只要ECU处理了,就会重置S3定时器。
- 发送专门的“心跳”报文——TesterPresent(0x3E):如果你暂时没有其他操作,但又需要保持会话,就可以周期性地(比如每隔2000ms)发送
3E 00这个服务。它本身不执行具体功能,唯一的作用就是告诉ECU:“我还活着,保持会话”。这就是 TesterPresent保活策略。
在代码里,这个逻辑必须清晰。我通常会在诊断任务主循环里维护一个S3定时器计数器,每次收到非0x3E的诊断请求,就重置它;收到0x3E请求,也重置它。同时,这个计数器会不断递减,一旦超时,立刻触发会话回退到默认会话的流程。
2.3 复杂的会话状态转换规则
会话切换不是简单的“开”和“关”,它伴随着一系列资源的清理与重置。标准文档里那几条状态转换规则(从默认到非默认、从非默认到另一个非默认等),看着头大,但我们可以结合实际场景来理解。
场景一:从编程会话切回扩展会话(非默认到非默认)
假设你刚刷完软件(在编程会话),现在想进行一些功能标定(需要扩展会话)。你发送 10 03。
这时ECU会做什么?
- 停止所有事件响应:如果之前用0x86服务配置了某些事件上报,现在全部停掉。
- 安全访问重新上锁:这是重中之重!编程会话可能拥有最高权限,但切换到扩展会话后,所有依赖于安全访问的功能(比如某些0x2E写操作)必须重新锁定,要求诊断仪重新走一遍“请求种子-发送密钥”的解锁流程。我踩过坑,有一次切换会话后安全状态没清干净,导致本该被拒绝的写操作意外成功,引发了数据错乱。
- 保持会话无关功能:比如你用0x28服务关闭了某些报文,或者用0x85关闭了DTC记录,这些状态通常会保留,因为它们不依赖于特定会话。
场景二:任何会话切回默认会话
无论是从扩展还是编程会话,只要切回默认会话(10 01或者S3超时),ECU就会进行一次“大扫除”:
- 停止所有0x86事件。
- 安全访问锁定。
- 终止所有非默认会话支持的功能:比如你正在进行的周期性数据采集、对某个执行器的直接控制,都会被强制停止。通信控制(0x28)和DTC设置控制(0x85)的状态也会被重置,恢复正常通信。
把这些规则用代码实现,就是一个清晰的会话状态机。下面这个表格可以帮助你理清不同转换时的核心动作:
| 转换类型 | 触发条件 | ECU核心动作 |
|---|---|---|
| 默认 -> 非默认 | 收到 10 02 或 10 03 | 停止默认会话下的0x86事件;初始化新会话资源;启动S3定时器。 |
| 非默认A -> 非默认B | 收到另一个10请求 | 停止所有0x86事件;安全访问重新锁定;保持0x28/0x85等状态;重置S3定时器。 |
| 非默认 -> 默认 | 收到 10 01 或 S3超时 | 停止所有0x86事件;安全访问锁定;终止所有非默认会话功能;重置所有临时设置。 |
| 上电/复位 -> 默认 | ECU重启 | 完整初始化,进入默认会话。但注意编程会话可能有特殊时间窗。 |
3. 实战代码设计:状态机与模块实现
理论懂了,不落地成代码就是纸上谈兵。我们来聊聊怎么在嵌入式C语言里实现它。我的经验是,一定要模块化,把会话控制当成一个独立的状态机模块来设计。
3.1 定义清晰的数据结构
首先,定义好会话类型和状态机上下文。别用一堆离散的全局变量,封装起来。
/* uds_session_control.h */
typedef enum {
DEFAULT_SESSION = 0x01,
PROGRAMMING_SESSION = 0x02,
EXTENDED_DIAG_SESSION = 0x03,
// ... 其他厂商自定义会话
} UDS_SessionType_t;
typedef struct {
UDS_SessionType_t currentSession; // 当前会话
uint32_t s3TimerCounter; // S3定时器计数(单位ms)
bool isSecurityUnlocked; // 安全访问解锁状态
// 其他与会话相关的状态标志,如0x28通信控制状态、0x85 DTC设置状态等
} UDS_SessionCtrlContext_t;
extern UDS_SessionCtrlContext_t gUdsSessionContext;
3.2 实现核心状态机函数
状态机的核心是一个处理函数,它根据收到的0x10请求子函数和当前状态,决定如何转换。
/* uds_session_control.c */
UDS_NegativeResponseCode_t UDS_HandleSessionControl(uint8_t subFunction) {
UDS_SessionType_t requestedSession = (UDS_SessionType_t)subFunction;
/* 1. 检查请求的会话是否被支持 */
if (!IsSessionSupported(requestedSession)) {
return NRC_SERVICE_NOT_SUPPORTED;
}
/* 2. 检查是否存在依赖条件(如编程会话可能要求车辆静止) */
if (!CheckSessionDependencies(requestedSession)) {
return NRC_CONDITIONS_NOT_CORRECT;
}
/* 3. 根据当前会话和请求会话,执行状态转换 */
switch (gUdsSessionContext.currentSession) {
case DEFAULT_SESSION:
if (requestedSession != DEFAULT_SESSION) {
// 转换2:默认 -> 非默认
StopDefaultSessionEventHandlers(); // 停止默认会话的0x86事件
}
// 转换1:默认 -> 默认 (重新初始化)
// 这里可以执行一些默认会话的清理工作
break;
case PROGRAMMING_SESSION:
case EXTENDED_DIAG_SESSION:
// 当前已在非默认会话
if (requestedSession == DEFAULT_SESSION) {
// 转换4:非默认 -> 默认
TerminateNonDefaultSessionActivities(); // 终止所有非默认会话功能
LockSecurityAccess(); // 安全上锁
} else if (requestedSession != gUdsSessionContext.currentSession) {
// 转换3:非默认A -> 非默认B
ReLockSecurityAccess(); // 关键!安全重新锁定
StopAllEventHandlers(); // 停止所有0x86事件
// 注意:保持0x28, 0x85等状态
}
// 如果请求会话与当前相同,只需重置S3定时器
break;
default:
break;
}
/* 4. 应用新会话 */
gUdsSessionContext.currentSession = requestedSession;
/* 5. 重置S3定时器 */
ResetS3Timer();
/* 6. 初始化新会话需要的资源 */
InitSessionResources(requestedSession);
/* 7. 发送肯定响应 */
SendPositiveResponse(SID_SESSION_CONTROL, subFunction);
return NRC_POSITIVE_RESPONSE;
}
3.3 定时器管理与保活机制
S3定时器的管理需要集成到系统时钟滴答(SysTick)中断或者一个高优先级任务中。
/* 在1ms的定时器中断服务程序或任务中调用 */
void UDS_SessionTimerManager_1ms(void) {
if (gUdsSessionContext.currentSession != DEFAULT_SESSION) {
if (gUdsSessionContext.s3TimerCounter > 0) {
gUdsSessionContext.s3TimerCounter--;
if (gUdsSessionContext.s3TimerCounter == 0) {
// S3超时!强制回退到默认会话
ForceSwitchToDefaultSession();
}
}
}
}
/* 重置S3定时器,在收到任何诊断请求或TesterPresent时调用 */
void ResetS3Timer(void) {
if (gUdsSessionContext.currentSession != DEFAULT_SESSION) {
// S3时间从配置中读取,例如5000ms
gUdsSessionContext.s3TimerCounter = GetS3TimeoutValue();
}
}
/* 处理TesterPresent服务 */
UDS_NegativeResponseCode_t UDS_HandleTesterPresent(uint8_t subFunction) {
// 子函数0x00(带响应)或0x80(无响应)
ResetS3Timer(); // 核心就是重置定时器
if (subFunction == 0x00) {
SendPositiveResponse(SID_TESTER_PRESENT, 0x00);
}
// 0x80不需要响应
return NRC_POSITIVE_RESPONSE;
}
4. 避坑指南与高级话题
在实际开发中,光是实现基本状态机还不够,有些“坑”需要提前留意。
4.1 安全访问(0x27)的联动处理
这是最容易出错的地方。务必记住:绝大多数会话转换都会导致安全访问锁定。特别是“非默认会话A切换到非默认会话B”,安全状态必须清零。你的安全访问模块和会话控制模块之间需要有清晰的接口。在会话状态机的转换动作中,一定要调用 Security_ForceLock() 这样的函数。
4.2 0x28与0x85服务的状态保持
与安全访问不同,通信控制(0x28)和DTC设置控制(0x85)服务的状态,在非默认会话之间切换时通常需要保持。这意味着你需要把这些状态存储在会话上下文结构体里,并且在会话初始化函数 InitSessionResources 中,不是简单地重置它们,而是根据存储的值来恢复通信开关或DTC记录使能状态。
4.3 编程会话的特殊时间窗
这是一个特殊规定:ECU在复位或上电后的一个很短时间(例如20ms)内,允许直接接收 10 02 进入编程会话,而不用先经过默认会话。这是为了满足产线端快速刷写的需求。实现时,可以在Bootloader的初始化阶段设置一个标志位 postResetWindow,并启动一个20ms的硬件定时器。在这个定时器超时前,诊断调度器应能处理 10 02 请求。超时后,则必须回到常规流程,先进入默认会话。
4.4 否定响应码的正确使用
不是所有 10 请求都会成功。你的代码要能处理各种异常情况并返回正确的NRC(否定响应码):
NRC_SUB_FUNCTION_NOT_SUPPORTED (0x12):请求的会话类型本ECU不支持。NRC_CONDITIONS_NOT_CORRECT (0x22):条件不满足,比如车辆在行驶中请求编程会话。NRC_REQUEST_SEQUENCE_ERROR (0x24):请求序列错误,但在这个服务里较少用。
实现好会话控制这个“大管家”,你的UDS诊断系统就打下了最坚实的基础。剩下的服务,很多都是在这个框架下添砖加瓦。刚开始可能会觉得状态转换绕来绕去,多画几次状态图,多写几个测试用例跑一跑,很快就能理顺。记住,清晰的模块划分和严谨的状态机逻辑,是写出稳定可靠诊断代码的关键。
388




被折叠的 条评论
为什么被折叠?



