ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • UDS 진단 통신 (4 / t.b.d.) - ReadDTC 응답 해석을 위 미니프로그램
    TSMaster 2024. 10. 18. 17:10

    시작하기 전에

    UDS 진단 통신을 위해 ‘Diagnostic Basic Config’ 화면에서 진단 서비스 요청/응답 메시지를 설정하는 방법과 ‘Diagnostic Console’ 화면에서 진단 서비스 실행 및 응답 메시지 해석 방법 설명( UDS 진단 통신 (3 / t.b.d.) - 진단 요청/ 응답 메시지 설정 :: hsl's blog)에 이어지는 설명이다.

    지난 설명에서 DiagnosticSessionControl을 예로 들었다. 이 진단 서비스 메시지는 요청과 응답 메시지 모두 각각 길이가 일정하다. 이번에는 ReadAllDTC 서비스의 요청/응답 메시지 설정 방법을 설명하며 길이가 고정되지 않은 메시지를 처리하는 방법을 설명한다.

     

    개요

    • ReadAllDTC 요청 메시지 설정.
      • DiagnosticSessionControl 요청 메시지 설정과 방법 측면에서 동일하다.
    • ReadAllDTC 응답 메시지 설정.
      • DTC 수가 일정하지 않기에 응답 메시지의 길이도 고정이 아니다.
    • ReadAllDTC 응답 메시지 해석
      • 응답 메시지의 정보를 DTC 별로 분리하고, 분리된 DTC의 의미를 화면에 표시한다.
      • 미니프로그램을 작성한다.

     

    ReadDTC 요청 메시지 설정

    • ReadDTC의 서비스 요청 메시지의 구조는 아래와 같다.
    SID (Service ID) ReportType DTCStatusMask
    $19 $02 $09

     

    ReadAllDTC 응답 메시지 설정

    • 아래 그림의 빨간색 테두리 안의 항목들을 설정한다.

    ReadAllDTC 응답 메시지의 설정 항목들

    • (아래 그림) 서비스 요청 메시지 ‘19 02’의 긍정 응답 메시지는 ’59 02’로 시작한다. 기본 설정이 이미 되어있다.
    • ‘59 02’ 다음에 XX들로 표시된 부분들이 DTC 정보이다.
    • XX들 중에 첫 XX는 요청 메시지의 DTCStatusMask이다. 요청 메시지에서는 $09로 고정이지만 응답의 범용성을 위해 Parameters에 첫번째 파라미터로 1 바이트로 설정한다.

    ReadAllDTC 응답 메시지의 포맷. 기본 설정이 되어있다.
    요청 메시지 19 02 XX에서 XX가 응답 메시지 59 02 XX XX ... 에서 첫 XX에 해당한다. DTCStatusMask라고 한다. 다양한 경우에 사용할 수 있도록 고정값이 아닌 파라미터로 한다.

    • 나머지 XX들은 DTC 수에 따라 데이터 길이가 달라진다. Parameters에서 두번째 파라미터로 한다.
    • TSMaster의 다른 기능들에서 사용하기 위해 Value Type을 SystemVar로 한다. 그렇지 않는다면, Hex Array로 했을 것이다.

    DTCAndStatusRecord를 TSMaster의 다른 기능들에서 사용할 수 있도록 SystemVar 타입으로 한다.

    • Length(bits)를 544 비트 (71 바이트)로 한다.
      • 길이 설정에는 약간의 반복 작업이 필요했다.
      • 우선 Length를 임의의 값으로 설정한다. 예, 400 비트 (8의 배수. 50 바이트)
      • Diagnostic Console에서 ReadAllDTC를 실행한 후 응답의 길이를 확인한다.
      • 내 실험 대상인 VDC 제어기는 전원, 이그니션, CAN만 연결되어 있어서 DTC가 굉장히 많다. DTC가 많아 제어기는 최대 응답 메시지 길이로 응답 한 것 같다. 응답 메시지 중 DTCAndStatusRecord에 해당하는 데이터를 확인하니 544 비트였다. 
    • Response Value 칸을 선택하면 변수를 선택할 수 있는 + 버튼이 생긴다. 버튼을 클릭하면 Select System Variables 창이 열린다.

    SystemVar로 선택하면 Response Value에서 시스템 변수를 선택할 수 있다.

     

    시스템 변수 선택 창

    • 아래 그림과 같이 calc.DTC_records 변수를 만든다. Type UInt8 Array로 한다. Array의 크기는 위 Length를 설정이 적용된다. (시스템 변수를 만드는 방법은  CAN 신호들로 실시간 연산하기 :: hsl's blog (tistory.com)에서 “yaw_rate_ws 사용자 변수 만들기” 부분을 참조해 주십시오.)

    ReadAllDTC의 응답에서 DTC들의 정보를 담을 시스템 변수 DTC_records를 선언한다.

    • Diagnostic Console 창에서 ReadAllDTC를 실행하면 아래 그림과 같은 실행 결과를 얻을 수 있다. ReadAllDTC 요청에 대한 제어기의 응답을 볼 수 있다.

    • DTCAndStatusRecord 파라미터를 응답을 해석해야 한다.  이 파라미터의 값은 아래 구조가 DTC 만큼 반복되는 바이트 어레이이다. (아래 구조를 DTC Record라고 하나보다.)
      • DTCHighByte: 1 byte
      • DTCMiddleByte: 1 byte
      • DTCLowByte: 1 byte
      • statusOfDTC: 1 byte
    • DTCAndStatusRecord를 해석하는 미니프로그램을 작성한다.

     

    DTCAndStatusRecord 해석

    • 파이썬으로 미니프로그램을 작성한다.
    • 나는 미니프로그램에서 어레이형 시스템 변수를 읽고 쓰는 방법을 아직 모른다. 그래서 아래와 같이 하려고 한다.
      • CAN 메시지들 중에서 VDC의 진단 응답 메시지(0x7D9)를 읽는다.
      • 첫 바이트를 보고 메시지의 종류(SF(Single Frame), FF(First Frame), FC(Flow Control), CF(Consecutive Frame))를 구분한다.
      • 메시지 종류에 따라 메시지에 탑재된 데이터의 위치와 크기가 다르다. 상위 두 바이트를 보고 전체 응답 데이터의 길이를 확인한다.
      • 메시지들에서 TP 관련 신호들을 제외한 데이터들을 추출한다. 데이터들을 연결하여 하나의 긴 메시지로 조립한다. (제어기가 TP로 하나의 긴 메시지를 분리해서 여러 메시지들로 나눠서 보낸 것이니까, 나는 반대로 한다.)
      • 전체 데이터를 모두 받았으면 (전체 데이터의 길이를 알기 때문에 쉽게 확인할 수 있다.) 긴 메시지에서 DTCAndStatusRecord를 찾고, 이를 DTC Record들로 분리한다. 
      • 각 DTC를 ISO 15031-6 (ISO-15031-6-2005.pdf (iteh.ai))에 따라 변환한다.
      • DTC표 (현대기술정보 웹 사이트에서 베뉴 VDC의 DTC와 설명을 찾아 xlsx로 만들기 :: hsl's blog (tistory.com))에서 DTC를 찾아서 설명과 함께 출력한다.
    • 코드를 작성한 방법과 이유를 설명한다.

     

    글로벌 변수 선언

    • 코드의 여러 함수들에서 사용하는 변수들을 글로벌 변수로 선언한다.
    • Python Code Editor에서 Global Definition을 선택한다.
    • 오른쪽 코드 창에 코드를 입력한다.

    Global Definition에서 모듈을 임포트하고, 글로벌 변수를 선언한다.

    # import
    '''
    코드와 코드 설명이 있는 DTC 코드표를 xlsx에 표 형대로 사전에 저장해 두었다. 
    제어기에서 받은 DTC를 표에서 찾아 설명을 화면에 출력한다.
    표를 다루는데 Pandas의 데이터프레임을 사용하기 위해서 pandas를 임포트한다. 
    파일 경로를 다룰 때 편리하도록 pathlib 모듈을 임포트한다.
    '''
    
    from pathlib import Path
    import pandas as pd
    
    
    # constant
    '''
    코드를 읽기 쉽게 Service ID를 상수로 정의해 둔다.
    실제로는 값을 변경할 수 있는 변수이다. 변수 이름 앞에 k_ 를 붙여 상수로 취급한다.
    '''
    
    k_response_service_id_readDTC = 0x19 + 0x40
    k_response_service_id_readECUId = 0x22 + 0x40
    
    
    # global
    
    # 현재 service id를 저장한다.
    service_id = 0x00
    
    # 응답이 Transport Protocol로 분할되어 전송되는 경우, 분할된 응답을 조립하기 위한 변수들이다.
    
    # 분할된 메시지를 카운트한다.
    counter_response = 0
    
    # 응답 메시지들을 연결한 값을 저장한다.
    response_assembled = []
    
    # 제어기가 응답한 메시지의 길이를 저장한다. 
    # 응답 메시지들을 연결한 길이가 제어기가 응답한 메시지의 길이와 같거나 크면 조립이 완료된 것이다.
    length_response = 0
    
    
    # 현대기술정보 홈페이지에서 복붙한 DTC와 DTC 설명이 있는xlsx 파일을 읽는다.
    
    # xlsx 파일 경로
    xlsx_dtc_table = Path('C:\\data\\tosun\\projects\\uds_esc_mobis\\MiniProgram\\PySrc\\베뉴_VDC_DTC_현대기술정보.xlsx')
    
    # DTC표를 Pandas 데이터프레임으로 읽는다.
    df_dtc = pd.read_excel(xlsx_dtc_table, index_col='DTC')

     

    함수 선언

    • 반복적으로 사용되는 코드를 함수로 선언한다.
    • 글로벌 변수 선언과 같은 방법이다. 
      • Python Code Editor에서 Global Definition을 선택한다.
      • 오른쪽 코드 창에 코드를 입력한다.

    Global Definition에서 함수를 선언한다.

    • xlsx에 저장된 DTC표 아래 그림과 같다.

    xlsx에 저장된 DTC표. DTC와 Description으로 구성되어 있다.

    • get_dtc_description(dtc) 함수는 dtc에 해당하는 description을 반환한다.
    # function
    def get_dtc_description(dtc):
        '''
        입력으로 받은 DTC에 대한 설명인 description을 반환한다.
        '''
    
        global df_dtc
    
        try:
            description = df_dtc.loc[dtc, 'Description']
        except KeyError:
            description = 'DTC를 찾을 수 없습니다.'
    
        return description
    • DTC표의 DTC를 보면 C120001 처럼 알파벳으로 시작한다. ISO 표준에 따른 DTC이다. hex 코드에 ISO 표준 형식의 DTC로 변환이 필요하다. convert_dtc(dtc_bytes) 함수가 이 기능을 한다.
    def convert_dtc(dtc_bytes):
        '''
        제어기가 응답한 결함 코드를 표준 코드로 변환한다.
        '''
        
        # DTC 타입 결정을 위한 딕셔너리
        dtc_type = {0: 'P', 1: 'C', 2: 'B', 3: 'U'}
        
        # 첫 번째 바이트의 상위 2비트를 사용하여 DTC 타입 결정
        first_char = dtc_type[(dtc_bytes[0] >> 6) & 0x3]
        
        # 첫 번째 바이트의 하위 6비트와 두 번째 바이트를 결합하여 중간 부분 생성
        middle_part = ((dtc_bytes[0] & 0x3F) << 8) | dtc_bytes[1]
        
        # 세 번째 바이트를 마지막 부분으로 사용
        last_part = dtc_bytes[2]
        
        # DTC 문자열 생성
        # claude ai가 짠 원래 코드
        # 숫자 부분을 10진수로 표시한다.
        dtc_string = f"{first_char}{middle_part:04d}{last_part:02d}"
    
    	# 숫자 부분을 16진수로 표시해야 한다.
        dtc_string = f"{first_char}{middle_part:04x}{last_part:02x}"
        
        return dtc_string
    • 나는 위 코드를 claude.ai에게 문의하여 작성했다. (TSMaster가 C, Python 같은 ai의 코딩 완성도가 높은 프로그래밍 언어를 지원하여 좋다.) 내가 직접 작성했다면 파이썬 문법을 검색하며 코딩하느라고 15 ~ 30분은 걸렸을 것 같다. 2분도 안 걸린 것 같다.

     

    esc_gst (0x7D9) 응답 메시지 처리

    • VDC의 진단 응답 메시지(esc_gst: 0x7D9)를 수신할 때마다 DTC 관련 데이터를 추출하고 여러 메시지들로 분할 전송되는 경우 데이터들을 조립을 해야 한다.
    • On CAN Rx에 esc_gst 메시지 수신 이벤트를 추가하고 데이터 처리 방법을 코딩한다.
      • TP로 받은 분해된 응답 메시지들을 조립한다.
      • 조립된 응답 메시지를 DTC 별로 분리한다.
      • DTC에 해당하는 설명을 찾아, DTC와 설명을 시스템 메시지 창에 출력한다.

    esc_gst 메시지(0x7D9)를 수신할 때마다 실행할 함수를 On CAN Rx에 정의한다.

    def on_canfd_rx_esc_gst(ACAN: RawCAN) -> None:
        '''
        Transport Protocol(TP)로 분할된 메시지들을 받아서 조립하려면 이전 함수 호출 시 데이터들을 기억해야 한다.
        이를 위해 전역 변수를 사용한다. 
        '''
        global counter_response
        global response_assembled
        global length_response
        global service_id
           
        # TP로 분할된 response 메시지들을 카운트한다.
        counter_response += 1
    
        # response의 8 바이트를 hex로 표시한다.
        # 왜 8바이트 인가? 
        # 8 바이트는 클래식 CAN의 최대 메시지 길이이다.  
        # 모비스 VDC는 CAN-FD로 통신한다. 그래서
        # 진단 통신 response 메시지의 길이는 64 바이트이다. 
        # 하지만 처음 8 바이트 외에는 0x00으로 채우고 사용하지 않는다.  
        
        # 시스템 메시지 창에는 출력할 때, app.log_text()를 사용한다.
        # app.log_text는 스트링을 인자로 받는다.
        # response를 hex 스트링으로 만들고 출력한다.
        resp_hex = []
        for i in range(8):
            resp_hex.append(f'{ACAN.data[i]:02x}')
    	app.log_text(f'ESC_GST response {counter_response:2}: {resp_hex}', lvlOK)
        
    
        # TP로 전송된 분해된 response를 조립한다.
        pci = ACAN.data[0] & 0xF0   
        if pci == 0x00:        # single frame
            length_response = ACAN.data[0] & 0x0F
            response_assembled.extend(ACAN.data[1:8])
        elif pci == 0x10:      # first frame 
            length_response = (ACAN.data[0] & 0x0F) * 256 + ACAN.data[1]
            response_assembled.extend(ACAN.data[2:8])
        elif (pci == 0x20):    # consecutive frame
            response_assembled.extend(ACAN.data[1:8])
        else:                  # TP로 전송된 메시지가 아닌 일반 메시지 (ordinary message)
            length_response = 8
            response_assembled.extend(ACAN.data[:8]) 
        
    
        # TP로 전송된 메시지의 조립이 완료되면 출력한다. 
        if (length_response != 0) and (length_response <= len(response_assembled)):
            resp_hex = []
            for byte_response in response_assembled:
                resp_hex.append(f'{byte_response:02x}')
    
            app.log_text(f'{resp_hex}', lvlOK)
    
            service_id = response_assembled[0]
        else:
            return   
    
    
        # TP로 전송받아 조립을 완료한 response_assembled를 해석한다. 
    
        # TODO: 서비스별로 함수로 만든다.
    
        # Read DTC 서비스의 응답을 처리한다.
        if service_id == k_response_service_id_readDTC:    
            # 처음 3 바이트는 dtc 정보가 아니다.
            # 4 바이트씩 DTC 정보가 있다.
            n_dtc = (length_response - 3) // 4  
            
            for i_dtc in range(1, n_dtc + 1):
                dtc = response_assembled[3 + 4 * i_dtc: 3 + 4 * i_dtc + 3]
    
                dtc_hex = []
                for byte_dtc in dtc:
                    dtc_hex.append(f'{byte_dtc:02x}')
                dtc_hex_joined = ''.join(dtc_hex)
    
    	        # 패딩 바이트면 해석을 하지 않는다.
    	        if dtc_hex_joined == 'aaaaaa':
                    continue
    
    			# ISO 표준에 정해진 대로 DTC를 변환한다.
                dtc_converted = convert_dtc(dtc)
    
    			# DTC의 설명을 찾는다.
    			dtc_description = get_dtc_description(dtc_converted)
    
    			dtc_state = response_assembled[3 + 4 * i_dtc + 3] 
    
                app.log_text(f'{i_dtc = }', lvlOK) 
                app.log_text(f'dtc = {dtc_converted} (0x{dtc_hex_joined})', lvlOK) 
                app.log_text(f'{dtc_description}', lvlOK)             
                app.log_text(f'{dtc_state = :02x}', lvlOK) 
                app.log_text('---', lvlOK) 
    
    
        # read data by identifier 서비스의 응답을 처리한다.
        if service_id == k_response_service_id_readECUId:
    
            # response_assembled에서 data_id를 찾는다.    
            data_id_high = response_assembled[1] 
            data_id_low = response_assembled[2] 
    
            # data_id 별로 response 처리 방법이 다르다.
    
            # data_id == ECU Id인 경우
            if data_id_high == 0xF1 and data_id_low == 0x00: # data_id for ECU Id
                ecu_id = ''.join([chr(c) for c in response_assembled[3:] if c != 0xaa])
                app.log_text(f'{ecu_id = }', lvlOK) 
    
            # TODO: data_id == SW Version인 경우 처리 방법을 추가한다.
            
    	return

     

    esc_gst (0x7D1) 요청 메시지 처리

    • 데이터 처리가 완료되면 글로벌 변수들을 초기화 해야 한다.
    • 진단 서비스는 진단기가 제어기에 요청을 하면서 시작된다. 여기에 착안하여 진단기가 제어기에 보내는 메시지인 gst_esc (0x7D1) 메시지가 전송될 때마다 글로벌 변수들을 초기화 한다.
    • 단, gst_esc 메시지로 FC를 전송할 때도 있다. 이때는 글로벌 변수들을 초기화 하면 안 된다.

    gst_esc 메시지 (0x7D1)을 송신할 때마다 실행할 함수를 On CAN Tx에 정의한다.

    def on_canfd_tx_gst_esc(ACAN: RawCAN) -> None:
        '''
        데이터 처리가 완료되면 글로벌 변수들을 초기화 해야 한다.
        진단 서비스는 진단기가 제어기에 요청을 하면서 시작된다. 
        여기에 착안하여 진단기가 제어기에 보내는 메시지인 gst_esc (0x7D1) 메시지가 전송될 때마다 글로벌 변수들을 초기화 한다.
        단, gst_esc 메시지로 FC를 전송할 때도 있다. 이때는 글로벌 변수들을 초기화 하면 안 된다.
        '''
        global counter_response
        global length_response
        global response_assembled
        global service_id
        
    
        pci = ACAN.data[0] & 0xF0
    
        # flow control message가 아닌지 확인한다.    
        if pci != 0x30:
            counter_response = 0
            length_response = 0
            response_assembled = []
            service_id = 0x00
    
        return

     

    결과

    • 미니프로그램을 실행한 후, Diagnostic Console에서 ReadAllDTC를 실행하면, 아래와 System Messages 창에서 DTC와 DTC 설명을 볼 수 있다. 잘 작동한다.

    미니프로그램을 실행한 상태에서 ReadAllDTC를 실행하면 DTC 별로 설명이 출력된다.

     

    마무리

    • Basic Diagnostic Configuration에서 ReadAllDTC 진단 요청 메시지와 응답 메시지를 설정하고, 응답 메시지를 해석하여 DTC들을 추출하고, xlsx 파일의 DTC 표에서 DTC의 설명을 찾아서, 시스템 메시지 창에 DTC와 설명을 출력하는 미니프로그램 작성법을 설명하였다.
    • 이 기능들을 이용하면 제어기의 진단 통신 기능 검증 시험을 할 수 있다. 
    • 다음은 제어기의 진단 통신 기능 검증 "시험 자동화"에 관해서 설명한다.

     

    첨부: 코드

    diag_vdc.py
    0.01MB

    댓글