-
TIO 측정 데이터 분석 - 디지털 입출력application 2025. 9. 2. 16:29
이 글은 Jupyter 노트북으로 작성되었습니다.
tio_measure_data_analysis - digital.ipynb0.68MBTIO 측정 데이터 분석 - 2. 디지털 입출력 점검¶
- TIO의 디지털 입출력 성능을 보기 위해 아래 실험을 하였다.
- TSMaster에서 TIO로 500msec 주기로 출력 요청용 CAN 메시지를 전송한다.
- 디지털 출력 신호가 on/off로 토글하도록 요청 메시지의 해당 신호값을 토글시킨다.
- TIO의 디지털 출력 포트를 TIO의 디지털 입력 포트에 직접 연결한다.
- TIO는 입력 포트의 상태를 10msec 주기로 CAN 메시지로 전송한다.
- 위 과정의 CAN 메시지들을 측정하여 파일로 저장한다.
- 출력을 요청한 CAN 메시지의 요청 신호와 입력 신호를 전송한 CAN 메시지의 입력 신호를 비교한다. 아래 항목들을 확인한다.
- 지연 시간은 몇 msec인가?
- 요청 대비 출력 오차는 몇 % 인가?
In [1]:# import from pathlib import Path import h5py import pandas as pd import numpy as np import plotly.express as px import plotly.graph_objects as go from plotly.subplots import make_subplotsIn [2]:# 블로그에 올리기 위해 plotly renderer의 설정이 필요하다. import plotly.io as pio pio.renderers.default = "notebook_connected"In [3]:# constant # 데이터 파일 저장 경로 k_dir_data = Path('data') k_dir_data.mkdir(exist_ok=True) # data 디렉토리의 첫 번째 .mat 파일을 대상으로 한다.(파일 이름을 기억하기가 귀찮다.^^) mats = list(Path(k_dir_data).rglob('*.mat')) if len(mats) == 0: print('No mat files found in data directory') else: print('mats =') for i, mat in enumerate(mats): print(f'{i}: {mat.name}') # 1개 mat 파일을 대상으로 데이터를 처리한다. i_mat = 0 mat = mats[i_mat] print(f'{mat = }')mats = 0: tio_2025_08_28_17_44_55.MAT mat = WindowsPath('data/tio_2025_08_28_17_44_55.MAT')mat 파일을 읽어서 데이터프레임으로 변환하기¶
- digital output request, digitial input, analog output request, analog input 신호들을 pandas df(DataFrame)으로 만든다.
- 메시지별로 전송 주기가 다르다. 전송 주기를 맞춰야 계산이 편리하다.
- 메시지별로 데이터프레임을 만들고, 데이터프레임들을 합친다.
In [4]:# Read mat file. So simple trace = h5py.File(mat, 'r') trace_keys = list(trace.keys()) for key in trace_keys: print(key)CAN1_TIO9011_N01_REP_DI_LEVEL_TIO9011_CH7_Level CAN1_TIO9011_N01_REP_DI_LEVEL_TIO9011_CH9_Level CAN1_TIO9011_N01_SET_DO_LEVEL_REQ_TIO9011_Channel_Index CAN1_TIO9011_N01_SET_DO_LEVEL_REQ_TIO9011_IO_LEVEL CAN1_TIO9015_N02_REP_AI_CHN1_4_Voltage_CH2 CAN1_TIO9015_N02_REP_AO_CHN1_4_Voltage_CH1 CAN1_TIO9015_N02_SET_AO_VALUE_REQ_A_Value CAN1_TIO9015_N02_SET_AO_VALUE_REQ_Channel노트¶
- 신호의 정의는 아래와 같다.
디지털 신호¶
- 나는 디지털 아웃풋으로 CH7을 디지털 인풋으로 CH9를 사용했다.
- CAN1_TIO9011_N01_SETDO_LEVELREQTIO9011Channel_Index:
- SET 메시지에 있는 Digitial Output 채널 지정
- SET 메시지는 TSMaster가 TIO로 필요할 때마다 전송한다.
- df 컬럼명: do_ch_set
- CAN1_TIO9011_N01_SETDO_LEVELREQTIO9011_IOLEVEL:
- SET 메시지에 있는 Digitial Output 요청값
- SET 메시지는 TSMaster가 TIO로 필요할 때마다 전송한다.
- df 컬럼명: do_req_set
- CAN1_TIO9011_N01_REPDI_LEVEL_TIO9011_CH9Level: do_act_req
- REP 메시지에 있는 Digital Input 신호값
- REP 메시지는 TIO가 주기적으로 전송한다.
- REP 메시지의 전송 주기는 SET 메시지로 설정 가능하다.
- df 컬럼명: do_act_req
- CAN1_TIO9011_N01_REPDI_LEVEL_TIO9011_CH7Level:
- REP 메시지에 있는 Digital Output의 요청값 <-- 아웃풋으로 설정된 경우, Input 값이 아닌 Output 요청이다.
- REP 메시지는 TIO가 주기적으로 전송한다.
- REP 메시지의 전송 주기는 SET 메시지로 설정 가능하다.
- df 컬럼명: do_req_rep
digital 신호들을 한 df로 만든다.¶
In [5]:# 측정 주기 (= 메시지 전송 주기)에 따라 각각 df를 만든다. df_d1 = pd.DataFrame( { 'ts': trace['CAN1_TIO9011_N01_REP_DI_LEVEL_TIO9011_CH7_Level'][0], 'do_req_rep': trace['CAN1_TIO9011_N01_REP_DI_LEVEL_TIO9011_CH7_Level'][1], 'di_act_rep': trace['CAN1_TIO9011_N01_REP_DI_LEVEL_TIO9011_CH9_Level'][1] } ) df_d2 = pd.DataFrame( { 'ts': trace['CAN1_TIO9011_N01_SET_DO_LEVEL_REQ_TIO9011_Channel_Index'][0], 'ch': trace['CAN1_TIO9011_N01_SET_DO_LEVEL_REQ_TIO9011_Channel_Index'][1], 'do_req_set': trace['CAN1_TIO9011_N01_SET_DO_LEVEL_REQ_TIO9011_IO_LEVEL'][1] } )In [6]:px.line( x=df_d1['ts'], y=df_d1['ts'].diff(), labels={'x': 'ts', 'y': 'ts_diff'}, title='ts_diff of df_d1', range_y=[df_d1['ts'].diff().mean()*0.8, df_d1['ts'].diff().mean()*1.2], width=600, height=400 )In [7]:px.line( x=df_d2['ts'], y=df_d2['ts'].diff(), labels={'x': 'ts', 'y': 'ts_diff'}, title='ts_diff of df_d2', range_y=[df_d2['ts'].diff().mean()*0.8, df_d2['ts'].diff().mean()*1.2], width=600, height=400 )노트¶
- df_d1 ts의 주기는 10msec이다.
- df_d2 ts의 주기는 500msec이다.
In [8]:# 두 데이터프레임을 ts 기준으로 합친다. (outer join) df = ( pd.merge(df_d1, df_d2, on='ts', how='outer', suffixes=('_d1', '_d2')) .sort_values('ts') .reset_index(drop=True) ) # 합쳐진 df_digital을 출력한다. df.head()Out[8]:ts do_req_rep di_act_rep ch do_req_set 0 26.998460 1.0 1.0 NaN NaN 1 27.008549 1.0 1.0 NaN NaN 2 27.018482 1.0 1.0 NaN NaN 3 27.028371 1.0 1.0 NaN NaN 4 27.038503 1.0 1.0 NaN NaN 타임스탬프를 살펴본다.¶
- 주기가 다른 두 타임스탬프들이 합쳐진 것이라 타임스탬프의 간격이 들쭉날쭉할 것으로 예상된다.
In [9]:# ts의 diff를 구한다. df['ts_diff'] = df['ts'].diff() df['ts_diff'].describe()Out[9]:count 3009.000000 mean 0.009854 std 0.001141 min 0.000090 25% 0.009963 50% 0.009979 75% 0.010025 max 0.012109 Name: ts_diff, dtype: float64In [10]:px.line( df, x='ts', y='ts_diff', title='ts_diff of df (= df_d1과 df_d2가 합쳐짐)', labels={'ts': 'Timestamp', 'ts_diff': 'Difference'}, width=800, height=600 )발견¶
- ts_diff는 mean은 9.85msec이다. std, min을 보면 주기에 큰 변동이 있는 것을 알 수 있다. 그래프로 보면 분명하다.
- df에는 df_d1 메시지들과 df_d2 메시지들이 합쳐져 있다. df_d1 메시지와 df_d2 메시지가 합쳐진 부근의 주기는 df_d1의 10msec나 df_d2의 500msec와 크게 다를 수 있다. 이것이 df ts_diff에 영향을 미쳤을 것이다.
두 메시지 사이의 전송 시간 차이¶
- df_d1과 df_d2의 타임스탬프가 일치하지 않는다는 것을 확인했다.
- df_d1과 df_d2를 합쳐서 df를 만들었다. 각 df 관점에서 보면 없던 타임스탬프가 유입된다. 새로 유입된 타임스탬프의 신호값은 None이다.
- ts와 df_d2의 임의의 신호로 연산을 하면, 원래 있던 타임스탬프의 신호 값은 None이 아니라 정상적으로 연산이 될 것이다. 새로 유입된 타임스탬프의 신호 값은 None이라 연산 결과가 None이 될 것이다.
- 위 특징을 이용하면 df_d1 메시지와 df_d2 메시지 전송 시간 사이의 간격을 알 수 있다.
In [11]:# df_d1 메시지와 원래 타임스탬프가 있는 df_d2 메시지 사이의 시간 차이를 구할 수 있다. filt = (df['ts'] - df['do_req_rep']).notnull() df.loc[filt, ['ts_diff']].describe()Out[11]:ts_diff count 2965.000000 mean 0.009985 std 0.000305 min 0.000268 25% 0.009966 50% 0.009979 75% 0.010027 max 0.012109 발견¶
- 위 계산의 평균은 0.00103 sec, 즉, 1.03msec 이다.
- 즉, df_d1 메시지와 df_d2 메시지 사이에 간격은 1msec 정도이다.
그래프¶
In [12]:# plotly로 do_req_rep, do_req_rep, di_act_rep 시각화한다. # 세 신호를 분리해서 표시한다. fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.02, subplot_titles=('DO Req Set', 'DO Req Rep', 'DI Act Rep')) fig.add_trace(go.Scatter(x=df['ts'], y=df['do_req_set'], name='DO Req Set'), row=1, col=1) fig.add_trace(go.Scatter(x=df['ts'], y=df['do_req_rep'], name='DO Req Rep'), row=2, col=1) fig.add_trace(go.Scatter(x=df['ts'], y=df['di_act_rep'], name='DI Act Rep'), row=3, col=1) fig.update_layout(title='DO Req Set/Rep and DI Act Rep over Time', height=800) fig.update_traces(marker=dict(size=6, symbol='circle')) fig.show()발견¶
- None 값들 때문에 그래프가 위와 같이 보인다.
- ffill을 한다. 디지털 신호의 경우, 메시지가 전송된 순간부터 다음 메시지가 전동될 때까지 현재 값이 지속될 테니까.
In [13]:df['do_req_set'] = df['do_req_set'].ffill() df['do_req_rep'] = df['do_req_rep'].ffill() df['di_act_rep'] = df['di_act_rep'].ffill()In [14]:# plotly로 do_req_rep, do_cmd_rep, di_act 시각화한다. # 세 신호를 분리해서 표시한다. fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.02, subplot_titles=('DO Req Set', 'DO Req Rep', 'DI Act Rep')) fig.add_trace(go.Scatter(x=df['ts'], y=df['do_req_set'], name='DO Req Set'), row=1, col=1) fig.add_trace(go.Scatter(x=df['ts'], y=df['do_req_rep'], name='DO Req Rep'), row=2, col=1) fig.add_trace(go.Scatter(x=df['ts'], y=df['di_act_rep'], name='DI Act Rep'), row=3, col=1) fig.update_layout(title='DO Req Set/Rep and DI Act Rep over Time', height=800) fig.update_traces(marker=dict(size=6, symbol='circle')) fig.show()In [15]:# 지연 시간을 알기 위해서 do_req_rep과 do_req_rep의 차이를 계산한다. # do_req_rep과 di_act의 차이도 계산한다. df['do_req_set-req_rep'] = df['do_req_set'] - df['do_req_rep'] df['do_req_set-act_rep'] = df['do_req_set'] - df['di_act_rep'] df['do_req_rep-act_rep'] = df['do_req_rep'] - df['di_act_rep']In [16]:# do_req_set-req_rep를 그래프로 그린다. px.line(df, x='ts', y='do_req_set-req_rep', title='Time Series Difference', labels={'ts': 'Timestamp', 'value': 'do_req_set-req_rep'}, markers=True)발견¶
- 위 그래프를 확대하면, 1 틱 차이임을 알 수 있다.
In [17]:# do-cmd-act를 그래프로 그린다. px.line(df, x='ts', y='do_req_set-act_rep', title='Time Series Difference', labels={'ts': 'Timestamp', 'value': 'do_req_set-act_rep'}, markers=True)In [18]:px.line(df, x='ts', y='do_req_rep-act_rep', title='Time Series Difference', labels={'ts': 'Timestamp', 'value': 'do_req_rep-act_rep'}, markers=False)발견¶
- 1 틱만큼 지연이 있다.
- 이 실험에서 1 틱은 10msec이다. 전송 주기를 더 짧게 하여 1 틱의 크기를 줄이면 어떻게 될까? 전송 주기를 1msec로 했으면 지연 시간이 1msec로 줄었을까?
- on/off 라는 디지털 신호의 특성상 오차는 없다.
TODO¶
- TIO의 전송 주기를 더 짧게하여 최소 지연 시간을 찾는다.
- 1msec 이하가 가능할까?
'application' 카테고리의 다른 글
조향과 구동 조종을 위한 판넬을 만드는 법 (0) 2025.09.18 TIO 측정 데이터 분석 - mat 파일을 데이터프레임으로 변환하기 (mdf2df) (1) 2025.09.01 UDS 기반 제어기 프로그래머 개발 - libTSCAN (5) 2025.08.11 ts_can_flash_programmer 개발 - libTSCAN (3) 2025.07.26 claude.ai와 코드, 문서 분석 및 코드 작성 (3) 2025.07.26 - TIO의 디지털 입출력 성능을 보기 위해 아래 실험을 하였다.