ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • libTSCAN의 CAN 메시지 전송 주기 정확도 살펴보기
    api 2026. 2. 17. 13:57

    시작하기 전에 

    '투썬(Tosun) 하드웨어와 libTSCAN를 이용하여 CAN(CAN-FD를 포함한다. 편의상 CAN이라 하겠다.) 메시지들을 전송한다면, 전송 주기는 얼마나 정확한가?'라는 문의를 받았다. 

     

    • libTSCAN에는 CAN 메시지 송신을 위한  tsapp_transmit_canfd_async() 함수가 있다. 
    • PC 프로그램에서 이 함수를 호출하면, PC는 USB 포트에 연결된 CAN 인터페이스 하드웨어에 메시지를 보낸다. PC의 OS(Windows / Linux)에 따라 함수 호출과 메시지 전달 사이에 지연이 있을 수 있다.
    • CAN 인터페이스 하드웨어는 이 메시지를 CAN 콘트롤러(CAN 콘트롤러는 칩 혹은 칩의 일부이다. 어쨌든, 소프트웨어가 아니라 하드웨어이다.)의 메일 박스(버퍼)에 저장할 것이다. CAN 콜트롤러는 CAN 트랜시버(CAN 트랜시버도 칩이다.)로 CAN 버스에 연결되어 있다. CAN 콘트롤러는 버스의 상황을 보고 메시지를 버스에 올린다. 메시지를 올릴 때, 버스의 다른 CAN 콘트롤러와 누가 먼저 메시지를 올릴 지 우선 순위를 다툰다. 우선 순위는 메시지의 ID로 정한다. ID가 낮을 수록 우선 순위가 높다.
      • CAN 버스의 상황에 따라 메시지 송신에 지연이 있을 수 있다. (이 영향이 클 것으로 짐작한다.)
      • CAN 인터페이스 하드웨어의 성능 때문에 메시지 송신에 지연이 있을 수 있다. (이 영향은 작을 것으로 짐작한다.)

    USB - CAN 인터페이스 하드웨어 (MCU) - CAN Controller - CAN Transceiver - Bus의 연결 관계

    • PC에서 일정한 주기로 CAN 메시지들을 전송할 때, 전송 주기의 정확성과 OS 그리고/혹은 버스 로드와의 관계를 살펴본다. 

     

     

    개요

    • 시험 계획
    • Windows에서 전송 주기 정확도
    • Linux에서 전송 주기 정확도

     

    시험 계획

    시험 환경

    • 아래 그림과 같이 실험 환경을 구성하였다.

    • 왼쪽의 TSMaster PC는 CAN 버스를 모니터링하고 메시지들을 파일로 저장하는 목적이다. USB 포트에 Tlog1004 CAN 인터페이스 하드웨어를 연결하고, 하드웨어를 CAN 버스에 연결하였다.
    • 오른쪽의 libTSCAN PC는 주기 메시지들을 송수신하기 위한 목적이다. USB 포트에 2 채널 CAN-FD 인터페이스 하드웨어를 연결하였다. ch 1과 ch 2를 직접 연결하여 버스를 구성하였다. ch 1에서 송신된 메시지들은 ch 2에서 수신된다. ch 2에서 송신된 메시지들은 ch 1에 수신된다.
    • libTSCAN PC를 1) Windows PC로하여 2) Linux PC로 하여 각각 실험하였다.

    송신

    • libTSCAN PC에는 메시지 송신을 위한 파이썬 프로그램을 실행하였다.
    • 프로그램은 10ms, 20ms, 50ms, 100ms, 500ms, 1000ms 주기의 메시지들을 전송한다.
    • 프로그램은 송신에 소요된 시간을 기록하여 파일로 저장한다.

    수신

    • 파이썬 프로그램에 메시지 수신 기능도 구현하였다. 수신으로 인해 인터페이스 하드웨어에 걸리는 연산 부하가 송신 주기 정확성에 영향을 미칠수도 있을 것으로 생각하여 고려해야 한다고 생각했다.
    • 프로그램은 수신에 소요된 시간을 기록하여 파일로 저장한다.

    송신 주기 계산

    • TSMaster PC에서 버스의 데이터를 blf 파일로 저장하였다. 
    • blf 파일을 분석하여 메시지 아이디별 전송 주기의 평균, 최소값, 최대값을 구했다.
    • 메시지 아이디별 전송 주기의 평균, 최소값, 최대값으로 그래프를 그렸다.

     

    시험 환경

    • 메시지는 CAN-FD(CAN이 아닌)로 하였다.
    • 메시지 길이는 32 바이트로 동일하게 하였다.
    • arbitration 구간의 보드 레이트는 500kbps로, 데이터 구간의 보드 레이트는 2,000kbps로 하였다.

     

    송신

    메시지 개수 정하기

    • 버스 로드를 50%, 55%, 60%, 65%, 70%, 75%, 80%, 85%, 90%, 95%로 변경하며 실험하였다.
    • 전송 주기는 10ms, 20ms, 50ms, 100ms, 500ms, 1000ms으로 하였다.
    • 32 바이트 길이의 10ms 주기 메시지를 전송하면 측정한 버스 로드는 약 6.8%이다. TSMaster의 Statistics 기능으로 측정하였다. 같은 방식으로 측정한 전송 주기별 버스 로드는 아래와 같다.
      • 10ms 주기 1개 ≈ 6.8%
      • 20ms 주기 1개 ≈ 3.4%
      • 50ms 주기 1개 ≈ 1.36%
      • 100ms 주기 1개 ≈ 0.68%
      • 500ms 구기 1개 ≈ 0.136%
      • 1000ms 주기 1개 ≈ 0.068%
    • 목표 버스 로드에 따라 주기별 전송 메시지 수를 아래 방식으로 정했다. 버스 로드 80%의 경우를 예로 설명한다.
      • 목표 버스 로드: 80%
      • (2 채널에서 송신을 하므로) 채널당 목표 버스 로드: 40% (=80% / 2)
      • 채널당 목표 버스 로드의 절반 (20% = 40% / 2)을 10ms 주기 메시지들로 채우기로 정한다.
        • 채널당 10ms 메시지 수: 3(= round(20% / 6.8%/msg)) 메시지 
      • 남은 채널당 목표 버스 로드의 절반(10% = 20% / 2)을 20ms 주기 메시지로  채우기로 정한다.
        • 채널당 20ms 메시지 수: 3 (= round(10% / 3.4%/msg)) 메시지
      • 위 방법을 50ms, 100ms, 500ms, 1000ms 주기에 대해 반복한다.
      • 그렇게 계산한 결과는 아래와 같다.
    주기 채널당 메시지 수
    10 3
    20 3
    50 3
    100 4
    500 10
    1000 9
    전체 32 / 채널 = 64 / 버스

     

    메시지 아이디 정하기

    • 각 메시지들의 아이디는 아래 방식으로 정했다.
      • 0x123
        • 1: 채널
        • 2: 주기 표시. 0x1:10ms, 0x2:20ms, 0x5:50ms, 0xA:100ms, 0xF:500ms, 0xFF:1000ms
        • 3: 메시지 일련 번호
      • 처음에는 500ms, 1000ms 주기를 고려하지 않았었다. 그때 10ms는 0x1로, 20ms는 0x2로 50ms는 0x5로 100ms는 0xA로 하기로 정했다. 주기 / 10으로 단순하게. 이 방식을 기준으로 코딩을 했다.
      • 나중에 실제 버스 로드를 목표 버스 로드에 더욱 가깝게 만들기 위해서 500ms, 1000ms 메시지들을 추가하기로 했다. 이때 나는 메시지 아이디에 관해 잊고 있었다. AI가 500ms에 0xF으로 1000ms에 0xFF으로 정했다. 나는 데이터 수집을 마치고 분석 중에 이 사실을 발견하였다.
    • 500ms는 메시지는 별 문제가 안 된다.
    • 1000ms 메시지는 문제가 된다.  메시지 아이디를 아래와 같이 정했기 때문이다. 
        msg_id = (channel_gui << 8) | (0xFF + i)
    • 0x200 | 0xFF + 1 = 0x200 | 0x100 = 0x300이 된다. 첫 자리는 채널 표시를 위한 용도인데, 0x300에서 3은 채널 3이 아니다. 어쨌든 메시지 아이디를 보고 주기를 알아볼 수는 있다.   

    메지시 전송 스케줄표 만들기

    • 버스 로드를 80%로 만들기 위해서 필요한 채널당 주기당 메시지 수는 위 표와 같다. 채널당 32개 메시지들이다. 버스로 보면 64개 메시지들이다. 
    • 10ms 루프를 돌면서, 10ms 메시지들은 매 루프마다, 20ms 메시지들은 매2회 루프마다, ..., 1000ms 메시지들은 매100회 루프마다 전송하는 방식을 생각할 수 있다. 100ms 메시지를 전송하는 루프에서는 10ms, 20ms, ..., 1000ms 메시지들을 모두 전송해야 한다. 즉, 64개 메시지들을 전송해야 한다. 어떤 루프에서는 10ms 메시지들 4개만 전송하면 된다. 루프별 연산 부하의 차이가 크다. 
    • 연산 부하를 고르게 배분하기 위해 아래 방식의 전송 스케줄을 적용하였다.
      • 10, 20, 50, 100, 500, 1000의 최소공배수(LCM)를 구한다. LCM은 1000이다. 
      • 1000ms를 1ms 슬롯으로 나눈다. 1000개의 슬롯들이 있다. 1번부터 1000번까지라고 하자. 
      • 1번 슬롯에 0x111 메시지를 넣었다고 하자. 10ms 주기 메시지 이미로 11, 21, ..., 991번 슬롯에도 넣어야 한다. 2번 슬롯에 0x223 메시지를 넣었다고 하자. 20ms 주기 메시지 이므로 22, 42, ..., 982번 슬롯에도 넣어야 한다. 이런 방식으로 64개 메시지 아이디들을 1000개의 슬롯에 넣을 수 있다. 빈 슬롯은 없는데 아직 슬롯에 담지 못한 메시지 아이디가 있다면,  앞에 있는 슬롯부터 순차적으로 슬롯을 채운다.    
    • 버스 로드 80%인 경우 스케줄표는 아래와 같다.  
    슬롯 번호 슬롯 내 메시지 수 메시지 ID 목록 채널 목록 주기 목록
    0 2 0x111, 0x151 CH0, CH0 10ms, 50ms
    1 2 0x112, 0x152 CH0, CH0 10ms, 50ms
    2 2 0x113, 0x153 CH0, CH0 10ms, 50ms
    3 2 0x211, 0x251 CH1, CH1 10ms, 50ms
    4 2 0x212, 0x252 CH1, CH1 10ms, 50ms
    5 2 0x213, 0x253 CH1, CH1 10ms, 50ms
    6 2 0x121, 0x2F1 CH0, CH1 20ms, 500ms
    7 2 0x122, 0x2F2 CH0, CH1 20ms, 500ms
    8 2 0x123, 0x2F3 CH0, CH1 20ms, 500ms
    9 2 0x221, 0x2F4 CH1, CH1 20ms, 500ms
    10 2 0x111, 0x2F5 CH0, CH1 10ms, 500ms
    11 2 0x112, 0x2F6 CH0, CH1 10ms, 500ms
    12 2 0x113, 0x2F7 CH0, CH1 10ms, 500ms
    13 2 0x211, 0x2F8 CH1, CH1 10ms, 500ms
    14 2 0x212, 0x2F9 CH1, CH1 10ms, 500ms
    15 2 0x213, 0x2FA CH1, CH1 10ms, 500ms
    16 2 0x222, 0x100 CH1, CH0 20ms, 1000ms
    17 2 0x223, 0x101 CH1, CH0 20ms, 1000ms
    18 2 0x1A1, 0x102 CH0, CH0 100ms, 1000ms
    19 2 0x1A2, 0x103 CH0, CH0 100ms, 1000ms
    20 2 0x111, 0x104 CH0, CH0 10ms, 1000ms
    21 2 0x112, 0x105 CH0, CH0 10ms, 1000ms
    22 2 0x113, 0x106 CH0, CH0 10ms, 1000ms
    23 2 0x211, 0x107 CH1, CH0 10ms, 1000ms
    24 2 0x212, 0x108 CH1, CH0 10ms, 1000ms
    25 2 0x213, 0x300 CH1, CH1 10ms, 1000ms
    26 2 0x121, 0x301 CH0, CH1 20ms, 1000ms
    27 2 0x122, 0x302 CH0, CH1 20ms, 1000ms
    28 2 0x123, 0x303 CH0, CH1 20ms, 1000ms
    29 2 0x221, 0x304 CH1, CH1 20ms, 1000ms
    30 2 0x111, 0x305 CH0, CH1 10ms, 1000ms
    31 2 0x112, 0x306 CH0, CH1 10ms, 1000ms
    32 2 0x113, 0x307 CH0, CH1 10ms, 1000ms
    33 2 0x211, 0x308 CH1, CH1 10ms, 1000ms
    34 1 0x212 CH1 10ms
    35 1 0x213 CH1 10ms
    36 1 0x222 CH1 20ms
    37 1 0x223 CH1 20ms
    38 1 0x1A3 CH0 100ms
    39 1 0x1A4 CH0 100ms
    40 1 0x111 CH0 10ms
    41 1 0x112 CH0 10ms
    42 1 0x113 CH0 10ms
    43 1 0x211 CH1 10ms
    44 1 0x212 CH1 10ms
    45 1 0x213 CH1 10ms
    46 1 0x121 CH0 20ms
    47 1 0x122 CH0 20ms
    48 1 0x123 CH0 20ms
    49 1 0x221 CH1 20ms
    50 2 0x111, 0x151 CH0, CH0 10ms, 50ms
    51 2 0x112, 0x152 CH0, CH0 10ms, 50ms
    52 2 0x113, 0x153 CH0, CH0 10ms, 50ms
    53 2 0x211, 0x251 CH1, CH1 10ms, 50ms
    54 2 0x212, 0x252 CH1, CH1 10ms, 50ms
    55 2 0x213, 0x253 CH1, CH1 10ms, 50ms
    56 1 0x222 CH1 20ms
    57 1 0x223 CH1 20ms
    58 1 0x2A1 CH1 100ms
    59 1 0x2A2 CH1 100ms
    60 1 0x111 CH0 10ms

     

    송신 소요 시간 측정

    • 100초간 메시지를 송수신하면서 실험하였다. 1초에 1000개의 슬롯이 있으므로 100,000개 슬롯이 있다. 
    • 1ms 슬롯 내에서 메시지 수만큼 tsapp_transmit_canfd_async() 함수를 반복 호출하는 방식으로 메시지를 송신하였다. 송신 시작 직전과 직후에 타이머를 읽어서 송신 소요 시간을 슬롯별로 측정하였다.
    • 프로그램에서 슬롯 폭(1ms)은 아래 방법으로 정확하게 맞추려고 했다. 
      • 슬롯 시작 때, slot_start_time을 기록한다.
      • 다음 슬롯의 시작 시작 시간 next_time을 구한다. next_time = slot_start_time + 1ms 이다.
      • 메시지들을 송신한다.
      • 메시지들을 수신한다.
      • 현재 시간 current_time을 기록한다.
      • next_time - current_time 만큼 sleep 한다. 
      • 슬롯이 정확하게 1ms인지 별도로 측정하지는 않았다. 
            while True:
                iteration += 1
                next_time += interval
                
                # 슬롯 시작 시간 기록
                slot_start_time = time.perf_counter()
                
                # 메시지 전송
                tx_count = scheduler.process_slot()
                if tx_count > max_tx_per_slot:
                    max_tx_per_slot = tx_count
                
                # 메시지 수신
                rx_count = scheduler.receive_messages()
                if rx_count > max_rx_per_slot:
                    max_rx_per_slot = rx_count
                
                # 전체 실행 시간 및 RX 카운트 기록
                slot_idx = scheduler.slot_count - 1
                if slot_idx < scheduler.max_slots:
                    scheduler.slot_exec_times[slot_idx] = time.perf_counter() - slot_start_time
                    scheduler.slot_rx_counts[slot_idx] = rx_count
                
                # 진행 상황 출력 (1초마다)
                current_time = time.perf_counter()
                if current_time - last_progress_time >= 1.0:
                    elapsed = current_time - start_time
                    print(f"  {elapsed:.1f}초... (TX/슬롯 최대: {max_tx_per_slot}, RX/슬롯 최대: {max_rx_per_slot})")
                    last_progress_time = current_time
                    # 1초 구간별 최대값 표시를 위해 초기화
                    max_tx_per_slot = 0
                    max_rx_per_slot = 0
                
                # 실행 시간 확인
                if time.perf_counter() - start_time >= run_seconds:
                    break
                
                # 타이밍 유지
                sleep_time = next_time - time.perf_counter()
                
                # sleep_time 기록
                slot_idx = scheduler.slot_count - 1
                if slot_idx < scheduler.max_slots:
                    scheduler.slot_sleep_times[slot_idx] = sleep_time
                
                if sleep_time > 0:
                    time.sleep(sleep_time)
                elif sleep_time < -0.001:
                    scheduler.timing_warnings.append((scheduler.slot_count, -sleep_time * 1000))

     

    수신

    수신 소요 시간 측정

    • 메시지 수신이 인터페이스 하드웨어에 연산 부하를 증가시킬 것이고 이 부하가 송신 주기에 영향을 줄 수 있기에, 메시지를 수신하도록 파이썬 프로그램을 작성했다.  
    • 매슬롯마다 수신  tsfifo_receive_canfd_msgs() 함수를 호출하여 CAN 콘트롤러의 메일 박스(버퍼)에 있는 메시지들을 PC로 읽어왔다.   
    • 함수를 호출하기 전직과 직후에 타이머를 읽어서 수신 소용 시간을 슬롯별로 측정하였다.

     

    송신 주기 정확성 계산

    • 수신 메시지들의 타임스탬프를 이용하여 메시지 아이디별 전송 주기의 평균, 최대, 최소를 계산하였다. 

    윈도에서 버스 로드별 송신 주기 

    • 윈도에서 버스 로드별로 측정한 메시지 송신 주기의 평균, 최대, 최소는 아래 그림들과 같다.
    • 버스 로드 50%와 55% 그래프를 비교하면, 50%의 주기 정확성이 55%의 경우보다 좋지 않을 것을 볼 수 있다. 윈도 OS가 갖는 특징일 것으로 생각한다. 나는 버스 로드를 변경하면서 반복적으로 실험하였다. 맨 처음 시험으로 무엇인지는 모르겠지만 캐시 처리 같은 면에서 불리함이 있었을 것으로 짐작한다.  
    • 버스 로드 50%에서 10ms 주기를 보면 최대값이 20ms가 넘는다. 100ms 가까이 되는 경우도 있다. 가장 버스 로드를 많이 차지하는 10ms 주기가 많이 틀어지니까 나머지 주기들도 영향을 많이 받는다. 
    • 버스 로드와 주기 정확성 사이에 어느 정도 비례 관계가 있지만 일정하지 않다.
    • 같은 송신 주기에서 메시지 아이디가 클 수록 최대와 최소 사이가 벌어지는 것을 볼 수 있다. 우선 순위가 낮아 버스 점유에서 밀리기 때문에 그런 것으로 짐작한다.  

     

    • 설정 주기 대비 평균, 최대, 최소값들의 차이(= 오차)를 그래프로 그리면 아래와 같다. (y축 스케일을 주의하여 보십시오.)
    • 50% 버스 로드에서 10ms 주기 메시지들 (0x111, 0x112, 0x211, 0x212)의 최대 차이는 50ms가 넘는다. 오차를 퍼센트로 표시하면 500%가 넘는다. 위에서 언급한 것처럼 50% 버스 로스의 경우, 윈도의 캐시 메모리 관리 등과 관련이 있을 것으로 짐작한다. 반면에, 70% 버스 로드에서 10ms 주기 메시지들의 최대 차이는 는 12ms 정도이다. 120% 오차율이다. 

    리눅스에서 버스 로드별 송신 주기

    • 리눅스에서 버스 로드별로 측정한 메시지 송신 주기의 평균, 최대, 최소는 아래 그림들과 같다.
    • 윈도의 경우보다 송신 주기의 변동이 눈에 띄게 작다.
    • 버스 로드 증가에 따라 송신 주기 변동도 증가한다.  
    • 같은 송신 주기에서 메시지 아이디가 크면 송신 주기 정확도가 낮아진다. 
    • 나는 일반 Ubuntu 24.04.04로 실험하였다. Real-time을 지원하는 우분투를 사용했다면 더 정확도 높은 결과를 얻을 수 있었을 것으로 기대한다.  

    • 설정 주기 대비 평균, 최대, 최소값들의 차이를 그래프로 그리면 아래와 같다. (y축 스케일을 주의하여 보십시오.)
    • 최대, 최소값은 단 1회만 주기가 틀어져도 크게 변동할 수 있다. 최대와 최대가 평균을 기준으로 상하 대칭이 된다는 것은 주기가 일정하다는 반증이라고 볼 수 있다.  
    • 버스 로드가 80% 이상일 경우, 상하 대칭이 틀어지기 시작한다. 

    리눅스에서 버스 로드별 슬롯당 송수신 시간 

    • 버스 로드별로 100,000개 1ms 슬롯의 송수신에 소요된 시간은 아래 그래프와 같다. 
    • 버스 로드 70% 실험에서 1회 수신 시간이 1ms를 초과하는 결과를 제외하고, 모두 충분한 시간 여유를 갖고 송수신을 하였다. 

    윈도에서 버스 로드별 슬롯당 송수신 시간 

    • 버스 로드별로 100,000개 1ms 슬롯의 송수신에 소요된 시간은 아래 그래프와 같다. 
    • 윈도가 왜 리얼-타임 처리에 적합하지 않은지 볼 수 있다.  

     

    버스 로드가 송신 주기 정확도에 미치는 영향

    • 아래 그림은 리눅스와 윈도에서 버스 로드가 50%와 95%인 상태에서 설정 송신 주기가 100ms인 0x2A1 메시지의 실제 주기의  히스토그램이다.
    • 버스 로드가 낮을 때(50%), 실제 주기가 설정 주기에 가깝게 몰려 있는 것을 볼 수 있다. 리눅스에서 주기가 윈도에서 주기보다 더 좁은 영역에 분포하는 것을 알 수 있다. 즉, 정확도가 더 높다. 
    • 버스 로드가 높을 때(95%), 실제 주기는 설정 주기를 중심으로 넓게 분포하는 것을 볼 수 있다. 버스 점유 경쟁이 격하기 때문일 것이다. 윈도에서 분포가 리눅스에서 분포 보다 더 넓은데, 히스토그램의 x축의 상하한을 제한하여 분포가 비슷한 것으로 보인다.
    • 버스 로드가 증가하면 전송 주기 정확도가 떨어지는 것은 피할 수 없다. 이것은 톨게이트를 통과한 후 진입로를 거쳐 고속도로에 진입하는 상황에서 고속도로 진입에 지연 시간이 발생하는 것과 비슷하다. 고속도로에 차들이 별로 없으면 진입로에서 고속도로로 진입하는데 지연이 작다. 극단적으로 고속도로에 차가 한 대도 없다면, 지연 시간은 0이다. 고속도로에 차가 많으면 지나가는 차들을 보낸 후에 진입해야 한다. 지연이 발생한다. 지나가는 차들이 너무 많으면 진입로에 차들이 줄이 길어진다. 지연 시간은 더 길어진다. 
    • 실험 중에는 95% 버스 로드에서도 전송이 안 되는 메시지는 없었다. 단지 지연 시간이 주기의 몇 배가 될 정도로 길어졌다. 이런 현상은 주기가 짧은 메시지들에 주로 발생한다.  

    리눅스/ 버스로드에 따른 송신 주기
    윈도/ 버스 로드에 따른 송신 주기

     

     

     

    결론

    • 투썬의 CAN 인터페이스 하드웨어와 libTSCAN 라이브러리를 이용하여 버스 로드를 변경하며 송신 메시지 주기의 정확성을 살펴보았다.
    • 위 조합은 윈도에서는 송신 주기 정확성이 중요한 유즈 케이스에 적합하지 않다. 리눅스에는 적합하게 보인다. 얼마나 높은 정확도가 필요한 지에 따라 판정이 다를 것이다.
    • 1) 메시지 아이디가 크면 낮은 아이디의 경우보다 상대적으로 송신 주기 정확성이 낮다. 버스 점유 순위에서 밀리기 때문으로 보인다.
    • 2) 버스 로드가 증가하면 송신 주기 정확성이 낮아진다.   
    • CAN 네트워크를 설계할 때  위 1)과 2)가 고려되면 좋을 것이라고 생각한다. 

     

    hsl's tsmaster 사용기 목차 :: hsl's tsmaster 사용기    

     

     

    'api' 카테고리의 다른 글

    TraceRoute - GPS 데이터를 지도에 표시하기  (0) 2026.03.05
    libTSCAN API - Python 설명서  (0) 2026.03.04
    libTSCAN 예제 코드  (0) 2026.02.05
    libTSCAN 함수 목록  (0) 2026.02.05