[들어가기에 앞서]
차량 네트워크는 CAN 통신
이라는 구조로 이루어진다.
CAN Data Transaction 자체를 구현하는 것은 더 깊은 영역이기 때문에
(Driver 개발) 차량 네트워크 개발자라면, 이미 개발된 IC칩을 쓴다.
즉, CAN 통신에서 핵심이 되는 형태만 간단하게 집고 넘어간다.
CANIdentifier(CAN MessageID)
해당 ID를 가지고 CAN 통신이 이루어진다. (출처)
이 ID는 Header
에 속하기 때문에 Data와는 별개의 영역이다.
11bit는 기본 CAN의 경우이며(CAN2.0a(표준))
29bit까지 사용이 가능한 Extendede-CAN도 있다.(CAN2.0b(확장))
Data - 이 부분이 CAN과 CAN-FD의 주요한 차이를 만드는 요소가 된다.
CAN의 경우 0~8byte 즉 0,1,2,3..,8이 되지만
CAN-FD의 경우 8~64byte로 넘어가는 영역에서
9,10... 이런 개별 byte가 전송이 불가하다.
8byte 이상은 특정 byte만 전송이 가능하며 아래와 같다.
8/12/16/20/24/32/48/64byte(출처)
-> 위의 DLC(DataLengthCode) 정보까지가 Header에 담긴다.
CAN에 대해서는 더 이야기 하지 않고 다음으로는 UDS에 대한 핵심을 알아보자
MultiFrame
Protocol이며 이를 이야기 하기 전에 UDS에 대한 간단한 윤곽을 이해하고 넘어가자.UDS는 규약
(Protocol)이다.
CAN
도 규약이지 않냐 하면 맞다.UDS는 CAN을 활용하는 규약
이다.여기서 UDS의 많은 Service ID
와 각 데이터를 통신할 때 쓰이는
Frame Protocol
이 개발자에게 주요한 역량이 된다.
특히나, 처음 전장 SW를 개발하면, SingleFrame에 대한 이해는 간단하게 할 수 있을지 모르지만, MultiFrame은 상당히 복잡하다고 여긴다.
사실 SingleFrame의 내용을 제대로 이해한 게 아닐 확률이 높다. 단순히 1회 송신하면 1회 응답이 오니까 테스트하기 편하고 이해했다고 생각하는 것일 뿐 Service 내용과 positive-negative 응답 등 생각할 부분과 따져봐야 할 부분이 상당히 많다.
그래서 이번 주제를 작성해야겠다고 생각했다.
SingleFrame 뿐만 아니라, MultiFrame에 대한 윤곽과 이해에 도움이 되면 좋곘다. 해외 자료 말고 국내 자료로 예를 들며 만드는 자료가 없어서...
(출처)
위의 표는 CAN 통신
인 경우 UDS의 FrameProtocol 사용 방식이다.
(CAN-FD는 차이가 있다. -> 해당 부분은 별도의 포스트로 다룬다.)
Frame Protocol을 간단하게 이야기 할 필요가 있는데
Frame Protocol은 Data
를 활용해 정보를 주고 받는다.
CAN
통신이 이루어진다.즉, UDS를 활용하기 위해서는 UDS 진단 통신을
요청하는 CAN 통신 메시지ID
회신 하는 CAN 통신 메시지ID
가 쌍을 이루어 통신이 이루어진다.해당 CAN 통신 메시지ID는 민감 정보
이기 때문에 앞으로 쓸 예시로
UDS 진단 요청을 보내는 CANID는 0x01(1)
이에 응답하는 CANID는 0x02(2)로 예를 들겠다.
아래는 실제로 사용되는 DiagnosticSessionControl
을 활용한 예시다.(출처)
예로 가게를 생각해 보면, 가게의 이용자는 기본적인 것만 이용하면 된다.
반면, 점원은 더 높은 권한을 가진다. 더 나아가 점주는 더 높은 접근 권한이 있으며, 특히나 이를 접근할 수 있는 암호화된 키를 가진다.
이런 상식이 UDS 진단 통신의 SessionControl에도 적용된다.
예로, 0x01(1)이 Default, 0x02(2)를 Programming, 0x03(3)을 Extended라고 하자.
위와 같은 내용은 UDS 규약에 상세하게 규정되는데, 일반적으로
Service안에서 동작하는 특정 SubFunction
이라고 부른다.
SubFunction은 공통된 명칭이며,
DiagnosticSessionControl의 SubFunction은 diagnosticSessionType
이라고 정의된다.
subfunction이 몇 byte일지, 이외에도 다른 정보를 담아야 하는지는 UDS 문서에 상세하게 기술된다.
그럼 실제 메시지 구조를 확인해 볼 필요가 있다.
CAN 통신은 0~8byte 길이로 통신을 한다고 했는데 UDS는 8byte
로 길이가 고정이다!
그렇다면 전송할 정보의 개수가 짧다면 어떻게 해야하는가?라는 문제가 생긴다.
이는 FILLBYTE
라는 정해진 dummyData를 채운다.
그런데, 또 이렇게 했을 때의 문제가 있다.
바로 실제 통신에 필요한 데이터 길이 정보
를 별도로 담아야 한다는 점이다.
실제로, UDS에서는 FrameProtocol을 보면 DL
이라는 항목을 볼 수 있다. 즉, 여기에 실제 전송할 정보의 데이터를 담는다.
SingleFrame의 4~7bit(high nibble)은 SingleFrame이기 때문에 0b(0000)이고 0~3bit(low nibble)에 SFDL을 담는다.
다만, CAN통신의 SingleFrame의 경우, 0번째 byte를에 이미 data가 할당되기 때문에 실제로 담을 수 있는 정보는 최대 7개로 제한된다.
이렇게 자세히 이야기 한 이유는
위의 DiangosticSessionControl을 사용하기 위해서다.
앞으로도 FILLBYTE는 0xFF로 통일한다.(request/response가 구분되어야 하지만 예는 위와 같이 쓴다.)
DiagnosticSessionControlMessage example(상세 예시)
Header
CANID : 0x01(1), DLC : 0x08(8)
Data
| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 |
| 0x02(2) | 0x10(16) | 0x03(3) | 0xFF(255) | 0xFF | 0xFF | 0xFF | 0xFF |
위와 같이 메시지를 ECU에 보내고 해당 UDS에 대한 구현이 되어 있는 ECU라면
이에 대해서 아래와 같은 형태의 회신이 올 수 있다.
Header
CANID : 0x02(2), DLC : 0x08(8)
Data
| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 |
| 0x02(2) | 0x50(80) | 0x03(3) | 0xFF(255) | 0xFF | 0xFF | 0xFF | 0xFF |
실제로 회신의 경우, 데이터의 길이가 다를 수 있는데 더 많은 정보가 담길 수 있기 때문이다.
여기서 중요한 것은 위는 정상 응답을 하는 경우고 특징은
d1의 차이
에 있다.
0x10(16) -> 0x50(80)로 +0x40(64) 인 형태처럼 보낸 ServiceID
가
정상 통신이 되면 위와 같은 형태를 회신한다.
반면, 비정상 통신을 한다면
Header
CANID : 0x02(2), DLC : 0x08(8)
Data
| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 |
| 0x03(3) | 0x7F(127) | 0x10(16) | 0x12(18) | 0xFF(255) | 0xFF | 0xFF | 0xFF |
와 같은 형태로 부정
응답이 올 수 있다.
이 역시, SingleFrame 형태이며,
d1의 0x7F는 부정응답의 대표 규약이다.
d2는 ServiceID를 담아 어떤 서비스가 비정상 응답을 했는지 보여주며
d3~은 부정응답의 상세한 이유를 담는다.
여기서 가져온 예시는 0x12(18) SubFunctionNotSupported라는 규약입니다. 이외에도 많은 규약이 UDS 문서에 정의되어 있으며 서비스마다 지원하는 것에 차이가 있으며, 이는 간단하게 생각해서, 웹의 404에러와 같은 간단한 에러 상태를 알려주는 규약이라고 생각하면 됩니다.
이제 MultiFrame 차례입니다.
SingleFrame과 딱 봐도 차이가 많이 나는데요. 심지어, MultiFrame이라는 건 하나를 뜻하는 게 아니라 FirstFrame-FlowControl-ConsecutiveFrame
이라는 하나의 Sequence(과정/절차)를 이루는 표현입니다.
다시 한 번 아래의 FrameProtocol을 보고 SecurityAccess에 대한 정보로 이어 가겠습니다.
위의 프레임 구조를 잘 생각하셔야 합니다 :)
그리고 여기서 최초에 전장 SW 개발을 하면서 헷갈리는 부분이 누가 주체이고 누가 어떻게 보내주느냐에 대한 내용입니다.
제가 굳이 회신
(0x02(2))이라는 표현을 쓴 이유도 여기에 있습니다.
표현에 따라서는 모두가 주체가 될 수 있기 때문입니다.
자 그럼, 다시 돌아와서, SecuritAccess에 대해서 알아보죠.
상세예제
이는 맨 먼저, 든 가게의 예시처럼 점주가 가지는 보안키입니다.
해당 과정을 거친 이후에 정보 변경 및 조작 등의 행위를 허용 합니다.
서비스ID는 0x27(39)이며 이에 대한 subfunctionID로 요청을 2번 하는 특징이 있습니다.
여기서 신기하다고 생각할 수 있는데 이는 SecurityAccess의 절차를 생각해 보면 쉽게 납득할 수 있습니다.
위와 같이 2가지 과정이 발생합니다.
하지만 위의 과정에 표현으로 빠진 부분이 있는데 아래의 그림 설명을 먼저 보고 해당 구조를 기반으로 설명을 드리겠습니다.
위의 과정이 SecurityAccess 전체의 흐름도입니다.
보시면 1, 2는 금방 이해가 되는데, AccessKeyMake 부분이 이해가 선뜻 안 될 수 있습니다.
이는 암호를 발급하는 내부로직이 있고 이는 SecurityAcccess로 통신할 ECU간에 동일해야 합니다.
쉽게 말하면 암호화 방식이 AES-128이라고 가정했을 때, 양쪽이 동일한 암호일 때, 2번 과정이 암호 확인 절차에서 통과할 수 있습니다.
만약, 한쪽은 AES-128 암호화 방식을 사용하고, 다른 쪽은 AES-256을 사용한다면 서로 다른 암호가 발생하므로, SecurityAccess가 되지 않을 것입니다.
우선, 이와 같은 SecurityAccess에 대한 선제적 이해가 이루어졌다면, 이제 실제 이를 UDS 통신에서 멀티 프레임으로 어떻게 구현하는지를 알아 봐야 합니다.
전제는 동일한 암호화 방식
이며 전체 흐름도를 먼저 보여드리겠습니다.
seed 요청을 하는 subfunction byte는 0x01(1),
key를 요청하는 subfunction byte는 0x02(2)로 하겠습니다.
실제로도 key를 확인하는 subfunctionID는 seed 요청 보다 1 큰 값으로 쌍을 만듭니다.
실제 Transaction은 위와 같이 발생합니다. 이미지만 놓고 보면 상당히 복잡해 보일 수 있고,
실제로 차량 개발자 이외에는 쓸 일도 없으니, 해당 내용이 장벽이 되는 것 같습니다. 하지만 FrameProtocol을 이해하고, 이에 대한 위 예시를 적확하게 이해하면,
앞으로 UDS건, 해당 프로토콜의 예시 및 구성에 대한 이해가 더는 어렵지 않게 느껴질 거라는 것도 분명한 요소기 때문에 차근차근 알아가 보겠습니다.
먼저, 맨 마지막 negative response에 해당하는 0x03(3) 0x7F(127) 0x27(39) 0x10(16)
에서 보여지는 0x10(16)
의 의미는 genearl reject
로 하겠습니다.
많은 NegativeResponseCode(NRC)
가 있으며, 이는 개발하며 직접 확인해야하는 요소입니다.
차근차근 하나씩 알아보도록 하겠습니다.
아래의 순서로 설명 드릴 에정입니다.
위의 8가지 과정에 대한 흐름의 이해와 내용을 전반적으로 이해하면
앞으로 OTA를 할 때 겪게 되는 TransferData
와 같은 내용이 오더라도
스스로 분석을 통해 문제를 해결할 수 있습니다.
하지만, 해당 부분에도 어려운 부분이 있기 때문에 다음 시리즈로 다룰 수 있도록 하겠습니다.
하나하나 상세히 다시 복습을 겸해서 설명 드리겠습니다.
위의 FrameProtocol을 다시 보고 가죠!
그리고 제가 위에 언급한 8가지 과정을 보시면 아실 수 있지만, 요청과 응답에서 모두! 모든 Frame을 사용할 수 있습니다! 이게 처음에는 이해가 안 되고 제가 어려움을 겪었던 요소여서 살짝 말씀드려 보고 글을 통해 상세히 알려 드리겠습니다. :)
예시 내 공통 요소
ServiceID
SubFunctionID
암호화 방식은 동일하다고 가정
암호 seed 및 key 데이터는 각각 8byte로 가정
NRC
Header
CANID : 0x01(1) DLC : 0x08(8)
Data
| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 |
| 0x02(2) | 0x27(39) | 0x01(1) | 0xFF(255) | 0xFF | 0xFF | 0xFF | 0xFF |
해당 내용은 크게 어려울 게 없습니다.
CANID라던지, FILLBYTE라던지 간단하게 복습을 한다고 생각하면 될 거 같습니다.
Header
CANID : 0x02(2) DLC : 0x08(8)
Data
| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 |
| 0x10(16) | 0x0A(10) | 0x67(103) | 0x01(1) | seed1 | seed2 | seed3 | seed4 |
이제부터 멀티프레임의 시작입니다.
멀티 프레임은 소개할 때 마다 다시 한 번 구조와 기타 상세 내용을 한 번 더 말씀드리겠습니다.
몇 가지 포인트를 말씀드리자면,
이건 회신하는 ECU에서 보냈다는 점입니다.
위 로직에서, seed를 만들고 해당 시드가 8개의 데이터인데, singleFrame에 담을 수 있는 데이터는 최대7개이기 때문에 8개의 데이터를 보내기 위해서 MultiFrame 방식으로 보내겠다는 시작
을 FirstFrame으로 알려줍니다.
첫 번째 d0를 보면 0001|0000b(0x10 == 16)이라는 점을 아실 수 있습니다.
그리고 d1은 0000|1010b(0x0A == 10)입니다.
d1까지가 firstframe의 주요 정보이므로 이를 이진수로 나열해서 분석해 보면
0001|0000|0000|1010b(0x100A)이 되며 가장 앞의 7~4bit가 1이므로 FirstFrame이 됩니다.
이후 12bit를 통해 최대 4095까지의 데이터 길이 정보를 주고 받을 수 있습니다.
다만, 이때 실제로 송수신 하는 데이터는 결과적으로 MCU의 성능에 영향을 받습니다.
이를 송수신 할 수 있는 데이터의 크기가 실제로는 개발 환경의 제약을 크게 받는다는 점을 알고
해당 칩의 성능을 검토해 보고 실제로 데이터 송수신을 해 보며 적절한 데이터 송수신 크기의 제약을 두어야 합니다. 특히, 이러한 제약은 이후 OTA 관련한 포스트에 작성할 TransferData
에 대한 소개에서도 다시 한 번 언급드릴 수 있도록 하겠습니다.
우선, 일반적인 SecurityAccess는 위와 같은 일반적인 8byte에 의한 데이터 전송이 이루어집니다.
그런데 8개의 데이터를 전송하는데 왜 0x0A(10)10개의 데이터가 있는지 의문이실 수 있습니다.
이는 FirstFrame에서도 정상적인 응답이 이루어졌다라는 정보를 담아주는 데이터가 2개 더 들어왔기 때문입니다. -> 다른 UDS 서비스에서도 이와 같이 추가적인 정보를 담는 경우가 더러 있습니다. 이게 실제 데이터와 길이에 차이가 있다는 점! 이 부분을 가장 깊이 있게 보시면 좋겠습니다.
이제, 실질적인 정보를, 남은 D4..D7에 담습니다. 하지만 이렇게 해도 아직 남은 데이터가 4개가 있습니다. 이를 받기 위해 아래와 같은 절차를 거칩니다.
Header
CANID : 0x01(1) DLC : 0x08(8)
Data
| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 |
| 0x30(48) | 0x00(0) | 0x05(5) | 0xFF(255) | 0xFF | 0xFF | 0xFF | 0xFF |
대제목으로 설정했지만, 해당 내용이 크게 엄청난 걸 담고 있지는 않습니다. 다만, 이 글을 써 오면서 주체
에 대해서 지속적으로 설명 드렸던 부분에서 해당 요소가 꽤 중요합니다.
클라이언트를 개발할 때, 클라이언트가 요청을 보내는 주체가 되며, 회신을 하는 ECU는 이미 개발이 되어 있는 샘플입니다. 그렇기 때문에 MultiFrame 및 FrameProtocol이 녹아 있는 UDS 관련 내용이 이미, 해당 ECU에는 개발이 완료된 상태입니다. 다만 이를 개발하는 개발자가 처음 UDS 통신 클라이언트를 개발할 때, 멀티프레임에서 그것도 주체가 누구인지, 어떻게 보내줘야 하는지, 거기에 정보는 어떤 것들이 있는지 끊임없이 이해가 되지 않았습니다. 특히 FlowControl의 경우, 부수적인 정보도 굉장히 많습니다.
FC Flag(FS)
그림에도 나와 있지만, D0의 low nibble(3-0)은 FlowControl의 Flag다 라고 되어 있는데 flag가 개인적으로 foobar로 대표되는 true/false처럼 여겨지니 개인적인 해석을 덧붙여 말씀드리면 enum
과 같은 상태값을 나타냅니다.(Flow Status 출처 : ISO 15765-3)
그림에도, 0/1/2라고 나와 있는데
준비가 되었다는 의미
입니다.대기
하라는 것이며중단
하라는 의미가 됩니다.BlockSize(BS)
그리고 D1에 해당하는 BlockSize는 1byte기 때문에 0x00~0xFF(0~255)으로 표시 됩니다.
이때 두 가지로 위 값을 나눌 수 있는데
0x00(0)과 0x01~0xFF(1~255)입니다.
0x00(0)의 경우 더 이상 분할 메시지에서 FlowControl을 받지 않겠다는 의미로, 자동 설정
이라고 볼 수 있습니다. 앞으로 받는 메시지는 ConsecutiveFrame만 받는다는 의미가 됩니다.
0x01~0xFF(1~255)의 경우 반대로 수동 설정
에 해당한다고 생각할 수 있습니다. 앞으로 나오는 ConsecutiveFrame의 개수가 1~255번 이상이 되면 FlowControl이 다시 등장해야 합니다.
저의 경우, 자동 설정으로 쓰이는 경우가 대다수이지만, 더 낮은 성능의 MCU인 경우 아래와 같은 수동 설정을 통해 데이터 송수신 크기 및 시간을 조절할 수 있을 것이라 생각됩니다.
STMin
STmin은 낯선 용어를 제외하면 크게 어려울 게 없는 내용입니다.
Minimum Separation Time이라는 의미로, ConsecutiveFrame사이에 허용하는 최소 시간 간격입니다.
이는 지연 또는 문제가 될 시 이를 카운트하는 용도로 사용합니다.
값의 범위는 아래와 같이 나뉩니다.
microsecond은 0.000001
millisecond는 0.001 이다.
이제 정보는 모두 이해했으니 다시 한 번 FlowControl을 분석해 보자
high nibble
이 0011b(3)이기 때문에 FlowControllow nibble
이 0000b(0)이기 때문에 ConsecutiveFrame을 받을 준비 완료5
로 ms 단위 이며,위와 같은 정보를 항상 분석해야 하는 것은 아니다. 하지만, 처음에 이해를 위해서는 이를 상세하게 쪼개 볼 필요가 있다.
특히, 다시 한 번 말하지만, 해당 값을 직접 보내야 한다는 점이 굉장히 구현에서는 주요하다.
Header
CANID : 0x02(2) DLC : 0x08(8)
Data
| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 |
| 0x21(33) | seed5 | seed6 | seed7 | seed8 | 0xFF(255) | 0xFF | 0xFF |
ConsecutiveFrame에서 핵심이 되는 요소는 lownibble의 SequenceNumber다.
특히 여기서 주요한 요소는 아래와 같다.
0x0(0)
이 된다.이제 모든 Seed를 받았다.
그리고 내부 로직으로 구현되어 있는 key를 만들고 해당 8byte의 키 데이터를 보내야 한다.
이번에도 데이터가 8개가 되었기 때문에 MultiFrame Protocol을 활용해야 한다.
이번에는 FirstFrame(요청)-FlowControl(회신)-ConsecutiveFrame(요청) 순이며 완료되면
SingleFrame의 결과 응답이 이루어진다.
Header
CANID : 0x01(1) DLC : 0x08(8)
Data
| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 |
| 0x10(16) | 0x0A(10) | 0x27(39) | 0x02(2) | key1 | key2 | key3 | key4 |
Header
CANID : 0x02(2) DLC : 0x08(8)
Data
| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 |
| 0x30(48) | 0x00(0) | 0x05(5) | 0xFF(255) | 0xFF | 0xFF | 0xFF | 0xFF |
Header
CANID : 0x01(1) DLC : 0x08(8)
Data
| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 |
| 0x21(33) | key5 | key6 | key7 | key8 | 0xFF(255) | 0xFF | 0xFF |
이제 마지막 내용의 해석을 해 보자. 데이터의 아래에 설명을 상술 하겠다.
Positive
Header
CANID : 0x02(2) DLC : 0x08(8)
Data
| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 |
| 0x02(2) | 0x67(103) | 0x02(2) | 0xFF(255) | 0xFF | 0xFF | 0xFF | 0xFF |
Positive는 최초에 정리한 싱글프레임의 내용과 차이가 없다.0x40(64)를 더한 값과 SubfunctionID가 담기는 것을 확인할 수 있다.
Negative
Header
CANID : 0x02(2) DLC : 0x08(8)
Data
| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 |
| 0x03(3) | 0x7F(127) | 0x27(39) | 0x10(16) | 0xFF(255) | 0xFF | 0xFF | 0xFF |
Negative 역시 앞서 살펴 본 바와 큰 차이가 없다. 다만, 0x10 generalReject를 간단하게 소개해 보자면, 해당 상태의 경우 개발할 때 하드웨어적인 문제인 경우가 많았습니다. 연결 상태의 불량, 허브에 모니터링 툴을 연결했을 때, 부족한 전압에 의해 발생하는 문제 등, 하드웨어 점검을 통해서 문제 해결을 먼저 시도해 보시기를 추천 드립니다.
임베디드는 항상 하드웨어 연결과의 사투...
이와 같은 과정을 거치면 securityaccess가 정상적으로 되는 것을 확인할 수 있으며
이 글을 통해 MultiFrame의 이해에 조금이라도 도움이 되면 좋겠습니다.
다시 한 번 정리하자면, SingleFrame은 SingleFrame만으로 완전하지만,
MultiFrame은 FirstFrame-FlowControl-ConsecutiveFrame의 Sequence 전체 과정을 담고 있는 내용입니다.
긴 글 감사합니다.