sleemanj의 MCP41100 디지털 포텐셔미터 라이브러리 사용 (감사합니다!). 아다프루트 모듈용 라이브러리도 제품 페이지에서 제공됩니다.
아두이노 IDE (Arduino IDE)
프로젝트 소개
Mr ThermoParcel: 샘플 운송용 온도 제어 용기
일반 우편 서비스를 이용해 민감한 샘플을 안전하게 운송할 수 있는 온도 제어 용기를 개발하는 것이 목표입니다. 프로토타입 장치인 'Mr ThermoParcel'은 4~37°C 범위에서 작동하며, 메인 어댑터, 내부 배터리, 또는 자동차 시거잭이나 노트북 충전기 같은 12V 전원으로 구동할 수 있습니다.
프로젝트의 기원
이 아이디어는 학술 연구에서 겪은 실제 문제에서 비롯되었습니다: 공동 연구자들과 온도에 민감한 생물학적 샘플을 안전하게 교환하는 문제입니다. Viola는 말라리아 질병을 연구하며, 종종 혈액 샘플을 보내거나 받아야 합니다. 하지만 저장 및 운송 중 올바른 온도를 유지하지 않으면 샘플이 쉽게 손상되어 쓸모없게 됩니다. 이는 말라리아에 특별한 반응을 보이는 희귀 환자의 혈액처럼 귀중한 샘플일 때 더욱 답답합니다.
이러한 유형의 혈액 샘플을 배송하는 일반적인 방법은 배송 전 완전히 동결시키고, 냉동 화물 전문 택배를 이용한 후, 배송 후 조심스럽게 해동하는 것입니다. 비용이 많이 들 뿐만 아니라, 동결/해동 과정은 항상 샘플의 일부를 변형시키거나 손상시키며, 해동 과정 자체도 추가 화학물질이 필요한 특정 프로토콜을 따릅니다. 게다가 물품이 대학 캠퍼스 택배 보관소로 배송되는 경우가 많은데, 부적절한 보관 조건과 배송 알림 지연이 샘플 열화의 빈번한 원인이 됩니다.
설계 개념
화학물질 배송용으로 재활용한 일반 폴리스티렌 용기(소형 소포 크기)에 온도 제어 시스템을 통합했습니다. 혈액 샘플로 최적화하고 테스트했지만, 동일한 시스템은 세포, 배양액, 온도에 민감한 화학물질, 유화제, 효소 등 다양한 생물학적 물질에 사용할 수 있으며, 고체 물품에도 적용 가능합니다.
시스템 개념은 그림 1에 나와 있습니다. 냉각은 펠티어 모듈로 이루어지며, 냉각면은 폴리스티렌 용기 내부의 작은 샘플 박스에 부착되고, 발열면은 외부 히트싱크에 연결됩니다. 가열은 샘플 박스 내부에 배치된 히터 매트로 수행합니다. 샘플과 접촉한 센서로 온도를 지속적으로 모니터링하며, 아두이노 컨트롤러가 냉각/가열 강도를 조절합니다.
구현
용기 하드웨어 설계
외부 용기는 250x250x250mm, 벽 두께 45mm의 폴리스티렌 박스로, 표준 우편 서비스로 배송되는 실험실 배송에 흔히 사용되는 유형입니다. 내부에는 125x70x40mm 크기의 플라스틱 인클로저가 있으며, 두 개의 50mL 원심분리 튜브가 들어있습니다. 이 튜브들은 생물학적 및 감염성 물질 규정(B 카테고리 UN3373)에 따른 2차 포장 역할을 합니다. 1차 샘플은 흡수성 티슈로 분리된 여섯 개의 2mL 에펜도르프 튜브에 담긴 시편입니다. Mr ThermoParcel은 단단한 2차 포장을 유연한 파우치로 교체하여 최대 50mL의 액체 샘플을 저장할 수 있습니다(예: 15mL 튜브 3~4개까지). 내부 박스 내 냉난방 균일성을 높이고 구리 시트, 샘플, 온도 센서 간 열 접촉을 보장하기 위해 샘플 주변에 전기를 통하지 않는 고체 젤을 부었습니다. 샘플을 담은 내부 박스는 그림 2에 나와 있습니다.
냉각 및 가열 시스템
내부 온도는 샘플과 접촉한 100Ω 백금 저항 온도계(RTD, Resistance Temperature Detector)로 모니터링하며, 펠티어 모듈(냉각) 또는 히터 매트(가열)로 PID 시스템을 통해 제어합니다. 펠티어에서 발생한 열을 박스 밖으로 방출하기 위해 펠티어의 발열면을 외부 구리 조각과 히트싱크(CPU 쿨러)에 세 개의 구리 히트파이프로 열 접촉시켰습니다. 냉각면은 박스를 관통하는 얇은 구리 시트에 부착되어 샘플을 균일하게 냉각시킵니다. 열 결합 세부 사항은 그림 3에 나와 있습니다.
히터 매트는 내부 인클로저 내부에 온도 센서 및 샘플과 접촉하도록 배치되었습니다.
전자 회로 및 배선
냉각 및 가열 제어
Mr ThermoParcel에 사용된 펠티어 모듈은 최대 냉각 전력에서 3.9A, 7.6V로 정격되어 있습니다. 온도를 효과적으로 제어하기 위해 펠티어 모듈에 공급되는 전력량을 PTN78020W 스텝다운 조절 가능 스위칭 레귤레이터로 전자적으로 관리합니다. 이 레귤레이터는 7~36V 입력 전압을 받아 2.5~12.6V를 출력하며, 출력이 입력보다 2V 이상 높을 수 없다는 제약이 있습니다. 출력 전압은 데이터시트 표에 따라 두 조절 핀 사이의 저항값을 설정하여 조절합니다. Mr ThermoParcel은 아두이노로 제어되는 MCP41100 100kΩ 디지털 포텐셔미터를 사용하여 온도 읽기에 기반해 출력 전압을 전자적으로 조절합니다. PTN78020W의 전체 출력 전압 범위에는 1MΩ 이상의 저항 변화가 필요하므로, 디지털 포텐셔미터를 100kΩ로 설정해도 펠티어 모듈에 전압이 인가되어 디지털 포텐셔미터만으로는 펠티어를 '끄기'가 불가능합니다. 동일한 조절 개념이 히터 매트를 이용한 가열에도 적용됩니다. 매트는 단순히 전류를 열로 소모하는 저항이므로, 전압을 조절하는 것이 공급 전력을 제어하는 직접적인 방법입니다.
전원 공급
메인 전원 소켓 근처에서 사용할 때는 12V DC 파워 서플라이를 PTN78020W에 직접 연결합니다. 이는 최대 10V 출력을 허용하여 펠티어를 최대 전력으로 구동하고, 히터 매트를 프로젝트 목적에 충분한 전력으로 구동할 수 있게 합니다. PTN78020W 레귤레이터의 7~36V 입력 범위를 고려하면, 노트북 등 전자기기용 대부분의 DC 파워 서플라이와 자동차 시거잭으로도 Mr ThermoParcel을 구동할 수 있습니다. 외부 전원을 사용할 수 없을 때는 3.7V, 10400mAh 리튬 이온 배터리로 구동합니다. 배터리는 여전히 PTN78020W 레귤레이터에 전원을 공급하지만, 펠티어 모듈 구동에 필요한 입력 전압(PTN78020W 입력단에서 10~12V)에 도달하기 위해 XL6019 스텝업 DC-DC 컨버터를 배터리 출력단에 먼저 연결합니다.
아두이노 배선
외부 12V 입력이 가능하면 아두이노 보드의 잭 소켓을 통해 직접 아두이노에 전원을 공급합니다. 내부 배터리를 사용할 때는 XL6019 컨버터 출력을 사용하여 소켓에 유사한 전압을 설정합니다.
아두이노는 디지털 포텐셔미터의 저항을 제어하여 펠티어 모듈/히터 매트에 공급되는 전력을 조절합니다. 배선은 sleemanj의 MCP41 시리즈 라이브러리 지침에 따라 가변 저항 구성으로 수행합니다. 아두이노는 또한 온도 센서를 읽기 위한 Adafruit MAX31865 PT100 RTD 증폭기와 온도 데이터 및 시스템 작동 상태를 표시하기 위한 Adafruit RGB LCD 실드에 연결됩니다. 둘 다 제품 페이지의 상세한 Adafruit 문서에 따라 배선됩니다.
소프트웨어
아두이노에 연결된 모든 Adafruit 모듈은 각각의 라이브러리로 작동하며, 디지털 포텐셔미터는 sleemanj의 MCP41 시리즈 라이브러리로 제어합니다. Mr ThermoParcel의 아두이노 코드 핵심 기능은 온도 제어와 관련되어 있으며, PID 폐루프 시스템으로 구현됩니다. 온도 설정값은 사용자가 LCD 디스플레이 실드 버튼으로 입력합니다. 측정된 각 온도 읽기를 사용하여 설정값과의 편차를 구하고, 이를 통해 디지털 포텐셔미터에 공급되어 냉각/가열 전력을 조절하는 PID 값을 계산합니다. 외부 물리 토글 스위치로 전원 출력이 펠티어 모듈(냉각) 또는 히터 매트(가열) 중 어느 쪽으로 향하는지 결정합니다. 시스템에 전자 스위치가 없으므로 코드에서 가열 모드와 냉각 모드를 구분하며, 사용자는 LCD 디스플레이 실드 버튼으로 적절한 모드를 선택해야 합니다. 이 구분은 계산된 PID 값이 올바른 부호를 갖도록 보장합니다. 테스트 중 PID 인자 범위를 다양하게 시도해본 결과, 대부분의 상황에서 P 항만으로도 설정값으로부터 ±0.5°C 이내를 유지할 수 있어 결국 I와 D 항을 제거했습니다. 이는 샘플과 내부 젤 충전 박스의 상대적으로 큰 열용량 때문에 온도 변화가 느리기 때문입니다(가장 빠른 구간에서도 평균 0.02°C/s).
성능
메인 전원 사용 시, Mr ThermoParcel은 실온 21~23°C에서 시작하여 약 1시간 만에 4°C까지 냉각됩니다. 8~10°C는 처음 20분 내에 도달합니다. 다시 실온에서 시작하여 히터 매트 사용 시, 약 10분 만에 37°C에 도달합니다. 모든 온도는 설정값으로부터 ±0.5°C 이내로 유지됩니다.
내부 배터리만 사용할 때는 약 10°C가 도달 가능한 최저 온도이며, 1.5~2시간이 소요됩니다. 히터 매트로는 여전히 37°C에 도달 가능하지만 40~60분이 걸립니다. 이 제한은 배터리 방전율 때문입니다: Mr ThermoParcel의 리튬 이온 배터리는 3.7V에서 최대 7A 방전 전류로 정격되어 있지만, 10~12V로 스텝업 변환 시 펠티어 모듈의 최대 전력을 유지하려면 더 높은 방전 전류가 필요합니다. 배터리에 과전류 차단 회로가 내장되어 있어, 냉난방 시스템이 최대 정격보다 큰 전류를 끌어내려 하면 출력이 차단되어 시스템이 작동하지 않습니다. 배터리 사용 시 소프트웨어로 전력 소모를 안전한 수준으로 제한합니다. 이 제한은 여기 사용된 배터리 탓이며, 더 높은 방전율의 배터리는 널리 구할 수 있습니다. 또는 직렬 3셀, 공칭 전압 11.1V인 리튬 이온 배터리를 사용하면 문제를 해결하고 스텝업 DC 컨버터도 불필요해집니다.
향후 방향
현재 개발 단계에서는 CPU 쿨러의 크기와 움직이는 부품, 구조의 견고성 문제로 배송이 불가능합니다. 하지만 히트싱크를 수동 냉각 시스템으로 교체하고 95kPa 2차 포장을 사용하면, Mr ThermoParcel을 적절한 강성 용기에 넣어 안전한 운송이 가능해지며, 비행기 등 모든 운송 수단으로 샘플을 배송하는 표준 택배사의 요건을 충족할 수 있습니다.
핵심 목표 달성 후, 다른 구성요소를 추가해 장치 기능을 확장할 수 있습니다. 운송 중 온도 프로파일을 로컬 메모리에 저장해 나중에 검증하거나, GSM 아두이노 모듈을 사용해 정기적으로 사용자에게 SMS로 전송할 수 있습니다. 독립적인 소포 추적과 적시 수령을 위해 GPS 수신기도 포함할 수 있습니다.
코드
PID_LCD_controller_v03 (Arduino)
#include // PT100 온도 센서 라이브러리
#include // 디지털 포텐셔미터 라이브러리
#include // LCD 디스플레이 & 버튼 실드 라이브러리
#include // I2C 익스팬더 라이브러리
#include
// 디지털 포텐셔미터 설정
MCP41_Simple digitalPotentiometer;
const uint8_t digitalPotentiometer_CS = 10;
// PT100 온도 센서 설정
// 소프트웨어 SPI 사용: CS, DI, DO, CLK
Adafruit_MAX31865 PT100amplifier = Adafruit_MAX31865(2, 3, 4, 5);
// Rref 저항값 설정. PT100용 430.0 사용
#define RREF 430.0
// 센서의 0도 저항값, PT100은 100.0ohm
#define RNOMINAL 100.0
// LCD 디스플레이 실드 설정
Adafruit_RGBLCDShield LCD_shield = Adafruit_RGBLCDShield();
void setup() {
Serial.begin(115200);
Serial.println("Mr ThermoParcel, starting operation...");
PT100amplifier.begin(MAX31865_4WIRE); // 2WIRE 또는 4WIRE 설정, 여기서는 4선식 RTD
// 디지털 포텐셔미터 초기화
digitalPotentiometer.begin(digitalPotentiometer_CS);
// 와이퍼를 0~255 사이 임의 지점으로 설정
digitalPotentiometer.setWiper(200);
// LCD 디스플레이 실드 초기화
LCD_shield.begin(16, 2);
// 올바른 간격으로 설정값 및 측정 온도 텍스트 표시
LCD_shield.print("Tsetpoint: C");
LCD_shield.setCursor(0, 1);
LCD_shield.print("Tsample: C");
}
// PID 상수, 온도 관련 변수, 실드 버튼 값 초기화
int powerMode = 1; // 1: 배터리, -1: 메인 전원; 배터리 과부하 방지용
int operationMode = 1; // 1: 냉각, -1: 가열; PID 항 부호 보정용
float PT100ratio;
uint8_t buttonsPressed = 0;
float kp = 500.0;
int ki = 5;
int kd = 3.9;
float PID_p = 0.0;
int PID_d = 0;
float Tmeasured = -1.0;
float Tsetpoint = 22.0; // 실온 근처에서 시작
float PID_error = 5;
float PID_value = 0;
// LCD에 설정 온도 올바르게 출력하는 함수
static char TsetpointString[3];
void print_Tsetpoint(int T) {
LCD_shield.setCursor(10, 0);
dtostrf(T, 3, 0, TsetpointString);
LCD_shield.print(TsetpointString);
}
// LCD에 측정 온도 올바르게 출력하는 함수
static char TmeasuredString[4];
void print_Tmeasured(float T) {
LCD_shield.setCursor(8, 1);
dtostrf(T, 5, 1, TmeasuredString);
LCD_shield.print(TmeasuredString);
}
// 전원 모드 출력 함수 (B: 배터리, M: 메인)
void print_powerMode() {
LCD_shield.setCursor(15, 0);
if (powerMode == 1) LCD_shield.print("B");
else if (powerMode == -1) LCD_shield.print("M");
}
// 작동 모드 출력 함수 (C: 펠티어 냉각, H: 히터 매트)
void print_operationMode() {
LCD_shield.setCursor(15, 1);
if (operationMode == 1) LCD_shield.print("C");
else if (operationMode == -1) LCD_shield.print("H");
}
// *** 메인 루프 ***
void loop() {
// 온도 읽기
uint16_t rtd = PT100amplifier.readRTD();
PT100ratio = rtd;
PT100ratio /= 32768;
Tmeasured = PT100amplifier.temperature(RNOMINAL, RREF);
Serial.print("Setpoint Temperature = "); Serial.println(Tsetpoint);
Serial.print("Temperature = "); Serial.println(Tmeasured);
// 온도값 및 모드 출력
print_Tsetpoint(Tsetpoint);
print_Tmeasured(Tmeasured);
print_powerMode();
print_operationMode();
// 설정값과 측정값 간 오차 계산
PID_error = Tmeasured - Tsetpoint;
// P 값 계산
PID_p = operationMode * kp * PID_error;
// 전체 PID 값 계산, 최대 255 최소 0으로 제한
PID_value = (int) PID_p; // + PID_i + PID_d;
Serial.print("PID_p = "); Serial.println(PID_p);
Serial.print("powerMode = "); Serial.println(powerMode);
Serial.print("operationMode = "); Serial.println(operationMode);
Serial.print("PID_error = "); Serial.println(PID_error);
Serial.print("PID_value = "); Serial.println(PID_value);
// 배터리 모드(powerMode=1)일 때 배터리 과부하 방지를 위해 출력 제한
// 메인 모드(powerMode=-1)일 때는 최대 전력(255) 허용
if (powerMode == 1) {
if (PID_value < 0) PID_value = 0;
if (PID_value > 120) PID_value = 120;
} else if (powerMode == -1) {
if (PID_value < 0) PID_value = 0;
if (PID_value > 255) PID_value = 255;
}
Serial.print("Adjusted PID_value = "); Serial.println(PID_value);
// PID 값으로부터 디지털 포텐셔미터 저항 설정
digitalPotentiometer.setWiper(255 - PID_value);
// 버튼 입력 감지, 설정값 변경 및 측정/설정 온도 표시
delay(1000);
buttonsPressed = LCD_shield.readButtons();
if (buttonsPressed & BUTTON_SELECT) {
// 커서 깜빡임으로 편집 모드 표시
LCD_shield.setCursor(14, 0);
LCD_shield.blink();
delay(1000);
buttonsPressed = 0;
// SELECT 버튼을 다시 누를 때까지 편집 모드 유지
// UP/DOWN 버튼으로 Tsetpoint 변경
// LEFT로 작동 모드(가열/냉각) 토글, RIGHT로 전원 모드(배터리/메인) 토글
while (!(buttonsPressed & BUTTON_SELECT)) {
buttonsPressed = LCD_shield.readButtons();
if (buttonsPressed & BUTTON_UP) Tsetpoint += 1;
if (buttonsPressed & BUTTON_DOWN) Tsetpoint -= 1;
if (buttonsPressed & BUTTON_RIGHT) { powerMode *= -1; print_powerMode(); }
if (buttonsPressed & BUTTON_LEFT) { operationMode *= -1; print_operationMode(); }
print_Tsetpoint(Tsetpoint);
LCD_shield.setCursor(14, 0);
delay(500);
}
// 편집 모드 종료 및 커서 깜빡임 중지
LCD_shield.noBlink();
buttonsPressed = 0;
}
Serial.println();
}
회로도
온도 제어 용기의 모든 주요 구성요소를 포함한 회로도.
커뮤니티 사용자 요청으로 제공한 배선도. 실제 조립에 사용한 것으로 지저분하고 공개용으로 만든 것은 아니지만 도움이 될 수 있습니다.