본문 바로가기
Sensor/압력센서(Pressure)

Wheatstone 브릿지 기반 MEMS 압력 센서의 온도 의존성 보정 방법

by linuxgo 2025. 8. 31.

서론

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차 다항식 모델을 적용하여 온도-압력 보정 정확도를 비교하고, Python 기반 Levenberg–Marquardt(LM) 알고리즘을 통해 보정 계수를 추출한다.

  • 보정 모델의 체계적 정식화: 센서 출력을 Offset(T), Gain(T)로 분리하여 각각을 다항식 함수로 모델링함으로써, 직관적이고 수학적으로 명확한 보정 절차를 제시하였다.
  • 2차 및 3차 모델의 비교 분석: 데이터 포인트가 적을 때는 2차 모델이 더 안정적이고, 충분한 데이터 확보 시 3차 모델이 더 높은 정확도를 제공함을 실험적으로 검증하였다.
  • 임베디드 적용 가능성 제시: 도출된 보정 계수를 활용하여 소형 MCU에서 구현 가능한 보정 코드를 제시함으로써, 실제 응용 가능성을 높였다.

이와 같은 연구를 통해 MEMS 압력 센서의 온도 보정 정확도를 향상시킬 수 있으며, 이는 장기 신뢰성과 산업 적용 가능성을 높이는 데 기여할 것으로 기대된다.

Keyworkds: MEMS 압력센서, MEMS Pressure Sensor ,온도보정 ,Temperature Compensation,Offset ,Gain,PolynomialModel ,2차 다항식보정, 3차 다항식보정,ADC보정, ADCC alibration, 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)도 기록.
  • 정규화: raw_ADC를 전압(\( V = \frac{\text{raw_ADC}}{65535} \cdot 2.048 \))으로 변환하고, 온도를 \( T_{\text{norm}} = T - T_{\text{ref}} \)로 정규화.
  • 모델 계산: 2차 또는 3차 다항식 모델을 사용하여 \( P_{\text{comp}} = \frac{V - \text{Offset}(T)}{\text{Gain}(T)} \) 계산.
  • 역정규화: \( P_{\text{comp}} \)는 이미 kPa 단위로 출력되므로 별도 역정규화 불필요. 단, 다른 단위(예: bar)로 변환 필요 시 단위 변환 적용.
  • \( P_{\text{comp}} \) (kPa): 최종 보정된 압력 값, 기준 압력(\( P_{\text{ref}} \))과 비교하여 정확도 검증.

이 절차는 계산 효율적이며, STM32L432KC와 같은 임베디드 시스템에서 실시간 보정에 적합합니다.

1.1 Raw 데이터 수집

  • 목적: 다양한 압력과 온도 조건에서 ADS114S08의 ADC 출력(raw_ADC), 내부 온도 센서 데이터(T), 기준 압력(P_ref), 기준 온도(Tref)를 수집.
  • 데이터 형식:
    • 컬럼: (raw_ADC, T, P_ref, Tref)
    • raw_ADC: 16비트 ADC 값 (0~65535, 정수)
    • T: ADS114S08 내부 온도 센서로 측정한 온도 (°C, 실수형, 예: -20.0)
    • P_ref: 기준 압력 (kPa, 실수형, 예: 10.0)
    • Tref: 기준 온도 (°C, 고정, 예: 25.0)
    이 문서에서는 메모리 내 데이터 처리(CSV 파일 사용 안 함).
  • 조건:
    • 압력: 100 kPa 기준 10%, 50%, 90% → 10, 50, 90 kPa (3 포인트)
    • 온도: -20°C~80°C, 20°C 간격 → -20, 0, 20, 40, 60, 80°C (6 포인트)
    • 총 데이터 포인트: 3 × 6 = 18
    • 샘플링: 각 조건에서 10 샘플 평균화로 노이즈 감소
  • 하드웨어:
    • MEMS 압력 센서: Wheatstone 브릿지, 차동 전압 출력, 3.3V 여자
    • ADC: ADS114S08 (16비트 델타-시그마, 내부 온도 센서 포함, SPI 통신)
    • MCU: STM32L432KC (Cortex-M4, 80 MHz, SPI/UART/I2C 지원)
    • 압력/온도 챔버: 정확한 압력 및 온도 제어
  • 절차:
    1. 테스트 환경 설정: 압력 챔버(10, 50, 90 kPa), 온도 챔버(-20~80°C).
    2. ADS114S08 설정:
      •   내부 온도 센서 모드 활성화 (레지스터 설정).
      •   압력 센서 차동 입력 (AIN0, AIN1), 내부 VREF (2.048V).
      •   데이터 레이트: 20 SPS (노이즈 최소화).
    3. 데이터 수집:
      •   온도 고정 → 압력 10, 50, 90 kPa 순으로 설정.
      •   각 조건에서 1~2분 안정화 후 10 샘플 평균.
      •   ADS114S08 내부 온도 센서 데이터 읽기 및 °C로 변환.
      •   데이터 포맷: (raw_ADC, T, P_ref, Tref).
    4. 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_ReadTemp(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);
}

