一、UDS 简介
UDS(Unified Diagnostic Services,统一诊断服务)是汽车电子领域广泛使用的诊断协议标准,定义于 ISO 14229 系列规范。它基于客户端-服务器模型,外部诊断仪(Tester)通过请求报文与车内电子控制单元(ECU)通信,实现读取故障码、读写数据、执行例程、控制会话状态等功能。
UDS 服务使用服务标识符(SID)区分不同功能,每个服务通常包含一个或多个子功能(Subfunction),子功能用于细化操作(例如启用/禁用某种功能、切换模式等)。请求和响应报文遵循固定格式:
请求报文:
[SID] + [子功能(可选)] + [数据参数(可选)]肯定响应:
[SID + 0x40] + [子功能(若请求中包含)] + [响应数据]否定响应:
[0x7F] + [请求 SID] + [错误码]
0x10服务用于控制 ECU 内部的诊断会话状态。ECU 通常支持多种诊断会话模式,例如:
**默认会话 (Default Session)**:基本诊断功能,如读取故障码、读取数据等,安全等级最低。
**编程会话 (Programming Session)**:允许写入内存、刷写固件等高风险操作,通常需要先解锁安全访问。
不同会话下,ECU 可用的服务集合、通信定时参数、安全权限均不相同。通过切换会话,诊断仪可以根据当前任务激活最合适的 ECU 状态。
2. 子功能定义(符合 ISO 14229-1)
子功能值(十六进制)
会话名称
0x01
默认会话 (Default)
上电后默认会话,支持基础诊断服务
0x02
编程会话 (Programming)
用于 ECU 刷写、BootLoader 激活
0x03
扩展会话 (Extended)
提供额外的诊断服务(如写数据)
0x04
~0x7F
保留
ISO 预留
0x80
~0xBF
制造商自定义
OEM 专用会话
0xC0
~0xFF
供应商自定义
供应商专用会话
3. 报文格式 请求报文(诊断仪 → ECU)
字节
名称
值(示例)
1
SID
0x10
诊断会话控制服务标识
2
子功能
0x01
要切换的会话类型(见上表)
注意:子功能的最高位(Bit7)是抑制肯定响应位(suppressPosRspMsgIndicationBit)。当该位为1时,ECU 不发送肯定响应,仅发送否定响应(若出错)。常见实现中,该位默认为0(即发送肯定响应)。上表中的子功能值均为0x01、0x02等,未设置该位。
肯定响应报文(ECU → 诊断仪)
名称
值(示例)
1
响应 SID
0x50
请求 SID + 0x40
2
子功能
0x01
当前激活的会话(与请求相同)
3
会话参数记录(可选)
0x00
,0x00...
包含定时参数、会话超时时间等(长度可变)
否定响应报文(ECU → 诊断仪)
字节
名称
值(示例)
1
否定响应 SID
0x7F
固定值
2
请求 SID
0x10
指明哪个服务被拒绝
3
错误码 (NRC)
0x12
具体错误原因(见 NRC 表)
常见的否定响应码(NRC):
0x11:服务不支持(子功能无效)0x12:子功能不支持或不合法0x13:报文长度错误0x22:条件不满足(例如当前会话下无法切换)0x7E:子功能序列错误0x7F:服务未激活(在当前会话下不可用)
请求:10 01(SID=0x10,子功能=0x01)
肯定响应:50 01(SID=0x50,子功能=0x01,无参数记录)
示例 2:切换到编程会话
请求:10 02
肯定响应:50 02
示例 3:切换到扩展会话
请求:10 03
肯定响应:50 03
若 ECU 处于默认会话且已执行安全访问解锁,则0x02和0x03的切换应被允许。否则可能返回0x22(条件不满足)。
三、C++ 代码示例
以下代码模拟一个简单的 UDS 诊断客户端,实现0x10服务的请求构造、发送(使用模拟的传输层)和响应解析。实际应用中,底层传输可能基于 CAN(ISO 15765)、DoIP 或串口,此处用控制台打印模拟通信过程。
代码说明#include
#include
#include
// 模拟诊断通信类
class UDSClient {
public:
// 模拟发送UDS请求并接收响应(实际应实现CAN/LIN/TCP等通信)
// 这里用控制台模拟传输过程,返回模拟的ECU响应数据
std::vector Transceive(const std::vector& request) {
std::cout << "发送UDS请求: ";
for (auto byte : request) {
printf("%02X ", byte);
}
std::cout << std::endl;
// 根据请求内容模拟ECU响应
if (request.size() < 2 || request[0] != 0x10) {
// 非0x10服务或长度错误,返回否定响应: 7F 10 13 (报文长度错误)
return {0x7F, 0x10, 0x13};
}
uint8_t subFunction = request[1];
// 检查子功能是否有效(仅支持0x01,0x02,0x03)
if (subFunction != 0x01 && subFunction != 0x02 && subFunction != 0x03) {
// 子功能不支持: 7F 10 12
return {0x7F, 0x10, 0x12};
}
// 模拟条件检查:例如从当前会话切换到编程/扩展会话可能需要先解锁安全访问
// 这里简化处理,总是允许切换,但打印提示
if (subFunction == 0x02) {
std::cout << "模拟条件检查: 切换到编程会话,假设安全解锁已完成。" << std::endl;
} else if (subFunction == 0x03) {
std::cout << "模拟条件检查: 切换到扩展会话,允许。" << std::endl;
}
// 构造肯定响应: 0x50 + 子功能
std::vector positiveResponse = {0x50, subFunction};
return positiveResponse;
}
// 诊断会话控制服务
bool DiagnosticSessionControl(uint8_t subFunction) {
// 构建请求报文: [SID, 子功能]
std::vector request = {0x10, subFunction};
// 发送请求并获取响应
std::vector response = Transceive(request);
// 解析响应
if (response.empty()) {
std::cerr << "错误: 未收到响应。" << std::endl;
return false;
}
// 检查否定响应
if (response[0] == 0x7F) {
if (response.size() >= 3 && response[1] == 0x10) {
std::cerr << "否定响应: NRC = 0x" << std::hex << (int)response[2] << std::dec;
switch (response[2]) {
case 0x12: std::cerr << " (子功能不支持)"; break;
case 0x13: std::cerr << " (报文长度错误)"; break;
case 0x22: std::cerr << " (条件不满足)"; break;
default: std::cerr << " (未知错误)";
}
std::cerr << std::endl;
return false;
} else {
std::cerr << "无效的否定响应格式" << std::endl;
return false;
}
}
// 检查肯定响应
else if (response[0] == 0x50 && response.size() >= 2) {
uint8_t respSubFunc = response[1];
if (respSubFunc == subFunction) {
std::cout << "肯定响应: 成功切换到会话 0x" << std::hex << (int)subFunction << std::dec;
// 额外输出会话名称
if (subFunction == 0x01) std::cout << " (默认会话)";
else if (subFunction == 0x02) std::cout << " (编程会话)";
else if (subFunction == 0x03) std::cout << " (扩展会话)";
std::cout << std::endl;
return true;
} else {
std::cerr << "响应中的子功能(" << std::hex << (int)respSubFunc << ")与请求("
<< (int)subFunction << ")不匹配" << std::dec << std::endl;
return false;
}
}
else {
std::cerr << "未知响应格式: SID = 0x" << std::hex << (int)response[0] << std::dec << std::endl;
return false;
}
}
};int main() {
UDSClient uds;
// 测试切换到默认会话
std::cout << "\n--- 测试: 切换到默认会话 (0x01) ---" << std::endl;
uds.DiagnosticSessionControl(0x01);
// 测试切换到编程会话
std::cout << "\n--- 测试: 切换到编程会话 (0x02) ---" << std::endl;
uds.DiagnosticSessionControl(0x02);
// 测试切换到扩展会话
std::cout << "\n--- 测试: 切换到扩展会话 (0x03) ---" << std::endl;
uds.DiagnosticSessionControl(0x03);
// 测试无效子功能
std::cout << "\n--- 测试: 无效子功能 (0xFF) ---" << std::endl;
uds.DiagnosticSessionControl(0xFF);
return 0;
}
**类
UDSClient**:封装诊断通信基本功能。
构建请求报文
{0x10, subFunction}。调用
Transceive()获取响应。解析响应,区分肯定响应(
0x50)与否定响应(0x7F),打印对应信息并返回布尔结果。
Transceive():模拟底层传输。实际应用中,该函数应替换为真实的 CAN/LIN/TCP 发送与接收。代码内部根据请求内容模拟肯定响应或否定响应(子功能校验、长度校验)。DiagnosticSessionControl():实现0x10服务的完整流程:
主函数测试:分别请求切换到默认会话、编程会话、扩展会话,并测试一个无效子功能(0xFF),验证否定响应处理。
运行输出示例
四、总结--- 测试: 切换到默认会话 (0x01) ---
发送UDS请求: 10 01
肯定响应: 成功切换到会话 0x1 (默认会话)
--- 测试: 切换到编程会话 (0x02) ---
发送UDS请求: 10 02
模拟条件检查: 切换到编程会话,假设安全解锁已完成。
肯定响应: 成功切换到会话 0x2 (编程会话)
--- 测试: 切换到扩展会话 (0x03) ---
发送UDS请求: 10 03
模拟条件检查: 切换到扩展会话,允许。
肯定响应: 成功切换到会话 0x3 (扩展会话)--- 测试: 无效子功能 (0xFF) ---
发送UDS请求: 10 FF
否定响应: NRC = 0x12 (子功能不支持)
0x10服务是 UDS 中基础且重要的服务,用于在不同诊断会话间切换,控制 ECU 对外提供的诊断接口权限。子功能(0x01~0x03)定义标准会话,更高值保留或制造商自定义。
请求报文简单(2字节),肯定/否定响应格式清晰。
实现时需注意:
子功能的抑制肯定响应位(Bit7)会影响 ECU 是否返回肯定响应。
某些会话切换可能需要前置条件(例如安全解锁)。
实际产品中,需结合传输协议(如 ISO 15765-2 的 CAN 传输层)处理分段和流控。
通过上述 C++ 示例,开发者可以快速理解并集成0x10服务到自己的诊断工具或 ECU 软件中,仅需替换Transceive()函数中的模拟通信为实际总线操作即可。
热门跟贴