본문 바로가기
Sensor/온도센서(Temperature)

RTD 온도 센서 보상 알고리즘 (RTD Temperature Sensor Compensation Algorithm)

by linuxgo 2025. 8. 5.
반응형

 

 

 

 

 

이 문서는 STM32L432KC 마이크로컨트롤러와 MAX31865를 사용한 RTD 온도 센서의 보상 알고리즘을 설명합니다 (This document explains the compensation algorithm for RTD temperature sensors using STM32L432KC and MAX31865).

1. RTD 온도 센서 개요 (RTD Temperature Sensor Overview)

RTD는 온도에 따라 저항이 변화하는 센서입니다 (RTD is a sensor whose resistance changes with temperature). 백금(Pt100: 0°C에서 100Ω, Pt1000: 1000Ω, Pt10: 10Ω)과 니켈(Ni120: 120Ω)이 주로 사용됩니다 (Platinum (Pt100: 100Ω at 0°C, Pt1000: 1000Ω, Pt10: 10Ω) and nickel (Ni120: 120Ω) are commonly used).

1.1 표준별 사양 (Standards Specifications)

  • IEC 60751 (α=0.00385):
    • 범위 (Range): -200°C ~ +850°C
    • 저항-온도 관계 (Resistance-Temperature Relationship):
      0°C 이상 (Above 0°C): \( R_t = R_0 \cdot [1 + A \cdot t + B \cdot t^2] \)
      0°C 미만 (Below 0°C): \( R_t = R_0 \cdot [1 + A \cdot t + B \cdot t^2 + C \cdot (t - 100) \cdot t^3] \)
    • 계수 (Coefficients): \( A = 3.9083 \times 10^{-3} \), \( B = -5.775 \times 10^{-7} \), \( C = -4.183 \times 10^{-12} \)
  • 미국 표준 (American Standard, α=0.00392):
    • 범위 (Range): -200°C ~ +850°C
    • 계수 (Coefficients): \( A = 3.926 \times 10^{-3} \), \( B = -5.92 \times 10^{-7} \), \( C = -4.0 \times 10^{-12} \)
  • Ni120 (ASTM E1137):
    • 범위 (Range): -80°C ~ +320°C
    • 계수 (Coefficients): \( A = 5.485 \times 10^{-3} \), \( B = 6.650 \times 10^{-6} \), \( C = 2.805 \times 10^{-11} \)
  • Pt10 (극저온, Cryogenic):
    • 범위 (Range): -260°C ~ +500°C
    • 계수 (Coefficients): IEC 60751과 동일 (Same as IEC 60751), \( R_0 = 10\Omega \)

1.2 보상 알고리즘 (Compensation Algorithm)

  1. 저항-온도 변환 (Resistance-to-Temperature Conversion)
    • 0°C 이상 (Above 0°C): 이차 방정식 풀이 (Quadratic equation solution)
    • 0°C 미만 (Below 0°C): 뉴턴-랩슨 반복법 (Newton-Raphson iteration)
    • 선형 근사 (Linear Approximation, -50°C ~ 150°C):
      \( t \approx \frac{R_t / R_0 - 1}{\alpha} \)
    • LUT: 1°C 간격 저항 저장, 선형 보간 (Store resistances at 1°C intervals, linear interpolation)
  2. 리드선 저항 보상 (Lead Resistance Compensation):
    • 2선식 (2-Wire): \( R_t = R_{\text{measured}} - 2 \cdot R_{\text{lead}} \)
    • 3선식 (3-Wire): \( R_t = R_{\text{measured}} - \frac{R_{\text{L1}} + R_{\text{L3}}}{2} \)
    • 4선식 (4-Wire): \( R_t = \frac{V_{\text{measured}}}{I_{\text{excitation}}} \)
  3. 자가 발열 보상 (Self-Heating Compensation):
    \( \Delta T = \frac{I^2 \cdot R_t \cdot 1000}{\text{Dissipation Constant (2 mW/°C)}} \)
    여기 전류 (Excitation Current): 0.1mA~1mA
  4. 노이즈 필터링 (Noise Filtering):
    • 이동 평균 (Moving Average): 5개 샘플 (5 samples)
    • 칼만 필터 (Kalman Filter): Q=0.01, R=0.1
    • MAX31865 50Hz 필터 (50Hz Filter): 65ms 변환 시간 (Conversion time)
  5. 캘리브레이션 (Calibration): 알려진 온도-저항 쌍으로 오프셋 보정 (Offset correction using known temperature-resistance pairs)
  6. 정확도 등급 (Accuracy Class):
    • Class A: \( \pm(0.15 + 0.002|t|) \) °C
    • Class B: \( \pm(0.3 + 0.005|t|) \) °C
  7. 에러 처리 (Error Handling): 저항(0Ω ~ 400Ω) 및 온도 범위 검증 (Validate resistance and temperature ranges)
  8. 로깅 (Logging): USART2로 온도, 저항, 오차 출력 (Output temperature, resistance, error via USART2)