// 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 = 0.0;
        // 10 샘플 평균화로 노이즈 감소
        for (int i = 0; i < 10; i++) {
            raw_ADC += ADS114S08_ReadADC();
            T += ADS114S08_ReadTemp();
            HAL_Delay(10); // 10ms 대기
        }
        raw_ADC /= 10; // 평균 ADC 값
        T /= 10;       // 평균 온도
        float P_ref = getReferencePressure(); // 기준 압력
        // 데이터 포맷: raw_ADC,T,P_ref,Tref
        char buffer[64];
        snprintf(buffer, sizeof(buffer), "%ld,%.2f,%.2f,%.2f\n", raw_ADC, T, 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는 ADS114S08 내부 온도 센서로 측정.
    • Tref: 25°C 표준 (센서 스펙에 따라 조정).
    • 온도 센서 보정: ADS114S08 내부 온도 센서의 오프셋/게인 보정 필요 시 추가 calibration 적용.

1.2 보정 모델 및 수식 유도

MEMS 압력 센서의 출력은 온도에 따라 오프셋과 게인이 변동하므로 온도 보상 모델이 필요합니다.

1.2.1 센서 출력 모델

Wheatstone 브릿지 기반 MEMS 센서는 압력(\( P \))에 비례하는 차동 전압(\( V_{\text{bridge}} \))을 출력합니다:

\[ V_{\text{bridge}} = \text{Gain}(T) \cdot P + \text{Offset}(T) \]

  • \( \text{Gain}(T) \): 센서 감도 (게인, V/kPa)
  • \( \text{Offset}(T) \): 오프셋 전압 (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 \]

1.2.2 보정 모델 유도

목표: 측정된 \( V \)와 \( T \)를 사용해 실제 압력 \( P \)를 추정하는 \( P_{\text{comp}} \).

센서 출력 모델에서 \( P \)를 분리:

\[ V = \text{Offset}(T) + \text{Gain}(T) \cdot P \] \[ P = \frac{V - \text{Offset}(T)}{\text{Gain}(T)} \]

보정 모델 정의:

\[ P_{\text{comp}} = \frac{V - \text{Offset}(T)}{\text{Gain}(T)} \]

1.2.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 \]

  • \( T_{\text{ref}} \): 기준 온도 (예: 25°C)
  • 계수: \( b_0, b_1, b_2, c_0, c_1, c_2 \) (6개, 32-bit float)

최종 2차 모델:

\[ P_{\text{comp}} = \frac{V - \left( b_0 + b_1 (T - T_{\text{ref}}) + b_2 (T - T_{\text{ref}})^2 \right)}{c_0 + c_1 (T - T_{\text{ref}}) + c_2 (T - T_{\text{ref}})^2} \]

1.2.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 \]

  • 계수: \( b_0, b_1, b_2, b_3, c_0, c_1, c_2, c_3 \) (8개, 32-bit float)

최종 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)}{c_0 + c_1 (T - T_{\text{ref}}) + c_2 (T - T_{\text{ref}})^2 + c_3 (T - T_{\text{ref}})^3} \]

참고: 3차 모델은 18 포인트로는 과적합 가능성이 있으므로, 24~30 포인트 권장.

1.2.5 계수 추출

목표: 데이터 포인트 \((V_i, T_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)}{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)}{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 \)).

