![]()
一、服务概述
在 UDS(ISO 14229-1)协议中,0x3E 服务(Tester Present)用于让诊断测试工具(Tester)告知 ECU:“我还在线,请不要自动切回默认会话状态”。
ECU 通常有一个会话超时定时器(S3 Server 定时器),如果在规定时间内未收到任何诊断请求,它会自动从非默认会话(如编程会话、扩展诊断会话)返回到默认会话(Default Session)。
0x3E 服务正是用来重置这个定时器,维持当前会话状态,防止因超时而丢失非默认会话中的功能(如解锁安全访问、写入数据等)。
简单来说:Tester 定期发送 0x3E 请求,ECU 收到后刷新会话保持计时器,从而保持在当前会话。
二、报文格式 2.1 请求报文
字节位置
参数名称
值(例)
0
SID
0x3E
服务标识符
1
SubFunction
0x00 或 0x80
子功能,决定 ECU 是否需要发送肯定响应
子功能(SubFunction):
0x00:suppressResponse= true,ECU不应发送肯定响应。0x80:suppressResponse= false,ECU必须发送肯定响应。
实际应用中,为了保证通信可靠性并确认 ECU 仍在线,通常使用 0x80(请求响应)。但很多简单实现也直接发送 3E 00,并不期待响应。ISO 14229-1 规定:若子功能为 0x00,ECU 不得发送任何肯定响应;若为 0x80,ECU 应回复 7E 80。
常见报文示例:
3E 00—— 仅刷新定时器,ECU 不回复响应。3E 80—— 刷新定时器,并要求 ECU 回复肯定响应。
字节位置
参数名称
值(例)
0
SID
0x7E
肯定响应标识符(0x3E+0x40)
1
SubFunction
0x80
与请求中的子功能相同
若请求为
3E 80,则肯定响应为7E 80。若请求为
3E 00,ECU不会发送肯定响应。
当 ECU 无法处理该请求时,返回否定响应(格式:7F SID NRC)。常见的否定响应码(NRC)包括:
NRC
含义
原因
0x12
子功能不支持
子功能参数不是 0x00 或 0x80
0x13
请求报文长度错误
报文长度不为 2 字节
0x22
条件不满足
当前会话不允许执行该服务
0x78
请求正确,但响应延迟
ECU 忙,稍后重新请求
三、使用场景
诊断会话保持:当 ECU 处于扩展诊断会话(0x03)或编程会话(0x02)时,Tester 需每隔 ≤ S3_Server 时间(通常为 5 秒)发送一次 0x3E 服务,否则 ECU 会自动切回默认会话(0x01)。
安全访问保持:解锁安全访问后,若长时间无活动,ECU 会重新锁定,此时可通过 0x3E 维持解锁状态。
写入操作保护:在进行多帧写入(如 0x34/0x36 服务)过程中,可穿插 0x3E 防止会话超时导致写入中断。
下面提供一个简单的 C++ 类UdsClient,封装了发送 Tester Present 请求(支持响应)的功能。
假设:底层已实现 CAN 通信接口(例如 Linux SocketCAN),提供了CanSend()和CanReceive()函数。实际使用时需替换为真实的 CAN 驱动代码。
4.1 辅助函数定义(伪实现)
4.2 UdsClient 类实现#include
#include
#include
#include
#include
// 模拟 CAN 发送(实际应调用 SocketCAN 等接口)
bool CanSend(const std::vector& data, uint32_t can_id) {
std::cout << "[CAN TX] ID=0x" << std::hex << can_id << " Data: ";
for (auto byte : data) printf("%02X ", byte);
std::cout << std::dec << std::endl;
// 这里替换为真实的 write() 调用
return true;
}// 模拟 CAN 接收(非阻塞,带超时)
bool CanReceive(std::vector& data, uint32_t& can_id, int timeout_ms) {
// 模拟接收数据(实际应从 socket 读取)
// 此示例中直接返回 false,演示超时场景
std::this_thread::sleep_for(std::chrono::milliseconds(timeout_ms));
return false; // 无数据
}
4.3 使用示例class UdsClient {
public:
UdsClient(uint32_t req_id, uint32_t resp_id)
: req_can_id_(req_id), resp_can_id_(resp_id) {}
// 发送 Tester Present 请求
// sub_func: 0x00(无响应) 或 0x80(期望响应)
// timeout_ms: 等待响应的超时时间(毫秒),仅当 sub_func==0x80 时有效
bool SendTesterPresent(uint8_t sub_func, int timeout_ms = 100) {
// 构造请求报文:2 字节
std::vector request = { 0x3E, sub_func };
if (!CanSend(request, req_can_id_)) {
std::cerr << "Send TesterPresent request failed" << std::endl;
return false;
}
// 如果不需要响应,直接返回成功
if (sub_func == 0x00) {
std::cout << "TesterPresent with suppressResponse, no ack expected." << std::endl;
return true;
}
// 等待肯定响应
std::vector response;
uint32_t rx_id;
if (!CanReceive(response, rx_id, timeout_ms)) {
std::cerr << "Timeout waiting for TesterPresent response" << std::endl;
return false;
}
// 检查 CAN ID 是否正确
if (rx_id != resp_can_id_) {
std::cerr << "Unexpected response CAN ID" << std::endl;
return false;
}
// 检查响应长度
if (response.size() < 2) {
std::cerr << "Response too short" << std::endl;
return false;
}
// 检查肯定响应 SID (0x7E)
if (response[0] == 0x7E && response[1] == sub_func) {
std::cout << "Received positive response: 7E " << std::hex << (int)sub_func << std::dec << std::endl;
return true;
}
// 检查否定响应 (0x7F)
else if (response[0] == 0x7F && response[1] == 0x3E) {
uint8_t nrc = response[2];
std::cerr << "Negative response, NRC = 0x" << std::hex << (int)nrc << std::dec << std::endl;
return false;
}
else {
std::cerr << "Unexpected response format" << std::endl;
return false;
}
}private:
uint32_t req_can_id_; // 请求使用的 CAN ID(通常为 0x7DF 或物理寻址 ID)
uint32_t resp_can_id_; // 响应使用的 CAN ID(通常为 ECU 的物理响应 ID)
};
五、注意事项int main() {
// 假设 ECU 物理请求 ID 为 0x701,响应 ID 为 0x702
UdsClient client(0x701, 0x702);
// 场景1:要求 ECU 回复肯定响应(推荐)
std::cout << "=== Send TesterPresent with response (subfunc=0x80) ===" << std::endl;
bool ok = client.SendTesterPresent(0x80, 200);
if (ok) {
std::cout << "ECU stays in current session." << std::endl;
} else {
std::cout << "Failed to refresh session timer." << std::endl;
}
// 场景2:不要求响应(仅刷新定时器)
std::cout << "\n=== Send TesterPresent without response (subfunc=0x00) ===" << std::endl;
ok = client.SendTesterPresent(0x00);
if (ok) {
std::cout << "TesterPresent sent (no response needed)." << std::endl;
}
// 模拟周期性发送(每 3 秒一次,假设会话超时为 5 秒)
std::cout << "\n=== Start periodic TesterPresent (every 3s) ===" << std::endl;
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::seconds(3));
if (!client.SendTesterPresent(0x80, 200)) {
std::cerr << "Periodic TesterPresent failed at iteration " << i << std::endl;
break;
}
}return 0;
}
发送周期:应小于 ECU 的 S3_Server 超时时间(通常为 5 秒)。建议周期为 2~3 秒,留有余量。
子功能选择:
如果诊断流程需要确认 ECU 仍然在线,使用
0x80并等待响应。如果仅需保持会话且不希望增加总线负载,可使用
0x00(无响应)。
会话切换影响:如果 ECU 已经返回默认会话(例如因超时),则 0x3E 服务依然可以执行,但不会自动切换回之前的会话。此时应重新请求需要的会话(如 0x10 服务)。
错误处理:若收到 NRC 0x78(响应延迟),Tester 应稍后重发请求(但注意不要过于频繁)。
CAN ID 设置:示例中使用了物理寻址(点对点)。也可使用功能寻址(如 0x7DF),但需注意 ECU 是否支持功能寻址响应。
六、总结
UDS 0x3E 服务(Tester Present)是一个简单但至关重要的诊断服务,用于维持 ECU 的非默认会话状态。其报文格式简洁(仅 2 字节),通过子功能控制是否需要响应。
在实际嵌入式开发中,正确实现该服务可以避免因会话超时导致的诊断流程中断。本文提供的 C++ 代码演示了如何在客户端侧封装该服务,并可根据需要集成到真实的诊断工具中。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.