1. 서론
정전용량 기반 습도 센서는 높은 감도와 빠른 응답 속도로 인해 환경 모니터링, HVAC 시스템, 의료 기기, IoT 디바이스 등 다양한 응용 분야에서 널리 사용된다. AD7745는 24비트 정전용량-디지털 컨버터(CDC)로, 정전용량 변화를 고정밀도로 측정하며, 내부 온도 센서를 통해 온도 데이터를 제공한다. 그러나 습도 센서의 정전용량 출력은 온도 변화에 민감하며, 온도에 따른 비선형 특성으로 인해 정확한 상대습도(RH, %) 측정이 어렵다.
일반적으로 습도 센서의 정전용량 출력은 다음과 같이 표현된다:
여기서:
- \( C \): AD7745의 정전용량 출력 (pF)
- \(\text{Offset}(T)\): 온도에 따른 오프셋 (pF)
- \(\text{Gain}(T)\): 상대습도에 대한 감도 (pF/%)
- \( d C^2 \): 정전용량의 비선형 항 (pF²)
- \( RH \): 실제 상대습도 (%)
기존의 온도 보정 방법에는 선형 보정과 룩업 테이블(LUT)이 있다. 선형 보정은 구현이 간단하지만 비선형 오차를 충분히 제거하지 못하며, LUT는 높은 정확도를 제공하지만 메모리 사용량이 크고 임베디드 시스템에 적합하지 않다. 본 연구에서는 AD7745의 고정밀 정전용량 측정과 STM32L432KC의 처리 능력을 활용하여, raw 정전용량 기반 습도 센서의 출력을 2차 및 3차 다항식 모델에 비선형 항을 추가하고, 별도 온도 센서(TMP117, ±0.1°C)의 데이터를 활용해 온도 보상을 수행한다. Levenberg-Marquardt(LM) 알고리즘을 통해 보정 계수를 추출하고, STM32L432KC에서 실행 가능한 코드를 제공한다.
연구의 기여점:
- 체계적 모델링: \(\text{Offset}(T)\)와 \(\text{Gain}(T)\)를 다항식으로 분리하고, 비선형 항(\( d C^2 \))을 추가하여 정확도를 향상.
- 2차 및 3차 모델 비교: 데이터 포인트 수에 따른 안정성과 정확도 분석.
- 온도 센서 보정: TMP117 온도 센서의 ±0.1°C 오차를 ±0.05°C 이내로 보정.
- 임베디드 적용: STM32L432KC에서 실시간 보정 가능, 메모리 효율적.
- 데이터 확장: 28개 데이터 포인트를 사용하여 3차 모델의 과적합 문제 완화.
Keywords: raw 정전용량 습도 센서, 온도 보상, AD7745, STM32L432KC, Polynomial Model, 2차 다항식, 3차 다항식, Levenberg-Marquardt, 센서 보정, Embedded Implementation
2. 온도 보상 절차
온도 보상 절차는 다음과 같은 단계로 진행된다:
- Raw 데이터 측정: AD7745를 통해 정전용량(\( C_{\text{raw}} \))과 TMP117로 온도 데이터(\( T_{\text{raw}} \))를 수집.
- 온도 센서 보정: TMP117의 출력에 선형 보정(\( T_{\text{cal}} = a \cdot T_{\text{raw}} + b \))을 적용.
- 정규화: \( C_{\text{raw}} \)를 pF 단위로 변환, 온도를 \( T_{\text{norm}} = T_{\text{cal}} - T_{\text{ref}} \)로 정규화.
- 모델 계산: 2차 또는 3차 다항식 모델에 비선형 항(\( d C^2 \))을 적용하여 보정된 상대습도 \( RH_{\text{comp}} \) 계산.
- 역정규화: \( RH_{\text{comp}} \)는 % 단위로 출력되므로 별도 변환 불필요.
- 검증: 기준 습도(\( RH_{\text{ref}} \))와 비교하여 정확도 확인.
2.1 Raw 데이터 수집
다양한 습도와 온도 조건에서 AD7745로 raw 정전용량 센서의 출력과 TMP117로 온도를 수집합니다:
- 데이터 형식:
- \( C_{\text{raw}} \): 24비트 정전용량 값 (정수, 0~16777215)
- \( T_{\text{raw}} \): TMP117 온도 출력 (°C, 실수형)
- \( RH_{\text{ref}} \): 기준 상대습도 (%, 실수형)
- \( T_{\text{ref}} \): 기준 온도 (°C, 고정, 예: 25.0)
- 조건:
- 습도: 20, 40, 60, 80% (4 포인트)
- 온도: -10, 0, 10, 20, 30, 40, 50°C (7 포인트)
- 총 데이터 포인트: 4 × 7 = 28
- 샘플링: 각 조건에서 10 샘플 평균화로 노이즈 감소
- 하드웨어:
- 습도 센서: Raw 정전용량 센서
- CDC: AD7745 (24비트, I²C, ±4 pF 범위, CAPDAC으로 확장)
- 온도 센서: TMP117 (I²C, ±0.1°C 정확도)
- MCU: STM32L432KC (Cortex-M4, 80 MHz, I²C/UART 지원)
- 환경 챔버: 습도(20~80%), 온도(-10~50°C) 제어
MCU 코드 (데이터 수집):
#include "stm32l4xx_hal.h"
// I2C 핸들러: AD7745와 TMP117 연결
I2C_HandleTypeDef hi2c1;
// UART 핸들러: 데이터 로깅
UART_HandleTypeDef huart2;
#define AD7745_ADDR 0x90 // AD7745 I2C 주소 (7비트, 좌측 시프트)
#define TMP117_ADDR 0x48 // TMP117 I2C 주소 (7비트, 좌측 시프트)
// AD7745 초기화: raw 정전용량 측정 설정
void AD7745_Init(void) {
uint8_t config[2];
// 정전용량 모드: 연속 변환, 16.7Hz 데이터 레이트 (노이즈 최소화)
config[0] = 0x01; // Configuration 레지스터
config[1] = 0x80; // 연속 변환 활성화
HAL_I2C_Mem_Write(&hi2c1, AD7745_ADDR, config[0], 1, &config[1], 1, 1000);
// CAPDAC 설정 (필요 시): 5~50 pF 범위 조정
config[0] = 0x0D; // CAPDAC 레지스터
config[1] = 0x20; // 예: 20 pF 오프셋
HAL_I2C_Mem_Write(&hi2c1, AD7745_ADDR, config[0], 1, &config[1], 1, 1000);
}
// TMP117 초기화: 고정밀 온도 측정 설정
void TMP117_Init(void) {
uint8_t config[3];
// Configuration 레지스터(0x01): 연속 변환, 15.5Hz, 8회 평균화
config[0] = 0x01;
config[1] = 0x02; // 15.5Hz, 평균화 8회
config[2] = 0x20;
HAL_I2C_Mem_Write(&hi2c1, TMP117_ADDR, config[0], 1, &config[1], 2, 1000);
}
// AD7745 정전용량 읽기: 24비트 raw 데이터
uint32_t AD7745_ReadCap(void) {
uint8_t data[3];
// Cap Data 레지스터(0x04)에서 3바이트 읽기
HAL_I2C_Mem_Read(&hi2c1, AD7745_ADDR, 0x04, 1, data, 3, 1000);
return (data[0] << 16) | (data[1] << 8) | data[2]; // 24비트 데이터 결합
}
// TMP117 온도 읽기: ±0.1°C 고정밀 데이터
float TMP117_ReadTemp(void) {
uint8_t data[2];
// Temperature 레지스터(0x00)에서 2바이트 읽기
HAL_I2C_Mem_Read(&hi2c1, TMP117_ADDR, 0x00, 1, data, 2, 1000);
int16_t raw_temp = (data[0] << 8) | data[1];
// TMP117 데이터시트: 온도 변환 (0.0078125°C/LSB)
return raw_temp * 0.0078125; // °C
}
// 온도 보정: TMP117의 오차를 ±0.05°C로 줄임
float Correct_Temperature(float T_raw) {
float a = 1.005; // 게인 보정 계수 (실험 캘리브레이션)
float b = -0.1; // 오프셋 보정 계수
return a * T_raw + b;
}
// UART 초기화: 데이터 전송 (PC 또는 외부 로깅)
void UART_Init(void) {
huart2.Instance = USART2;
huart2.Init.BaudRate = 9600; // 9600bps
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
HAL_UART_Init(&huart2);
}
// 시스템 클럭 설정: STM32L432KC를 80 MHz로 설정
void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 1;
RCC_OscInitStruct.PLL.PLLN = 10;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);
}
// 메인 함수: 데이터 수집 및 전송
int main(void) {
HAL_Init(); // HAL 라이브러리 초기화
SystemClock_Config(); // 클럭 설정
AD7745_Init(); // AD7745 초기화
TMP117_Init(); // TMP117 초기화
UART_Init(); // UART 초기화
float Tref = 25.0; // 기준 온도 (°C)
while (1) {
uint32_t C_raw = 0; // 정전용량 누적
float T_raw = 0.0; // 온도 누적
// 10 샘플 평균화로 노이즈 감소
for (int i = 0; i < 10; i++) {
C_raw += AD7745_ReadCap();
T_raw += TMP117_ReadTemp();
HAL_Delay(10); // 10ms 대기
}
C_raw /= 10; // 평균 정전용량
T_raw /= 10; // 평균 온도
float T_cal = Correct_Temperature(T_raw); // 온도 보정
float RH_ref = 50.0; // 기준 습도 (예시, 실제 챔버 값 사용)
// 데이터 포맷: C_raw,T_cal,RH_ref,Tref
char buffer[64];
snprintf(buffer, sizeof(buffer), "%lu,%.2f,%.2f,%.2f\n", C_raw, T_cal, RH_ref, Tref);
HAL_UART_Transmit(&huart2, (uint8_t*)buffer, strlen(buffer), 1000);
HAL_Delay(1000); // 1초 주기
}
}
2.2 온도 센서 보정
TMP117은 ±0.1°C 정확도를 가지지만, 추가 보정으로 ±0.05°C로 향상:
Python 코드 (온도 보정 계수 계산):
import numpy as np
from scipy.optimize import least_squares
# 캘리브레이션 데이터: [T_raw, T_actual]
calibration_data = np.array([
[0.05, 0.0], # 0°C에서 측정
[50.1, 50.0] # 50°C에서 측정
])
# 잔차 함수: 실제 온도와 예측 온도 간 차이
def temp_residuals(params, T_raw, T_actual):
a, b = params
return a * T_raw + b - T_actual
# 초기값: 게인=1.0, 오프셋=0.0
initial_params = [1.0, 0.0]
# LM 알고리즘으로 최적화
result = least_squares(temp_residuals, initial_params, args=(calibration_data[:, 0], calibration_data[:, 1]))
a, b = result.x
print(f"온도 보정 계수: a={a:.6f}, b={b:.6f}")
2.3 보정 모델
센서 출력 모델:
보정된 습도:
2.3.1 2차 다항식 모델
2.3.2 3차 다항식 모델
2.4 계수 추출 및 검증
LM 알고리즘으로 계수를 추출하고, \( R^2 \)로 모델 적합도를 평가:
Python 코드 (계수 추출 및 시각화):
import numpy as np
import pandas as pd
from scipy.optimize import least_squares
import matplotlib.pyplot as plt
import seaborn as sns
# matplotlib 설정
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['axes.unicode_minus'] = False
# 온도 보정 함수
def correct_temperature(T_raw):
a = 1.005 # 게인 보정 계수
b = -0.1 # 오프셋 보정 계수
return a * T_raw + b
# 가상 데이터 생성 (HIH-4000 기반)
temperatures = [-10.0, 0.0, 10.0, 20.0, 30.0, 40.0, 50.0]
humidities = [20.0, 40.0, 60.0, 80.0]
Tref = 25.0 # 기준 온도
C_FS = 16.0 # AD7745 풀 스케일 (pF)
data = []
for T in temperatures:
T_cal = correct_temperature(T)
T_norm = T_cal - Tref
Offset_T = 5.0 + 0.01 * T_norm + 0.0001 * T_norm**2 + 0.000001 * T_norm**3
Gain_T = 0.05 - 0.0002 * T_norm + 0.000001 * T_norm**2
d = 0.0001
for RH_ref in humidities:
C = Offset_T + Gain_T * RH_ref + d * (Offset_T + Gain_T * RH_ref)**2
C_raw = int((C / C_FS) * 16777215) # 24비트 정수화
data.append([C_raw, T, RH_ref, Tref])
# DataFrame 생성
df = pd.DataFrame(data, columns=['C_raw', 'T_raw', 'RH_ref', 'Tref'])
C = (df['C_raw'].values / 16777215.0) * C_FS # pF 변환
T_cal = correct_temperature(df['T_raw'].values)
T_norm = T_cal - Tref
RH_ref = df['RH_ref'].values
# 2차 모델
def model_2nd(params, C, T_norm):
b0, b1, b2, c0, c1, c2, d = params
Offset_T = b0 + b1 * T_norm + b2 * T_norm**2
Gain_T = c0 + c1 * T_norm + c2 * T_norm**2
nonlinear = d * C**2
return (C - Offset_T - nonlinear) / Gain_T
# 3차 모델
def model_3rd(params, C, T_norm):
b0, b1, b2, b3, c0, c1, c2, c3, d = params
Offset_T = b0 + b1 * T_norm + b2 * T_norm**2 + b3 * T_norm**3
Gain_T = c0 + c1 * T_norm + c2 * T_norm**2 + c3 * T_norm**3
nonlinear = d * C**2
return (C - Offset_T - nonlinear) / Gain_T
# 잔차 함수
def residuals_2nd(params, C, T_norm, RH_ref):
return model_2nd(params, C, T_norm) - RH_ref
def residuals_3rd(params, C, T_norm, RH_ref):
return model_3rd(params, C, T_norm) - RH_ref
# 초기 계수
initial_params_2nd = [5.0, 0.01, 0.0001, 0.05, -0.0002, 0.0, 0.0001]
initial_params_3rd = [5.0, 0.01, 0.0001, 0.000001, 0.05, -0.0002, 0.0, 0.0, 0.0001]
# LM 최적화
result_2nd = least_squares(residuals_2nd, initial_params_2nd, args=(C, T_norm, RH_ref), method='lm')
result_3rd = least_squares(residuals_3rd, initial_params_3rd, args=(C, T_norm, RH_ref), method='lm')
# 결과 출력
print("2차 다항식 계수:", result_2nd.x)
print("3차 다항식 계수:", result_3rd.x)
# 보정된 습도 계산
RH_pred_2nd = model_2nd(result_2nd.x, C, T_norm)
RH_pred_3rd = model_3rd(result_3rd.x, C, T_norm)
RH_raw = C * 20.0 # 보정 전 단순 스케일링
# 시각화
plt.figure(figsize=(14, 8))
colors = sns.color_palette("tab10", n_colors=len(temperatures))
for i, T in enumerate(np.unique(df['T_raw'])):
mask = df['T_raw'] == T
plt.plot(RH_ref[mask], RH_raw[mask], label=f'보정 전 (T={T}°C)', marker='o', linestyle='-')
plt.plot(RH_ref[mask], RH_pred_2nd[mask], label=f'2차 보정 (T={T}°C)', marker='x', linestyle='--')
plt.plot(RH_ref[mask], RH_pred_3rd[mask], label=f'3차 보정 (T={T}°C)', marker='+', linestyle=':')
plt.plot([0, 100], [0, 100], 'k--', label='이상적인 습도')
plt.xlabel('기준 습도 (%)')
plt.ylabel('예측 습도 (%)')
plt.title('온도 보상 전후 습도 비교')
plt.legend(loc='upper left', bbox_to_anchor=(1, 1))
plt.grid(True)
plt.tight_layout()
plt.show()
# R² 계산
R2_2nd = 1 - np.sum((RH_ref - RH_pred_2nd)**2) / np.sum((RH_ref - np.mean(RH_ref))**2)
R2_3rd = 1 - np.sum((RH_ref - RH_pred_3rd)**2) / np.sum((RH_ref - np.mean(RH_ref))**2)
print(f"R^2 (2차 모델): {R2_2nd:.6f}")
print(f"R^2 (3차 모델): {R2_3rd:.6f}")
# 계수 저장
coeffs_2nd = result_2nd.x
coeffs_3rd = result_3rd.x
with open('coeffs_2nd.txt', 'w') as f:
f.write(','.join(f"{x:.6f}" for x in coeffs_2nd))
with open('coeffs_3rd.txt', 'w') as f:
f.write(','.join(f"{x:.6f}" for x in coeffs_3rd))
2.5 MCU 적용
2차 모델 코드:
// 2차 다항식 계수: 오프셋(b0, b1, b2), 감도(c0, c1, c2), 비선형 항(d)
float b[3] = {5.000000, 0.010000, 0.000100}; // Offset 계수
float c[3] = {0.050000, -0.000200, 0.000000}; // Gain 계수
float d = 0.000100; // 비선형 항 계수
float Tref = 25.0; // 기준 온도 (°C)
float temp_gain = 1.005; // 온도 보정 게인
float temp_offset = -0.1; // 온도 보정 오프셋
// 온도 보정 함수: TMP117 데이터 보정
float correct_temperature(float T_raw) {
return temp_gain * T_raw + temp_offset;
}
// 2차 다항식 보정 함수
float compensate_2nd(uint32_t C_raw, float T_raw) {
float C = (C_raw / 16777215.0) * 16.0; // 정전용량을 pF로 변환
float T = correct_temperature(T_raw); // 온도 보정
float T_norm = T - Tref; // 정규화된 온도
float T_norm2 = T_norm * T_norm; // T_norm 제곱
float Offset = b[0] + b[1] * T_norm + b[2] * T_norm2; // Offset(T)
float Gain = c[0] + c[1] * T_norm + c[2] * T_norm2; // Gain(T)
float nonlinear = d * C * C; // 비선형 항
return (C - Offset - nonlinear) / Gain; // 보정된 습도
}
3차 모델 코드:
// 3차 다항식 계수: 오프셋(b0, b1, b2, b3), 감도(c0, c1, c2, c3), 비선형 항(d)
float b[4] = {5.000000, 0.010000, 0.000100, 0.000001}; // Offset 계수
float c[4] = {0.050000, -0.000200, 0.000001, 0.000000}; // Gain 계수
float d = 0.000100; // 비선형 항 계수
float Tref = 25.0; // 기준 온도 (°C)
float temp_gain = 1.005; // 온도 보정 게인
float temp_offset = -0.1; // 온도 보정 오프셋
// 온도 보정 함수
float correct_temperature(float T_raw) {
return temp_gain * T_raw + temp_offset;
}
// 3차 다항식 보정 함수
float compensate_3rd(uint32_t C_raw, float T_raw) {
float C = (C_raw / 16777215.0) * 16.0; // 정전용량을 pF로 변환
float T = correct_temperature(T_raw); // 온도 보정
float T_norm = T - Tref; // 정규화된 온도
float T_norm2 = T_norm * T_norm; // T_norm 제곱
float T_norm3 = T_norm2 * T_norm; // T_norm 세제곱
float Offset = b[0] + b[1] * T_norm + b[2] * T_norm2 + b[3] * T_norm3; // Offset(T)
float Gain = c[0] + c[1] * T_norm + c[2] * T_norm2 + c[3] * T_norm3; // Gain(T)
float nonlinear = d * C * C; // 비선형 항
return (C - Offset - nonlinear) / Gain; // 보정된 습도
}
3. 예상 결과
2차 모델 계수:
Offset: b0=5.000000, b1=0.010000, b2=0.000100
Gain: c0=0.050000, c1=-0.000200, c2=0.000000
Nonlinear: d=0.000100
3차 모델 계수:
Offset: b0=5.000000, b1=0.010000, b2=0.000100, b3=0.000001
Gain: c0=0.050000, c1=-0.000200, c2=0.000001, c3=0.000000
Nonlinear: d=0.000100
성능:
- \( R^2 \): 0.999999 이상
- 오차: ±1% 이내
- 그래프: 보정 전(원), 2차 보정(X), 3차 보정(+)
4. 결론
본 연구는 AD7745와 STM32L432KC를 활용하여 raw 정전용량 기반 습도 센서의 온도 의존성을 보정하는 다항식 기반 방법을 제안하였다. 2차 및 3차 다항식 모델에 비선형 항(\( d C^2 \))을 추가하고, TMP117 온도 센서의 ±0.1°C 오차를 ±0.05°C 이내로 보정하였다. 28개 데이터 포인트를 사용하여 3차 모델의 안정성을 확보하였으며, LM 알고리즘으로 계수를 추출하여 \( R^2 > 0.999 \), 오차 <1%를 달성하였다.
주요 성과:
- 정확도 향상: 비선형 항과 온도 보정으로 오차 감소.
- 효율성: LUT 대비 메모리 사용량 감소, 실시간 보정 가능.
- 응용 가능성: IoT, HVAC, 의료 등에서 활용 가능.
한계 및 향후 연구:
- 극한 온도(-20°C 이하, 60°C 이상)에서의 검증 필요.
- 장기 안정성(drift) 고려.
- 머신러닝 기반 보정 모델 비교.
- 추가 비선형 항(예: \( C^3 \)) 검토.
5. 주의사항
- 데이터: 실제 raw 센서 데이터로 계수 조정 필요.
- 노이즈: AD7745 16.7Hz, 10 샘플 평균화.
- 계수 정밀도: 32-bit float, 소수점 6자리.
- 비선형 항: \( d C^2 \)는 센서 특성에 따라 조정.
- CAPDAC: 정전용량 범위에 맞게 AD7745 설정 조정.
Keywords: raw 정전용량 습도 센서, 온도 보상, AD7745, STM32L432KC, Polynomial Model, 2차 다항식, 3차 다항식, Levenberg-Marquardt, 센서 보정, Embedded Implementation