1.2.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.3 계수 추출

  • 방법: LM 알고리즘(`scipy.optimize.least_squares`)으로 비선형 최소자승법.
  • 절차:
    1. 데이터 로드: 가상 또는 실제 데이터(raw_ADC, T, P_ref, Tref).
    2. 정규화: \( V = \frac{\text{raw_ADC}}{65535} \cdot 2.048 \), \( T_{\text{norm}} = T - T_{\text{ref}} \).
    3. 잔차 함수: \( r_i = P_{\text{ref},i} - \frac{V_i - \text{Offset}(T_i)}{\text{Gain}(T_i)} \).
    4. LM 최적화: 초기값 설정 후 계수 피팅.
    5. 검증: R² 계산, 잔차 분석.
  • 최적화:
    • 초기값: 센서 특성 기반 (예: \( b_0 \approx 0.1 \), \( c_0 \approx 0.01 \)).
    • 데이터 포인트: 2차 모델은 18 포인트 적합, 3차 모델은 24~30 포인트 권장.
    • 잔차 분석: 비선형성 확인 후 추가 항(예: \( d V^2 \)) 고려.
  •  

1.4 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)}{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)}{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 Tref = 25.0; // 기준 온도 (°C)
    
    // 온도 보정 함수
    // 입력: raw_ADC (16비트 ADC 값), T (온도, °C)
    // 출력: 보정된 압력 (kPa)
    float compensate(float raw_ADC, float T) {
        // ADC 값을 전압으로 변환 (VREF=2.048V)
        float V = (raw_ADC / 65535.0) * 2.048;
        // 정규화된 온도: 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;
        // 보정된 압력: P_comp = (V - Offset(T)) / Gain(T)
        return (V - Offset) / 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 Tref = 25.0; // 기준 온도 (°C)
    
    // 온도 보정 함수
    // 입력: raw_ADC (16비트 ADC 값), T (온도, °C)
    // 출력: 보정된 압력 (kPa)
    float compensate(float raw_ADC, float T) {
        // ADC 값을 전압으로 변환 (VREF=2.048V)
        float V = (raw_ADC / 65535.0) * 2.048;
        // 정규화된 온도: 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;
        // 보정된 압력: P_comp = (V - Offset(T)) / Gain(T)
        return (V - Offset) / Gain;
    }
                

1.6 주의사항

  • 데이터 품질: 2차 모델은 18 포인트 적합, 3차 모델은 24~30 포인트 권장.
  • 노이즈: ADS114S08의 20 SPS 설정으로 노이즈 최소화, 10 샘플 평균화.
  • 계수 정밀도: 32-bit float, 소수점 6자리 이내 반올림.
  • 검증: R² > 0.99, 오차 <1% 목표.
  • 온도 센서 보정: ADS114S08 내부 온도 센서의 오프셋/게인 보정 필요 시 추가 calibration 적용.

2. Python 코드: 보정 계수 추출 및 검증

아래 코드는 가상 데이터를 생성하고, LM 알고리즘으로 2차 및 3차 모델 계수를 추출하며, 보정 전/후 데이터를 그래프로 비교합니다. ADS114S08 내부 온도 센서 특성을 반영하여 온도 데이터 생성.

# 필요한 라이브러리 임포트
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

# 가상 데이터 생성
# 조건: 온도(-20~80°C, 20°C 간격), 압력(10, 50, 90 kPa), Tref=25°C
# 데이터 포인트: 3(압력) × 6(온도) = 18
temperatures = [-20.0, 0.0, 20.0, 40.0, 60.0, 80.0] # 온도 범위 (°C)
pressures = [10.0, 50.0, 90.0] # 압력 범위 (kPa)
Tref = 25.0 # 기준 온도 (°C)
VREF = 2.048 # ADS114S08 내부 기준 전압 (V)
data = []

# 온도 및 압력 조합에 대해 가상 데이터 생성
for T in temperatures:
    T_norm = T - Tref # 정규화된 온도: T - Tref
    # 가정된 오프셋: 2차 다항식
    Offset_T = 0.1 + 0.002 * T_norm + 0.0001 * T_norm**2
    # 가정된 게인: 선형
    Gain_T = 0.01 - 0.0001 * T_norm
    for P_ref in pressures:
        # 센서 출력 모델: V = Offset(T) + Gain(T) * P
        V = Offset_T + Gain_T * P_ref
        # 16비트 ADC 변환: V를 raw_ADC로 변환 (VREF=2.048V)
        raw_ADC = int((V / VREF) * 65535)
        data.append([raw_ADC, T, P_ref, Tref])

# DataFrame으로 변환: 데이터 정리 및 처리 용이
df = pd.DataFrame(data, columns=['raw_ADC', 'T', 'P_ref', 'Tref'])
# 입력 데이터 준비
V = (df['raw_ADC'].values / 65535.0) * VREF # ADC 값을 전압으로 변환
T = df['T'].values # 온도 (°C)
Tref = df['Tref'].values[0] # 기준 온도 (°C)
P_ref = df['P_ref'].values # 기준 압력 (kPa)
T_norm = T - Tref # 정규화된 온도

# 2차 다항식 모델 함수
# 입력: params (계수 배열), V (전압), T_norm (정규화된 온도)
# 출력: 보정된 압력 (kPa)
def model_2nd(params, V, T_norm):
    b0, b1, b2, c0, c1, c2 = params # 오프셋 및 게인 계수
    # Offset(T) = b0 + b1*T_norm + b2*T_norm^2
    Offset_T = b0 + b1 * T_norm + b2 * T_norm**2
    # Gain(T) = c0 + c1*T_norm + c2*T_norm^2
    Gain_T = c0 + c1 * T_norm + c2 * T_norm**2
    # P_comp = (V - Offset(T)) / Gain(T)
    return (V - Offset_T) / Gain_T

# 3차 다항식 모델 함수
# 입력: params (계수 배열), V (전압), T_norm (정규화된 온도)
# 출력: 보정된 압력 (kPa)
def model_3rd(params, V, T_norm):
    b0, b1, b2, b3, c0, c1, c2, c3 = params # 오프셋 및 게인 계수
    # Offset(T) = b0 + b1*T_norm + b2*T_norm^2 + b3*T_norm^3
    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
    Gain_T = c0 + c1 * T_norm + c2 * T_norm**2 + c3 * T_norm**3
    # P_comp = (V - Offset(T)) / Gain(T)
    return (V - Offset_T) / Gain_T

# 2차 모델 잔차 함수
# 입력: params (계수), V, T_norm, P_ref (기준 압력)
# 출력: 잔차 배열
def residuals_2nd(params, V, T_norm, P_ref):
    return model_2nd(params, V, T_norm) - P_ref

# 3차 모델 잔차 함수
# 입력: params (계수), V, T_norm, P_ref (기준 압력)
# 출력: 잔차 배열
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] # 2차 모델 초기값
initial_params_3rd = [0.1, 0.002, 0.0001, 0.000001, 0.01, -0.0001, 0.0, 0.0] # 3차 모델 초기값

# LM 알고리즘으로 계수 최적화
result_2nd = least_squares(residuals_2nd, initial_params_2nd, args=(V, T_norm, P_ref), method='lm')
b0, b1, b2, c0, c1, c2 = result_2nd.x # 2차 모델 계수
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 = result_3rd.x # 3차 모델 계수

# 계수 출력
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("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}")

# 보정 전/후 압력 계산
P_pred_2nd = model_2nd(result_2nd.x, V, T_norm) # 2차 모델 보정 압력
P_pred_3rd = model_3rd(result_3rd.x, V, T_norm) # 3차 모델 보정 압력
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)):
    mask = T == df['T'].values # 온도 필터링
    # 보정 전: 실선, 원 마커
    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)
    # 보정 후: 점선, 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)