1.3 추가 고려사항 (Additional Considerations)

  • 메모리 (Memory): STM32L432KC(256KB 플래시, 64KB SRAM) 내 동작 (Operates within 256KB Flash, 64KB SRAM). LUT: Pt100(4KB), Ni120(1.6KB), Pt10(3KB)
  • 저전력 (Low Power): Sleep 모드, WFI 사용 (Sleep mode, Wait For Interrupt)
  • 환경 (Environment): 방수/방진 케이스, EMI 차폐 (Waterproof/dustproof case, EMI shielding)
  • 확장 가능성 (Scalability): 다중 RTD, 시각화, 극저온 센서 (Multiple RTDs, visualization, cryogenic sensors)

2. 하드웨어 설정 (Hardware Configuration)

  • STM32L432KC:
    • 클럭 (Clock): HSI 80MHz
    • SPI1: PA5(SCK), PA6(MISO), PA7(MOSI), PA4(CS, GPIO Output)
    • USART2: PA2(TX), PA3(RX), 115200 baud
  • MAX31865:
    • 기준 저항 (Reference Resistance): 430Ω(Pt100), 4300Ω(Pt1000), 43Ω(Pt10), 480Ω(Ni120)
    • 필터 (Filter): 50Hz, 65ms 변환 (Conversion)

3. C 코드 구현 (C Code Implementation)

코드는 rtd.h, rtd.c, main.c로 분리됩니다 (Code is separated into rtd.h, rtd.c, main.c).

3.1 rtd.h

#ifndef RTD_H
#define RTD_H

#include "stm32l4xx_hal.h"
#include <stdint.h>

// MAX31865 레지스터 정의 (Register Definitions)
#define MAX31865_CONFIG_REG         0x00
#define MAX31865_RTD_MSB_REG        0x01
#define MAX31865_RTD_LSB_REG        0x02
#define MAX31865_FAULT_STATUS_REG   0x07
#define MAX31865_READ_ADDR(x)       (x)
#define MAX31865_WRITE_ADDR(x)      (x | 0x80)

// MAX31865 설정 (Configuration)
#define CONFIG_2WIRE        0xC0 // Vbias ON, 1-shot, 50Hz
#define CONFIG_3WIRE        0xD0
#define CONFIG_4WIRE        0xC0
#define R_REF_PT100         430.0f
#define R_REF_PT1000        4300.0f
#define R_REF_PT10          43.0f
#define R_REF_NI120         480.0f
#define ADC_MAX             32768.0f

// 필터 설정 (Filter Settings)
#define FILTER_SIZE         5
#define KALMAN_Q            0.01f
#define KALMAN_R            0.1f

// LUT 크기 (LUT Sizes)
#define LUT_SIZE_PT         1051 // -200°C ~ 850°C
#define LUT_SIZE_NI120      401  // -80°C ~ 320°C
#define LUT_SIZE_PT10       761  // -260°C ~ 500°C

