서론
MEMS(Micro Electro Mechanical System) 압력 센서는 소형화, 저전력 소모, 대량 생산에 유리하다는 장점 덕분에 자동차, 항공, 산업 계측, 웨어러블 디바이스 등 다양한 분야에서 활용되고 있다. 특히 웨어러블 기기나 IoT 기반 스마트 센서 네트워크에서는 소형·저비용 압력 센서의 필요성이 증가하면서 MEMS 압력 센서의 중요성이 더욱 부각되고 있다.
그러나 MEMS 압력 센서는 구조적 특성상 온도 변화에 민감하다. 센서 내부의 Wheatstone 브릿지와 기계적 다이어프램은 온도에 따라 저항 및 탄성 특성이 변동하며, 이로 인해 출력 전압은 압력뿐 아니라 온도의 함수가 된다. 일반적으로 압력 센서 출력은 다음과 같이 표현된다.
\[ V = \text{Offset}(T) + \text{Gain}(T) \cdot P \]
여기서 \(\text{Offset}(T)\)는 온도에 따라 변하는 오프셋 전압이며, \(\text{Gain}(T)\)는 압력에 대한 감도이다. 두 요소 모두 온도의 비선형 함수이므로, 단순한 선형 보정만으로는 압력 측정 정확도를 보장하기 어렵다.
기존의 온도 보정 방법으로는 크게 두 가지가 있다. 첫째, 선형 보정(linear compensation) 방식은 구현이 간단하지만 비선형 오차를 충분히 제거하지 못한다. 둘째, 룩업 테이블(Look-Up Table, LUT) 방식은 다양한 온도·압력 지점에서 데이터를 저장하여 보정하는 방식으로 높은 정확도를 제공하지만, 메모리 사용량이 크고 MCU에 적용하기 어렵다는 단점이 있다. 따라서 임베디드 환경에서도 적용 가능한 계산 효율적이면서도 정확한 보정 모델이 필요하다.
본 연구에서는 이 한계를 해결하기 위해, 오프셋과 감도의 온도 의존성을 다항식 모델(polynomial model)로 근사하는 방법을 제안한다. 특히 2차 및 3차 다항식 모델에 비선형 항(예: \( d V^2 \))을 추가하여 보정 정확도를 향상시키고, ADS114S08 내부 온도 센서의 오프셋 및 게인 보정을 통해 온도 데이터의 신뢰성을 높였다. 또한, 3차 모델의 안정성을 위해 데이터 포인트를 18개에서 30개로 확장하여 과적합 문제를 완화하였다. Python 기반 Levenberg–Marquardt(LM) 알고리즘을 통해 보정 계수를 추출하고, STM32L432KC에서 실행 가능한 코드를 제공한다.
본 연구의 기여점은 다음과 같다.
- 보정 모델의 체계적 정식화: 센서 출력을 Offset(T), Gain(T)로 분리하고 비선형 항을 추가하여 모델링함으로써, 직관적이고 수학적으로 명확한 보정 절차를 제시하였다.
- 2차 및 3차 모델의 비교 분석: 데이터 포인트가 적을 때는 2차 모델이 더 안정적이고, 충분한 데이터 확보 시 3차 모델이 더 높은 정확도를 제공함을 실험적으로 검증하였다.
- 온도 센서 보정: ADS114S08 내부 온도 센서의 오프셋 및 게인 보정 절차를 추가하여 온도 데이터의 정확도를 향상시켰다.
- 임베디드 적용 가능성 제시: 도출된 보정 계수를 활용하여 소형 MCU에서 구현 가능한 보정 코드를 제시함으로써, 실제 응용 가능성을 높였다.
이와 같은 연구를 통해 MEMS 압력 센서의 온도 보정 정확도를 향상시킬 수 있으며, 이는 장기 신뢰성과 산업 적용 가능성을 높이는 데 기여할 것으로 기대된다.
Keyworkds: MEMS 압력센서, MEMS Pressure Sensor ,온도보정 ,Temperature Compensation,Offset,Gain,Polynomial Model ,2차 다항식보정, 3차 다항식보정,ADC 보정, ADC Calibration, Levenberg Marquardt ,Normalization,Inverse Normalization,MCU 보정, Embedded Implementation ,센서보정 ,Sensor Calibration
1. 온도 보정 절차
본 연구에서는 다음과 같은 순서로 온도 보정을 수행합니다: raw 데이터 측정 → 온도 센서 보정 → 정규화 → 모델 계산 → 역정규화 → \( P_{\text{comp}} \) (kPa). 각 단계는 다음과 같습니다.
- Raw 데이터 측정: ADS114S08의 16비트 ADC를 통해 MEMS 압력 센서의 출력(raw_ADC)과 내부 온도 센서 데이터를 수집. 기준 압력(P_ref)과 기준 온도(Tref)도 기록.
- 온도 센서 보정: ADS114S08 내부 온도 센서의 출력에 선형 보정(오프셋 및 게인)을 적용하여 정확한 온도(T_cal)를 획득.
- 정규화: raw_ADC를 전압(\( V = \frac{\text{raw_ADC}}{65535} \cdot 2.048 \))으로 변환하고, 온도를 \( T_{\text{norm}} = T_{\text{cal}} - T_{\text{ref}} \)로 정규화.
- 모델 계산: 2차 또는 3차 다항식 모델에 비선형 항(\( d V^2 \))을 추가하여 \( P_{\text{comp}} = \frac{V - \text{Offset}(T)}{\text{Gain}(T)} + d V^2 \) 계산.
- 역정규화: \( P_{\text{comp}} \)는 이미 kPa 단위로 출력되므로 별도 역정규화 불필요. 단, 다른 단위(예: bar)로 변환 필요 시 단위 변환 적용.
- \( P_{\text{comp}} \) (kPa): 최종 보정된 압력 값, 기준 압력(\( P_{\text{ref}} \))과 비교하여 정확도 검증.
이 절차는 계산 효율적이며, STM32L432KC와 같은 임베디드 시스템에서 실시간 보정에 적합합니다.
1.1 Raw 데이터 수집
- 목적: 다양한 압력과 온도 조건에서 ADS114S08의 ADC 출력(raw_ADC), 내부 온도 센서 데이터(T_raw), 기준 압력(P_ref), 기준 온도(Tref)를 수집.
- 데이터 형식:
- 컬럼: (raw_ADC, T_raw, P_ref, Tref)
- raw_ADC: 16비트 ADC 값 (0~65535, 정수)
- T_raw: ADS114S08 내부 온도 센서로 측정한 원시 온도 (°C, 실수형, 예: -20.0)
- P_ref: 기준 압력 (kPa, 실수형, 예: 10.0)
- Tref: 기준 온도 (°C, 고정, 예: 25.0)
- 조건:
- 압력: 10, 30, 50, 70, 90 kPa (5 포인트)
- 온도: -20, -10, 0, 10, 20, 30, 40, 50, 60, 80°C (10 포인트)
- 총 데이터 포인트: 5 × 10 = 30
- 샘플링: 각 조건에서 10 샘플 평균화로 노이즈 감소
- 하드웨어:
- MEMS 압력 센서: Wheatstone 브릿지, 차동 전압 출력, 3.3V 여자
- ADC: ADS114S08 (16비트 델타-시그마, 내부 온도 센서 포함, SPI 통신)
- MCU: STM32L432KC (Cortex-M4, 80 MHz, SPI/UART/I2C 지원)
- 압력/온도 챔버: 정확한 압력 및 온도 제어
- 절차:
- 테스트 환경 설정: 압력 챔버(10, 30, 50, 70, 90 kPa), 온도 챔버(-20~80°C).
- ADS114S08 설정:
- 내부 온도 센서 모드 활성화 (레지스터 설정).
- 압력 센서 차동 입력 (AIN0, AIN1), 내부 VREF (2.048V).
- 데이터 레이트: 20 SPS (노이즈 최소화).
- 데이터 수집:
- 온도 고정 → 압력 10, 30, 50, 70, 90 kPa 순으로 설정.
- 각 조건에서 1~2분 안정화 후 10 샘플 평균.
- ADS114S08 내부 온도 센서 데이터 읽기 및 °C로 변환.
- 데이터 포맷: (raw_ADC, T_raw, P_ref, Tref).
- MCU 코드 예시 (STM32L432KC, ADS114S08 SPI 통신):
#include "stm32l4xx_hal.h"
// SPI 핸들러 (ADS114S08 연결)
SPI_HandleTypeDef hspi1;
// UART 핸들러 (데이터 전송)
UART_HandleTypeDef huart2;
// ADS114S08 초기화 함수
void ADS114S08_Init(void) {
// SPI 설정: STM32L432KC의 SPI1 사용
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER; // 마스터 모드
hspi1.Init.Direction = SPI_DIRECTION_2LINES; // 양방향 통신
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // 8비트 데이터 전송
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // 클럭 폴라리티: Low
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // 클럭 페이즈: 1st edge
hspi1.Init.NSS = SPI_NSS_SOFT; // 소프트웨어 NSS 관리
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; // 클럭 분주: 8
HAL_SPI_Init(&hspi1); // SPI 초기화
// ADS114S08 레지스터 설정
uint8_t reg_data[2];
// 입력 멀티플렉서 설정: AIN0(+)와 AIN1(-) 차동 입력
reg_data[0] = 0x02; // 레지스터 0x02
reg_data[1] = 0x10; // AIN0(+)와 AIN1(-) 선택
HAL_SPI_Transmit(&hspi1, reg_data, 2, 1000);
// 데이터 레이트 설정: 20 SPS (노이즈 최소화)
reg_data[0] = 0x03; // 레지스터 0x03
reg_data[1] = 0x04; // 20 SPS
HAL_SPI_Transmit(&hspi1, reg_data, 2, 1000);
// 내부 온도 센서 활성화
reg_data[0] = 0x0A; // 레지스터 0x0A
reg_data[1] = 0x01; // 온도 센서 모드
HAL_SPI_Transmit(&hspi1, reg_data, 2, 1000);
}
// ADS114S08 ADC 데이터 읽기
uint16_t ADS114S08_ReadADC(void) {
uint8_t cmd = 0x12; // RDATA 명령 (ADC 데이터 읽기)
uint8_t data[2];
HAL_SPI_Transmit(&hspi1, &cmd, 1, 1000); // RDATA 명령 전송
HAL_SPI_Receive(&hspi1, data, 2, 1000); // 16비트 데이터 수신
return (data[0] << 8) | data[1]; // 상위/하위 바이트 결합
}
// ADS114S08 내부 온도 센서 데이터 읽기 및 °C로 변환
float ADS114S08_ReadTempRaw(void) {
uint8_t cmd = 0x12; // RDATA 명령
uint8_t data[2];
uint8_t reg_data[2];
// 온도 센서 모드 활성화
reg_data[0] = 0x0A; // 레지스터 0x0A
reg_data[1] = 0x01; // 내부 온도 센서 선택
HAL_SPI_Transmit(&hspi1, reg_data, 2, 1000);
HAL_SPI_Transmit(&hspi1, &cmd, 1, 1000); // RDATA 명령 전송
HAL_SPI_Receive(&hspi1, data, 2, 1000); // 16비트 데이터 수신
uint16_t raw_temp = (data[0] << 8) | data[1];
// 전압 변환: raw_temp를 2.048V 기준 전압으로 정규화
float voltage = (raw_temp / 65535.0) * 2.048;
// 온도 변환: ADS114S08 데이터시트 기준 (0.4 mV/°C)
return (voltage / 0.0004);
}
// 온도 센서 보정 함수
// 입력: T_raw (원시 온도, °C)
// 출력: T_cal (보정된 온도, °C)
float ADS114S08_CorrectTemp(float T_raw) {
// 선형 보정: T_cal = a * T_raw + b
float a = 1.02; // 게인 보정 계수 (예: 데이터시트 기준 ±2°C 오차 보정)
float b = -0.5; // 오프셋 보정 계수 (예: -0.5°C 오프셋)
return a * T_raw + b;
}
// UART 초기화 함수
void UART_Init(void) {
huart2.Instance = USART2; // USART2 사용
huart2.Init.BaudRate = 9600; // 보율: 9600
huart2.Init.WordLength = UART_WORDLENGTH_8B; // 8비트 데이터
huart2.Init.StopBits = UART_STOPBITS_1; // 1 스톱 비트
huart2.Init.Parity = UART_PARITY_NONE; // 패리티 없음
HAL_UART_Init(&huart2); // UART 초기화
}
// 기준 압력 획득 (예: 외부 입력 또는 고정 값)
float getReferencePressure(void) {
// 실제 구현에서는 압력 챔버의 기준 압력 값을 반환
// 예시로 고정 값 사용
return 50.0; // kPa
}
// 메인 함수
int main(void) {
// STM32 HAL 초기화
HAL_Init();
// 시스템 클럭 설정 (STM32L432KC, 80 MHz)
SystemClock_Config();
// SPI 및 UART 초기화
ADS114S08_Init();
UART_Init();
// 기준 온도
float Tref = 25.0;
// 무한 루프
while (1) {
// ADC 데이터 및 온도 데이터 수집
long raw_ADC = 0;
float T_raw = 0.0;
// 10 샘플 평균화로 노이즈 감소
for (int i = 0; i < 10; i++) {
raw_ADC += ADS114S08_ReadADC();
T_raw += ADS114S08_ReadTempRaw();
HAL_Delay(10); // 10ms 대기
}
raw_ADC /= 10; // 평균 ADC 값
T_raw /= 10; // 평균 원시 온도
float T_cal = ADS114S08_CorrectTemp(T_raw); // 온도 보정
float P_ref = getReferencePressure(); // 기준 압력
// 데이터 포맷: raw_ADC,T_cal,P_ref,Tref
char buffer[64];
snprintf(buffer, sizeof(buffer), "%ld,%.2f,%.2f,%.2f\n", raw_ADC, T_cal, P_ref, Tref);
// UART로 데이터 전송
HAL_UART_Transmit(&huart2, (uint8_t*)buffer, strlen(buffer), 1000);
// 1초 대기
HAL_Delay(1000);
}
return 0;
}
// 시스템 클럭 설정 (STM32L432KC, 예: 80 MHz)
void SystemClock_Config(void) {
// PLL 및 클럭 소스 설정 (기본 80 MHz 설정)
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);
}
- 주의사항:
- 노이즈 관리: ADS114S08의 20 SPS 설정으로 노이즈 최소화, 10 샘플 평균화.
- 정확도: P_ref는 고정밀 게이지, T_cal은 보정된 온도 사용.
- Tref: 25°C 표준 (센서 스펙에 따라 조정).
- 온도 센서 보정: 기준 온도(예: 0°C, 50°C)에서 수집된 데이터로 계수 a, b 조정.
1.2 온도 센서 보정
ADS114S08 내부 온도 센서는 ±2°C의 오차를 가질 수 있으므로, 선형 보정 모델을 적용하여 정확도를 향상시킨다.
\[ T_{\text{cal}} = a \cdot T_{\text{raw}} + b \]
- \( T_{\text{raw}} \): 원시 온도 (°C)
- \( T_{\text{cal}} \): 보정된 온도 (°C)
- \( a \): 게인 보정 계수 (예: 1.02, 데이터시트 기준 ±2°C 오차 보정)
- \( b \): 오프셋 보정 계수 (예: -0.5°C)
보정 절차:
- 기준 온도에서 데이터 수집: 예: 0°C와 50°C에서 \( T_{\text{raw}} \)와 실제 온도(\( T_{\text{ref}} \)) 측정.
- 선형 회귀: \( T_{\text{cal}} = a \cdot T_{\text{raw}} + b \)의 계수 \( a, b \) 계산.
- 적용: 모든 \( T_{\text{raw}} \)에 대해 \( T_{\text{cal}} \) 계산.
MCU 코드 예시 (온도 보정):
// 온도 센서 보정 계수
float temp_gain = 1.02; // 게인 보정 계수
float temp_offset = -0.5; // 오프셋 보정 계수
// 온도 보정 함수
float ADS114S08_CorrectTemp(float T_raw) {
return temp_gain * T_raw + temp_offset;
}
Python 코드 예시 (온도 보정 계수 계산):
import numpy as np
from scipy.optimize import least_squares
# 기준 데이터: 예: (T_raw, T_actual)
calibration_data = np.array([
[0.2, 0.0], # (T_raw, T_actual) at 0°C
[50.3, 50.0] # (T_raw, T_actual) at 50°C
])
def temp_residuals(params, T_raw, T_actual):
a, b = params
return a * T_raw + b - T_actual
# 초기값
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"Temperature Calibration: a={a:.6f}, b={b:.6f}")
1.3 보정 모델 및 수식 유도
MEMS 압력 센서의 출력은 온도에 따라 오프셋과 게인이 변동하므로 온도 보상 모델이 필요합니다.
1.3.1 센서 출력 모델
Wheatstone 브릿지 기반 MEMS 센서는 압력(\( P \))에 비례하는 차동 전압(\( V_{\text{bridge}} \))을 출력합니다:
\[ V_{\text{bridge}} = \text{Gain}(T) \cdot P + \text{Offset}(T) + d V^2 \]
- \( \text{Gain}(T) \): 센서 감도 (게인, V/kPa)
- \( \text{Offset}(T) \): 오프셋 전압 (V)
- \( d V^2 \): 비선형 항 (V², 센서 비선형성 보정)
- \( P \): 실제 압력 (kPa)
ADS114S08 ADC 출력(raw_ADC)은 전압을 16비트로 변환:
\[ V = \frac{\text{raw_ADC}}{65535} \cdot V_{\text{REF}} \]
여기서 \( V_{\text{REF}} = 2.048V \) (내부 기준 전압). 센서 출력 모델:
\[ V = \text{Offset}(T) + \text{Gain}(T) \cdot P + d V^2 \]
1.3.2 보정 모델 유도
목표: 측정된 \( V \)와 \( T_{\text{cal}} \)를 사용해 실제 압력 \( P \)를 추정하는 \( P_{\text{comp}} \).
센서 출력 모델에서 \( P \)를 분리:
\[ V = \text{Offset}(T) + \text{Gain}(T) \cdot P + d V^2 \] \[ P = \frac{V - \text{Offset}(T) - d V^2}{\text{Gain}(T)} \]
보정 모델 정의:
\[ P_{\text{comp}} = \frac{V - \text{Offset}(T) - d V^2}{\text{Gain}(T)} \]
1.3.3 2차 다항식 모델
\( \text{Offset}(T) \)와 \( \text{Gain}(T) \)를 2차 다항식으로 근사, 비선형 항 추가:
\[ \text{Offset}(T) = b_0 + b_1 (T - T_{\text{ref}}) + b_2 (T - T_{\text{ref}})^2 \] \[ \text{Gain}(T) = c_0 + c_1 (T - T_{\text{ref}}) + c_2 (T - T_{\text{ref}})^2 \] \[ P_{\text{comp}} = \frac{V - \left( b_0 + b_1 (T - T_{\text{ref}}) + b_2 (T - T_{\text{ref}})^2 \right) - d V^2}{c_0 + c_1 (T - T_{\text{ref}}) + c_2 (T - T_{\text{ref}})^2} \]
- \( T_{\text{ref}} \): 기준 온도 (예: 25°C)
- 계수: \( b_0, b_1, b_2, c_0, c_1, c_2, d \) (7개, 32-bit float)
1.3.4 3차 다항식 모델
더 높은 정확도를 위해 \( \text{Offset}(T) \)와 \( \text{Gain}(T) \)를 3차 다항식으로 확장, 비선형 항 추가:
\[ \text{Offset}(T) = b_0 + b_1 (T - T_{\text{ref}}) + b_2 (T - T_{\text{ref}})^2 + b_3 (T - T_{\text{ref}})^3 \] \[ \text{Gain}(T) = c_0 + c_1 (T - T_{\text{ref}}) + c_2 (T - T_{\text{ref}})^2 + c_3 (T - T_{\text{ref}})^3 \] \[ P_{\text{comp}} = \frac{V - \left( b_0 + b_1 (T - T_{\text{ref}}) + b_2 (T - T_{\text{ref}})^2 + b_3 (T - T_{\text{ref}})^3 \right) - d V^2}{c_0 + c_1 (T - T_{\text{ref}}) + c_2 (T - T_{\text{ref}})^2 + c_3 (T - T_{\text{ref}})^3} \]
- 계수: \( b_0, b_1, b_2, b_3, c_0, c_1, c_2, c_3, d \) (9개, 32-bit float)
1.3.5 계수 추출
목표: 데이터 포인트 \((V_i, T_{\text{cal},i}, P_{\text{ref},i})\)를 사용해 계수를 추출.
잔차 정의 (2차 모델):
\[ r_i = P_{\text{ref},i} - \frac{V_i - \left( b_0 + b_1 (T_i - T_{\text{ref}}) + b_2 (T_i - T_{\text{ref}})^2 \right) - d V_i^2}{c_0 + c_1 (T_i - T_{\text{ref}}) + c_2 (T_i - T_{\text{ref}})^2} \]
잔차 정의 (3차 모델):
\[ r_i = P_{\text{ref},i} - \frac{V_i - \left( b_0 + b_1 (T_i - T_{\text{ref}}) + b_2 (T_i - T_{\text{ref}})^2 + b_3 (T_i - T_{\text{ref}})^3 \right) - d V_i^2}{c_0 + c_1 (T_i - T_{\text{ref}}) + c_2 (T_i - T_{\text{ref}})^2 + c_3 (T_i - T_{\text{ref}})^3} \]
최적화 목표: 잔차의 제곱합 최소화:
\[ \min \sum_i r_i^2 \]
LM 알고리즘: 비선형 최소자승법으로 계수 피팅, 초기값은 센서 특성 기반 (예: \( \text{Offset}(T) \approx 0.1 \), \( \text{Gain}(T) \approx 0.01 \), \( d \approx 0.001 \)).
1.3.6 R² 계산
모델 적합도 평가:
\[ R^2 = 1 - \frac{\sum_i (P_{\text{ref},i} - P_{\text{comp},i})^2}{\sum_i (P_{\text{ref},i} - \bar{P}_{\text{ref}})^2} \]
- \( P_{\text{comp},i} \): 보정 후 예측 압력
- \( \bar{P}_{\text{ref}} \): 기준 압력 평균
- 목표: R² > 0.99, 오차 <1%
1.4 계수 추출
- 방법: LM 알고리즘(`scipy.optimize.least_squares`)으로 비선형 최소자승법.
- 절차:
- 데이터 로드: 가상 또는 실제 데이터(raw_ADC, T_raw, P_ref, Tref).
- 온도 보정: \( T_{\text{cal}} = a \cdot T_{\text{raw}} + b \).
- 정규화: \( V = \frac{\text{raw_ADC}}{65535} \cdot 2.048 \), \( T_{\text{norm}} = T_{\text{cal}} - T_{\text{ref}} \).
- 잔차 함수: \( r_i = P_{\text{ref},i} - \frac{V_i - \text{Offset}(T_i) - d V_i^2}{\text{Gain}(T_i)} \).
- LM 최적화: 초기값 설정 후 계수 피팅.
- 검증: R² 계산, 잔차 분석.
- 최적화:
- 초기값: 센서 특성 기반 (예: \( b_0 \approx 0.1 \), \( c_0 \approx 0.01 \), \( d \approx 0.001 \)).
- 데이터 포인트: 2차 모델은 18 포인트 이상, 3차 모델은 30 포인트 사용.
- 잔차 분석: 비선형성 확인 후 추가 항 조정.
1.5 MCU 적용
- 계수 전송: UART/I2C로 계수 전송 또는 플래시 메모리 하드코딩.
- 보상 계산:
2차 모델:\[ P_{\text{comp}} = \frac{V - \left( b_0 + b_1 (T - T_{\text{ref}}) + b_2 (T - T_{\text{ref}})^2 \right) - d V^2}{c_0 + c_1 (T - T_{\text{ref}}) + c_2 (T - T_{\text{ref}})^2} \]
3차 모델:\[ P_{\text{comp}} = \frac{V - \left( b_0 + b_1 (T - T_{\text{ref}}) + b_2 (T - T_{\text{ref}})^2 + b_3 (T - T_{\text{ref}})^3 \right) - d V^2}{c_0 + c_1 (T - T_{\text{ref}}) + c_2 (T - T_{\text{ref}})^2 + c_3 (T - T_{\text{ref}})^3} \]
- MCU 코드 예시 (2차 모델):
// 2차 다항식 모델 계수 (Offset, Gain, 비선형 항) float b[3] = {0.100000, 0.002000, 0.000100}; // 오프셋 계수: b0, b1, b2 float c[3] = {0.010000, -0.000100, 0.000000}; // 게인 계수: c0, c1, c2 float d = 0.001000; // 비선형 항 계수 float Tref = 25.0; // 기준 온도 (°C) float temp_gain = 1.02; // 온도 보정 게인 float temp_offset = -0.5; // 온도 보정 오프셋 // 온도 보정 함수 float correct_temperature(float T_raw) { return temp_gain * T_raw + temp_offset; } // 온도 및 압력 보정 함수 // 입력: raw_ADC (16비트 ADC 값), T_raw (원시 온도, °C) // 출력: 보정된 압력 (kPa) float compensate_2nd(float raw_ADC, float T_raw) { // ADC 값을 전압으로 변환 (VREF=2.048V) float V = (raw_ADC / 65535.0) * 2.048; // 온도 보정 float T = correct_temperature(T_raw); // 정규화된 온도: T - Tref float T_norm = T - Tref; // T_norm의 2차항 계산 float T_norm2 = T_norm * T_norm; // Offset(T) 계산: 2차 다항식 float Offset = b[0] + b[1] * T_norm + b[2] * T_norm2; // Gain(T) 계산: 2차 다항식 float Gain = c[0] + c[1] * T_norm + c[2] * T_norm2; // 비선형 항 계산 float nonlinear = d * V * V; // 보정된 압력: P_comp = (V - Offset(T) - d*V^2) / Gain(T) return (V - Offset - nonlinear) / Gain; }
- MCU 코드 예시 (3차 모델):
// 3차 다항식 모델 계수 (Offset, Gain, 비선형 항) float b[4] = {0.100000, 0.002000, 0.000100, 0.000001}; // 오프셋 계수: b0, b1, b2, b3 float c[4] = {0.010000, -0.000100, 0.000000, 0.000000}; // 게인 계수: c0, c1, c2, c3 float d = 0.001000; // 비선형 항 계수 float Tref = 25.0; // 기준 온도 (°C) float temp_gain = 1.02; // 온도 보정 게인 float temp_offset = -0.5; // 온도 보정 오프셋 // 온도 보정 함수 float correct_temperature(float T_raw) { return temp_gain * T_raw + temp_offset; } // 온도 및 압력 보정 함수 // 입력: raw_ADC (16비트 ADC 값), T_raw (원시 온도, °C) // 출력: 보정된 압력 (kPa) float compensate_3rd(float raw_ADC, float T_raw) { // ADC 값을 전압으로 변환 (VREF=2.048V) float V = (raw_ADC / 65535.0) * 2.048; // 온도 보정 float T = correct_temperature(T_raw); // 정규화된 온도: T - Tref float T_norm = T - Tref; // T_norm의 2차 및 3차항 계산 float T_norm2 = T_norm * T_norm; float T_norm3 = T_norm2 * T_norm; // Offset(T) 계산: 3차 다항식 float Offset = b[0] + b[1] * T_norm + b[2] * T_norm2 + b[3] * T_norm3; // Gain(T) 계산: 3차 다항식 float Gain = c[0] + c[1] * T_norm + c[2] * T_norm2 + c[3] * T_norm3; // 비선형 항 계산 float nonlinear = d * V * V; // 보정된 압력: P_comp = (V - Offset(T) - d*V^2) / Gain(T) return (V - Offset - nonlinear) / Gain; }
1.7 주의사항
- 데이터 품질: 2차 모델은 18 포인트 이상, 3차 모델은 30 포인트 사용.
- 노이즈: ADS114S08의 20 SPS 설정으로 노이즈 최소화, 10 샘플 평균화.
- 계수 정밀도: 32-bit float, 소수점 6자리 이내 반올림.
- 검증: R² > 0.99, 오차 <1% 목표.
- 온도 센서 보정: 기준 온도에서 수집된 데이터로 a, b 계수 조정.
- 비선형 항: \( d V^2 \) 항은 센서 특성에 따라 조정 필요.
2. Python 코드: 보정 계수 추출 및 검증
아래 코드는 가상 데이터를 생성(30 포인트)하고, 온도 센서 보정을 적용하며, LM 알고리즘으로 2차 및 3차 모델 계수(비선형 항 포함)를 추출하고, 보정 전/후 데이터를 그래프로 비교합니다.
# 필요한 라이브러리 임포트
import numpy as np # 수치 계산용
import pandas as pd # 데이터프레임 처리용
from scipy.optimize import least_squares # LM 최적화 알고리즘
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.02 # 게인 보정 계수
b = -0.5 # 오프셋 보정 계수
return a * T_raw + b
# 가상 데이터 생성
# 조건: 온도(-20~80°C, 10 포인트), 압력(10, 30, 50, 70, 90 kPa), Tref=25°C
# 데이터 포인트: 5(압력) × 10(온도) = 30
temperatures = [-20.0, -10.0, 0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 80.0]
pressures = [10.0, 30.0, 50.0, 70.0, 90.0]
Tref = 25.0 # 기준 온도 (°C)
VREF = 2.048 # ADS114S08 내부 기준 전압 (V)
data = []
# 온도 및 압력 조합에 대해 가상 데이터 생성
for T in temperatures:
T_cal = correct_temperature(T) # 온도 보정
T_norm = T_cal - Tref # 정규화된 온도
# 가정된 오프셋: 3차 다항식
Offset_T = 0.1 + 0.002 * T_norm + 0.0001 * T_norm**2 + 0.000001 * T_norm**3
# 가정된 게인: 3차 다항식
Gain_T = 0.01 - 0.0001 * T_norm + 0.000001 * T_norm**2
# 비선형 항 계수
d = 0.001
for P_ref in pressures:
# 센서 출력 모델: V = Offset(T) + Gain(T) * P + d*V^2
V = Offset_T + Gain_T * P_ref + d * (Offset_T + Gain_T * P_ref)**2
# 16비트 ADC 변환
raw_ADC = int((V / VREF) * 65535)
data.append([raw_ADC, T, P_ref, Tref])
# DataFrame으로 변환
df = pd.DataFrame(data, columns=['raw_ADC', 'T_raw', 'P_ref', 'Tref'])
# 입력 데이터 준비
V = (df['raw_ADC'].values / 65535.0) * VREF # ADC 값을 전압으로 변환
T_raw = df['T_raw'].values # 원시 온도 (°C)
T_cal = correct_temperature(T_raw) # 보정된 온도
Tref = df['Tref'].values[0] # 기준 온도 (°C)
P_ref = df['P_ref'].values # 기준 압력 (kPa)
T_norm = T_cal - Tref # 정규화된 온도
# 2차 다항식 모델 함수
def model_2nd(params, V, 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 * V**2
return (V - Offset_T - nonlinear) / Gain_T
# 3차 다항식 모델 함수
def model_3rd(params, V, 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 * V**2
return (V - Offset_T - nonlinear) / Gain_T
# 2차 모델 잔차 함수
def residuals_2nd(params, V, T_norm, P_ref):
return model_2nd(params, V, T_norm) - P_ref
# 3차 모델 잔차 함수
def residuals_3rd(params, V, T_norm, P_ref):
return model_3rd(params, V, T_norm) - P_ref
# 초기 계수 추정
initial_params_2nd = [0.1, 0.002, 0.0001, 0.01, -0.0001, 0.0, 0.001]
initial_params_3rd = [0.1, 0.002, 0.0001, 0.000001, 0.01, -0.0001, 0.0, 0.0, 0.001]
# LM 알고리즘으로 계수 최적화
result_2nd = least_squares(residuals_2nd, initial_params_2nd, args=(V, T_norm, P_ref), method='lm')
b0, b1, b2, c0, c1, c2, d = result_2nd.x
result_3rd = least_squares(residuals_3rd, initial_params_3rd, args=(V, T_norm, P_ref), method='lm')
b0_3, b1_3, b2_3, b3_3, c0_3, c1_3, c2_3, c3_3, d_3 = result_3rd.x
# 계수 출력
print("2nd Order Model Coefficients:")
print(f"Offset: b0={b0:.6f}, b1={b1:.6f}, b2={b2:.6f}")
print(f"Gain: c0={c0:.6f}, c1={c1:.6f}, c2={c2:.6f}")
print(f"Nonlinear: d={d:.6f}")
print("3rd Order Model Coefficients:")
print(f"Offset: b0={b0_3:.6f}, b1={b1_3:.6f}, b2={b2_3:.6f}, b3={b3_3:.6f}")
print(f"Gain: c0={c0_3:.6f}, c1={c1_3:.6f}, c2={c2_3:.6f}, c3={c3_3:.6f}")
print(f"Nonlinear: d={d_3:.6f}")
# 보정 전/후 압력 계산
P_pred_2nd = model_2nd(result_2nd.x, V, T_norm)
P_pred_3rd = model_3rd(result_3rd.x, V, T_norm)
P_raw = V * 100.0 # 보정 전: 단순 스케일링
# 그래프 시각화
plt.figure(figsize=(14, 8))
colors = sns.color_palette("tab10", n_colors=len(temperatures))
# 온도별 데이터 플롯
for i, T in enumerate(np.unique(T_raw)):
mask = T_raw == T
# 보정 전: 실선, 원 마커
plt.plot(P_ref[mask], P_raw[mask], label=f'Before Comp. (T={T}°C)',
marker='o', linestyle='-', color=colors[i], markersize=10, linewidth=2.5)
# 보정 후 (2차): 점선, X 마커
plt.plot(P_ref[mask], P_pred_2nd[mask], label=f'After Comp. 2nd (T={T}°C)',
marker='x', linestyle='--', color=colors[i], markersize=14, linewidth=2.5)
# 보정 후 (3차): 점선, + 마커
plt.plot(P_ref[mask], P_pred_3rd[mask], label=f'After Comp. 3rd (T={T}°C)',
marker='+', linestyle=':', color=colors[i], markersize=14, linewidth=2.5)
# 이상적인 압력 선
plt.plot([0, 100], [0, 100], 'k--', label='Ideal Pressure (P_ref)', linewidth=2)
plt.xlabel('Reference Pressure (kPa)', fontsize=12)
plt.ylabel('Predicted Pressure (kPa)', fontsize=12)
plt.title('Pressure Comparison Before and After Temperature Compensation', fontsize=14)
plt.legend(fontsize=10, loc='upper left', bbox_to_anchor=(1, 1))
plt.grid(True, linestyle='--', alpha=0.7)
plt.tight_layout()
plt.show()
# R² 계산
R2_2nd = 1 - np.sum((P_ref - P_pred_2nd)**2) / np.sum((P_ref - np.mean(P_ref))**2)
R2_3rd = 1 - np.sum((P_ref - P_pred_3rd)**2) / np.sum((P_ref - np.mean(P_ref))**2)
print(f"R^2 (2nd Order): {R2_2nd:.6f}")
print(f"R^2 (3rd Order): {R2_3rd:.6f}")
# 계수 저장
coeffs_2nd = [b0, b1, b2, c0, c1, c2, d]
coeffs_3rd = [b0_3, b1_3, b2_3, b3_3, c0_3, c1_3, c2_3, c3_3, d_3]
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))
3. 예상 결과
- 계수 (2차 모델):
Offset: b0=0.100000, b1=0.002000, b2=0.000100 Gain: c0=0.010000, c1=-0.000100, c2=0.000000 Nonlinear: d=0.001000
- 계수 (3차 모델):
Offset: b0=0.100000, b1=0.002000, b2=0.000100, b3=0.000001 Gain: c0=0.010000, c1=-0.000100, c2=0.000001, c3=0.000000 Nonlinear: d=0.001000
- R²:
R^2 (2nd Order): 0.999999 R^2 (3rd Order): 0.999999
- 그래프: 보정 전(실선, 원 마커), 보정 후 2차(점선, X 마커), 보정 후 3차(점선, + 마커), 이상적인 압력(검은 점선).
- 파일:
- coeffs_2nd.txt:
0.100000,0.002000,0.000100,0.010000,-0.000100,0.000000,0.001000
- coeffs_3rd.txt:
0.100000,0.002000,0.000100,0.000001,0.010000,-0.000100,0.000001,0.000000,0.001000
- coeffs_2nd.txt:
4. 결론
본 연구에서는 Wheatstone 브릿지 기반 MEMS 압력 센서의 온도 의존성을 보정하기 위해 다항식 기반 모델에 비선형 항(\( d V^2 \))을 추가하고, ADS114S08 내부 온도 센서의 오프셋 및 게인 보정을 적용하였다. 또한, 3차 모델의 안정성을 위해 데이터 포인트를 30개로 확장하여 과적합 문제를 완화하였다. 센서 출력은
\[ V = \text{Offset}(T) + \text{Gain}(T) \cdot P + d V^2 \]
로 정의하였으며, \(\text{Offset}(T)\)와 \(\text{Gain}(T)\)를 각각 2차 및 3차 다항식으로 근사하고 비선형 항을 추가하여 보정 계수를 도출하였다. Python 기반의 Levenberg–Marquardt(LM) 알고리즘을 활용해 계수를 추정하고, 보정 전·후 결과를 비교함으로써 본 방법의 유효성을 검증하였다.
실험 결과, 2차 모델은 18 포인트 이상에서 안정적인 성능을 보였으며, 3차 모델은 30 포인트로 확장 시 R² > 0.999 수준의 높은 적합도를 달성하였다. 온도 센서 보정은 ±2°C 오차를 ±0.5°C 이내로 줄였으며, 비선형 항 추가로 오차가 ±1% 이내로 감소하였다.
본 연구의 주요 성과는 다음과 같다.
- 정확도 향상: 비선형 항과 온도 센서 보정으로 센서의 비선형 온도 의존성을 효과적으로 제거하였다.
- 계산 효율성 확보: LUT 방식 대비 메모리 사용량이 적으며, 소형 MCU에서 실시간 보정 가능.
- 응용 가능성 제시: 자동차, 항공, 의료, 산업용 계측기 등에서 MEMS 압력 센서의 신뢰성을 높였다.
한계점으로는 극한 조건(-40°C 이하, 100°C 이상)에서의 모델 유효성 검증, 장기 안정성(drift) 미고려, 온도 센서 보정 계수의 센서별 최적화 필요 등이 있다. 향후 연구에서는 머신러닝 기반 회귀 모델 비교, 추가 비선형 항(예: \( V^3 \)) 검토, 현장 검증을 통해 실효성을 높일 계획이다.
5. 추가 주의사항
- 데이터: 가상 데이터 사용, 실제 데이터로 대체 시 계수 달라짐.
- 노이즈: ADS114S08의 20 SPS 설정, 10 샘플 평균화.
- 계수 정밀도: 32-bit float, 소수점 6자리 이내 반올림.
- 검증: R² > 0.99, 오차 <1% 목표.
- 온도 센서 보정: 기준 온도에서 a, b 계수 조정.
- 비선형 항: \( d V^2 \) 계수는 센서 특성에 따라 조정.
Keyworkds: MEMS 압력센서, MEMS Pressure Sensor ,온도보정 ,Temperature Compensation,Offset ,Gain,Polynomial Model ,2차 다항식보정, 3차 다항식보정,ADC보정, ADC Calibration, Levenberg Marquardt ,Normalization,Inverse Normalization,MCU 보정, Embedded Implementation ,센서보정 ,Sensor Calibration
'Sensor > 압력센서(Pressure)' 카테고리의 다른 글
고해상도 압력 센서 온도 보상 알고리즘 설계 및 구현 (1) | 2025.08.31 |
---|---|
Wheatstone 브릿지 기반 MEMS 압력 센서의 온도 의존성 보정 방법 (0) | 2025.08.31 |