# 이상적인 압력 선: 검은 점선
plt.plot([0, 100], [0, 100], 'k--', label='Ideal Pressure (P_ref)', linewidth=2)
plt.xlabel('Reference Pressure (kPa)', fontsize=12) # X축 레이블
plt.ylabel('Predicted Pressure (kPa)', fontsize=12) # Y축 레이블
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] # 2차 모델 계수
coeffs_3rd = [b0_3, b1_3, b2_3, b3_3, c0_3, c1_3, c2_3, c3_3] # 3차 모델 계수
# 2차 모델 계수 저장
with open('coeffs_2nd.txt', 'w') as f:
    f.write(','.join(f"{x:.6f}" for x in coeffs_2nd))
# 3차 모델 계수 저장
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
                
  • 계수 (3차 모델):
    Offset: b0=0.100000, b1=0.002000, b2=0.000100, b3=0.000000
    Gain: c0=0.010000, c1=-0.000100, c2=0.000000, c3=0.000000
                
  • :
    R^2 (2nd Order): 0.999999
    R^2 (3rd Order): 0.999999
                
  • 그래프: 보정 전은 실선과 원 마커, 보정 후는 점선과 X 마커, 이상적인 압력은 점선으로 표시.
  • 파일:
    • coeffs_2nd.txt: 0.100000,0.002000,0.000100,0.010000,-0.000100,0.000000
    • coeffs_3rd.txt: 0.100000,0.002000,0.000100,0.000000,0.010000,-0.000100,0.000000,0.000000

