-
진단 통신으로 제어기 확인하기application 2025. 1. 26. 13:10
시작하기 전에
CAN 통신 데이터를 수집하고, 수집한 데이터에서 메시지 아이디들을 찾고, 찾은 메시지 아이디들을 dbc에서 찾는 방식으로 차에 어떤 제어기들이 있는 지 확인할 수 있다. (dbc에서 m_id들을 찾기 :: hsl's tsmaster 사용기) 이는 차량 형상 관리를 위한 최소한의 점검이다. 이 점검을 통해서 차에 있는 제이기들과 빠진 제어기들을 확인할 수 있다. 제어기들의 하드웨어와 소프트웨어가 설계 사양에 맞는지까지 확인하기는 쉽지 않다. 필요가 있으면 발명이 있다.
UDS 서비스 중에 Read Data By Identifier라는 것이 있다. 자동차사들은 진단 통신 사양서에 이 서비스를 명시한다. 그래서 제어기의 하드웨어, 소프트웨어 정보를 읽도록 한다. Read Data By Identifier 서비스로 읽은 하드웨어와 소프트웨어 정보를 기준이 되는 설계 사양과 비교하여 차량 형상을 점검할 수 있다.
진단 통신으로 차에 있는 제어기들의 하드웨어와 소프트웨어 정보를 구하고 설계 기순 사양과 비교하여 형상 점검을 할 수 있다. 개요
- Read Data By Identifier 요청 전송
- Read ECU ID 요청 메시지
- 0x700부터 0x7FF까지 스캐닝
- Read Data By Identifier 응답 처리
- 진단 통신 요청과 응답 m_id 짝 찾기
- 제어기들의 ECU ID를 읽어 차량 형상 확인하기
Read Data By Identifier 요청 전송
Read ECU ID 요청 메시지
- UDS 메시지의 구조는 아래 그림과 같다.
UDS 진단 "요청" 메시지의 구조 - m_id는 제어기별로 지정되어 있다.
- 나는 제어기별 m_id 정보가 없다. (자동차사와 제어기 개발사는 당연히 갖고 있다.)
- 나는 진단 통신에는 0x700부터 0x7FF까지의 m_id가 사용된다는 것을 안다.
- m_id를 0x700부터 0x7FF까지 1씩 변경하면서 Read Data By Identifier 요청을 보내고 응답을 기다리는 방식으로 m_id를 찾을 것이다. 이를 스캐닝이라고 부르겠다.
- UDS 표준에 따르면 Read Data By Identifier의 SID(Service IDentifier)는 0x22이다.
- Sub-function Byte는 옵션이며, Read Data By Identifier 서비스에는 sub-function이 없다 (없을 것이다).
- DID(Data IDentifier. Request Data Parameters)는 2 byte이다.
- 표준 DID가 있으나 OEM별로 고유의 DID를 사용하는 경우가 더 많은 것 같다. (자동차 산업의 사람들이 표준을 무시/무지해서가 아니라 표준이 나중에 생겨서 그런 것이라고 생각한다.)
- 현대자동차 베뉴의 경우, ECU ID의 DID는 0xF1 0x00 이다. (실험으로 찾았다. 아래를 보시라.)
- "Read Data By Identifier DID = ECU ID"는 너무 길다. Read ECU ID라고 부르겠다.
- Read ECU ID 서비스 요청은 "0x22 0xF1 0x00" 3 바이트가 된다.
- UDS 메시지 구조에서 PCI(Protocol Control Info) 자리에 서비스 요청의 길이를 표시하는 3이 들어간다. 이를 추가하면 CAN 메시지의 데이터는 "0x03 0x22 0xF1 0x00"이 된다.
- UDS on CAN에서 메시지의 길이는 8 바이트를 사용한다. Read ECU ID의 경우 4 바이트가 남는다. Padding해야 한다.
- Padding 바이트로 0xAA를 사용한다.
- 참고: 0xAA는 바이너리로 1과 0이 교차한다. b1010101010101010
- Padding 바이트로 0xAA를 사용한다.
- Read ECU ID 요청 CAN 메시지의 데이터는 "0x03 0x22 0xF1 0x11 0xAA 0xAA 0xAA 0xAA"가 된다.
0x700부터 0x7FF까지 스캐닝
- m_id를 바꿔가며 Read ECU ID 요청을 주기적으로 전송하여 스캐닝을 하는 미니프로그램을 작성한다.
- 주기적으로 전송하기 위해서 timer_diag_req를 정의한다.
timer_diag_req의 정의. 500msec 마다 진단 메시지를 전송하도록 설정하였다. - 타이머가 트리거할 때마다 m_id를 +1 만큼 증가하고 Read ECU ID 요청을 전송하도록 On Timer에 diag_req 함수를 정의한다.
On Timer에 timer_diag_req가 트리거할 때마다 실행할 함수인 on_timer_diag_req()를 정의한다. req에 저장해둔 진단 메시지를 전송한다. - on_timer_diag_req() 함수는 아래 코드와 같다. 아래의 기능들을 수행한다.
- m_id (m_id_diag_req)를 1 증가한다.
- req에 미리 담아둔 Read ECU ID 요청 ("0x03 0x22 0xF1 0x11 0xAA 0xAA 0xAA") 메시지를 전송한다. CAN과 CAN-FD의 경우 메시지 설정이 조금 다르다. 이를 고려하였다.
- m_id가 0x7FF가 되면 timer_diag_req를 정지시킨다.
def on_timer_diag_req() -> None: global k_is_can_fd global m_id_diag_req global req global is_timer_diag_req_on global is_timer_tester_present_on m_id_diag_req += 1 # diag_req 메시지를 만든다. if k_is_can_fd: # CAN-FD 메시지 msg_diag_req = RawCAN(m_id_diag_req, 8, CH1, 0, req, 0x306) else: # CAN 메시지 msg_diag_req = RawCAN(m_id_diag_req, 8, CH1, 0, req) # diag_req 메시지를 전송한다. if com.transmit_can_async(msg_diag_req) == 0: # app.log_text(f'{msg_diag_req}', lvlOK) # 출력량이 너무 많아서 디버깅할 때만 사용한다. # 트레이스 윈도에서 전송 메시지를 확인할 수 있다. pass # 모든 m_id를 처리한 후에 timer_diag_req를 멈춘다. if m_id_diag_req >= k_m_id_scan_stop: timer_diag_req.Stop() is_timer_diag_req_on = False app.log_text('timer_diag_req stopped', lvlOK) timer_tester_present.Stop() is_timer_tester_present_on = False app.log_text('timer_tester_present stopped', lvlOK) m_id_diag_req = k_m_id_scan_start
- 미니프로그램을 시작하면 timer_diag_req는 정지 상태이다. 이 타이머를 시작시켜야 한다. 그리고 req에 Read ECU ID 요청도 넣어줘야 한다. 키보드에서 Ctrl+1(Ctrl키를 누른 상태에서 숫자 1)을 누르면 이 작업이 실행되도록 한다.
- On Shortcut에 마우스 우클릭하여 아래 그림과 같이 팝-업 메뉴를 열고, Shortcut을 클릭한 후에 Ctrl키를 누른 상태에서 숫자 1을 누르면 "Ctrl+1"이 입력된다. Ctrl+1을 입력했을 때 수행할 함수의 이름을 입력한다. 그리고 코드 창에서 함수를 정의한다.
키보드로 Ctrl-1을 입력하면 실행할 함수를 정의한다. 키보드에서 Ctrl+1을 입력하면, req에 Read ECU ID을 저장하고, m_id에 시작값인 0x700을 저장하고, timer_diag_req를 시작한다. timer_diag_req 주기마다 m_id를 증가하면서 Read ECU ID 요청을 전송한다. - 미니프로그램의 전체 코드를 첨부한다.
mobis_esc_ecu_id_rev_eng.py0.03MBRead Data By Identifier 응답 처리
- Read ECU ID의 응답은 길어서 여러 메시지들에 분리되어 전송되기도 한다. 이 경우 TP (Transport Protocol)가 필요하다. UDS 진단 통신 (1 / t.b.d.) - Transport Protocol, UDS의 개요 :: hsl's tsmaster 사용기
- PC는 제어기에 비해 매우 빠르고 메모리도 매우 크다. (가격 차이만큼 차이가 나는 것 같다.) 제어기가 여러 메시지들로 응답을 나누어 보낸다고 할 때, PC는 제어기에게 Block Size(BS)나 Wait Time (Stmin)에 제한 없이 보내라는 FC (Flow Control) 메시지를 한 번 보내는 것만으로 Read ECU ID 요청에 충분한 TP가 만들어진다. (TSMaster의 UDS 기능에 완전한 TP가 구현되어 있다. TSMaster의 UDS 기능은 고가이다. 그래서 이것을 poor man's TP라고 하겠다. ;-)
- 수신 메시지들에서 진단 메시지들 (0x700 ~ 0x7FF)을 추려서 점검해야 한다. On CAN Rx에 any_message를 대상으로 수신 메시지를 처리하는 함수 on_canfd_rx_any_message( )를 정의한다. CAN-FD뿐 아니라 CAN도 대상으로 하기 위해 CAN FD (Incl. Classical CAN) 체크 박스를 체크한다.
def on_canfd_rx_any_message(ACAN: RawCAN) -> None: # 진단 통신 m_id 범위 # 0x700 부터 0x7FF 까지 # k_m_id_diag_lowest = 0x700 # k_m_id_diag_highest = 0x7FF if ((k_m_id_diag_lowest <= ACAN.id) and (ACAN.id <= k_m_id_diag_highest)): # 실제 처리는 on_can_dia_resp()에서 이뤄진다. on_can_diag_resp(ACAN)
- on_can_diag_resp( ) 함수의 코드는 아래와 같다.
- 수신 메시지 (ACAN)의 첫 바이트 (ACAN.data[0])에서 PCI (Protocol Control Info)를 추출한다. PCI가 0x10인 경우, 응답을 여러 메시지들로 분리하여 전송할 것이며 이 메시지가 첫 메시지(First Frame)라는 의미이다. First Frame을 수신한 경우, FC (Flow Control) 메시지를 전송해야 한다.
- FC 메시지는 "0x30 0x00 0x00 0xAA 0xAA 0xAA 0xAA 0xAA" 이다. 첫 번째 0x00은 제어기가 보내는 메시지의 수에 (Block Size)에 제한이 없음을 표시한다. 두 번째 0x00은 제어기가 보내는 메시지들 사이의 전송 간격에 제한이 없음을 표시한다.
def on_can_diag_resp(ACAN: RawCAN) -> None: # TP와 무관한 부분의 코드를 삭제했다. # TP로 전송된 분해된 response를 조립한다. pci = ACAN.data[0] & 0xF0 if pci == 0x00: # single frame # single frame을 처리하는 코드를 삭제했다. elif pci == 0x10: # first frame # 같은 m_id의 다음 응답 처리를 위해 초기화 한다. # counter는 초기화하지 않는다. 응답 횟수를 추적하기 위해서 # diag_resp[m_id_hex] = {} # diag_resp[m_id_hex]['length'] = 0 # diag_resp[m_id_hex]['counter'] = 1 diag_resp[m_id_hex]['s_id'] = 0x00 diag_resp[m_id_hex]['assembled'] = [] diag_resp[m_id_hex]['hex'] = [] diag_resp[m_id_hex]['length'] = (ACAN.data[0] & 0x0F) * 256 + ACAN.data[1] diag_resp[m_id_hex]['assembled'].extend(ACAN.data[2:8]) # NOTE # TSMaster의 UDS 기능을 사용하지 않는 경우 # flow control 메시지를 전송해야 한다. # k_flow_control = [0x30, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA] # is can_fd? if k_is_can_fd: # CAN-FD 메시지 msg_diag_req = RawCAN(m_id_diag_req, 8, CH1, 0, k_flow_control, 0x306) else: # CAN 메시지 msg_diag_req = RawCAN(m_id_diag_req, 8, CH1, 0, k_flow_control) if com.transmit_can_async(msg_diag_req) == 0: app.log_text(f'Tx fc = {msg_diag_req}', lvlOK) # 이하 코드를 삭제했다.
- 진단기(이 경우 미니프로그램)는 제어기가 여러 메시지들로 분리해서 보낸 응답을 모아서(assemble)해서 전체 응답을 구성한다.
- 아래 예는 ESC에서 받은 응답과 해석이다.
# 조립된 제어기의 Read ECU ID 요청에 대한 응답 ['62', 'f1', '00', '4e', '58', '34', '20', '45', '53', '43', '20', '03', '20', '41', '30', '31', '20', '23', '09', '03', '20', '35', '38', '39', '31', '30', '2d', '4e', '38', '37', '30', '30', 'aa', 'aa'] # ASCII로 표현한 Read ECU ID의 응답 'NX4 ESC \x03 A01 #\t\x03 58910-N8700'
Read ECU ID 응답 메시지 처리 과정. 응답이 여러 메시지들로 분리되어 전송되는 경우 First Frame에 대응하는 Flow Control 메시지를 전송해야 한다. 그리고 응답들을 조립해야 한다. 진단 통신 요청과 응답 m_id 짝 찾기
- 진단 스펙이 있으면 안 해도 될 일이다.
- 위에서 설명한대로 m_id를 0x700에서 0x7FF까지 변경하며 진단 요청을 전송하는 방법으로 스캐닝을 한다.
- 진단 요청 후 500msec 동안 응답을 기다린다.
- 500msec는 실험을 통해서 정한 충분히 긴 시간이다. 대부분의 제어기들은 진단 요청 후 몇 십 msec 이내에 응답을 한다.
- 펑셔널 진단 요청의 경우 "대부분"의 (요청에 대해 응답 하지 않는 제어기들이 있을 수 있다. 그래서 "전체"가 아닐 수도 있다.) 제어기들이 응답을 한다. 이를 고려하여 응답 대기 시간을 500msec로 길게 잡았다.
- 응답 대기 시간 안에 응답이 있는 경우, 요청 m_id와 응답 m_id를 한 쌍으로 판정한다.
- 응답 대기 시간 안에 응답이 없는 경우, 요청 m_id는 사용되지 않는 것으로 판정한다.
- TSMaster 미니프로그램을 작성할 수도 있지만 스캐닝을 하면서 측정한 CAN 버스 데이터를 blf 파일로 저장하고, 파일을 분석하는 방법으로 m_id 짝을 찾았다. 진단 요청과 응답 m_id 짝 찾기 :: hsl's tsmaster 사용기
- 13쌍을 찾았다. 아래와 같다.
진단 요청/ 응답 m_id 짝 7C4/ 7CC 770/ 778 7F1/ 7F9 7D4/ 7DC 796/ 79E 7D1/ 7D9 7E0/ 7E8 7C3/ 7CB 7B3/ 7BB 7C6/ 7CE 7A0/ 7A8 7D2/ 7DA 7E1/ 7E9
- 펑셔널 진단 요청(0x7DF)에 응답한 제어기는 총 10개로 m_id들은 아래와 같다.
펑셔널 요청에 응답한 m_id 778 79E 7A8 7CB 7CC 7D9 7DA 7DC 7E8 7E9
제어기들의 ECU ID를 읽어 차량 형상 확인하기
- Read ECU ID 요청으로 스캐닝한 응답을 정리하면 차량에 장착된 제어기들의 종류와 버전을 알 수 있(어야한)다.
- 진단 응답 해석하기 :: hsl's tsmaster 사용기
- 아래는 Read ECU ID 요청에 대한 현대자동차 베뉴 FATC 제어기의 실제 응답이다. (차종을 명시하는 이유는 요청과 응답이 차종별로 다를 수 있기 때문이다.)
'QX 97250-K2300HEATER-CONTROL FATC V1-02-903-00\x00\x00\x00'
- QX는 베뉴를 나타내는 차종 코드일 것이다.
- 97250-K2300은 아마도 제어기의 품번일 것이다.
- HEATER-CONTROL에서 무슨 제어기인지 알 수 있다.
- FATC는 제어기 이름일 것이다. 구글링으로 Full Automatic Temperature Control 이라고 찾았다.
- V1-02-903-00는 제어기의 버전 정보일 것이다. 일부는 하드웨어 다른 일부는 소프트웨어 버전을 나타낼 수도 있다.
- 0x00 0x00 0x00은 패딩 바이트로 짐작한다.
- ECU ID 관리가 잘 이뤄진다면, 전체 제어기들의 ECU ID들을 읽어서 형상 관리를 할 수 있을 것이다.
- 모든 제어기들이 FATC처럼 명확하게 응답하면 좋은데, 현실은 그렇지 않다. 실제 응답은 아래와 같다.
0x778 ecu_id = '19' 0x79e ecu_id = '0.3\x00\x00\x00\x00\x00\x00\x00\x00\x00' 0x7a8 ecu_id = '220' 0x7bb ecu_id = 'QX 97250-K2300HEATER-CONTROL FATC V1-02-903-00\x00\x00\x00' 0x7cc ecu_id = 'QX MFC AT KOR LHD 1.00 1.01 99211-K2100 210329' 0x7ce ecu_id = '941\x00' 0x7d9 ecu_id = 'QX ESC \x05 100\x19\x04\x01 58910-K2500' 0x7dc ecu_id = 'QX MDPS C 1.00 1.06 56340-K2000 0B06'
- "진단 요청과 응답 m_id 짝 찾기" 에서 찾은 응답 m_id가 13개이다. Read ECU ID에 응답한 제어기들은 8개이다. 5개는 응답을 하지 않았다. 응답을 한 제어기들도 응답 양식에 맞춰 응답한 것인지 불분명하다. (양산차인데 이래도 되는거구나 ^^.)
- 모든 제어기들이 ECU ID 요청에 응답한다면, 이 응답들과 설계 기준에 따른 정답들을 비교하여 형상 관리에 이용할 수 있다.
- (이왕 ECU ID에 응답을 하도록 한다면, FATC, MFC, ESC, MDPS 처럼 사람이 읽어서 이해할 수 있도록 하는 것도 나쁘지 않을 것 같다.)
결론
- 진단 통신을 통해서 각 제어기들의 ECU ID를 읽을 수 있다.
- 제어기들의 ECU ID를 이용하여 실차의 형상 정보를 수집할 수 있다.
- 확인된 실차의 형상 정보와 설계 기준 형상을 비교하여 형상 관리를 할 수 있다.
차량 형상 점검 :: hsl's tsmaster 사용기
'application' 카테고리의 다른 글
진단 응답 해석하기 (2) 2025.01.26 진단 요청과 응답 m_id 짝 찾기 (0) 2025.01.26 dbc에서 m_id들을 찾기 (0) 2025.01.26 차량 형상 점검 (1) 2025.01.19 주행 중인 도로의 경사도 구하기 1/t.b.d. (2) 2025.01.15 - Read Data By Identifier 요청 전송