[Haptics - Dynamic compensation] Calibration Using NI DAQ, Python and Matlab

2025. 2. 25. 19:21·HCI/Haptics
728x90
반응형

이번 게시글에서는 앞서 포스팅한 게시글

https://sillon-coding.tistory.com/631

 

[Haptics - Dynamic compensation] Matlab에서 Chirp Signal 출력하기

Chirp SignalChirp Signal은 주파수가 점진적으로 증가하거나 감소하는 특성을 가진 신호로, 다음과 같은 특징이 있다.Chirp-Up: 주파수가 점차 증가하는 신호Chirp-Down: 주파수가 점차 감소하는 신호넓은

sillon-coding.tistory.com

 

에서 추출한 Chirp Signal을 바탕으로 30~ 300 Hz 주파수 대역에서 진동을 사용하도록 Calibaration 을 진행할것입니다.

 

간단히 코드만 짚고 넘어가겠습니다.

chirp_test.m (matlab 코드입니다.) 

% 샘플링 레이트 및 시간 벡터 설정
Fs = 10000;  % 샘플링 주파수 (Hz)
T = 10;       % 총 지속 시간 (초)
t = linspace(0, T, Fs * T); % 시간 벡터

% Chirp Signal 생성 (30Hz -> 300Hz)
f0 = 30;  % 시작 주파수 (Hz)
f1 = 300; % 종료 주파수 (Hz)
y = chirp(t, f0, T, f1);

% 생성된 신호 플로팅
figure;
plot(t, y);
xlabel('Time (s)');
ylabel('Amplitude');
title('Chirp Signal (30Hz to 300Hz)');
grid on;

% 텍스트 파일로 저장
fileID = fopen('chirp_signal.txt', 'w');
fprintf(fileID, '%f\n', y);
fclose(fileID);

 
In [10]:
# Modules

import nidaqmx
import nidaqmx.system
import numpy as np
import datetime
 
 

Acceleration Using Chirp Signal¶

 
In [11]:
## Variable Definition
samplingRate = 10000
duration = 10
ReadCh = 3
now = datetime.datetime.now()
 
In [12]:
import csv
import os