typedef struct {
    float r0;              // 기준 저항 (Reference Resistance)
    float alpha;           // 온도 계수 (Temperature Coefficient)
    char wire_type[8];     // 연결 방식 (Wire Type)
    char accuracy_class;   // 'A', 'B'
    char sensor_type[8];   // "Pt100", "Pt1000", "Ni120", "Pt10"
    float A, B, C;         // RTD 계수 (RTD Coefficients)
    float max_temp;        // 최대 온도 (Max Temperature)
    float min_temp;        // 최소 온도 (Min Temperature)
    float dissipation_constant; // 자가 발열 상수 (Self-Heating Constant, mW/°C)
    float calibration_offset;   // 캘리브레이션 오프셋 (Calibration Offset)
    float resistance_buffer[FILTER_SIZE]; // 이동 평균 버퍼 (Moving Average Buffer)
    uint8_t buffer_index;
    uint8_t buffer_count;
    float kalman_x;        // 칼만 필터 상태 (Kalman Filter State)
    float kalman_p;        // 칼만 필터 공분산 (Kalman Filter Covariance)
    const float* lut;      // LUT 포인터 (LUT Pointer)
    uint16_t lut_size;     // LUT 크기 (LUT Size)
} RTD;

// 함수 프로토타입 (Function Prototypes)
void RTD_Init(RTD* rtd, float r0, float alpha, const char* wire_type, char accuracy_class, const char* sensor_type);
float RTD_Linear_Approximation(RTD* rtd, float r_measured);
float RTD_LUT_Lookup(RTD* rtd, float r_measured);
float RTD_To_Temperature(RTD* rtd, float r_measured, uint8_t use_linear, uint8_t use_lut);
float RTD_Self_Heating_Compensation(RTD* rtd, float i_excitation, float r_measured);
float RTD_Moving_Average_Filter(RTD* rtd, float r_measured);
float RTD_Kalman_Filter(RTD* rtd, float r_measured);
void RTD_Calibrate(RTD* rtd, float r_known, float t_known);
float RTD_Compensate_Lead_Resistance(RTD* rtd, float v_measured, float i_excitation, float r_lead1, float r_lead3);
float RTD_Check_Accuracy(RTD* rtd, float temperature);
float MAX31865_Read_Resistance(RTD* rtd, SPI_HandleTypeDef* hspi);
void RTD_Read_MAX31865(RTD* rtd, SPI_HandleTypeDef* hspi, float i_excitation, float r_lead1, float r_lead3, 
                       uint8_t use_linear, uint8_t use_lut, float* temperature, float* tolerance);
void log_message(const char* message, UART_HandleTypeDef* huart);

#endif
            

3.2 rtd.c

#include "rtd.h"
#include <string.h>
#include <math.h>
#include <stdio.h>

// LUT (샘플로 Pt100만 포함, Sample includes only Pt100)
static const float pt100_lut[LUT_SIZE_PT] = {
    18.52f, 22.83f, /* ... */ 390.48f // -200°C ~ 850°C
    // 실제 LUT는 Python으로 생성 (Generate actual LUT using Python)
};

