UDS诊断协议:上传下载功能单元——0x34、0x36、0x37服务详解

一、概述
上传下载功能单元是UDS协议中用于ECU软件刷写和内存数据读写的核心组成部分。在实际应用中,该功能单元主要服务于两个场景:
-
ECU软件刷写:将新的固件或软件包下载到ECU的非易失性存储器中 -
数据上传:从ECU的内存中读取运行日志、故障快照等诊断数据
本文将详细讲解三个核心服务:0x34 RequestDownload(请求下载)、0x36 TransferData(传输数据)和0x37 RequestTransferExit(请求传输终止),并提供完整的C++代码示例。
二、0x34 RequestDownload(请求下载)服务
2.1 服务功能
诊断仪通过0x34服务向ECU发起下载请求,告知ECU即将下载数据的起始地址和总长度。ECU在肯定响应中返回其能够接收的最大数据块大小。
2.2 报文格式详解
请求报文格式
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
参数说明:
-
dataFormatIdentifier:0x00表示无压缩无加密,其他值由车企自定义 -
addressAndLengthFormatIdentifier:例如0x44表示memoryAddress和memorySize各占4字节 -
memoryAddress:地址长度由addressAndLengthFormatIdentifier的高4位指定 -
memorySize:长度由addressAndLengthFormatIdentifier的低4位指定
肯定响应格式
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
2.3 支持的否定响应码
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
三、0x36 TransferData(传输数据)服务
3.1 服务功能
0x36服务用于实际数据的传输。在下载场景中,诊断仪将数据分块发送给ECU;在上传场景中,ECU在响应中返回数据。
3.2 报文格式详解
请求报文格式(下载场景)
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
肯定响应格式(上传场景)
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
3.3 块序列计数器机制
-
从0x01开始计数 -
每发送一块数据,计数器加1 -
达到0xFF后循环从0x00开始 -
确保数据块的顺序性和完整性
四、0x37 RequestTransferExit(请求传输终止)服务
4.1 服务功能
诊断仪使用0x37服务正常终止与ECU之间的数据传输会话。
4.2 报文格式
请求报文
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
肯定响应
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
五、完整交互流程示例
诊断仪 ECU | | |------ 0x34 RequestDownload -------->| | (地址:0x60200010, 长度:0xFFFF) | | | |<------ 0x74 Positive Response -------| | (maxBlockLength:0x0081) | | | |------ 0x36 TransferData ----------->| | (块序号:0x01, 数据块1) | | | |<------ 0x76 Positive Response -------| | (块序号:0x01) | | | |------ 0x36 TransferData ----------->| | (块序号:0x02, 数据块2) | | | |<------ 0x76 Positive Response -------| | (块序号:0x02) | | | |------ 0x37 RequestTransferExit ---->| | | |<------ 0x77 Positive Response -------| | |
六、C++代码实现
6.1 数据结构定义
#include<iostream>#include<vector>#include<cstdint>#include<functional>#include<memory>namespace UDS {// 否定响应码枚举enumclassNRC :uint8_t { OK = 0x00, GENERAL_REJECT = 0x10, SERVICE_NOT_SUPPORTED = 0x11, SUBFUNCTION_NOT_SUPPORTED = 0x12, INCORRECT_MESSAGE_LENGTH = 0x13, CONDITIONS_NOT_CORRECT = 0x22, REQUEST_SEQUENCE_ERROR = 0x24, REQUEST_OUT_OF_RANGE = 0x31, SECURITY_ACCESS_DENIED = 0x33, INVALID_DATA = 0x71, BLOCK_SEQUENCE_COUNTER_ERROR = 0x73};// 数据格式标识符structDataFormatIdentifier {uint8_t compression : 4; // 压缩算法uint8_t encryption : 4; // 加密算法 DataFormatIdentifier() : compression(0), encryption(0) {}uint8_ttoByte()const{ return (compression << 4) | encryption; }voidfromByte(uint8_t byte){ compression = (byte >> 4) & 0x0F; encryption = byte & 0x0F; }};// 传输状态机enumclassTransferState { IDLE, // 空闲 DOWNLOADING, // 下载中 UPLOADING, // 上传中 COMPLETED // 传输完成};// 传输上下文structTransferContext { TransferState state;uint32_t memoryAddress;uint32_t memorySize;uint32_t bytesTransferred;uint8_t nextBlockCounter;uint16_t maxBlockLength;bool isDownload; TransferContext() : state(TransferState::IDLE) , memoryAddress(0) , memorySize(0) , bytesTransferred(0) , nextBlockCounter(1) , maxBlockLength(0) , isDownload(true) {}};/** * @brief UDS传输层处理类 * * 实现了0x34/0x36/0x37服务的核心逻辑 */classUDSTransferHandler {public: UDSTransferHandler() : securityPassed_(false) {}virtual ~UDSTransferHandler() = default;/** * @brief 处理0x34 RequestDownload服务 * @param request 完整请求报文 * @param response 输出参数,存放响应报文 * @return 处理结果NRC */NRC handleRequestDownload(conststd::vector<uint8_t>& request, std::vector<uint8_t>& response){// 检查最小报文长度if (request.size() < 4) {return NRC::INCORRECT_MESSAGE_LENGTH; }// 检查安全访问if (!securityPassed_) {return NRC::SECURITY_ACCESS_DENIED; }// 解析地址和长度格式标识符uint8_t addrLenFormat = request[2];uint8_t addrLen = (addrLenFormat >> 4) & 0x0F;uint8_t sizeLen = addrLenFormat & 0x0F;// 检查报文长度是否匹配if (request.size() != static_cast<size_t>(3 + addrLen + sizeLen)) {return NRC::INCORRECT_MESSAGE_LENGTH; }// 解析数据格式标识符 DataFormatIdentifier dfi; dfi.fromByte(request[1]);// 检查数据格式是否支持if (dfi.compression != 0 || dfi.encryption != 0) {return NRC::REQUEST_OUT_OF_RANGE; }// 解析内存地址uint32_t memoryAddress = 0;for (int i = 0; i < addrLen; i++) { memoryAddress = (memoryAddress << 8) | request[3 + i]; }// 解析数据总长度uint32_t memorySize = 0;for (int i = 0; i < sizeLen; i++) { memorySize = (memorySize << 8) | request[3 + addrLen + i]; }// 检查地址和长度是否有效if (!isValidMemoryRange(memoryAddress, memorySize)) {return NRC::REQUEST_OUT_OF_RANGE; }// 检查预置条件(如:是否已进入编程会话)if (!checkPreconditions()) {return NRC::CONDITIONS_NOT_CORRECT; }// 保存传输上下文 transferCtx_.state = TransferState::DOWNLOADING; transferCtx_.memoryAddress = memoryAddress; transferCtx_.memorySize = memorySize; transferCtx_.bytesTransferred = 0; transferCtx_.nextBlockCounter = 1; transferCtx_.isDownload = true; transferCtx_.maxBlockLength = getMaxBlockLength();// 构建肯定响应 response.clear(); response.push_back(0x74); // 服务ID + 0x40// maxNumberOfBlockLength参数(假设最大块长度为0x81 = 129字节)uint16_t maxBlockLen = transferCtx_.maxBlockLength;if (maxBlockLen <= 0xFF) { response.push_back(0x01); // lengthFormatIdentifier: 1字节长度 response.push_back(static_cast<uint8_t>(maxBlockLen)); } else { response.push_back(0x02); // lengthFormatIdentifier: 2字节长度 response.push_back(static_cast<uint8_t>((maxBlockLen >> 8) & 0xFF)); response.push_back(static_cast<uint8_t>(maxBlockLen & 0xFF)); }return NRC::OK; }/** * @brief 处理0x36 TransferData服务 * @param request 完整请求报文 * @param response 输出参数,存放响应报文 * @return 处理结果NRC */NRC handleTransferData(conststd::vector<uint8_t>& request,std::vector<uint8_t>& response){// 检查传输状态if (transferCtx_.state != TransferState::DOWNLOADING && transferCtx_.state != TransferState::UPLOADING) {return NRC::REQUEST_SEQUENCE_ERROR; }// 最小报文检查(至少包含服务ID和块序号)if (request.size() < 2) {return NRC::INCORRECT_MESSAGE_LENGTH; }// 获取块序列计数器uint8_t receivedCounter = request[1];// 检查块序号是否正确if (receivedCounter != transferCtx_.nextBlockCounter) {return NRC::BLOCK_SEQUENCE_COUNTER_ERROR; }// 对于下载场景,处理数据if (transferCtx_.state == TransferState::DOWNLOADING) {// 提取数据负载std::vector<uint8_t> data(request.begin() + 2, request.end());// 检查数据长度是否超过ECU接收能力if (data.size() > transferCtx_.maxBlockLength) {return NRC::INVALID_DATA; }// 检查是否会超出总数据长度if (transferCtx_.bytesTransferred + data.size() > transferCtx_.memorySize) {return NRC::INVALID_DATA; }// 写入数据到内存if (!writeDataToMemory(transferCtx_.memoryAddress + transferCtx_.bytesTransferred, data.data(), data.size())) {return NRC::GENERAL_REJECT; }// 更新传输进度 transferCtx_.bytesTransferred += data.size(); transferCtx_.nextBlockCounter++;// 构建肯定响应 response.clear(); response.push_back(0x76); // 服务ID + 0x40 response.push_back(receivedCounter);// 可选:添加CRC校验值// response.push_back(calculateCRC(data)); }// 对于上传场景,返回数据elseif (transferCtx_.state == TransferState::UPLOADING) {// 计算本次要上传的数据大小uint32_t remaining = transferCtx_.memorySize - transferCtx_.bytesTransferred;uint32_t blockSize = std::min(remaining, static_cast<uint32_t>(transferCtx_.maxBlockLength));// 读取数据std::vector<uint8_t> data(blockSize);if (!readDataFromMemory(transferCtx_.memoryAddress + transferCtx_.bytesTransferred, data.data(), blockSize)) {return NRC::GENERAL_REJECT; }// 构建肯定响应(包含上传数据) response.clear(); response.push_back(0x76); // 服务ID + 0x40 response.push_back(receivedCounter); response.insert(response.end(), data.begin(), data.end());// 更新传输进度 transferCtx_.bytesTransferred += blockSize; transferCtx_.nextBlockCounter++; }return NRC::OK; }/** * @brief 处理0x37 RequestTransferExit服务 * @param request 完整请求报文 * @param response 输出参数,存放响应报文 * @return 处理结果NRC */NRC handleRequestTransferExit(conststd::vector<uint8_t>& request,std::vector<uint8_t>& response){// 检查传输状态if (transferCtx_.state != TransferState::DOWNLOADING && transferCtx_.state != TransferState::UPLOADING) {return NRC::REQUEST_SEQUENCE_ERROR; }// 检查是否所有数据都已传输完成if (transferCtx_.bytesTransferred != transferCtx_.memorySize) {// 数据不完整,可选择性返回错误// 这里允许提前终止 }// 执行传输结束后的处理if (!onTransferComplete()) {return NRC::GENERAL_REJECT; }// 重置传输上下文 transferCtx_.state = TransferState::COMPLETED;// 构建肯定响应 response.clear(); response.push_back(0x77); // 服务ID + 0x40return NRC::OK; }// 设置安全访问标志voidsetSecurityPassed(bool passed){ securityPassed_ = passed; }// 重置传输状态(用于异常恢复)voidresetTransfer(){ transferCtx_ = TransferContext(); }protected:// 以下为虚函数,实际使用时需要根据具体ECU实现/** * @brief 检查内存地址范围是否有效 */virtualboolisValidMemoryRange(uint32_t address, uint32_t size){// 实际实现中需根据ECU的内存映射表检查// 例如:Flash地址范围0x60000000-0x60FFFFFFreturn (address >= 0x60000000 && address + size <= 0x60FFFFFF); }/** * @brief 检查ECU状态是否满足传输条件 */virtualboolcheckPreconditions(){// 检查是否已进入扩展会话或编程会话returntrue; }/** * @brief 获取ECU能接收的最大块长度 */virtualuint16_tgetMaxBlockLength(){// 实际实现中需要考虑:// 1. 传输层缓冲区大小(如CAN FD的64字节)// 2. 应用层处理能力return0x81; // 129字节 }/** * @brief 将数据写入内存 */virtualboolwriteDataToMemory(uint32_t address, constuint8_t* data, uint32_t length){// 实际实现中需调用具体的Flash驱动std::cout << "Writing " << length << " bytes to address 0x" << std::hex << address << std::dec << std::endl;returntrue; }/** * @brief 从内存读取数据 */virtualboolreadDataFromMemory(uint32_t address, uint8_t* data, uint32_t length){std::cout << "Reading " << length << " bytes from address 0x" << std::hex << address << std::dec << std::endl;returntrue; }/** * @brief 传输完成后的处理 */virtualboolonTransferComplete(){std::cout << "Transfer completed. Total bytes: " << transferCtx_.bytesTransferred << std::endl;returntrue; }private: TransferContext transferCtx_;bool securityPassed_;};} // namespace UDS
6.2 使用示例
#include<iostream>#include<vector>usingnamespace UDS;/** * @brief 演示UDS下载流程的完整示例 */voiddemoDownloadFlow(){ UDSTransferHandler handler;// 模拟安全访问通过 handler.setSecurityPassed(true);std::vector<uint8_t> request;std::vector<uint8_t> response; NRC result;// Step 1: 发送0x34 RequestDownload// 34 00 44 60 20 00 10 00 00 FF FF request = {0x34, 0x00, 0x44, 0x60, 0x20, 0x00, 0x10, // memoryAddress = 0x602000100x00, 0x00, 0xFF, 0xFF}; // memorySize = 0x0000FFFF result = handler.handleRequestDownload(request, response);if (result == NRC::OK) {std::cout << "RequestDownload success. Response: ";for (auto byte : response) {printf("%02X ", byte); }std::cout << std::endl; } else {std::cout << "RequestDownload failed with NRC: 0x" << std::hex << static_cast<int>(result) << std::dec << std::endl;return; }// Step 2: 模拟发送多块数据for (int blockNum = 1; blockNum <= 5; blockNum++) {// 构造数据块std::vector<uint8_t> data(128, static_cast<uint8_t>(blockNum)); // 测试数据 request.clear(); request.push_back(0x36); // 服务ID request.push_back(blockNum); // 块序列计数器 request.insert(request.end(), data.begin(), data.end()); result = handler.handleTransferData(request, response);if (result == NRC::OK) {std::cout << "TransferData block " << blockNum << " success. Response: ";for (auto byte : response) {printf("%02X ", byte); }std::cout << std::endl; } else {std::cout << "TransferData block " << blockNum << " failed with NRC: 0x" << std::hex << static_cast<int>(result) << std::dec << std::endl;return; } }// Step 3: 发送0x37 RequestTransferExit request = {0x37}; result = handler.handleRequestTransferExit(request, response);if (result == NRC::OK) {std::cout << "RequestTransferExit success. Response: ";for (auto byte : response) {printf("%02X ", byte); }std::cout << std::endl; } else {std::cout << "RequestTransferExit failed with NRC: 0x" << std::hex << static_cast<int>(result) << std::dec << std::endl; }}/** * @brief 演示错误处理场景 */voiddemoErrorHandling(){ UDSTransferHandler handler; handler.setSecurityPassed(true);std::vector<uint8_t> request;std::vector<uint8_t> response; NRC result;std::cout << "\n=== 错误场景测试 ===" << std::endl;// 场景1: 未发送34服务直接发送36服务 request = {0x36, 0x01, 0x01, 0x02, 0x03}; result = handler.handleTransferData(request, response);std::cout << "Test 1 - Missing 34 service: NRC = 0x" << std::hex << static_cast<int>(result) << std::dec;std::cout << " (Expected: 0x24 - REQUEST_SEQUENCE_ERROR)" << std::endl;// 场景2: 错误的块序列计数器 handler.resetTransfer(); request = {0x34, 0x00, 0x44, 0x60, 0x20, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10}; handler.handleRequestDownload(request, response); request = {0x36, 0x05, 0x01, 0x02, 0x03}; // 块序号5,期望是1 result = handler.handleTransferData(request, response);std::cout << "Test 2 - Wrong block sequence: NRC = 0x" << std::hex << static_cast<int>(result) << std::dec;std::cout << " (Expected: 0x73 - BLOCK_SEQUENCE_COUNTER_ERROR)" << std::endl;// 场景3: 安全访问未通过 UDSTransferHandler handler2; request = {0x34, 0x00, 0x44, 0x60, 0x20, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10}; result = handler2.handleRequestDownload(request, response);std::cout << "Test 3 - Security not passed: NRC = 0x" << std::hex << static_cast<int>(result) << std::dec;std::cout << " (Expected: 0x33 - SECURITY_ACCESS_DENIED)" << std::endl;}intmain(){std::cout << "=== UDS 0x34/0x36/0x37 Service Demo ===" << std::endl; demoDownloadFlow(); demoErrorHandling();return0;}
七、工程实践注意事项
7.1 传输层考量
在实际CAN或CAN FD总线上实现时,需要注意:
-
多帧传输:当数据块超过单帧CAN报文长度(CAN为8字节,CAN FD为64字节)时,需要实现ISO 15765-2传输层协议 -
流控制:ECU通过流控制帧通知诊断仪发送速率 -
超时处理:需要合理设置P2Server、P2*Server等超时参数
7.2 刷写安全
-
完整性校验:建议在36服务的响应中增加CRC校验 -
断点续传:支持刷写中断后的恢复机制 -
版本验证:刷写前验证固件版本和兼容性
7.3 性能优化
// 示例:内存地址对齐检查boolisAddressAligned(uint32_t address, uint32_t alignment){return (address & (alignment - 1)) == 0;}// 示例:Flash编程时的扇区擦除booleraseFlashSectors(uint32_t startAddr, uint32_t endAddr){// 根据具体Flash特性实现擦除逻辑// 注意:擦除操作可能耗时较长,需要适当延长响应超时returntrue;}
八、总结
本文详细介绍了UDS协议中的上传下载功能单元,重点讲解了0x34、0x36和0x37三个服务的报文格式、参数含义、响应行为及错误处理机制。通过完整的C++代码实现,展示了如何在ECU软件中集成这些服务。
关键要点回顾:
-
0x34服务:启动下载/上传会话,协商传输参数 -
0x36服务:实际数据传输,使用块序列计数器保证顺序 -
0x37服务:正常终止传输会话 -
状态机管理:确保服务的正确调用顺序 -
安全机制:配合安全访问服务保护敏感操作
实际项目中,还需要结合具体的ECU硬件特性和通信总线要求进行适配和优化。
夜雨聆风