在CANoe中使用CAPL脚本测试UDS(统一诊断服务)需要结合ISO-TP传输协议和UDS服务格式。以下是分步说明及示例代码:
### 步骤1:配置诊断数据库
在CANoe中导入CDD或ODX文件,定义UDS服务及参数。
### 步骤2:编写CAPL测试脚本
// 声明诊断请求和响应对象
diagRequest sessionCtrlReq;
diagResponse sessionCtrlResp;
testcase TC_UDS_SessionControl()
{
// 切换到扩展会话(0x03)
DiagSetService(sessionCtrlReq, 0x10); // 诊断会话控制服务
DiagSetParameter(sessionCtrlReq, "SubFunction", 0x03); // 子功能:扩展会话
// 发送请求
diagSendRequest(sessionCtrlReq);
// 等待响应,超时2000ms
testWaitForDiagResponse(sessionCtrlReq, 2000);
if (testGetLastError() == 0)
{
// 获取响应并验证
if (diagGetService(sessionCtrlResp) == 0x50 &&
DiagGetParameter(sessionCtrlResp, "SubFunction") == 0x03)
{
testStepPass("成功进入扩展会话");
}
else
{
testStepFail("响应不符合预期");
}
}
else
{
testStepFail("无响应或超时");
}
}
// 示例:读取数据标识符(0x22服务)
diagRequest readDataReq;
diagResponse readDataResp;
testcase TC_UDS_ReadDataByIdentifier()
{
word dataIdentifier = 0xF190; // 示例数据标识符
DiagSetService(readDataReq, 0x22);
DiagSetParameter(readDataReq, "DataIdentifier", dataIdentifier);
diagSendRequest(readDataReq);
testWaitForDiagResponse(readDataReq, 2000);
if (testGetLastError() == 0)
{
word respDataId;
byte dataValue[4];
DiagGetParameter(readDataResp, "DataIdentifier", respDataId);
DiagGetParameter(readDataResp, "DataValue", dataValue);
if (respDataId == dataIdentifier && dataValue[0] == 0xAA) // 假设期望值为0xAA
{
testStepPass("数据读取成功");
}
else
{
testStepFail("数据不符");
}
}
else
{
testStepFail("读取数据失败");
}
}
// 使用定时器处理异步操作
on timer ResponseTimer
{
testStepFail("响应超时");
}
on diagResponse sessionCtrlReq
{
cancelTimer(ResponseTimer); // 收到响应则取消超时计时器
// 处理响应...
}
### 步骤3:处理多帧传输(ISO-TP)
若需手动处理多帧:
variables {
message CAN1. txMsg = {id=0x7E0}; // 请求地址
message CAN1. rxMsg = {id=0x7E8}; // 响应地址
}
on start {
// 发送单帧请求(示例:0x10 0x03)
txMsg.dlc = 8;
txMsg.byte(0) = 0x02; // SF,长度2
txMsg.byte(1) = 0x10;
txMsg.byte(2) = 0x03;
output(txMsg);
}
on message CAN1::0x7E8 {
if (this.byte(0) & 0xF0 == 0x00) { // 单帧
byte len = this.byte(0) & 0x0F;
if (len >= 2 && this.byte(1) == 0x50 && this.byte(2) == 0x03) {
write("会话控制成功");
}
}
}
### 步骤4:执行测试并验证
1. 在CANoe中加载配置和测试模块。
2. 运行测试用例,观察测试结果窗口。
3. 分析日志,确保所有测试步骤通过。
### 关键点
- **诊断配置**:确保CDD/ODX正确,包含服务定义。
- **超时处理**:合理设置等待时间,处理网络延迟。
- **响应验证**:严格检查服务ID、子功能及数据。
- **错误处理**:捕获无响应、否定响应(NRC码)等异常情况。
在CAPL中测试多个`22`服务(ReadDataByIdentifier)的用例时,可以通过**参数化测试设计**和**循环结构**高效实现。以下是具体实现方案:
### **方法1:参数化测试用例(批量读取多个DID)**
// 定义测试参数结构体(数据标识符 + 期望值)
variables {
struct TestData {
word dataIdentifier; // DID(如0xF190)
byte expectedValue[]; // 期望数据值(数组形式)
dword timeout; // 超时时间(可选)
};
// 定义多个测试用例参数
TestData testCases[] = {
{0xF190, {0xAA, 0xBB}, 2000}, // 测试DID 0xF190,期望数据[AA BB]
{0xF1A0, {0xCC}, 1000}, // 测试DID 0xF1A0,期望数据[CC]
{0xF1B0, {0x11, 0x22, 0x33}, 2000}
};
}
// 通用读取函数
void ReadDataByIdentifier(TestData data)
{
diagRequest req;
diagResponse resp;
// 设置请求参数
DiagSetService(req, 0x22);
DiagSetParameter(req, "DataIdentifier", data.dataIdentifier);
diagSendRequest(req);
// 等待响应
if (testWaitForDiagResponse(req, data.timeout)) {
word respDID;
byte respData[64];
dword dataLen;
// 提取响应数据
DiagGetParameter(resp, "DataIdentifier", respDID);
DiagGetParameter(resp, "DataValue", respData, dataLen);
// 验证DID和数据
if (respDID == data.dataIdentifier &&
ArraysEqual(respData, data.expectedValue, elCount(data.expectedValue)))
{
testStepPass("DID 0x%04X 数据匹配", respDID);
} else {
testStepFail("DID 0x%04X 数据不符", respDID);
}
} else {
testStepFail("DID 0x%04X 无响应或超时", data.dataIdentifier);
}
}
// 执行所有测试用例
testcase TC_UDS_ReadMultipleDIDs()
{
int i;
for (i = 0; i < elCount(testCases); i++) {
ReadDataByIdentifier(testCases[i]);
}
}
### **方法2:处理否定响应(NRC码)**
当ECU返回否定响应(如NRC-0x22条件不满足)时,需额外验证NRC码:
// 在ReadDataByIdentifier函数中添加NRC处理逻辑
void ReadDataByIdentifier(TestData data)
{
// ...(同上)...
if (testWaitForDiagResponse(req, data.timeout)) {
if (diagIsNegativeResponse(resp)) { // 检查是否为否定响应
byte nrc;
DiagGetNegativeResponseCode(resp, nrc);
if (nrc == 0x22) { // 假设期望的NRC为0x22(条件不满足)
testStepPass("DID 0x%04X 返回预期NRC: 0x%02X", data.dataIdentifier, nrc);
} else {
testStepFail("DID 0x%04X 返回错误NRC: 0x%02X", data.dataIdentifier, nrc);
}
} else {
// ...正常数据验证逻辑...
}
}
}
### **方法3:动态生成测试用例(高级用法)**
若需为每个DID生成独立的测试用例(便于测试报告分类):
// 使用TestData数组自动生成独立测试用例
variables {
TestData dynamicTestCases[] = {
{0xF190, {0xAA}, 2000},
{0xF1A0, {0xCC}, 2000}
};
}
// 动态生成测试用例
testcase* GenerateTestCases() {
char tcName[64];
int i;
for (i = 0; i < elCount(dynamicTestCases); i++) {
snprintf(tcName, elCount(tcName), "TC_ReadDID_0x%04X", dynamicTestCases[i].dataIdentifier);
testcase tcName {
ReadDataByIdentifier(dynamicTestCases[i]);
}
}
}
### **关键点总结**
| 关键点 | 说明 |
|-------------------------|----------------------------------------------------------------------|
| **参数化设计** | 使用结构体数组定义测试参数,支持灵活扩展测试范围 |
| **通用函数封装** | 将诊断请求、响应验证逻辑封装为函数,减少代码冗余 |
| **否定响应处理** | 检查NRC码,覆盖异常场景(如权限不足、DID不存在) |
| **动态测试用例生成** | 批量生成独立测试用例,提升测试报告可读性 |
| **数据验证严谨性** | 使用`ArraysEqual`严格比较字节数组,避免部分匹配导致的假Pass |
| **超时配置** | 为不同DID设置独立超时,适应网络延迟差异 |
### **执行与调试**
1. **日志监控**:在CANoe的Write窗口查看`testStepPass/Fail`输出。
2. **图形化报告**:通过Test Module生成可视化测试报告,直观查看每个DID的测试结果。
3. **断点调试**:在CAPL Browser中设置断点,逐步跟踪请求发送和响应解析过程。
通过这种模块化设计,可以轻松扩展至其他UDS服务(如`0x2E`写数据、`0x27`安全访问等),构建完整的诊断自动化测试框架。