乐于分享
好东西不私藏

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

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

一、概述

上传下载功能单元是UDS协议中用于ECU软件刷写和内存数据读写的核心组成部分。在实际应用中,该功能单元主要服务于两个场景:

  1. ECU软件刷写:将新的固件或软件包下载到ECU的非易失性存储器中
  2. 数据上传:从ECU的内存中读取运行日志、故障快照等诊断数据

本文将详细讲解三个核心服务:0x34 RequestDownload(请求下载)、0x36 TransferData(传输数据)和0x37 RequestTransferExit(请求传输终止),并提供完整的C++代码示例。

二、0x34 RequestDownload(请求下载)服务

2.1 服务功能

诊断仪通过0x34服务向ECU发起下载请求,告知ECU即将下载数据的起始地址和总长度。ECU在肯定响应中返回其能够接收的最大数据块大小。

2.2 报文格式详解

请求报文格式

字节位置
参数名称
长度(字节)
说明
0
服务ID
1
0x34
1
dataFormatIdentifier
1
高4位:压缩算法,低4位:加密算法
2
addressAndLengthFormatIdentifier
1
高4位:memoryAddress长度,低4位:memorySize长度
3…
memoryAddress
变长
数据写入的起始地址
memorySize
变长
待传输数据的总长度

参数说明:

  • dataFormatIdentifier:0x00表示无压缩无加密,其他值由车企自定义
  • addressAndLengthFormatIdentifier:例如0x44表示memoryAddress和memorySize各占4字节
  • memoryAddress:地址长度由addressAndLengthFormatIdentifier的高4位指定
  • memorySize:长度由addressAndLengthFormatIdentifier的低4位指定

肯定响应格式

字节位置
参数名称
长度(字节)
0
服务ID+0x40
1 (0x74)
1
lengthFormatIdentifier
1
2…
maxNumberOfBlockLength
变长

2.3 支持的否定响应码

NRC
名称
说明
0x13
incorrectMessageLength
报文长度错误
0x22
conditionsNotCorrect
条件不满足
0x31
requestOutOfRange
参数超出范围
0x33
securityAccessDenied
安全访问未通过

三、0x36 TransferData(传输数据)服务

3.1 服务功能

0x36服务用于实际数据的传输。在下载场景中,诊断仪将数据分块发送给ECU;在上传场景中,ECU在响应中返回数据。

3.2 报文格式详解

请求报文格式(下载场景)

字节位置
参数名称
长度(字节)
说明
0
服务ID
1
0x36
1
blockSequenceCounter
1
块序列计数器,从0x01开始
2…
transferRequestParameterRecord
变长
待传输的数据

肯定响应格式(上传场景)

字节位置
参数名称
长度(字节)
0
服务ID+0x40
1 (0x76)
1
blockSequenceCounter
1
2…
transferResponseParameterRecord
变长

3.3 块序列计数器机制

  • 从0x01开始计数
  • 每发送一块数据,计数器加1
  • 达到0xFF后循环从0x00开始
  • 确保数据块的顺序性和完整性

四、0x37 RequestTransferExit(请求传输终止)服务

4.1 服务功能

诊断仪使用0x37服务正常终止与ECU之间的数据传输会话。

4.2 报文格式

请求报文

字节位置
参数名称
长度(字节)
0
服务ID
1 (0x37)
1…
transferRequestParameterRecord
变长(可选)

肯定响应

字节位置
参数名称
长度(字节)
0
服务ID+0x40
1 (0x77)
1…
transferResponseParameterRecord
变长(可选)

五、完整交互流程示例

诊断仪                                    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()constreturn (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_tdata(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_tdata(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 = {0x340x000x440x600x200x000x10,  // memoryAddress = 0x602000100x000x000xFF0xFF}; // 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_tdata(128static_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 = {0x360x010x010x020x03};    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 = {0x340x000x440x600x200x000x100x000x000x000x10};    handler.handleRequestDownload(request, response);    request = {0x360x050x010x020x03};  // 块序号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 = {0x340x000x440x600x200x000x100x000x000x000x10};    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总线上实现时,需要注意:

  1. 多帧传输:当数据块超过单帧CAN报文长度(CAN为8字节,CAN FD为64字节)时,需要实现ISO 15765-2传输层协议
  2. 流控制:ECU通过流控制帧通知诊断仪发送速率
  3. 超时处理:需要合理设置P2Server、P2*Server等超时参数

7.2 刷写安全

  1. 完整性校验:建议在36服务的响应中增加CRC校验
  2. 断点续传:支持刷写中断后的恢复机制
  3. 版本验证:刷写前验证固件版本和兼容性

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硬件特性和通信总线要求进行适配和优化。