다채널 온도 모니터링 시스템 설계: MCU 기반 구현

다채널 온도 모니터링 시스템은 임베디드 환경에서 여러 지점의 온도를 동시에 측정하고 제어하는 데 사용된다. 본 설계에서는 중앙 처리 장치로 8051 계열 마이크로컨트롤러를 택하며, 각 채널의 온도 정보는 독립적인 디지털 온도 트랜스듀서를 통해 수집한다. 수집된 데이터는 내부 알고리즘을 통해 필터링되고 분석되며, 이 결과에 따라 경보 모듈과 외부 구동 회로의 동작 상태가 결정된다. 동시에 측정 온도는 액정 디스플레이를 통해 실시간으로 시각화되며, 사용자는 물리적 입력 장치를 통해 임계값을 직접 설정할 수 있다.

아키텍처 개요

시스템은 크게 네 가지 기능 블록으로 구성된다. 첫째, 센서 인터페이스 계층은 1-Wire 프로토콜을 기반으로 여러 개의 DS18B20 도 센서와 통신한다. 둘째, 중앙 처리 계층에서는 산술 연산과 논리 판단을 수행한다. 셋째, 출력 계층은 피에조 부저와 전자기계식 릴레이를 구동한다. 넷째, 인터페이스 계층은 16x2 문자 LCD와 택트 스위치를 포함한다.

1-Wire 통신 구현

DS18B20은 단일 데이터선으로 전원과 통신을 모두 처리하는 특징이 있다. 다음 코드는 핀 정의와 기본 타이밍 함수를 재구성한 것이다.

#include <reg51.h>
#include <intrins.h>

sbit ONEWIRE_PIN = P1^3;  // DS18B20 데이터 핀: P1.3

// 짧은 지연 함수 (약 10μs 단위)
void delay_10us(unsigned int count) {
    while (count--) {
        _nop_(); _nop_(); _nop_(); _nop_();
    }
}

// 긴 지연 함수 (약 1ms 단위)
void delay_ms(unsigned int ms) {
    unsigned int i, j;
    for (i = 0; i < ms; i++)
        for (j = 0; j < 120; j++);
}

// 1-Wire 리셋 및 프리즌스 감지
bit ow_reset(void) {
    bit presence;
    
    ONEWIRE_PIN = 0;           // 버스 드라이브 로우
    delay_10us(50);            // 480μs 이상 유지
    ONEWIRE_PIN = 1;           // 버스 릴리스
    delay_10us(7);             // 70μs 대기
    
    presence = ONEWIRE_PIN;    // 프리즌스 신호 샘플링
    
    delay_10us(50);            // 나머지 타임슬롯 완료
    return !presence;          // 감지 성공 시 1 반환
}

// 마스터에서 슬레이브로 1바이트 전송
void ow_write_byte(unsigned char value) {
    unsigned char idx;
    
    for (idx = 0; idx < 8; idx++) {
        ONEWIRE_PIN = 0;       // 쓰기 타임슬롯 시작
        _nop_(); _nop_();      // 짧은 지연
        
        if (value & 0x01) {
            ONEWIRE_PIN = 1;   // '1' 비트 전송
        } else {
            ONEWIRE_PIN = 0;   // '0' 비트 전송
        }
        
        delay_10us(6);         // 60μs 유지
        ONEWIRE_PIN = 1;       // 버스 복원
        
        value >>= 1;           // 다음 비트 준비
    }
}

// 슬레이브에서 마스터로 1바이트 수신
unsigned char ow_read_byte(void) {
    unsigned char idx, result = 0;
    
    for (idx = 0; idx < 8; idx++) {
        ONEWIRE_PIN = 0;       // 읽기 타임슬롯 시작
        _nop_(); _nop_();
        ONEWIRE_PIN = 1;       // 버스 릴리스
        
        _nop_(); _nop_();      // 샘플링 전 대기
        
        result >>= 1;          // 결과 레지스터 시프트
        if (ONEWIRE_PIN) {
            result |= 0x80;    // 수신 비트 기록
        }
        
        delay_10us(6);         // 타임슬롯 완료 대기
    }
    
    return result;
}

위 구현에서 ow_reset 함수는 버스를 초기화하고 연결된 센서의 응답을 확인한다. 쓰기 동작은 마스터가 타임슬롯을 생성하고 해당 구간 내 전압 레벨을 조절하는 방식으로, 읽기 동작은 마스터가 짧은 펄스를 발생시킨 뒤 버스 상태를 샘플링한다.

온도 변환 및 데이터 해석

DS18B20의 스크래치패드에서 얻은 원시 데이터는 16비트 2의 보수 형식으로 저장된다. 다음은 이를 실제 온도로 환산하는 과정이다.

