UDS 서비스 자동화 테스트를 위한 CAPL 구현 패턴
CANoe에서 CAPL을 사용하여 UDS(Unified Diagnostic Services) 테스트를 자동화할 때 기본적인 통신 프레임워크와 테스트 로직을 체계적으로 구성하는 것이 중요하다. 아래는 10번(세션 제어), 22번(DID 읽기), 19번(DTC 읽기) 서비스를 중심으로 한 실용적인 코드 예제와 핵심 포인트이다.
요청 전송 및 응답 처리 함수 설계
재사용성을 높이기 위해 요청 메시지를 조합하고, 응답을 비동기로 처리하는 구조를 추천한다.
variables
{
message 0x711 RequestMsg; // 요청 CAN ID
message 0x732 ResponseMsg; // 응답 수신 ID
byte rxBuffer[40];
int rxLength;
byte expectedResponseSID;
}
// 표준 단프레임 요청 생성 (예: 10, 22 서비스)
void UdsSendRequest(byte reqSID, byte subFunc, byte param[])
{
byte dataLen = 2;
if (reqSID == 0x22) dataLen = 4;
RequestMsg.dlc = dataLen;
RequestMsg.byte(0) = 0x03; // 길이 정보 (N_PCI 포함)
RequestMsg.byte(1) = reqSID;
RequestMsg.byte(2) = subFunc;
if (reqSID == 0x22)
{
RequestMsg.byte(3) = param[0]; // DID High Byte
RequestMsg.byte(4) = param[1]; // DID Low Byte
}
output(RequestMsg);
expectedResponseSID = reqSID + 0x40; // 예: 0x10 → 0x50
}
// 응답 메시지 이벤트 핸들러
on message ResponseMsg
{
if (this.byte(0) != 0x03) return; // 유효한 프레임 아님
byte rspSID = this.byte(1);
byte nrc = this.byte(2);
if (rspSID == expectedResponseSID)
{
// 정상 응답 처리
if (rspSID == 0x50)
{
write("✅ 세션 변경 성공: %d", this.byte(3));
}
else if (rspSID == 0x62)
{
word didValue = (this.byte(3) << 8) | this.byte(4);
handleDidReadSuccess(this.byte(2), didValue); // subFunc, 값 전달
}
}
else if (rspSID == 0x7F && this.byte(2) == expectedResponseSID - 0x40)
{
handleNegativeResponse(nrc);
}
}
10 서비스: 세션 전환 테스트 케이스
디폴트 세션과 프로그래밍 세션 간 전환을 검증하며, 보안 접근 필요 시 재시도 로직 포함.
testcase TC_DefaultSession()
{
UdsSendRequest(0x10, 0x01, {0, 0}); // Default Session
wait(200);
// 응답은 위의 on message 블록에서 처리됨
}
testcase TC_ProgrammingSession()
{
UdsSendRequest(0x10, 0x02, {0, 0});
wait(300);
// 가정: NRC 0x24(SecurityAccessRequired) 발생 시
if (getLastNrc() == 0x24)
{
performSecurityUnlock();
UdsSendRequest(0x10, 0x02, {0, 0}); // 재시도
}
}
22 서비스: DID 데이터 읽기 및 파싱
DID 0xF40A(가상 차속) 값을 읽고, 의미 있는 범위 내에 있는지 확인.
testcase TC_ReadVehicleSpeed()
{
byte targetDID[] = {0xF4, 0x0A};
UdsSendRequest(0x22, 0x00, targetDID);
wait(150);
}
void handleDidReadSuccess(byte subFunc, word value)
{
if ((subFunc == 0x00) && ((value > 300) || (value < 0)))
{
testWarning("⚠️ 이상치 감지: 차속 값 %d km/h", value);
}
}
19 서비스: DTC 존재성 및 상태 모니터링
DTC 리스트는 3바이트 단위로 인코딩되며, 상태 바이트와 함께 전송된다. 특정 DTC(P1234)의 존재 여부를 확인하고, ECU 전원 사이클 후 복구 동작을 검증.
testcase TC_VerifyDtcPresent()
{
UdsSendRequest(0x19, 0x0A, {0xFF}); // 모든 보고 가능한 DTC 요청
wait(200);
if (!containsDtc(0x1234))
{
testFail("❌ 필수 DTC(P1234) 없음");
}
}
int containsDtc(long dtcCode)
{
for (int i = 0; i < rxLength; i += 3)
{
long receivedDtc = (rxBuffer[i] << 16) | (rxBuffer[i+1] << 8) | rxBuffer[i+2];
if ((receivedDtc & 0xFFFF) == dtcCode) return 1;
}
return 0;
}
// 전원 오프/온 사이클 테스트
testcase TC_DtcRecoveryAfterPowerCycle()
{
// 1. 초기 DTC 확인
TC_VerifyDtcPresent();
// 2. ECU 전원 차단
setSignal(Node_ECU1.PowerEnable, 0);
delay(1000);
// 3. 전원 차단 후 DTC 사라졌는지 확인
UdsSendRequest(0x19, 0x0A, {0xFF});
wait(200);
if (containsDtc(0x1234)) testFail("❌ 전원 차단 후에도 DTC 유지됨");
// 4. 전원 복구 및 재검증
setSignal(Node_ECU1.PowerEnable, 1);
delay(2000); // 부팅 대기
cyclicUpdateTrigger(); // 진단 주기 실행 유도
TC_VerifyDtcPresent(); // 복구 여부 확인
}
고급 팁: 상태 마스크 필터링 활용
특정 상태(DTC Status: pending, confirmed 등)만 필터링하여 읽을 수 있다. 예: 미확인(0x08) 상태의 DTC만 조회.
byte statusFilter = 0x08;
UdsSendRequest(0x19, 0x0A, &statusFilter);
이러한 기본 스크립트들은 신규 ECU 개발 초기 단계에서 통신 안정성과 기본 진단 동작을 신속하게 검증하는 데 매우 효과적이다. 특히 CAPL의 이벤트 기반 특성을 적절히 활용하면, 복잡한 상태 전이 테스트도 직관적으로 구현 가능하다.