def DFT321(Arr):
    DFTArr = []

    U_temp = np.zeros((samplingRate * duration, 3)) * 1j
    U_temp[:,0] = np.fft.fft(Arr[0])
    U_temp[:,1] = np.fft.fft(Arr[1])
    U_temp[:,2] = np.fft.fft(Arr[2])
    Y_temp = np.zeros((samplingRate * duration//2 + 1, 1)) * 1j

    for i in range(samplingRate * duration//2 + 1):
        absY = np.real(np.sqrt(np.pow(abs(U_temp[i][0]), 2) + np.pow(abs(U_temp[i][1]), 2) + np.pow(abs(U_temp[i][2]), 2)))
        PhaseChoice = np.angle(sum(U_temp[i]))
        Y_temp[i] = absY * np.exp(1j * PhaseChoice)

    Y_flip = np.conj(np.flip(Y_temp))
    Y = np.concat((Y_temp, Y_flip[2:]))
    DFTArr = abs(np.real(np.fft.ifft(Y)))

    return DFTArr

def saveData(Arr, Date, T_start):
    dir = os.getcwd()
    SaveArr = np.zeros((1 + ReadCh + 1 + 1 + 1, samplingRate * duration))
    for i in range(samplingRate * duration):
        SaveArr[0][i] = T_start + 0.1 * i
        SaveArr[1][i] = Arr[0][i]
        SaveArr[2][i] = Arr[1][i]
        SaveArr[3][i] = Arr[2][i]
        # SaveArr[4][i] = # DFT
        # SaveArr[5][i] = # Speed
        if i == 0:
            SaveArr[5][i] = 0
        else:
            SaveArr[5][i] = (SaveArr[4][i] - SaveArr[4][i-1]) * samplingRate
            
    SaveArr[4] = DFT321(Arr).squeeze()
    print(np.shape(DFT321(Arr)))



    f = open("Accel//Accels_" + Date + ".txt", 'a')
    for i in range(samplingRate * duration):
        line = str(SaveArr[0][i]) + ", " + str(SaveArr[1][i]) + ", " + str(SaveArr[2][i]) + ", " + str(SaveArr[3][i]) + ", " + str(SaveArr[4][i]) + ", " + str(SaveArr[5][i]) + ", " + str(SaveArr[6][i]) + "\n"
        f.write(line)
    f.close()
 
In [13]:
## DAQ Ready
system = nidaqmx.system.System.local()
for device in system.devices:
    print(device)
device = system.devices['Dev1']
# out_chan = device.ao_physical_chans['ao0']
in_chan = device.ai_physical_chans['ai0:2']
 
 
Device(name=Dev1)
 
 

Matlab에서 생성한 Chrip Signal 불러오기¶

 
In [14]:
# 파일 경로 지정
file_path = "chirp_signal.txt"

with open(file_path, 'r') as file:
    lines = file.readlines()
    signal_data = [float(line.strip()) for line in lines if line.strip()]
signal_data 
 
Out[14]:
[1.0,
...
 0.676431,
 ...]
 
In [17]:
import time

for i in range(10):

    readtask = nidaqmx.Task()
    writetask = nidaqmx.Task()

    readtask.ai_channels.add_ai_voltage_chan("Dev1/ai0:2", terminal_config=nidaqmx.constants.TerminalConfiguration.RSE, min_val=-10, max_val=10)
    readtask.timing.cfg_samp_clk_timing(samplingRate, sample_mode=nidaqmx.constants.AcquisitionType.FINITE, samps_per_chan=samplingRate * duration)
    writetask.ao_channels.add_ao_voltage_chan("Dev1/ao0")
    writetask.timing.cfg_samp_clk_timing(samplingRate, sample_mode=nidaqmx.constants.AcquisitionType.FINITE, samps_per_chan=samplingRate * duration)

    writetask.write(signal_data)
    writetask.start()

    data = readtask.read(samplingRate * duration)

    writetask.wait_until_done()
    writetask.close()
    readtask.close()


    f = open("./Chirp/Chirp_Accels_"+str(i)+".txt" , "a")
    for j in range(samplingRate * duration):
        f.write(f"{data[0][j]}\n")
    f.close()


    time.sleep(2)
 
In [19]:
# 마지막으로 측정한 데이터의 파형 보기기
plt.plot(data[0]) # X
plt.show()
plt.plot(data[1]) # Y
plt.show()
plt.plot(data[2]) # Z
plt.show()
 
 
No description has been provided for this image
 
No description has been provided for this image
 
No description has been provided for this image
 
In [35]:
import numpy as np
def Mean_accels_data(title):
    num_measurements = 10  # 반복 횟수
    target_length = 100000

    all_measurements = []

    for i in range(num_measurements):
        data = np.loadtxt(f"{title}_{i}.txt", delimiter=",")  # 데이터 파일 불러오기
        
        # 데이터 길이 조정 (Zero Padding 또는 Truncation)
        if len(data) > target_length:
            data = data[:target_length]  # 길이가 길 경우 자름
        # else:
        #     padded_data = np.pad(data, (0, target_length - len(data)), 'constant', constant_values=0)
        
        all_measurements.append(data)

    # NumPy 배열 변환 후 평균 계산
    all_measurements = np.array(all_measurements)
    average_data = np.mean(all_measurements, axis=0)

    plt.figure(figsize=(10, 5))
    plt.plot(average_data, label="Mean Acceleration")
    plt.xlabel("Samples")
    plt.ylabel("Acceleration")
    plt.legend()
    plt.grid()
    plt.show()

    return average_data
 
 

Chirp Signal Mean plot¶

 
In [36]:
average_data = Mean_accels_data('Chirp/Chirp_Accels')
 
 
No description has been provided for this image
 
 

save mean data

 
In [38]:
print(average_data)

f = open("Chirp/Chirp_Accels_Mean.txt" , "a")
for j in range(len(average_data)):
    f.write(f"{average_data[j]}\n")
f.close()
 
 
[0.97474134 0.97271213 0.97152037 ... 0.92459089 0.92420437 0.92484857]
 
In [ ]:
 

 

이제 여기서 Chirp Signal의 평균을 보정해주는 식을 찾겠습니다.

dynamic_compensation.m

 

%% 전역변수
samplingRate = 10000;
time = 10;
samps = samplingRate * time;
fmin = 30;
fmax = 300;
voltage = 1;
t = linspace(0, time, samps);
f = linspace(0, samplingRate, samps);
iodelay = 0;
Ts = 1/samplingRate;
PI = 3.1415926536;



%% 입력
chrpName=sprintf('chirp_signal.txt');
fileName=sprintf('Chirp\\Chirp_Accels_Mean.txt');
chrp = textread(chrpName, '%f');
mtr = textread(fileName, '%f');
tr = mtr - mean(mtr);
HighpassedMotor = highpass(tr, fmin, samplingRate, 'Steepness', 0.9);

chrp = chirp(t, fmin, 1, fmax);
%%
fftChirp = fft(chrp);
fftOutput = fft(HighpassedMotor);
%InversetransferFunction = fftChirp ./ fftOutput;

%input output data encapsulation
%iddataTF = iddata(HighpassedMotor, chrp, 1/samplingRate);
iddataITF = iddata(chrp', HighpassedMotor, 1/samplingRate);

EstimatedITF = etfe(iddataITF, [], samplingRate*time);
%etfe Smoothing

[TFmag, TFphase, TFwout] = bode(EstimatedITF);
TFestsys = tfest(EstimatedITF, 14,12 , iodelay, 'Ts', Ts, 'Feedthrough', true, tfestOptions('WeightingFilter', [30*(2*PI), 300*(2*PI)]));

compare(ITFestsys, EstimatedITF);

현재 그래프(이미지)와 조정해야 할 부분 연결하기

현재 MATLAB의 compare(ITFestsys, EstimatedITF);에서 나타난 파란색(측정된 ITF)과 회색(추정된 ITF) 그래프가 겹쳐져야 한다.
즉, 추정된 전달 함수(Identified Transfer Function, ITF)가 실제 측정된 데이터와 일치하도록 조정해야 한다.


1️⃣ 그래프 분석 (회색 vs. 파란색)

  • 회색 그래프: 현재 모델이 추정한 주파수 응답(Estimated ITF)
  • 파란색 그래프: 실제 측정된 가속도 응답(실제 ITF 데이터)
  • 차이점: 일부 주파수 구간에서 두 그래프가 일치하지 않고 차이가 발생함

📌 이 차이를 줄이려면, 전달 함수의 차수(14,12)를 조정해야 한다.
차수가 너무 낮으면 전체적인 경향을 따라가지 못하고, 너무 높으면 특정 구간에서만 잘 맞을 수 있으므로,
최적의 차수를 찾아야 한다.


2️⃣ 해결 방법: tfest() 함수의 전달 함수 차수 조정

MATLAB의 tfest()를 사용하여 전달 함수(Transfer Function)의 차수를 조정할 수 있다.

ITFestsys = tfest(EstimatedITF, 14, 12, iodelay, 'Ts', Ts, ... 'Feedthrough', true, tfestOptions('WeightingFilter', [30*(2*PI), 300*(2*PI)]));
  • 14,12: 현재 사용된 전달 함수의 차수 (분자 14차, 분모 12차).
  • iodelay: 입출력 지연값.
  • 'WeightingFilter': 특정 주파수 대역(30Hz~300Hz)에서 성능을 최적화하도록 설정.

차수를 조정하는 이유

  • 전달 함수의 차수가 너무 낮으면, 모델이 단순해져서 전체적인 주파수 응답을 충분히 따라가지 못한다.
  • 전달 함수의 차수를 조정하면, 모델이 실제 측정값(파란색)과 더 유사한 주파수 응답을 보이도록 개선할 수 있다.
  • 적절한 차수를 찾으면 전 주파수 대역에서 파란색과 회색이 최대한 일치하도록 조정할 수 있다.

3️⃣ 차수 변경에 따른 조정

차수를 조정하면서 파란색과 회색이 더 잘 겹치는 값을 찾을 수 있다.
예를 들어:

ITFestsys = tfest(EstimatedITF, 18, 16, iodelay, 'Ts', Ts, ... 'Feedthrough', true, tfestOptions('WeightingFilter', [30*(2*PI), 300*(2*PI)]));

또는

ITFestsys = tfest(EstimatedITF, 20, 18, iodelay, 'Ts', Ts, ... 'Feedthrough', true, tfestOptions('WeightingFilter', [30*(2*PI), 300*(2*PI)]));

이렇게 차수를 높이거나 낮추면서 파란색과 회색이 최대한 겹치는 값을 찾으면 된다. 


4️⃣ 결론

  • 현재 compare(ITFestsys, EstimatedITF);의 결과에서 회색(추정값)과 파란색(실제 측정값)이 일치하도록 조정해야 한다.
  • 이를 위해 tfest()의 전달 함수 차수(14,12)를 바꿔가면서 실험해야 한다.
  • 차수가 너무 낮으면 전체적인 경향을 따라가지 못하고, 너무 높으면 특정 구간에서만 잘 맞을 수 있으므로 최적의 차수를 찾는 것이 중요하다.
  • 차수를 높이면 세밀한 주파수 응답을 추정할 수 있으며, 이를 통해 모델이 실제 데이터를 최대한 반영하도록 조정할 수 있다.

📌 최종 목표: 파란색과 회색이 최대한 겹치는지 확인하면서 전달 함수 차수를 조정하여, 측정된 데이터를 최대한 반영한 모델을 만드는 것이다.

 

ITFestsys = tfest(EstimatedITF, 15,11 , iodelay, 'Ts', Ts, 'Feedthrough', true, tfestOptions('WeightingFilter', [30*(2*PI), 300*(2*PI)]));

 

차수를 높게 한다고 잘되는건 아니다. (되도록 9 안의 값을 찾으면서 찾자)

 

차수 예시는 아래와 같음

 

728x90
반응형

'HCI > Haptics' 카테고리의 다른 글

[Haptics] 50 ~ 500 Hz Frequency 영역대 Calibration (진행중) - H(f) 구하기 (FFT)  (0) 2025.03.13
[Haptics] .wav 파일을 DAQ 에서 실행하기 / Python 파이썬  (0) 2025.02.25
[Haptics - Dynamic compensation] Matlab에서 Chirp Signal 출력하기  (1) 2025.02.25
[Haptics - Haptuator Single Frequency] Calibration Using NI DAQ and Python  (0) 2025.02.24
[Haptics] 진동 측정 캘리브레이션 이론 정리  (0) 2025.02.18
'HCI/Haptics' 카테고리의 다른 글
  • [Haptics] 50 ~ 500 Hz Frequency 영역대 Calibration (진행중) - H(f) 구하기 (FFT)
  • [Haptics] .wav 파일을 DAQ 에서 실행하기 / Python 파이썬
  • [Haptics - Dynamic compensation] Matlab에서 Chirp Signal 출력하기
  • [Haptics - Haptuator Single Frequency] Calibration Using NI DAQ and Python
sillon
sillon
꾸준해지려고 합니다..
    반응형
  • sillon
    sillon coding
    sillon
  • 전체
    오늘
    어제
    • menu (614)
      • notice (2)
      • python (68)
        • 자료구조 & 알고리즘 (23)
        • 라이브러리 (19)
        • 기초 (8)
        • 자동화 (14)
        • 보안 (1)
      • coding test - python (301)
        • Programmers (166)
        • 백준 (76)
        • Code Tree (22)
        • 기본기 문제 (37)
      • coding test - C++ (5)
        • Programmers (4)
        • 백준 (1)
        • 기본기문제 (0)
      • 공부정리 (5)
        • 신호처리 시스템 (0)
        • Deep learnig & Machine lear.. (41)
        • Data Science (18)
        • Computer Vision (17)
        • NLP (40)
        • Dacon (2)
        • 모두를 위한 딥러닝 (강의 정리) (4)
        • 모두의 딥러닝 (교재 정리) (9)
        • 통계 (2)
      • HCI (23)
        • Haptics (7)
        • Graphics (11)
        • Arduino (4)
      • Project (21)
        • Web Project (1)
        • App Project (1)
        • Paper Project (1)
        • 캡스톤디자인2 (17)
        • etc (1)
      • OS (10)
        • Ubuntu (9)
        • Rasberry pi (1)
      • App & Web (9)
        • Android (7)
        • javascript (2)
      • C++ (5)
        • 기초 (5)
      • Cloud & SERVER (8)
        • Git (2)
        • Docker (1)
        • DB (4)
      • Paper (7)
        • NLP Paper review (6)
      • 데이터 분석 (0)
        • GIS (0)
      • daily (2)
        • 대학원 준비 (0)
      • 영어공부 (6)
        • job interview (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Python
    소수
    programmers
    백준
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
sillon
[Haptics - Dynamic compensation] Calibration Using NI DAQ, Python and Matlab
상단으로

티스토리툴바