![]()
一、UDS诊断与OTA升级概述
UDS(Unified Diagnostic Services,统一诊断服务)是ISO 14229标准定义的汽车电子诊断协议,采用客户端-服务器模型,外部诊断仪(Tester)通过请求报文与ECU通信。UDS定义了26种服务,覆盖诊断管理、数据传输、故障诊断、例程控制、上传下载六大类。
OTA(Over-The-Air)远程升级本质上是UDS诊断协议在固件更新场景下的典型应用。OTA升级依赖两大核心技术:Bootloader(启动引导程序)与UDS协议。OTA升级过程中,诊断测试是必做项,贯穿升级前、升级中、升级后三个阶段的完整流程。
二、OTA升级全流程UDS服务详解 2.1 升级前:条件确认
升级前需要通过诊断服务确认ECU满足升级条件。
(1)0x22 ReadDataByIdentifier——读版本号
22服务用于按数据标识符(DID)读取ECU数据。DID是2字节编号,相当于每个数据的“门牌号”。常用DID包括:
DID
0xF195
Boot软件版本
0xF196
应用软件版本
0xF190
零件编号
0xF187
序列号
0xF188
ECU软件号码
0xF189
ECU软件版本号
(2)0x22 读ECU状态
通过读取特定DID获取ECU运行状态(如电压、温度、内存使用率等),判断是否满足升级条件。
2.2 升级中:Bootloader刷写流程
刷写流程涉及7个核心UDS服务,按顺序执行:
Step 1:0x10 DiagnosticSessionControl——会话切换
ECU上电后处于默认会话(Default Session, 0x01)。刷写前需切换到编程会话(Programming Session, 0x02)。不同会话支持不同的诊断服务权限。扩展会话(Extended Session, 0x03)通常用于解锁高权限诊断服务。
Step 2:0x27 SecurityAccess——安全访问
刷写操作涉及敏感内存区域,必须通过安全访问认证。流程为:请求种子(27 01)→ ECU返回随机种子 → 发送密钥(27 02)→ ECU验证。密钥由种子通过特定算法(如AES-128)计算得出。
Step 3:0x31 RoutineControl——擦除例程
31服务用于执行ECU中预定义的诊断例程。擦除例程用于在下载前清除目标存储区域。子功能0x01表示启动例程。
Step 4:0x34 RequestDownload——请求下载
34服务用于向ECU请求下载数据,告知ECU即将开始数据传输。请求中包含数据格式标识符、内存地址和大小等参数。
Step 5:0x36 TransferData——数据传输
36服务用于实际传输固件数据。数据分多次传输,每次包含块序列号(blockSequenceCounter)。所有固件数据通过36服务逐块发送。
Step 6:0x37 RequestTransferExit——退出传输
所有数据传输完成后,调用37服务通知ECU传输结束。如果34和36服务都顺利执行,37将收到肯定响应。
Step 7:0x31 RoutineControl——校验例程
传输完成后再次使用31服务执行校验例程(如CRC校验),验证固件数据的完整性和正确性。最后通过0x11服务实现ECU复位并跳转至新程序。
2.3 升级后:验证确认
(1)0x22 读版本号确认升级成功
再次使用22服务读取软件版本号DID,确认版本已更新为目标版本。
(2)验证所有诊断功能正常
确认22、10、27、31、34/36/37等核心诊断服务均能正常响应。
三、C++代码示例
以下代码演示OTA升级过程中各UDS服务的请求构建与发送(基于CAN通信)。
四、总结#include
#include
#include
#include
// ==================== 基础类型定义 ====================
using Byte = uint8_t;
using ByteArray = std::vector ;
// UDS服务ID (SID)
enum class UdsSid : Byte {
DiagnosticSessionControl = 0x10,
ReadDataByIdentifier = 0x22,
SecurityAccess = 0x27,
RoutineControl = 0x31,
RequestDownload = 0x34,
TransferData = 0x36,
RequestTransferExit = 0x37,
EcuReset = 0x11
};
// 否定响应码 (NRC)
enum class Nrc : Byte {
GeneralReject = 0x10,
ServiceNotSupported = 0x11,
SubFunctionNotSupported = 0x12,
IncorrectMessageLength = 0x13,
ConditionsNotCorrect = 0x22,
RequestSequenceError = 0x24,
SecurityAccessDenied = 0x33,
InvalidKey = 0x35,
ExceedNumberOfAttempts = 0x36,
RequiredTimeDelay = 0x37
};
// ==================== CAN通信接口抽象 ====================
class CanInterface {
public:
virtual ~CanInterface() = default;
virtual bool send(const ByteArray& data) = 0;
virtual bool receive(ByteArray& data, int timeoutMs) = 0;
};
// ==================== UDS客户端类 ====================
class UdsClient {
public:
explicit UdsClient(CanInterface* can) : m_can(can) {}
// ---------- 0x10 诊断会话控制 ----------
// subFunction: 0x01=默认会话, 0x02=编程会话, 0x03=扩展会话
bool diagnosticSessionControl(Byte subFunction) {
ByteArray request = {static_cast (UdsSid::DiagnosticSessionControl), subFunction};
return sendRequest(request);
}
// ---------- 0x22 读DID ----------
// did: 2字节数据标识符,如0xF196表示应用软件版本
ByteArray readDataByIdentifier(uint16_t did) {
ByteArray request;
request.push_back(static_cast (UdsSid::ReadDataByIdentifier));
request.push_back(static_cast ((did >> 8) & 0xFF));
request.push_back(static_cast (did & 0xFF));
if (!sendRequest(request)) {
return {};
}
ByteArray response;
if (!m_can->receive(response, 1000)) {
return {};
}
// 检查肯定响应: SID+0x40 = 0x62
if (response.size() >= 2 && response[0] == 0x62) {
// 返回数据部分(跳过SID和DID)
return ByteArray(response.begin() + 3, response.end());
}
return {};
}
// ---------- 0x27 安全访问 ----------
// step: 0x01=请求种子, 0x02=发送密钥
bool securityAccess(Byte step, const ByteArray& data = {}) {
ByteArray request;
request.push_back(static_cast (UdsSid::SecurityAccess));
request.push_back(step);
request.insert(request.end(), data.begin(), data.end());
return sendRequest(request);
}
// 完整的安全访问流程:请求种子 → 计算密钥 → 发送密钥
bool securityAccessFull() {
// Step 1: 请求种子 (27 01)
ByteArray requestSeed = {static_cast (UdsSid::SecurityAccess), 0x01};
if (!sendRequest(requestSeed)) return false;
ByteArray response;
if (!m_can->receive(response, 1000)) return false;
// 检查肯定响应: 0x67
if (response.size() < 3 || response[0] != 0x67) return false;
// 提取种子(跳过SID和子功能)
ByteArray seed(response.begin() + 2, response.end());
// Step 2: 根据种子计算密钥(使用AES-128或自定义算法)
ByteArray key = calculateKey(seed);
// Step 3: 发送密钥 (27 02)
ByteArray sendKey;
sendKey.push_back(static_cast (UdsSid::SecurityAccess));
sendKey.push_back(0x02);
sendKey.insert(sendKey.end(), key.begin(), key.end());
if (!sendRequest(sendKey)) return false;
if (!m_can->receive(response, 1000)) return false;
// 肯定响应为 0x67
return (response.size() >= 2 && response[0] == 0x67);
}
// ---------- 0x31 例程控制 ----------
// routineType: 0x01=启动, 0x02=停止, 0x03=获取结果
// routineId: 2字节例程标识符
bool routineControl(Byte routineType, uint16_t routineId,
const ByteArray& optionRecord = {}) {
ByteArray request;
request.push_back(static_cast (UdsSid::RoutineControl));
request.push_back(routineType);
request.push_back(static_cast ((routineId >> 8) & 0xFF));
request.push_back(static_cast (routineId & 0xFF));
request.insert(request.end(), optionRecord.begin(), optionRecord.end());
return sendRequest(request);
}
// ---------- 0x34 请求下载 ----------
// dataFormat: 数据格式标识符(压缩/加密方式)
// address: 起始内存地址
// size: 数据大小
bool requestDownload(Byte dataFormat, uint32_t address, uint32_t size) {
ByteArray request;
request.push_back(static_cast (UdsSid::RequestDownload));
request.push_back(dataFormat);
// 内存地址参数长度(通常4字节)
request.push_back(0x44); // 4字节地址 + 4字节大小
// 地址(大端序)
request.push_back(static_cast ((address >> 24) & 0xFF));
request.push_back(static_cast ((address >> 16) & 0xFF));
request.push_back(static_cast ((address >> 8) & 0xFF));
request.push_back(static_cast (address & 0xFF));
// 大小(大端序)
request.push_back(static_cast ((size >> 24) & 0xFF));
request.push_back(static_cast ((size >> 16) & 0xFF));
request.push_back(static_cast ((size >> 8) & 0xFF));
request.push_back(static_cast (size & 0xFF));
return sendRequest(request);
}
// ---------- 0x36 数据传输 ----------
bool transferData(Byte blockSeqNum, const ByteArray& data) {
ByteArray request;
request.push_back(static_cast (UdsSid::TransferData));
request.push_back(blockSeqNum);
request.insert(request.end(), data.begin(), data.end());
return sendRequest(request);
}
// ---------- 0x37 请求退出传输 ----------
bool requestTransferExit() {
ByteArray request = {static_cast (UdsSid::RequestTransferExit)};
return sendRequest(request);
}
// ---------- 完整OTA刷写流程 ----------
bool performOtaUpgrade(const ByteArray& firmware, uint32_t flashAddress) {
std::cout << "=== OTA升级开始 ===" << std::endl;
// ----- 升级前:条件确认 -----
std::cout << "[1] 读取当前版本号..." << std::endl;
ByteArray version = readDataByIdentifier(0xF196); // 应用软件版本
if (!version.empty()) {
std::cout << " 当前版本: ";
for (Byte b : version) std::cout << std::hex << (int)b;
std::cout << std::dec << std::endl;
}
// ----- 升级中:刷写流程 -----
// Step 1: 切换到编程会话 (10 02)
std::cout << "[2] 切换到编程会话..." << std::endl;
if (!diagnosticSessionControl(0x02)) {
std::cerr << " 切换会话失败" << std::endl;
return false;
}
// Step 2: 安全访问 (27)
std::cout << "[3] 安全访问认证..." << std::endl;
if (!securityAccessFull()) {
std::cerr << " 安全访问失败" << std::endl;
return false;
}
// Step 3: 擦除例程 (31 01)
std::cout << "[4] 擦除目标存储区域..." << std::endl;
if (!routineControl(0x01, 0xFF01)) { // 0xFF01 = 擦除例程ID
std::cerr << " 擦除失败" << std::endl;
return false;
}
// Step 4: 请求下载 (34)
std::cout << "[5] 请求下载 (固件大小: " << firmware.size() << " 字节)..." << std::endl;
if (!requestDownload(0x00, flashAddress, firmware.size())) {
std::cerr << " 请求下载失败" << std::endl;
return false;
}
// Step 5: 分块传输数据 (36)
std::cout << "[6] 传输固件数据..." << std::endl;
const size_t BLOCK_SIZE = 4096;
Byte blockNum = 1;
for (size_t offset = 0; offset < firmware.size(); offset += BLOCK_SIZE) {
size_t len = std::min(BLOCK_SIZE, firmware.size() - offset);
ByteArray block(firmware.begin() + offset, firmware.begin() + offset + len);
if (!transferData(blockNum, block)) {
std::cerr << " 数据块 " << (int)blockNum << " 传输失败" << std::endl;
return false;
}
blockNum++;
// 进度显示
if (offset % (BLOCK_SIZE * 10) == 0) {
std::cout << " 进度: " << (offset * 100 / firmware.size()) << "%" << std::endl;
}
}
std::cout << " 进度: 100%" << std::endl;
// Step 6: 退出传输 (37)
std::cout << "[7] 退出传输..." << std::endl;
if (!requestTransferExit()) {
std::cerr << " 退出传输失败" << std::endl;
return false;
}
// Step 7: 校验例程 (31 01)
std::cout << "[8] 校验固件完整性..." << std::endl;
if (!routineControl(0x01, 0xFF02)) { // 0xFF02 = 校验例程ID
std::cerr << " 校验失败" << std::endl;
return false;
}
// ----- 升级后:验证 -----
std::cout << "[9] 读取新版本号验证升级..." << std::endl;
ByteArray newVersion = readDataByIdentifier(0xF196);
if (!newVersion.empty()) {
std::cout << " 新版本: ";
for (Byte b : newVersion) std::cout << std::hex << (int)b;
std::cout << std::dec << std::endl;
}
std::cout << "=== OTA升级完成 ===" << std::endl;
return true;
}
private:
bool sendRequest(const ByteArray& request) {
// 实际实现需添加:ISO-TP分段、流控、超时重传等
return m_can->send(request);
}
ByteArray calculateKey(const ByteArray& seed) {
// 实际实现需使用与ECU一致的算法(如AES-128)
// 此处为简化示例
ByteArray key = seed;
for (auto& b : key) b ^= 0xAA;
return key;
}
private:
CanInterface* m_can;
};
// ==================== 使用示例 ====================int main() {
// 假设已实现CAN通信类
// CanInterface* can = new MyCanDriver();
// UdsClient client(can);
// 模拟固件数据
ByteArray firmware(1024 * 1024); // 1MB固件
for (size_t i = 0; i < firmware.size(); ++i) {
firmware[i] = static_cast (i & 0xFF);
}
// 执行OTA升级
// client.performOtaUpgrade(firmware, 0x08000000);
return 0;
}
OTA越普及,诊断测试的需求越大。OTA测试本身就是诊断测试的延伸方向。UDS通过标准化的服务ID(0x10会话控制、0x22读取数据、0x27安全访问、0x31例程控制、0x34~0x37刷写服务)实现了“一次开发、多ECU兼容”的工程目标。
完整的OTA诊断测试覆盖:
阶段
核心服务
测试目的
升级前
0x22读版本号、读ECU状态
确认升级条件满足
升级中
0x10→0x27→0x31→0x34→0x36→0x37→0x31
验证刷写流程各步骤
升级后
0x22读版本号、全服务功能验证
确认升级成功、功能正常
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
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.