// DS18B20 스크래치패드 읽기 및 온도 계산
float acquire_temperature(void) {
    unsigned char lsb, msb;
    signed int raw;
    float celsius;
    
    ow_reset();
    ow_write_byte(0xCC);       // 스 ROM
    ow_write_byte(0x44);       // 온도 변환 시작
    
    delay_ms(750);             // 12비트 해상도 기준 변환 대기
    
    ow_reset();
    ow_write_byte(0xCC);       // 스킵 ROM
    ow_write_byte(0xBE);       // 스크래치패드 읽기 명령
    
    lsb = ow_read_byte();      // 온도 LSB
    msb = ow_read_byte();      // 온도 MSB
    
    raw = (msb << 8) | lsb;    // 16비트 값 조합
    
    // 12비트 해상도: 0.0625°C/LSB
    celsius = (float)raw * 0.0625;
    
    return celsius;
}

측정된 온도는 저장된 임계값과 비교되어 하위 하드웨어의 동작이 결정된다. 다음 코드는 히스테리시스를 포함한 제어 로직을 보여준다.

sbit BUZZER = P2^4;   // 피에조 부저: P2.4 (활성 로우)
sbit RELAY  = P2^5;   // 릴레이 구동: P2.5 (활성 하이)

#define HYSTERESIS  1.5   // 히스테리시스 폭: 1.5°C

float threshold_high = 35.0;   // 상한 임계값
float threshold_low  = 30.0;   // 하한 임계값 (복원 지점)

// 제어 루프
void process_control(float current_temp) {
    static unsigned char alarm_state = 0;
    
    if (current_temp >= threshold_high) {
        BUZZER = 0;            // 경보 활성화
        RELAY  = 1;            // 냉각 장치 가동
        alarm_state = 1;
    } 
    else if (current_temp <= (threshold_high - HYSTERESIS) && alarm_state) {
        // 히스테리시스 구간 내 복원
        if (current_temp <= threshold_low) {
            BUZZER = 1;        // 경보 해제
            RELAY  = 0;        // 냉각 장치 정지
            alarm_state = 0;
        }
    }
}

LCD 출력 및 사용자 인터페이스

시스템 상태는 HD44780 호환 LCD에 표시되며, 사용자 입력은 외부 인터럽트를 통해 처리된다.

#include <string.h>
#include <stdio.h>

sbit LCD_RS = P0^0;
sbit LCD_RW = P0^1;
sbit LCD_EN = P0^2;
#define LCD_DATA  P0

sbit KEY_SET  = P3^2;   // 설정 버튼: INT0
sbit KEY_UP   = P3^3;   // 증가 버튼: INT1
sbit KEY_DOWN = P3^4;   // 감소 버튼

// LCD 명령어 전송
void lcd_cmd(unsigned char cmd) {
    LCD_RS = 0;
    LCD_RW = 0;
    LCD_DATA = (cmd & 0xF0);
    LCD_EN = 1; LCD_EN = 0;
    LCD_DATA = (cmd << 4);
    LCD_EN = 1; LCD_EN = 0;
    delay_ms(2);
}

// LCD 데이터 전송
void lcd_data(unsigned char dat) {
    LCD_RS = 1;
    LCD_RW = 0;
    LCD_DATA = (dat & 0xF0);
    LCD_EN = 1; LCD_EN = 0;
    LCD_DATA = (dat << 4);
    LCD_EN = 1; LCD_EN = 0;
    delay_ms(2);
}

// 문자열 표시
void lcd_string(unsigned char *str) {
    while (*str) {
        lcd_data(*str++);
    }
}

// 온도 정보 화면 갱신
void refresh_display(float temp, float setpoint) {
    unsigned char line1[16], line2[16];
    
    sprintf(line1, "T:%5.2f C", temp);
    sprintf(line2, "SP:%4.1f C", setpoint);
    
    lcd_cmd(0x80);           // 첫 번째 줄
    lcd_string(line1);
    
    lcd_cmd(0xC0);           // 두 번째 줄
    lcd_string(line2);
}

다중 채널 확장

여러 개의 DS18B20을 단일 버스에 연결하려면 각 센서의 64비트 ROM ID를 활용한 개별 주소 지정이 필요하다. 대안으로 각 채널을 독립적인 GPIO 핀에 할당하여 병렬 구조를 구성할 수도 있다.

// 다중 채널 구조체 정의
typedef struct {
    sbit *data_pin;          // 센서 연결 핀 포인터
    float last_reading;      // 마지막 측정값
    float channel_setpoint;  // 채널별 설정값
    unsigned char status;    // 상태 플래그
} TempChannel;

TempChannel channels[4];     // 최대 4채널 정의

// 채널 순회 측정
void scan_all_channels(void) {
    unsigned char i;
    for (i = 0; i < 4; i++) {
        channels[i].last_reading = acquire_temperature();
        process_control(channels[i].last_reading);
    }
}

이러한 구조는 향후 채널 수 확장이나 센서 종류 변경 시에도 모듈 단위의 유지보수가 가능하도록 설계되었다.

태그: ds18b20 1-Wire 8051 임베디드 시스템 온도 센서

6월 28일 22:40에 게시됨