// RTD 초기화 (RTD Initialization)
void RTD_Init(RTD* rtd, float r0, float alpha, const char* wire_type, char accuracy_class, const char* sensor_type) {
    rtd->r0 = r0;
    rtd->alpha = alpha;
    strncpy(rtd->wire_type, wire_type, 8);
    rtd->accuracy_class = (accuracy_class == 'A' || accuracy_class == 'B') ? accuracy_class : 'B';
    strncpy(rtd->sensor_type, sensor_type, 8);
    rtd->dissipation_constant = 2.0f;
    rtd->calibration_offset = 0.0f;
    rtd->buffer_index = 0;
    rtd->buffer_count = 0;
    rtd->kalman_x = r0;
    rtd->kalman_p = 1.0f;

    if (strcmp(sensor_type, "Ni120") == 0) {
        rtd->A = 5.485e-3f;
        rtd->B = 6.650e-6f;
        rtd->C = 2.805e-11f;
        rtd->max_temp = 320.0f;
        rtd->min_temp = -80.0f;
        rtd->lut = NULL;
        rtd->lut_size = LUT_SIZE_NI120;
    } else if (strcmp(sensor_type, "Pt10") == 0) {
        rtd->A = 3.9083e-3f;
        rtd->B = -5.775e-7f;
        rtd->C = -4.183e-12f;
        rtd->max_temp = 500.0f;
        rtd->min_temp = -260.0f;
        rtd->lut = NULL;
        rtd->lut_size = LUT_SIZE_PT10;
    } else {
        if (alpha == 0.00385f) { // IEC 60751
            rtd->A = 3.9083e-3f;
            rtd->B = -5.775e-7f;
            rtd->C = -4.183e-12f;
        } else if (alpha == 0.00392f) { // 미국 표준 (American Standard)
            rtd->A = 3.926e-3f;
            rtd->B = -5.92e-7f;
            rtd->C = -4.0e-12f;
        } else {
            char msg[64];
            snprintf(msg, sizeof(msg), "Error: Unsupported alpha: %.6f\r\n", alpha);
            log_message(msg, NULL);
            return;
        }
        rtd->max_temp = 850.0f;
        rtd->min_temp = -200.0f;
        rtd->lut = pt100_lut;
        rtd->lut_size = LUT_SIZE_PT;
    }

    if (strcmp(rtd->wire_type, "2wire") != 0 && strcmp(rtd->wire_type, "3wire") != 0 && strcmp(rtd->wire_type, "4wire") != 0) {
        char msg[64];
        snprintf(msg, sizeof(msg), "Error: Invalid wire type: %s\r\n", rtd->wire_type);
        log_message(msg, NULL);
    }
}

// 선형 근사 (Linear Approximation)
float RTD_Linear_Approximation(RTD* rtd, float r_measured) {
    return (r_measured / rtd->r0 - 1.0f) / rtd->alpha;
}

// LUT 조회 (LUT Lookup)
float RTD_LUT_Lookup(RTD* rtd, float r_measured) {
    if (!rtd->lut) {
        log_message("Warning: LUT not available, using linear approximation\r\n", NULL);
        return RTD_Linear_Approximation(rtd, r_measured);
    }
    int index = (int)((r_measured / rtd->r0 - 1.0f) / rtd->alpha + rtd->min_temp);
    if (index < 0 || index >= rtd->lut_size - 1) {
        char msg[64];
        snprintf(msg, sizeof(msg), "Error: LUT index out of range: %d\r\n", index);
        log_message(msg, NULL);
        return 0.0f;
    }
    float t1 = rtd->min_temp + (float)index;
    float t2 = t1 + 1.0f;
    float r1 = rtd->lut[index];
    float r2 = rtd->lut[index + 1];
    return t1 + (r_measured - r1) * (t2 - t1) / (r2 - r1);
}

