1. 실험 개요
진동자를 활용한 가속도 측정에서는 신호의 정확한 보정이 필수적입니다. 본 실험에서는 Single Frequency Calibration을 수행하여, 특정 주파수에서 입력 전압과 출력 가속도 값의 관계를 정의하고 이를 보정하는 과정을 기록합니다. 이를 통해 측정 장비가 올바른 값을 출력하도록 교정할 수 있습니다.
2. 사용 장비 및 구성
- DAQ (Data Acquisition Device): NI DAQ 장치
- 가속도계: ADXL354/355
- 진동자: HapCoil One HC121238O
- 소프트웨어: Python (DAQ 제어 및 데이터 로깅), MATLAB (데이터 분석 및 FFT 수행)
- 기본 설정:
- 아날로그 인풋(AI) 0, 1, 2번 채널 사용
- 아날로그 아웃풋(AO) 0번 채널 사용
- 샘플링 속도 및 전압 범위 설정
+ 🔍 참고해야 할 데이터 시트
- HapCoil One HC121238O 데이터 시트 → 진동자의 입력 전압(V-pp)과 가속도(g-pp) 관계 확인
- ADXL354-Cz데이터 시트 → 가속도 센서의 측정 범위, 민감도 및 포화(Saturation) 여부 확인
8g 모델이 우리가 사용하는 C가속도계여서 100mv -> 1g 가 나온다. 즉, 0.1 V -> 1g 임
- DAQ 장치 데이터 시트 (NI DAQ) → 아날로그 출력(AO)의 전압 범위 확인 (일반적으로 ±10V)
실제 장치 셋업 모습 입니다.
가속도계 + 햅추에이터 | 가속도계 필터 보드(?) | DAQ 연결 |
![]() |
![]() |
![]() |
햅추에이터의 X축 방향에 평행하게 가속도계를 둡니다. | 납땜해서 만듭니다. | 1번, 4번, 7번에 순차적으로 X, Y, Z 축 연결해줍니다. 아날로그 인풋(AI) 0, 1, 2번 채널로 연결됨 |
3. 실험 단계
Step 0: 4G값까지의 V 값 확인하기
우리는 4G까지 출력을 할거라, 이에 맞는 V값을 확인후 최대 V를 찾아서 그 이하로만 구해야함
근데 코드 유실 이슈로 나중에 작성...
Step 1: 신호 출력 및 측정 설정
먼저, DAQ에서 100Hz 사인파 신호를 생성하여 진동자로 출력하고, 가속도계를 통해 응답을 측정합니다.
- Python 코드를 사용하여 100Hz의 사인파 신호를 생성하고 출력.
- 가속도계가 측정하는 데이터를 DAQ의 아날로그 인풋으로 수집.
- 측정된 데이터의 변위를 시각적으로 확인하여 정상적으로 동작하는지 확인.
Step 2: 출력 데이터 검증
출력된 진동이 제대로 측정되고 있는지 확인하기 위해 가속도 데이터를 분석합니다.
- **FFT(푸리에 변환)**를 사용하여 특정 주파수에서의 응답 확인.
- 가속도 값과 입력 전압 비교하여 비례 관계 분석.
- 만약 예상과 다른 결과가 나온다면 측정 장치의 포화(Saturation) 여부 확인.
Step 3: Calibration Factor 결정
측정된 가속도 값과 입력 전압 사이의 관계를 분석하여 보정 계수를 결정합니다.
- 100Hz에서 입력 전압(V)과 출력 가속도(g)의 비율을 분석.
- 예를 들어, 0.5V를 입력했을 때 4g가 측정되었다면, 1g를 얻기 위해 필요한 입력 전압을 조정.
- Calibration Factor = 입력 전압 / 측정된 출력 가속도
이를 통해 원하는 가속도를 얻기 위해 입력 전압을 보정할 수 있습니다.
Step 4: 보정된 신호 출력 및 검증
보정 계수를 적용하여 다시 신호를 출력하고, 실제 가속도값이 원하는 값과 일치하는지 검증합니다.
- 보정된 입력 전압을 적용하여 진동자에 신호 출력.
- 다시 측정하여 FFT를 통해 원하는 주파수에서 올바른 응답이 나오는지 확인.
- 여러 번 반복 측정하여 평균값을 산출하고, Calibration Factor의 신뢰도를 검증.
Code
## Variable Definition
samplingRate = 10000
duration = 1
ReadCh = 3
now = datetime.datetime.now()
여기서 출력을 위해서 다음의 식을 정의해줍니다.
`adjustment` 값이 우리가 찾아야할 보정계수입니다.
sig = np.zeros((1, samplingRate))
Frequency = 100 # 100Hz 신호
Voltage = 0.2 # 입력 전압 값
# adjustment = (0.5 / 0.4) # 보정 계수 (Calibration Factor)
adjustment = 1 # 보정 안된 버전-> 우리가 구해야하는 값
for i in range(samplingRate):
sig[0][i] = adjustment * Voltage * np.sin(Frequency * 2 * np.pi * i / samplingRate) # 100Hz 사인파 생성
# 마지막 값 0으로 설정
sig[0][samplingRate-1] = 0
생성된 파형
다음으로 이 시그널을 바탕으로 실제 가속도계로 측정한 결과, 우리는 100Hz에서 0.2 Voltage 를 주었을때 다음과 같은 파형을 얻었습니다.
x | y | z |
![]() |
![]() |
![]() |
그럼 이제 이거를 10번 해줘서 평균을 구해줍니다.
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(sig[0])
writetask.start()
data = readtask.read(samplingRate * duration)
writetask.wait_until_done()
writetask.close()
readtask.close()
f = open("Accels_"+str(i)+".txt" , "a")
for j in range(samplingRate * duration):
f.write(f"{data[0][j]}\n") ## X 데이터만 저장장
f.close()
time.sleep(2)
그러면 다음과 같이 데이터들이 저장됩니다. 이제 이 데이터를 평균내서 변위를 살펴볼 것입니다.
num_measurements = 10 # 반복 횟수
frequency = 100 # Hz
target_length = frequency * 1000 # 목표 샘플 길이
all_measurements = []
for i in range(num_measurements):
data = np.loadtxt(f"Accels_{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 (g)")
plt.legend()
plt.grid()
plt.title(f"Mean Acceleration Data ({frequency}Hz, {target_length} samples)")
plt.show()
앞서 측정한 평균 가속도 값이 0.9G, 최대 가속도 값이 1G이므로, 진폭(Amplitude)은 약 0.1G입니다.
1️⃣ 이론적으로 기대되는 값 vs 실제 측정값
ADXL354-CZ의 감도는 `100 mV/g(=0.1V/g)`입니다.
따라서 0.2V 입력 시 2G가 나와야 하지만, 실제 측정된 값은 1G였습니다.
💡 여기서 "왜 10이 곱해지는지?"에 대한 의문이 있을 수 있습니다.
이는 전압(V) → 가속도(G) 변환 과정에서 감도(Sensitivity)의 역수를 적용한 결과입니다.
- 감도(Sensitivity) = 100 mV/g (= 0.1 V/g)
- 즉, 가속도(G) = 전압(V) ÷ 0.1V/g
- 0.2V ÷ 0.1V/g = 2G (이론값)
- 하지만 실제 측정값은 1G → 손실 발생
⚠ 즉, 이론적으로 2G가 나와야 하는데, 실제로는 1G만 나왔으므로, 신호 크기가 절반으로 감소한 상태입니다.
2️⃣ 신호 크기가 절반으로 감소한 이유
이러한 차이는 측정 과정에서 발생하는 여러 가지 손실 요인 때문일 수 있습니다.
- 진동자의 응답 특성
- 진동자가 입력한 전압(0.2V)을 완벽하게 2G로 변환하지 못했을 가능성이 있음.
- DAQ 장치의 설정 문제
- 샘플링 속도, 필터링 영향 등으로 인해 실제 출력 신호가 예상보다 작게 측정되었을 가능성이 있음.
- 센서 감도의 변화
- ADXL354-CZ의 감도(100 mV/g)가 온도나 환경 변화로 인해 오차가 생겼을 가능성이 있음.
이러한 이유로 보정 계수(adjustment)를 적용하여 신호 크기를 조정할 필요가 있습니다.
3️⃣ 보정 계수(adjustment) 값 결정
우리는 FFT 분석 결과를 함께 참고하여 보정 계수를 결정해야 합니다.
👉 FFT 이미지에서 100Hz 피크 값이 얼마나 나왔는지 확인해보겠습니다.
🔍 아래 FFT 이미지 분석:
- x축(주파수)에서 100Hz에서 뚜렷한 피크가 보입니다.
4️⃣ 결론: adjustment 값 조정
✅ 보정 계수(adjustment) 설정 방법:
adjustment = 2 # 측정된 값과 이론값을 맞추기 위한 보정 계수
이 값을 적용하면 출력 신호의 크기가 2배 증가하여 이론값과 맞아지게 됩니다.
즉, 보정을 통해 측정된 가속도 데이터가 원래 의도한 사인파처럼 나오도록 조정됩니다.
✅ 최종 정리
- 이론적으로 0.2V 입력 → 2G 출력이 되어야 하지만, 실제 측정값은 1G
- 감도(Sensitivity) = 100 mV/g (= 0.1 V/g)
- 즉, 가속도(G) = 전압(V) ÷ 0.1V/g
- 0.2V ÷ 0.1V/g = 2G (이론값)
- 하지만 실제 측정값은 1G → 손실 발생
- 보정 계수 adjustment = 2 적용 → 이론값과 실제 측정값 일치하도록 조정
- 이제 다시 보정된 신호를 측정하여 FFT를 확인하고 최종 결과를 검증해야 함
아래는 주피터노트북 코드입니다.
+ ) HighPassfilter 를 10Hz 주파수에서 해야하는데, 적용이 안되어있습니다. 다른 분들은 DC성분 제거를 위해 해당 부분을 하고 캘리브레이션하시길 바랍니다
'HCI > 이론' 카테고리의 다른 글
[Haptics] .wav 파일을 DAQ 에서 실행하기 / Python 파이썬 (0) | 2025.02.25 |
---|---|
[Haptics - Dynamic compensation] Calibration Using NI DAQ, Python and Matlab (0) | 2025.02.25 |
[Haptics - Dynamic compensation] Matlab에서 Chirp Signal 출력하기 (1) | 2025.02.25 |
[Haptics] 진동 측정 캘리브레이션 이론 정리 (0) | 2025.02.18 |
[Haptics] Macaron 햅틱 디자인 툴 (0) | 2025.02.17 |