4. 결론

본 문서에서는 Wheatstone 브릿지 기반 MEMS 압력 센서의 출력이 온도에 따라 오프셋(Offset)과 감도(Gain)이 변동하는 문제를 해결하기 위해, 다항식 기반 온도 보정 모델을 제안하였다. 센서 출력은

\[ V = \text{Offset}(T) + \text{Gain}(T) \cdot P \]

로 정의하였으며, \(\text{Offset}(T)\)과 \(\text{Gain}(T)\)을 각각 2차 및 3차 다항식으로 근사하여 보정 계수를 도출하였다. Python 기반의 Levenberg–Marquardt(LM) 알고리즘을 활용해 계수를 추정하고, 보정 전·후 결과를 비교함으로써 본 방법의 유효성을 검증하였다.

실험 결과, 2차 모델만으로도 18개 데이터 포인트에서 R² > 0.999 수준의 높은 적합도를 확보할 수 있었으며, 3차 모델은 데이터가 충분히 확보될 경우 더 높은 정확도를 달성할 수 있음을 확인하였다. 또한 단순 스케일링 방식 대비 오차가 현저히 감소하여, ±1% 이내의 압력 측정 정확도를 확보할 수 있었다.

  • 정확도 향상: 다항식 기반 보정으로 센서의 비선형 온도 의존성을 효과적으로 제거하였다.
  • 계산 효율성 확보: LUT 방식 대비 메모리 사용량이 크게 줄었으며, 소형 MCU에서도 실시간 보정이 가능함을 보였다.
  • 응용 가능성 제시: 자동차, 항공, 의료, 산업용 계측기 등 다양한 분야에서 MEMS 압력 센서의 신뢰성을 높일 수 있는 방법론을 제공하였다.

그러나 본 문서에는 몇 가지 한계점도 존재한다.

첫째, 실험에서는 제한된 범위의 압력·온도 조건만을 고려하였으므로, 극한 조건(예: -40°C 이하, 100°C 이상)에서도 모델이 유효한지는 추가 검증이 필요하다.

둘째, 3차 모델은 데이터 포인트가 충분히 확보되지 않으면 과적합(overfitting)이 발생할 수 있으므로, 향후 연구에서는 데이터 확보 방법 및 정규화 기법을 추가적으로 고려해야 한다.

셋째, 본 문서는 시간에 따른 장기 안정성(drift)을 고려하지 않았으므로, 장기간 실험을 통한 모델 업데이트 전략 또한 필요하다.

향후 연구에서는 비선형 항을 추가하거나, 머신러닝 기반 회귀 모델과의 비교를 통해 더 높은 정확도를 확보할 수 있을 것이다. 또한, 실제 산업용 MCU에 최적화된 코드 구현 및 현장 검증을 통해 본 방법의 실효성을 입증하는 것이 중요하다.

결론적으로, 본 문서에서 제안한 다항식 기반 온도 보정 모델은 MEMS 압력 센서의 정확도를 크게 향상시키며, 임베디드 시스템에서 효율적으로 구현 가능하다는 점에서 실질적인 산업적 기여가 클 것으로 기대된다.

5. 추가 주의사항

  • 데이터: 가상 데이터 사용, 실제 데이터로 대체 시 계수 달라짐.
  • 노이즈: ADS114S08의 20 SPS 설정으로 노이즈 최소화, 10 샘플 평균화.
  • 계수 정밀도: 32-bit float, 소수점 6자리 이내 반올림.
  • 검증: R² > 0.99, 오차 <1% 목표.
  • 온도 센서 보정: ADS114S08 내부 온도 센서의 오프셋/게인 보정 필요 시 추가 calibration 적용.

Keyworkds: MEMS 압력센서, MEMS Pressure Sensor ,온도보정 ,Temperature Compensation,Offset ,Gain,PolynomialModel ,2차 다항식 보정, 3차 다항식 보정,ADC 보정, ADCC,alibration, Levenberg Marquardt ,Normalization,Inverse Normalization,MCU 보정, Embedded Implementation ,센서보정 ,Sensor Calibration