// 저항-온도 변환 (Resistance-to-Temperature Conversion)
float RTD_To_Temperature(RTD* rtd, float r_measured, uint8_t use_linear, uint8_t use_lut) {
    if (r_measured <= 0.0f || r_measured > 400.0f) {
        char msg[64];
        snprintf(msg, sizeof(msg), "Error: Invalid resistance: %.2fΩ\r\n", r_measured);
        log_message(msg, NULL);
        return 0.0f;
    }

    float t;
    if (use_lut && rtd->lut) {
        t = RTD_LUT_Lookup(rtd, r_measured);
    } else if (use_linear && -50.0f <= (r_measured / rtd->r0 - 1.0f) / rtd->alpha && (r_measured / rtd->r0 - 1.0f) / rtd->alpha <= 150.0f) {
        t = RTD_Linear_Approximation(rtd, r_measured);
    } else {
        if (r_measured >= rtd->r0) {
            float a = rtd->B;
            float b = rtd->A;
            float c = 1.0f - r_measured / rtd->r0;
            float discriminant = b * b - 4.0f * a * c;
            if (discriminant < 0.0f) {
                log_message("Error: Invalid resistance value\r\n", NULL);
                return 0.0f;
            }
            t = (-b + sqrtf(discriminant)) / (2.0f * a);
        } else {
            t = 0.0f;
            for (int i = 0; i < 10; i++) {
                float rt_est = rtd->r0 * (1.0f + rtd->A * t + rtd->B * t * t + rtd->C * (t - 100.0f) * t * t * t);
                float drt_dt = rtd->r0 * (rtd->A + 2.0f * rtd->B * t + rtd->C * (4.0f * t * t * t - 300.0f * t * t));
                t -= (rt_est - r_measured) / drt_dt;
            }
        }
    }

    if (t > rtd->max_temp || t < rtd->min_temp) {
        char msg[64];
        snprintf(msg, sizeof(msg), "Error: Temperature out of range: %.2f°C\r\n", t);
        log_message(msg, NULL);
        return 0.0f;
    }
    return t + rtd->calibration_offset;
}

// 자가 발열 보상 (Self-Heating Compensation)
float RTD_Self_Heating_Compensation(RTD* rtd, float i_excitation, float r_measured) {
    if (i_excitation > 0.001f) {
        char msg[64];
        snprintf(msg, sizeof(msg), "Warning: High excitation current (%.2fmA)\r\n", i_excitation * 1000.0f);
        log_message(msg, NULL);
    }
    float power = i_excitation * i_excitation * r_measured;
    return power * 1000.0f / rtd->dissipation_constant;
}

// 이동 평균 필터 (Moving Average Filter)
float RTD_Moving_Average_Filter(RTD* rtd, float r_measured) {
    rtd->resistance_buffer[rtd->buffer_index] = r_measured;
    rtd->buffer_index = (rtd->buffer_index + 1) % FILTER_SIZE;
    if (rtd->buffer_count < FILTER_SIZE) rtd->buffer_count++;
    
    float sum = 0.0f;
    for (int i = 0; i < rtd->buffer_count; i++) {
        sum += rtd->resistance_buffer[i];
    }
    return sum / (float)rtd->buffer_count;
}

// 칼만 필터 (Kalman Filter)
float RTD_Kalman_Filter(RTD* rtd, float r_measured) {
    rtd->kalman_p += KALMAN_Q;
    float k = rtd->kalman_p / (rtd->kalman_p + KALMAN_R);
    rtd->kalman_x += k * (r_measured - rtd->kalman_x);
    rtd->kalman_p *= (1.0f - k);
    return rtd->kalman_x;
}

// 캘리브레이션 (Calibration)
void RTD_Calibrate(RTD* rtd, float r_known, float t_known) {
    float t_calculated = RTD_To_Temperature(rtd, r_known, 0, 0);
    rtd->calibration_offset = t_known - t_calculated;
    char msg[64];
    snprintf(msg, sizeof(msg), "Calibration: offset set to %.2f°C\r\n", rtd->calibration_offset);
    log_message(msg, NULL);
}

