网易首页 > 网易号 > 正文 申请入驻

OTA升级中的UDS诊断测试:从条件确认到刷写验证的完整方案

0
分享至


一、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.

相关推荐
热点推荐
四川大学生范宗德确诊癌症,21岁长得帅气,因脚疼确诊,暴瘦20斤

四川大学生范宗德确诊癌症,21岁长得帅气,因脚疼确诊,暴瘦20斤

180视角
2026-08-25 10:53:45
街上越来越多女性不穿胸罩,高跟鞋几乎绝迹:这是身体的解放!

街上越来越多女性不穿胸罩,高跟鞋几乎绝迹:这是身体的解放!

另子维爱读史
2026-08-25 20:02:40
易建联为妻儿建的洛杉矶豪宅烧成灰,如今2000平空地估价曝光,数字让人意外,中介爆出实情

易建联为妻儿建的洛杉矶豪宅烧成灰,如今2000平空地估价曝光,数字让人意外,中介爆出实情

背包旅行
2026-08-25 11:34:02
女子骑杜卡迪在高速狂飙,时速超200公里还单手玩手机,一辆保时捷和她并驾齐驱拍下视频……网友:我要报警

女子骑杜卡迪在高速狂飙,时速超200公里还单手玩手机,一辆保时捷和她并驾齐驱拍下视频……网友:我要报警

都市快报橙柿互动
2026-08-25 15:18:56
6-3 6-2 郑钦文横扫克星,终结三连败+复仇8年之耻 下轮对手更强

6-3 6-2 郑钦文横扫克星,终结三连败+复仇8年之耻 下轮对手更强

江启
2026-08-25 06:19:13
怒吼队友!皇马1.4亿巨星不甘做副手:只给姆总传 给我也传几个啊

怒吼队友!皇马1.4亿巨星不甘做副手:只给姆总传 给我也传几个啊

风过乡
2026-08-25 20:15:40
杭州警方通报“女子电梯遭骚扰反被索赔5000元”:王某某(女)控告李某某(男)对其殴打并猥亵与事实不符,王某某涉嫌虚构事实被立案调查

杭州警方通报“女子电梯遭骚扰反被索赔5000元”:王某某(女)控告李某某(男)对其殴打并猥亵与事实不符,王某某涉嫌虚构事实被立案调查

鲁中晨报
2026-08-25 21:18:04
全国人民都知道了!邢某彬每月来厦门都要魏莹必须陪他,细节曝光

全国人民都知道了!邢某彬每月来厦门都要魏莹必须陪他,细节曝光

文刀贰
2026-08-24 18:09:23
“我的死是xxx造成的”,疑因提建议受打压,59岁绿化工留下指向明确遗言后坠楼身亡,家属已提起民事诉讼

“我的死是xxx造成的”,疑因提建议受打压,59岁绿化工留下指向明确遗言后坠楼身亡,家属已提起民事诉讼

大风新闻
2026-08-25 21:24:04
要跑!扶老人店主搬空牌桌,房东称店主恐转让店铺,官方证实无责

要跑!扶老人店主搬空牌桌,房东称店主恐转让店铺,官方证实无责

蜜桔娱乐
2026-08-25 14:19:47
一名在韩失踪中国公民被确认遇害,嫌疑人已被捕;总领馆已要求韩国警方立即开展全面调查,依法严惩凶手

一名在韩失踪中国公民被确认遇害,嫌疑人已被捕;总领馆已要求韩国警方立即开展全面调查,依法严惩凶手

极目新闻
2026-08-25 22:17:02
8月25日俄乌最新:50国发表反俄宣言

8月25日俄乌最新:50国发表反俄宣言

西楼饮月
2026-08-25 20:54:42
怒骂华为爱国营销,狂喷华为用户是傻X,这位在日大V终于被封!更多极端言论被扒,评论区一片怒火!

怒骂华为爱国营销,狂喷华为用户是傻X,这位在日大V终于被封!更多极端言论被扒,评论区一片怒火!

谭谈社会
2026-08-25 13:47:28
苹果为库克举行告别派对,私人伴侣首度公开亮相,继任者Ternus强调“延续”

苹果为库克举行告别派对,私人伴侣首度公开亮相,继任者Ternus强调“延续”

华尔街见闻官方
2026-08-25 09:41:14
女子被迫和情夫睡觉,丈夫主动让床,2015年丈夫不堪其辱杀死情夫

女子被迫和情夫睡觉,丈夫主动让床,2015年丈夫不堪其辱杀死情夫

汉史趣闻
2026-08-24 17:06:37
由缺钱而引发的贫穷压抑已经很严重了,有人失业没钱吃饭饿到肾衰竭,有人在超市购物上演狸猫换太子被刑拘...

由缺钱而引发的贫穷压抑已经很严重了,有人失业没钱吃饭饿到肾衰竭,有人在超市购物上演狸猫换太子被刑拘...

黯泉
2026-08-25 11:33:06
重磅压哨交易!阿根廷之王恩佐点头加盟曼城,蓝月中场全面升级!

重磅压哨交易!阿根廷之王恩佐点头加盟曼城,蓝月中场全面升级!

田先生篮球
2026-08-25 22:43:35
四川省人大常委会原委员、经济委员会原主任委员刘中伯严重违纪违法被开除党籍和公职

四川省人大常委会原委员、经济委员会原主任委员刘中伯严重违纪违法被开除党籍和公职

极目新闻
2026-08-25 19:03:31
菲律宾拖船沉没!船体直立沉入24米海底,10名工人接连跳海求生

菲律宾拖船沉没!船体直立沉入24米海底,10名工人接连跳海求生

北海史记
2026-08-25 15:36:47
全球公认营养价值最高的5种水果,第一名毫无悬念

全球公认营养价值最高的5种水果,第一名毫无悬念

鬼菜生活
2026-08-25 03:01:34
2026-08-26 05:32:49
新能源自动驾驶 incentive-icons
新能源自动驾驶
专注于半导体行业资讯
1010文章数 348关注度
往期回顾 全部

科技要闻

机器人跑赢博尔特,身体太快,脑子还在追

头条要闻

赴韩遇害中国女生年仅25岁 原计划2天前入职新单位

头条要闻

赴韩遇害中国女生年仅25岁 原计划2天前入职新单位

体育要闻

穆里尼奥如何摆贝林修斯姆巴佩?

娱乐要闻

张韶涵成都演唱会中暑,中途吸氧

财经要闻

宇树科技最低跌破600元 5天缩水超2000亿

汽车要闻

硬派越野+华为智驾 泰钽700上市焕新价24.98万起

态度原创

房产
本地
旅游
公开课
军事航空

房产要闻

一年亏掉26个亿!三江潮声,吹不醒南沙开建豪宅美梦

本地新闻

无印良品竟是桐乡造?这座小城藏着一个刀具帝国

旅游要闻

相伴百年树与塔,浪漫传说传遍大江南北,背后藏着一段硝烟往事!

公开课

李玫瑾:为什么性格比能力更重要?

军事要闻

“林肯”号离开中东 留下“一地鸡毛”

无障碍浏览 进入关怀版