打开网易新闻 查看精彩图片

一、概述

上传下载功能单元是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长度

memoryAddress

变长

数据写入的起始地址

memorySize

变长

待传输数据的总长度

参数说明:

  • dataFormatIdentifier:0x00表示无压缩无加密,其他值由车企自定义

  • addressAndLengthFormatIdentifier:例如0x44表示memoryAddress和memorySize各占4字节

  • memoryAddress:地址长度由addressAndLengthFormatIdentifier的高4位指定

  • memorySize:长度由addressAndLengthFormatIdentifier的低4位指定

肯定响应格式

字节位置

参数名称

长度(字节)

0

服务ID+0x40

1 (0x74)

1

lengthFormatIdentifier

1

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开始

transferRequestParameterRecord

变长

待传输的数据

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

字节位置

参数名称

长度(字节)

0

服务ID+0x40

1 (0x76)

1

blockSequenceCounter

1

transferResponseParameterRecord

变长

3.3 块序列计数器机制

  • 从0x01开始计数

  • 每发送一块数据,计数器加1

  • 达到0xFF后循环从0x00开始

  • 确保数据块的顺序性和完整性

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

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

4.2 报文格式 请求报文

字节位置

参数名称

长度(字节)

0

服务ID

1 (0x37)

transferRequestParameterRecord

变长(可选)

肯定响应

字节位置

参数名称

长度(字节)

0

服务ID+0x40

1 (0x77)

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  

#include
#include
#include
#include

namespace UDS {

// 否定响应码枚举
enumclass NRC :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
};

// 数据格式标识符
struct DataFormatIdentifier {
uint8_t compression : 4; // 压缩算法
uint8_t encryption : 4; // 加密算法
DataFormatIdentifier() : compression(0), encryption(0) {}
uint8_t toByte() const { return (compression << 4) | encryption; }
void fromByte(uint8_t byte) {
compression = (byte >> 4) & 0x0F;
encryption = byte & 0x0F;
}
};

// 传输状态机
enumclass TransferState {
IDLE, // 空闲
DOWNLOADING, // 下载中
UPLOADING, // 上传中
COMPLETED // 传输完成
};

// 传输上下文
struct TransferContext {
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服务的核心逻辑
*/
class UDSTransferHandler {
public:
UDSTransferHandler() : securityPassed_(false) {}
virtual ~UDSTransferHandler() = default;
/**
* @brief 处理0x34 RequestDownload服务
* @param request 完整请求报文
* @param response 输出参数,存放响应报文
* @return 处理结果NRC
*/
NRC handleRequestDownload(const std::vector& request,
std::vector& 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(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(maxBlockLen));
} else {
response.push_back(0x02); // lengthFormatIdentifier: 2字节长度
response.push_back(static_cast((maxBlockLen >> 8) & 0xFF));
response.push_back(static_cast(maxBlockLen & 0xFF));
}
return NRC::OK;
}
/**
* @brief 处理0x36 TransferData服务
* @param request 完整请求报文
* @param response 输出参数,存放响应报文
* @return 处理结果NRC
*/
NRC handleTransferData(const std::vector& request,
std::vector& 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 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(transferCtx_.maxBlockLength));
// 读取数据
std::vector 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(const std::vector& request,
std::vector& 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 + 0x40
return NRC::OK;
}
// 设置安全访问标志
void setSecurityPassed(bool passed) { securityPassed_ = passed; }
// 重置传输状态(用于异常恢复)
void resetTransfer() {
transferCtx_ = TransferContext();
}

protected:
// 以下为虚函数,实际使用时需要根据具体ECU实现
/**
* @brief 检查内存地址范围是否有效
*/
virtual bool isValidMemoryRange(uint32_t address, uint32_t size) {
// 实际实现中需根据ECU的内存映射表检查
// 例如:Flash地址范围0x60000000-0x60FFFFFF
return (address >= 0x60000000 &&
address + size <= 0x60FFFFFF);
}
/**
* @brief 检查ECU状态是否满足传输条件
*/
virtual bool checkPreconditions() {
// 检查是否已进入扩展会话或编程会话
returntrue;
}
/**
* @brief 获取ECU能接收的最大块长度
*/
virtual uint16_t getMaxBlockLength() {
// 实际实现中需要考虑:
// 1. 传输层缓冲区大小(如CAN FD的64字节)
// 2. 应用层处理能力
return0x81; // 129字节
}
/**
* @brief 将数据写入内存
*/
virtual bool writeDataToMemory(uint32_t address, const uint8_t* data, uint32_t length) {
// 实际实现中需调用具体的Flash驱动
std::cout << "Writing " << length << " bytes to address 0x"
<< std::hex << address << std::dec << std::endl;
returntrue;
}
/**
* @brief 从内存读取数据
*/
virtual bool readDataFromMemory(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 传输完成后的处理
*/
virtual bool onTransferComplete() {
std::cout << "Transfer completed. Total bytes: "
<< transferCtx_.bytesTransferred << std::endl;
returntrue;
}

private:
TransferContext transferCtx_;
bool securityPassed_;
};

} // namespace UDS
6.2 使用示例

#include  

#include

usingnamespace UDS;

/**
* @brief 演示UDS下载流程的完整示例
*/
void demoDownloadFlow() {
UDSTransferHandler handler;
// 模拟安全访问通过
handler.setSecurityPassed(true);
std::vector request;
std::vector 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 = 0x60200010
0x00, 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(result) << std::dec << std::endl;
return;
}
// Step 2: 模拟发送多块数据
for (int blockNum = 1; blockNum <= 5; blockNum++) {
// 构造数据块
std::vector data(128, static_cast(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(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(result) << std::dec << std::endl;
}
}

/**
* @brief 演示错误处理场景
*/
void demoErrorHandling() {
UDSTransferHandler handler;
handler.setSecurityPassed(true);
std::vector request;
std::vector 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(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(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(result) << std::dec;
std::cout << " (Expected: 0x33 - SECURITY_ACCESS_DENIED)" << std::endl;
}

int main() {
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 性能优化

// 示例:内存地址对齐检查
bool isAddressAligned(uint32_t address, uint32_t alignment) {
return (address & (alignment - 1)) == 0;
}


// 示例:Flash编程时的扇区擦除
bool eraseFlashSectors(uint32_t startAddr, uint32_t endAddr) {
// 根据具体Flash特性实现擦除逻辑
// 注意:擦除操作可能耗时较长,需要适当延长响应超时
return true;
}
八、总结

本文详细介绍了UDS协议中的上传下载功能单元,重点讲解了0x34、0x36和0x37三个服务的报文格式、参数含义、响应行为及错误处理机制。通过完整的C++代码实现,展示了如何在ECU软件中集成这些服务。

关键要点回顾:

  • 0x34服务:启动下载/上传会话,协商传输参数

  • 0x36服务:实际数据传输,使用块序列计数器保证顺序

  • 0x37服务:正常终止传输会话

  • 状态机管理:确保服务的正确调用顺序

  • 安全机制:配合安全访问服务保护敏感操作

实际项目中,还需要结合具体的ECU硬件特性和通信总线要求进行适配和优化。