// 리드선 보상 (Lead Resistance Compensation)
float RTD_Compensate_Lead_Resistance(RTD* rtd, float v_measured, float i_excitation, float r_lead1, float r_lead3) {
    if (i_excitation <= 0.0f) {
        log_message("Error: Invalid excitation current\r\n", NULL);
        return 0.0f;
    }
    float r_t;
    if (strcmp(rtd->wire_type, "2wire") == 0) {
        float r_total = v_measured / i_excitation;
        r_t = r_total - 2.0f * r_lead1;
    } else if (strcmp(rtd->wire_type, "3wire") == 0) {
        float r_total = v_measured / i_excitation;
        r_t = r_total - (r_lead1 + r_lead3) / 2.0f;
    } else if (strcmp(rtd->wire_type, "4wire") == 0) {
        r_t = v_measured / i_excitation;
    } else {
        char msg[64];
        snprintf(msg, sizeof(msg), "Error: Invalid wire type: %s\r\n", rtd->wire_type);
        log_message(msg, NULL);
        return 0.0f;
    }
    if (r_t <= 0.0f || r_t > 400.0f) {
        char msg[64];
        snprintf(msg, sizeof(msg), "Error: Invalid compensated resistance: %.2fΩ\r\n", r_t);
        log_message(msg, NULL);
        return 0.0f;
    }
    return r_t;
}

// 정확도 검증 (Accuracy Verification)
float RTD_Check_Accuracy(RTD* rtd, float temperature) {
    return (rtd->accuracy_class == 'A') ? (0.15f + 0.002f * fabsf(temperature)) : (0.3f + 0.005f * fabsf(temperature));
}

