[아두이노] 가변저항 튜너 + 저항 형태의 온도센서와 보정 필요성에 대한 이해
아두이노와 NTC 서미스터를 이용해 생육 챔버나 스마트팜용 온도계를 만들 때,
단순히 전압을 읽어 map() 함수로 변환하면 반드시 실패합니다. 우리의 목표는 온도->전압 함수의 정확한 역함수(전압->온도)를 구하는 것입니다.
정밀한 제어를 위해서는 다음 3단계 공정이 필요합니다:
하드웨어(가변저항) → 수학(로그) → 보정(투 포인트)
이 과정을
👉 "세상에 하나뿐인 나만의 정밀 자(Scale)를 만드는 과정"
으로 비유해 보겠습니다.
1️⃣ 가변저항: 눈금을 진하고 촘촘하게 (Resolution 확보)
가변저항을 조절하는 것은
👉 내가 보고 싶은 온도 구간에서 데이터 밀도를 높이는 작업입니다.
✔️ 엔지니어링 포인트
서미스터는 저항이 변하는 소자이므로 전압 분배 회로를 사용합니다.
Vout = Vcc × (R_thermistor / (R_fixed + R_thermistor))
이때,
R_fixed (가변저항)을 조절하면
특정 온도 구간에서 전압 변화 폭이 달라집니다
👉 핵심 전략:
관심 온도 (예: 25°C)에서 ADC 값이 "중간값(≈512)" 근처가 되도록 조정
이렇게 하면:
1°C당 더 많은 ADC 값을 사용하게 됨
해상도(Resolution) 최대화
✔️ 비유
자 위의 눈금이 흐릿하면 측정 자체가 불가능합니다.
가변저항 조정은
👉 희미한 눈금을 진하고 촘촘하게 다시 그리는 작업
이 단계에서 우리는
👉 좋은 "생데이터"를 확보합니다.
2️⃣ 로그 보정: 휘어진 막대기를 반듯하게 (Linearization)
데이터를 촘촘하게 모았다고 끝이 아닙니다.
서미스터는 본질적으로 비선형 소자입니다.
👉 온도 vs 저항 관계:
R ∝ e^(1/T)
즉,
온도가 올라갈수록 저항은 지수적으로 감소
전압은 곡선 형태로 변화
✔️ 문제
저온 영역 → 변화 큼
고온 영역 → 변화 작음
👉 결과:
같은 1°C라도 구간마다 민감도가 다름
✔️ 해결 방법: 로그(Log) 변환
대표 공식: Steinhart-Hart Equation
1/T = A + B·ln(R) + C·(ln(R))^3
또는 단순화된 Beta 모델:
1/T = 1/T0 + (1/B)·ln(R/R0)
👉 로그를 취하면:
지수 곡선 → 직선에 가까워짐
전체 구간에서 균일한 감도 확보
✔️ 비유
자의 막대기 자체가 활처럼 휘어 있음
로그 보정은
👉 휘어진 막대를 일직선으로 펴는 작업
이제:
형태는 완벽한 직선
하지만 아직 "온도 값"은 없음
3️⃣ 투 포인트 보정: 숫자를 정확히 찍기 (Calibration)
수식이 완벽해도 실제 부품은 오차가 존재합니다.
👉 이유:
제조 편차
B값 오차
ADC 오차
✔️ 해결 방법: 2점 보정 (Two-Point Calibration)
두 개의 기준점을 실제로 측정합니다.
| 기준점 | 예시 |
|---|---|
| 저온 (T1) | 얼음물 (0°C) |
| 고온 (T2) | 기준 온도계 (예: 40°C) |
✔️ 보정 수식
선형 보정:
T_corrected = a × T_raw + b
계수 계산:
a = (T2 - T1) / (Raw2 - Raw1)
b = T1 - a × Raw1
✔️ 비유
이제 반듯한 막대기를 들고 표준 온도계 옆에 선다
그리고:
0°C 위치에 "0" 표시
40°C 위치에 "40" 표시
👉 이 과정이 바로 캘리브레이션
🎯 결론: 왜 이 3단계가 모두 필요한가?
이 중 하나라도 빠지면 실패합니다.
❌ 가변저항이 없으면
데이터가 듬성듬성
온도가 계단처럼 변함
👉 저해상도
❌ 로그 보정이 없으면
중간 온도에서 오차 발생
👉 비선형 오차
❌ 투 포인트 보정이 없으면
값이 실제 온도와 다름
👉 정확도 부족
✅ 핵심 요약
✔ 가변저항 → 밀도 확보
✔ 로그 → 형태 교정
✔ 보정 → 값 확정
👉 이 3단계를 거쳐야
진짜 "정밀 온도계"가 완성됩니다
💡 Tip (실전 구현)
#include <math.h>
// 캘리브레이션 데이터
const float T1 = 0.0;
const float T2 = 40.0;
const float Raw1 = 320.0;
const float Raw2 = 580.0;
float calibrate(float raw) {
float a = (T2 - T1) / (Raw2 - Raw1);
float b = T1 - a * Raw1;
return a * raw + b;
}
👉 팁:
캘리브레이션 값은 코드 상단에 상수로 관리
유지보수 / 재보정이 매우 쉬워짐
🧪 [실험 코드] 서미스터 비선형 vs 로그 보정 비교
앞서 설명한 내용을 실제로 눈으로 확인해보기 위해
👉 NTC 서미스터의 비선형성과 로그 보정 효과를 시뮬레이션해보겠습니다.
이 코드는 다음 두 가지를 비교합니다:
❌ 단순 선형 보정 (map 방식)
✅ 로그 기반 보정 (Steinhart-Hart)
📌 전체 코드
import numpy as np
import matplotlib.pyplot as plt
# 1. 설정값 (NTC 서미스터 및 전압 분배 회로)
B_CONSTANT = 3950 # 서미스터 특성 계수 (B-constant)
R_NOMINAL = 10000 # 25도에서의 저항 (10kΩ)
T_NOMINAL = 25 + 273.15 # 기준 온도 (Kelvin)
R_FIXED = 1000 # 가변저항 설정 (비선형성 강조를 위해 낮게 설정)
V_IN = 5.0 # 입력 전압 (5V)
# 2. 온도 범위 설정 (0도 ~ 50도) → "실제 온도"
true_celsius = np.linspace(0, 50, 100)
true_kelvin = true_celsius + 273.15
# 3. 물리적 현상 시뮬레이션
# (온도 → 저항 → 전압)
# 서미스터 저항 (지수 함수)
r_sensor = R_NOMINAL * np.exp(
B_CONSTANT * (1/true_kelvin - 1/T_NOMINAL)
)
# 전압 분배 (아두이노 A0 입력값)
v_out = V_IN * (r_sensor / (R_FIXED + r_sensor))
# 4. ❌ 보정 방식 A: 단순 선형 해석 (map 방식)
# 0도와 50도 기준으로 직선 매핑
v_start = v_out[0] # 0°C 전압
v_end = v_out[-1] # 50°C 전압
temp_linear = (v_out - v_start) * (50 - 0) / (v_end - v_start) + 0
# 5. ✅ 보정 방식 B: 로그 보정 (Steinhart-Hart / Beta 모델)
# 전압 → 저항 역산
r_calc = R_FIXED * (v_out / (V_IN - v_out))
# 로그 적용
inv_t = (1/T_NOMINAL) + (1/B_CONSTANT) * np.log(r_calc / R_NOMINAL)
# Kelvin → Celsius 변환
temp_log = (1/inv_t) - 273.15
# --- 그래프 시각화 ---
plt.figure(figsize=(14, 6))
# [왼쪽] 센서 자체의 비선형 출력
plt.subplot(1, 2, 1)
plt.plot(true_celsius, v_out, linewidth=2.5, label='Actual Sensor Output')
plt.title('Step 1: Sensor Output (Non-linear)')
plt.xlabel('Real Temperature (°C)')
plt.ylabel('Voltage (V)')
plt.grid(True, linestyle='--')
plt.legend()
# [오른쪽] 해석 방식 비교
plt.subplot(1, 2, 2)
# 로그 보정 (정확)
plt.plot(v_out, temp_log, linewidth=2.5,
label='Logarithmic Calibration (Accurate)')
# 선형 보정 (오차 발생)
plt.plot(v_out, temp_linear, linestyle='--',
linewidth=2,
label='Simple Linear Mapping (Inaccurate)')
plt.title('Step 2: Linear vs Log Interpretation')
plt.xlabel('Input Voltage (V)')
plt.ylabel('Calculated Temperature (°C)')
plt.legend()
plt.grid(True, linestyle='--')
plt.tight_layout()
plt.show()
🔍 코드 해설 (핵심만)
1️⃣ 물리 모델
r_sensor = R_NOMINAL * np.exp(...)
👉 서미스터는 지수 함수 형태로 동작
2️⃣ 전압 변환
v_out = V_IN * (r_sensor / (R_FIXED + r_sensor))
👉 아두이노는 "전압"만 읽을 수 있기 때문에
👉 반드시 전압 분배 회로 필요
3️⃣ 선형 방식 (실패 사례)
temp_linear = ...
👉 map()과 동일한 방식
👉 양 끝은 맞지만 중간에서 틀어짐
4️⃣ 로그 방식 (정답)
np.log(...)
👉 지수 → 로그로 변환
👉 곡선을 직선으로 펴는 핵심 단계
📊 결과 해석
✔️ 왼쪽 그래프
👉 온도 vs 전압
완벽한 직선 ❌
곡선 형태 (비선형)
✔️ 오른쪽 그래프
👉 전압 → 온도 변환 결과
🔵 로그 보정
실제 온도와 거의 일치
전 구간 정확
🟢 선형 보정
양 끝은 맞음
중간 구간 오차 발생
🎯 핵심 결론
이 실험이 보여주는 것:
❌ map() → 틀린 온도계
✅ log() → 진짜 온도계
💡 실무에서 중요한 포인트
이 코드에서 일부러:
R_FIXED = 1000
👉 이렇게 낮게 설정한 이유:
비선형을 더 극적으로 드러내기 위함
실제 설계에서는:
측정 범위에 맞게 저항값 튜닝 필요
🚀 한 줄 요약
서미스터는 "전압 → 온도"가 아니라
"전압 → 저항 → 로그 → 온도" 순서로 풀어야 한다
끝.

댓글
댓글 쓰기