// MAX31865 저항 읽기 (Read Resistance from MAX31865)
float MAX31865_Read_Resistance(RTD* rtd, SPI_HandleTypeDef* hspi) {
    uint8_t config = strcmp(rtd->wire_type, "3wire") == 0 ? CONFIG_3WIRE : 
                     strcmp(rtd->wire_type, "2wire") == 0 ? CONFIG_2WIRE : CONFIG_4WIRE;
    float r_ref = strcmp(rtd->sensor_type, "Pt100") == 0 ? R_REF_PT100 :
                  strcmp(rtd->sensor_type, "Pt1000") == 0 ? R_REF_PT1000 :
                  strcmp(rtd->sensor_type, "Pt10") == 0 ? R_REF_PT10 : R_REF_NI120;
    
    uint8_t tx_data[2] = { MAX31865_WRITE_ADDR(MAX31865_CONFIG_REG), config };
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
    HAL_SPI_Transmit(hspi, tx_data, 2, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
    
    HAL_Delay(65);
    
    uint8_t rx_data[2];
    tx_data[0] = MAX31865_READ_ADDR(MAX31865_RTD_MSB_REG);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
    HAL_SPI_TransmitReceive(hspi, tx_data, rx_data, 2, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
    
    uint16_t rtd_data = (rx_data[0] << 8) | (rx_data[1] >> 1);
    if (rtd_data == 0 || rtd_data == 0x7FFF) {
        log_message("Error: Invalid RTD data\r\n", NULL);
        return 0.0f;
    }
    return ((float)rtd_data * r_ref) / ADC_MAX;
}

// 통합 RTD 읽기 (Integrated RTD Reading)
void RTD_Read_MAX31865(RTD* rtd, SPI_HandleTypeDef* hspi, float i_excitation, float r_lead1, float r_lead3, 
                       uint8_t use_linear, uint8_t use_lut, float* temperature, float* tolerance) {
    float r_measured = MAX31865_Read_Resistance(rtd, hspi);
    if (r_measured == 0.0f) {
        *temperature = 0.0f;
        *tolerance = 0.0f;
        return;
    }
    
    float v_measured = r_measured * i_excitation;
    float r_compensated = RTD_Compensate_Lead_Resistance(rtd, v_measured, i_excitation, r_lead1, r_lead3);
    if (r_compensated == 0.0f) {
        *temperature = 0.0f;
        *tolerance = 0.0f;
        return;
    }
    
    float r_filtered = RTD_Moving_Average_Filter(rtd, r_compensated);
    r_filtered = RTD_Kalman_Filter(rtd, r_filtered);
    
    float delta_t = RTD_Self_Heating_Compensation(rtd, i_excitation, r_filtered);
    
    *temperature = RTD_To_Temperature(rtd, r_filtered, use_linear, use_lut) - delta_t;
    
    *tolerance = RTD_Check_Accuracy(rtd, *temperature);
    
    char msg[128];
    snprintf(msg, sizeof(msg), "R_measured: %.2fΩ, Temp: %.2f°C, Tolerance: ±%.2f°C\r\n", 
             r_filtered, *temperature, *tolerance);
    log_message(msg, hspi == &hspi1 ? &huart2 : NULL);
}

// UART 로깅 (UART Logging)
void log_message(const char* message, UART_HandleTypeDef* huart) {
    if (huart) {
        HAL_UART_Transmit(huart, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
    }
}
            

3.3 main.c

/* USER CODE BEGIN Header */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "rtd.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define CS_PIN GPIO_PIN_4
#define CS_PORT GPIOA
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
SPI_HandleTypeDef hspi1;
UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SPI1_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */
  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
  
  // RTD 초기화 (RTD Initialization, Pt100, IEC 60751, 3선식, Class A)
  RTD rtd;
  RTD_Init(&rtd, 100.0f, 0.00385f, "3wire", 'A', "Pt100");
  
  // 캘리브레이션 (Calibration, 0°C에서 100Ω)
  RTD_Calibrate(&rtd, 100.0f, 0.0f);
  
  // Pt10 초기화 (Pt10 Initialization, 4선식, Class B)
  RTD rtd_pt10;
  RTD_Init(&rtd_pt10, 10.0f, 0.00385f, "4wire", 'B', "Pt10");
  RTD_Calibrate(&rtd_pt10, 10.0f, 0.0f);
  
  float temperature, tolerance;
  float i_excitation = 0.001f; // 1mA
  float r_lead1 = 0.3f; // 리드선 저항 (Lead Resistance)
  float r_lead3 = 0.3f;
  uint8_t use_linear = 1;
  uint8_t use_lut = 0;
  
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    // Sleep 모드 (Sleep Mode)
    HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
    
    // Pt100 읽기 (Read Pt100)
    RTD_Read_MAX31865(&rtd, &hspi1, i_excitation, r_lead1, r_lead3, use_linear, use_lut, &temperature, &tolerance);
    
    // Pt10 읽기 (Read Pt10)
    RTD_Read_MAX31865(&rtd_pt10, &hspi1, i_excitation, 0.0f, 0.0f, use_linear, use_lut, &temperature, &tolerance);
    
    HAL_Delay(1000);
    
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  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;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  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;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief SPI1 Initialization Function
  * @retval None
  */
static void MX_SPI1_Init(void)
{
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 7;
  hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
  hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief USART2 Initialization Function
  * @retval None
  */
static void MX_USART2_UART_Init(void)
{
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 115200;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart2.Init.ClockPrescaler = UART_PRESCALER_DIV1;
  huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief GPIO Initialization Function
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  __HAL_RCC_GPIOA_CLK_ENABLE();

  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);

  GPIO_InitStruct.Pin = GPIO_PIN_4;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

/* USER CODE BEGIN 4 */
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  log_message("Error: System halted\r\n", &huart2);
  while (1)
  {
  }
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  char msg[128];
  snprintf(msg, sizeof(msg), "Assert failed: %s, line %lu\r\n", file, line);
  log_message(msg, &huart2);
}
#endif /* USE_FULL_ASSERT */
            

4. 코드 설명 (Code Explanation)

  • rtd.h: RTD 구조체와 함수 프로토타입 정의 (Defines RTD structure and function prototypes). Pt100, Pt1000, Ni120, Pt10 지원 (Supports Pt100, Pt1000, Ni120, Pt10).
  • rtd.c:
    • RTD_Init: 센서별 계수 및 LUT 초기화 (Initialize sensor coefficients and LUT)
    • RTD_Linear_Approximation: -50°C ~ 150°C 선형 근사 (Linear approximation for -50°C to 150°C)
    • RTD_LUT_Lookup: LUT 선형 보간 (LUT linear interpolation)
    • RTD_To_Temperature: 이차 방정식, 뉴턴-랩슨, LUT 지원 (Quadratic equation, Newton-Raphson, LUT support)
    • RTD_Self_Heating_Compensation: 자가 발열 계산 (Self-heating calculation)
    • RTD_Moving_Average_Filter: 5 샘플 평균 (5-sample average)
    • RTD_Kalman_Filter: 노이즈 제거 (Noise reduction)
    • RTD_Calibrate: 오프셋 보정 (Offset correction)
    • RTD_Compensate_Lead_Resistance: 2/3/4선식 보상 (2/3/4-wire compensation)
    • RTD_Check_Accuracy: Class A/B 허용 오차 (Class A/B tolerance)
    • MAX31865_Read_Resistance: SPI 통신 (SPI communication)
    • RTD_Read_MAX31865: 통합 처리 (Integrated processing)
  • main.c: STM32CubeMX 설정, Pt100/Pt10 읽기, Sleep 모드 (STM32CubeMX configuration, Pt100/Pt10 reading, Sleep mode)

5. 메모리 및 최적화 (Memory and Optimization)

  • 메모리 (Memory): 256KB 플래시, 64KB SRAM 내 동작 (Operates within 256KB Flash, 64KB SRAM). LUT: 약 8.6KB (Pt100: 4KB, Ni120: 1.6KB, Pt10: 3KB). 코드: 약 30KB 플래시, 12KB SRAM (Code: ~30KB Flash, 12KB SRAM).
  • 저전력 (Low Power): Sleep 모드, 인터럽트로 깨어남 (Sleep mode, wake by interrupt). Stop 모드 확장 가능 (Extendable to Stop mode).
  • 노이즈 필터링 (Noise Filtering): 50Hz 필터, 이동 평균, 칼만 필터 (50Hz filter, moving average, Kalman filter).
  • Pt10: 극저온(-260°C) 지원, R_REF=43Ω (Cryogenic support, R_REF=43Ω).

6. 사용 방법 (Usage Instructions)

  1. STM32CubeMX 설정 (STM32CubeMX Configuration):
    • 클럭 (Clock): HSI 80MHz
    • SPI1: PA5(SCK), PA6(MISO), PA7(MOSI), PA4(CS)
    • USART2: PA2(TX), PA3(RX), 115200 baud
  2. 코드 통합 (Code Integration):
    • rtd.h, rtd.cCore/Inc, Core/Src에 추가 (Add to Core/Inc, Core/Src)
    • main.c를 덮어쓰기 (Overwrite main.c)
  3. 하드웨어 연결 (Hardware Connection):
    • MAX31865: SPI1, CS(PA4), RTD(Pt100/Pt10)
    • UART: PA2, PA3를 USB-TTL로 연결 (Connect to USB-TTL)
  4. 빌드 및 실행 (Build and Run): STM32CubeIDE에서 빌드/플래시, PuTTY로 출력 확인 (Build/flash in STM32CubeIDE, check output in PuTTY)
  5. 입력 (Inputs):
    • r0: 100.0(Pt100), 1000.0(Pt1000), 10.0(Pt10), 120.0(Ni120)
    • alpha: 0.00385(IEC), 0.00392(미국, American)
    • i_excitation: 0.001(1mA)
    • r_lead1, r_lead3: 0.3Ω
  6. 출력 (Output): UART로 온도, 허용 오차 (Temperature, tolerance via UART)

7. 참고 자료 (References)

  • IEC 60751: 백금 RTD 표준 (Platinum RTD Standard)
  • ASTM E1137: Ni120 사양 (Ni120 Specification)
  • MAX31865 데이터시트 (Datasheet): Analog Devices
  • STM32L432KC 데이터시트 (Datasheet): STMicroelectronics
  • STM32CubeMX/IDE 문서 (Documentation): HAL 드라이버 (HAL Drivers)

 

반응형