UART 통신과 외부 세계 연결) 마이크로컨트롤러의 완성

안녕하세요! 드디어 마지막 편입니다! ㅠㅠㅠㅠ 지금까지 마이크로컨트롤러 내부의 모든 기능을 마스터했다면, 이번에는 외부 세계와의 소통을 완성해보겠습니다. UART 시리얼 통신을 통해 컴퓨터와 데이터를 주고받고, 여러 마이크로컨트롤러를 연결하며, 진짜 IoT 시스템을 만들어보겠습니다! 이러면 정말 기본적인 것은 할 수 있게 되었다고 볼 수 있습니다.

이번 편의 목표

  • UART 통신의 원리와 설정 완전 이해
  • 인터럽트 기반 송수신 버퍼 구현
  • 컴퓨터와 실시간 데이터 교환
  • 간단한 명령어 프로토콜 설계
  • 다중 장치 통신 네트워크 구축
  • 완전한 IoT 시스템 구현

UART란 무엇인가?

UART의 기본 개념

UART (Universal Asynchronous Receiver-Transmitter)

  • 비동기 시리얼 통신: 클럭 신호 없이 데이터 전송
  • 점대점 통신: 두 장치 간 직접 연결
  • 전이중 통신: 송신과 수신을 동시에 가능
  • 표준 프로토콜: PC, 스마트폰, 다양한 장치에서 지원

신호 구조

유휴 상태 (HIGH)
     ↓
스타트 비트 (LOW) → 데이터 비트 8개 → 스톱 비트 (HIGH)
     ↓                LSB → MSB           ↓
  [START][D0][D1][D2][D3][D4][D5][D6][D7][STOP]

핵심 파라미터:

  • Baud Rate: 1초당 전송되는 비트 수 (예: 9600bps)
  • 데이터 비트: 보통 8비트
  • 패리티: 오류 검출용 (일반적으로 없음)
  • 스톱 비트: 전송 끝을 알리는 신호 (1비트)

ATmega328P의 UART (USART0)

  • TX 핀: PD1 (송신)
  • RX 핀: PD0 (수신)
  • 최대 속도: 2Mbps (16MHz 클럭 기준)
  • 하드웨어 버퍼: 송신/수신 각각 1바이트
  • 인터럽트 지원: 송신 완료, 수신 완료, 버퍼 빔

UART 설정과 기본 사용법

UART 레지스터 설정

UBRR (USART Baud Rate Register)

c

// Baud rate 계산
// UBRR = (F_CPU / (16 * BAUD)) - 1 (Normal 모드)
// UBRR = (F_CPU / (8 * BAUD)) - 1  (Double Speed 모드)

#define BAUD 9600
#define UBRR_VALUE ((F_CPU / (16UL * BAUD)) - 1)

void uart_init(void) {
    // Baud rate 설정
    UBRR0H = (uint8_t)(UBRR_VALUE >> 8);
    UBRR0L = (uint8_t)UBRR_VALUE;
    
    // 송신, 수신 활성화
    UCSR0B = (1 << TXEN0) | (1 << RXEN0);
    
    // 프레임 포맷: 8 데이터 비트, 1 스톱 비트
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}

Baud Rate 계산 예시

c

// 16MHz 클럭, 9600 baud 계산
F_CPU = 16,000,000
BAUD = 9600

UBRR = (16,000,000 / (16 * 9600)) - 1
     = (16,000,000 / 153,600) - 1  
     = 104.166 - 1
     = 103

실제 baud rate = 16,000,000 / (16 * (103 + 1))
                = 16,000,000 / 1,664
                = 9,615.4 bps
                
오차율 = (9,615.4 - 9,600) / 9,600 * 100% = 0.16% (매우 좋음)

기본 송수신 함수

c

#include <avr/io.h>

void uart_init(void) {
    // 9600 baud, 8N1
    UBRR0H = 0;
    UBRR0L = 103;
    UCSR0B = (1 << TXEN0) | (1 << RXEN0);
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}

void uart_transmit(uint8_t data) {
    // 송신 버퍼가 빌 때까지 대기
    while (!(UCSR0A & (1 << UDRE0)));
    
    // 데이터 전송
    UDR0 = data;
}

uint8_t uart_receive(void) {
    // 수신 완료까지 대기
    while (!(UCSR0A & (1 << RXC0)));
    
    // 수신 데이터 반환
    return UDR0;
}

void uart_print_string(const char* str) {
    while (*str) {
        uart_transmit(*str++);
    }
}

int main(void) {
    uart_init();
    
    uart_print_string("Hello, World!\r\n");
    
    while(1) {
        uint8_t received = uart_receive();
        uart_transmit(received);  // 에코
    }
    
    return 0;
}

인터럽트 기반 UART 시스템

송수신 버퍼 구현

c

#include <avr/io.h>
#include <avr/interrupt.h>

#define BUFFER_SIZE 64

// 원형 버퍼 구조체
typedef struct {
    uint8_t buffer[BUFFER_SIZE];
    uint8_t head;
    uint8_t tail;
    uint8_t count;
} ring_buffer_t;

// 전역 버퍼
ring_buffer_t tx_buffer = {0};
ring_buffer_t rx_buffer = {0};

// 버퍼 함수들
uint8_t buffer_put(ring_buffer_t* buf, uint8_t data) {
    if (buf->count >= BUFFER_SIZE) {
        return 0;  // 버퍼 가득참
    }
    
    buf->buffer[buf->head] = data;
    buf->head = (buf->head + 1) % BUFFER_SIZE;
    buf->count++;
    
    return 1;  // 성공
}

uint8_t buffer_get(ring_buffer_t* buf, uint8_t* data) {
    if (buf->count == 0) {
        return 0;  // 버퍼 비어있음
    }
    
    *data = buf->buffer[buf->tail];
    buf->tail = (buf->tail + 1) % BUFFER_SIZE;
    buf->count--;
    
    return 1;  // 성공
}

// 수신 완료 인터럽트
ISR(USART_RX_vect) {
    uint8_t data = UDR0;
    buffer_put(&rx_buffer, data);
}

// 송신 버퍼 빔 인터럽트  
ISR(USART_UDRE_vect) {
    uint8_t data;
    
    if (buffer_get(&tx_buffer, &data)) {
        UDR0 = data;  // 다음 바이트 전송
    } else {
        // 전송할 데이터 없음, 인터럽트 비활성화
        UCSR0B &= ~(1 << UDRIE0);
    }
}

void uart_init_buffered(void) {
    // Baud rate 설정
    UBRR0H = 0;
    UBRR0L = 103;  // 9600 baud
    
    // 송신, 수신, 수신 인터럽트 활성화
    UCSR0B = (1 << TXEN0) | (1 << RXEN0) | (1 << RXCIE0);
    
    // 8N1 프레임
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}

uint8_t uart_send_byte(uint8_t data) {
    // 임계 영역에서 버퍼 조작
    cli();
    uint8_t result = buffer_put(&tx_buffer, data);
    
    // 송신 인터럽트 활성화
    UCSR0B |= (1 << UDRIE0);
    sei();
    
    return result;
}

uint8_t uart_receive_byte(uint8_t* data) {
    cli();
    uint8_t result = buffer_get(&rx_buffer, data);
    sei();
    
    return result;
}

void uart_send_string(const char* str) {
    while (*str) {
        // 버퍼 가득찰 때까지 대기
        while (!uart_send_byte(*str)) {
            _delay_ms(1);
        }
        str++;
    }
}

uint8_t uart_bytes_available(void) {
    cli();
    uint8_t count = rx_buffer.count;
    sei();
    
    return count;
}

printf와 scanf 지원

stdio 리다이렉션

c

#include <stdio.h>

// printf를 UART로 리다이렉션
static int uart_putchar(char c, FILE* stream) {
    if (c == '\n') {
        uart_send_byte('\r');  // LF를 CRLF로 변환
    }
    uart_send_byte(c);
    return 0;
}

// scanf를 UART로 리다이렉션
static int uart_getchar(FILE* stream) {
    uint8_t data;
    
    // 데이터가 올 때까지 대기
    while (!uart_receive_byte(&data));
    
    return data;
}

// 파일 스트림 정의
static FILE uart_stdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);
static FILE uart_stdin = FDEV_SETUP_STREAM(NULL, uart_getchar, _FDEV_SETUP_READ);

void stdio_init(void) {
    // stdout과 stdin을 UART로 설정
    stdout = &uart_stdout;
    stdin = &uart_stdin;
}

int main(void) {
    uart_init_buffered();
    stdio_init();
    sei();
    
    printf("System ready!\r\n");
    printf("Enter your name: ");
    
    char name[32];
    scanf("%31s", name);  // 버퍼 오버플로우 방지
    
    printf("Hello, %s!\r\n", name);
    
    // 센서 값 출력 예제
    while(1) {
        uint16_t adc_value = adc_read(0);
        float voltage = (adc_value * 5.0) / 1024.0;
        
        printf("ADC: %d, Voltage: %.2fV\r\n", adc_value, voltage);
        
        _delay_ms(1000);
    }
    
    return 0;
}

명령어 프로토콜 구현

간단한 명령어 파서

c

#include <string.h>
#include <stdlib.h>

#define CMD_BUFFER_SIZE 64

typedef struct {
    char name[16];
    void (*handler)(int argc, char* argv[]);
    char description[64];
} command_t;

// 명령어 처리 함수들
void cmd_help(int argc, char* argv[]);
void cmd_led(int argc, char* argv[]);
void cmd_adc(int argc, char* argv[]);
void cmd_pwm(int argc, char* argv[]);
void cmd_status(int argc, char* argv[]);
void cmd_reset(int argc, char* argv[]);

// 명령어 테이블
command_t commands[] = {
    {"help", cmd_help, "Show available commands"},
    {"led", cmd_led, "Control LED: led <pin> <on/off>"},
    {"adc", cmd_adc, "Read ADC: adc <channel>"},
    {"pwm", cmd_pwm, "Set PWM: pwm <channel> <value>"},
    {"status", cmd_status, "Show system status"},
    {"reset", cmd_reset, "Reset system"},
    {"", NULL, ""}  // 테이블 끝
};

void process_command_line(char* line) {
    char* argv[8];
    int argc = 0;
    
    // 공백으로 분리
    char* token = strtok(line, " \t\r\n");
    while (token && argc < 7) {
        argv[argc++] = token;
        token = strtok(NULL, " \t\r\n");
    }
    argv[argc] = NULL;
    
    if (argc == 0) return;
    
    // 명령어 찾기
    for (int i = 0; commands[i].handler; i++) {
        if (strcmp(argv[0], commands[i].name) == 0) {
            commands[i].handler(argc, argv);
            return;
        }
    }
    
    printf("Unknown command: %s\r\n", argv[0]);
    printf("Type 'help' for available commands.\r\n");
}

void cmd_help(int argc, char* argv[]) {
    printf("Available commands:\r\n");
    for (int i = 0; commands[i].handler; i++) {
        printf("  %-10s - %s\r\n", commands[i].name, commands[i].description);
    }
}

void cmd_led(int argc, char* argv[]) {
    if (argc != 3) {
        printf("Usage: led <pin> <on/off>\r\n");
        return;
    }
    
    int pin = atoi(argv[1]);
    int state = (strcmp(argv[2], "on") == 0) ? 1 : 0;
    
    if (pin >= 0 && pin <= 7) {
        if (state) {
            PORTB |= (1 << pin);
            printf("LED %d ON\r\n", pin);
        } else {
            PORTB &= ~(1 << pin);
            printf("LED %d OFF\r\n", pin);
        }
    } else {
        printf("Invalid pin number (0-7)\r\n");
    }
}

void cmd_adc(int argc, char* argv[]) {
    if (argc != 2) {
        printf("Usage: adc <channel>\r\n");
        return;
    }
    
    int channel = atoi(argv[1]);
    if (channel >= 0 && channel <= 7) {
        uint16_t value = adc_read(channel);
        float voltage = (value * 5.0) / 1024.0;
        
        printf("ADC%d: %d (%.3fV)\r\n", channel, value, voltage);
    } else {
        printf("Invalid channel (0-7)\r\n");
    }
}

void cmd_pwm(int argc, char* argv[]) {
    if (argc != 3) {
        printf("Usage: pwm <channel> <value>\r\n");
        return;
    }
    
    int channel = atoi(argv[1]);
    int value = atoi(argv[2]);
    
    if (value < 0) value = 0;
    if (value > 255) value = 255;
    
    switch(channel) {
        case 1:
            OCR1A = value;
            printf("PWM1 set to %d\r\n", value);
            break;
        case 2:
            OCR1B = value;
            printf("PWM2 set to %d\r\n", value);
            break;
        default:
            printf("Invalid PWM channel (1-2)\r\n");
    }
}

void cmd_status(int argc, char* argv[]) {
    printf("=== System Status ===\r\n");
    printf("Uptime: %lu ms\r\n", system_time_ms);
    printf("Free RAM: %d bytes\r\n", get_free_ram());
    printf("PORTB: 0x%02X\r\n", PORTB);
    printf("PORTC: 0x%02X\r\n", PORTC);
    printf("PORTD: 0x%02X\r\n", PORTD);
    
    // 센서 상태
    for (int i = 0; i < 4; i++) {
        uint16_t adc = adc_read(i);
        printf("ADC%d: %d\r\n", i, adc);
    }
}

void cmd_reset(int argc, char* argv[]) {
    printf("Resetting system...\r\n");
    _delay_ms(100);  // 메시지 전송 완료 대기
    
    // 소프트웨어 리셋
    asm volatile ("jmp 0");
}

// 메인 명령어 처리 루프
void command_loop(void) {
    static char cmd_buffer[CMD_BUFFER_SIZE];
    static uint8_t cmd_index = 0;
    
    uint8_t data;
    while (uart_receive_byte(&data)) {
        if (data == '\r' || data == '\n') {
            if (cmd_index > 0) {
                cmd_buffer[cmd_index] = '\0';
                printf("\r\n");
                
                process_command_line(cmd_buffer);
                
                cmd_index = 0;
                printf("> ");  // 프롬프트
            }
        } else if (data == '\b' || data == 127) {  // 백스페이스
            if (cmd_index > 0) {
                cmd_index--;
                printf("\b \b");  // 백스페이스 + 공백 + 백스페이스
            }
        } else if (cmd_index < CMD_BUFFER_SIZE - 1) {
            cmd_buffer[cmd_index++] = data;
            uart_send_byte(data);  // 에코
        }
    }
}

int get_free_ram(void) {
    extern int __heap_start, *__brkval;
    int v;
    return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}

다중 장치 통신 네트워크

Master-Slave 통신 프로토콜

c

// 프로토콜 정의
#define PROTOCOL_START    0xAA
#define PROTOCOL_END      0x55

#define CMD_READ_SENSOR   0x01
#define CMD_SET_OUTPUT    0x02
#define CMD_GET_STATUS    0x03
#define CMD_PING          0x04

typedef struct {
    uint8_t start;      // 0xAA
    uint8_t address;    // 장치 주소 (0~254)
    uint8_t command;    // 명령어
    uint8_t length;     // 데이터 길이
    uint8_t data[16];   // 최대 16바이트 데이터
    uint8_t checksum;   // 체크섬
    uint8_t end;        // 0x55
} protocol_packet_t;

uint8_t calculate_checksum(protocol_packet_t* packet) {
    uint8_t sum = 0;
    sum += packet->address;
    sum += packet->command;
    sum += packet->length;
    
    for (uint8_t i = 0; i < packet->length; i++) {
        sum += packet->data[i];
    }
    
    return (~sum) + 1;  // 2의 보수
}

void send_packet(protocol_packet_t* packet) {
    packet->start = PROTOCOL_START;
    packet->end = PROTOCOL_END;
    packet->checksum = calculate_checksum(packet);
    
    uart_send_byte(packet->start);
    uart_send_byte(packet->address);
    uart_send_byte(packet->command);
    uart_send_byte(packet->length);
    
    for (uint8_t i = 0; i < packet->length; i++) {
        uart_send_byte(packet->data[i]);
    }
    
    uart_send_byte(packet->checksum);
    uart_send_byte(packet->end);
}

uint8_t receive_packet(protocol_packet_t* packet) {
    static uint8_t state = 0;
    static uint8_t data_index = 0;
    uint8_t data;
    
    while (uart_receive_byte(&data)) {
        switch(state) {
            case 0:  // START 바이트 대기
                if (data == PROTOCOL_START) {
                    packet->start = data;
                    state = 1;
                }
                break;
                
            case 1:  // ADDRESS
                packet->address = data;
                state = 2;
                break;
                
            case 2:  // COMMAND
                packet->command = data;
                state = 3;
                break;
                
            case 3:  // LENGTH
                packet->length = data;
                data_index = 0;
                if (packet->length == 0) {
                    state = 5;  // 데이터 없음, 체크섬으로
                } else {
                    state = 4;  // 데이터 수신
                }
                break;
                
            case 4:  // DATA
                if (data_index < sizeof(packet->data)) {
                    packet->data[data_index++] = data;
                }
                
                if (data_index >= packet->length) {
                    state = 5;
                }
                break;
                
            case 5:  // CHECKSUM
                packet->checksum = data;
                state = 6;
                break;
                
            case 6:  // END
                packet->end = data;
                state = 0;
                
                if (data == PROTOCOL_END) {
                    // 체크섬 검증
                    uint8_t calc_checksum = calculate_checksum(packet);
                    if (calc_checksum == packet->checksum) {
                        return 1;  // 패킷 수신 성공
                    }
                }
                break;
        }
    }
    
    return 0;  // 아직 완전한 패킷 없음
}

// Master 장치 코드
void master_main(void) {
    protocol_packet_t tx_packet, rx_packet;
    
    while(1) {
        // 모든 슬레이브에게 센서 값 요청
        for (uint8_t addr = 1; addr <= 4; addr++) {
            tx_packet.address = addr;
            tx_packet.command = CMD_READ_SENSOR;
            tx_packet.length = 1;
            tx_packet.data[0] = 0;  // 센서 채널 0
            
            send_packet(&tx_packet);
            
            // 응답 대기 (타임아웃 포함)
            uint32_t timeout = system_time_ms + 1000;
            while (system_time_ms < timeout) {
                if (receive_packet(&rx_packet)) {
                    if (rx_packet.address == addr) {
                        uint16_t sensor_value = (rx_packet.data[0] << 8) | rx_packet.data[1];
                        printf("Slave %d sensor: %d\r\n", addr, sensor_value);
                        break;
                    }
                }
            }
        }
        
        _delay_ms(5000);  // 5초마다 폴링
    }
}

// Slave 장치 코드
#define MY_ADDRESS 1

void slave_main(void) {
    protocol_packet_t rx_packet, tx_packet;
    
    while(1) {
        if (receive_packet(&rx_packet)) {
            if (rx_packet.address == MY_ADDRESS) {
                switch(rx_packet.command) {
                    case CMD_READ_SENSOR:
                        {
                            uint8_t channel = rx_packet.data[0];
                            uint16_t value = adc_read(channel);
                            
                            tx_packet.address = MY_ADDRESS;
                            tx_packet.command = CMD_READ_SENSOR;
                            tx_packet.length = 2;
                            tx_packet.data[0] = value >> 8;
                            tx_packet.data[1] = value & 0xFF;
                            
                            send_packet(&tx_packet);
                        }
                        break;
                        
                    case CMD_SET_OUTPUT:
                        {
                            uint8_t pin = rx_packet.data[0];
                            uint8_t state = rx_packet.data[1];
                            
                            if (state) {
                                PORTB |= (1 << pin);
                            } else {
                                PORTB &= ~(1 << pin);
                            }
                            
                            // ACK 응답
                            tx_packet.address = MY_ADDRESS;
                            tx_packet.command = CMD_SET_OUTPUT;
                            tx_packet.length = 1;
                            tx_packet.data[0] = 0x00;  // Success
                            
                            send_packet(&tx_packet);
                        }
                        break;
                        
                    case CMD_PING:
                        // Echo back
                        tx_packet = rx_packet;
                        send_packet(&tx_packet);
                        break;
                }
            }
        }
        
        // 기타 슬레이브 작업들
        background_tasks();
    }
}

완전한 IoT 시스템 구현

센서 노드 + 게이트웨이 시스템

c

// IoT 센서 노드 (Slave)
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdio.h>

#define NODE_ID 1
#define REPORT_INTERVAL 30000  // 30초

typedef struct {
    uint8_t node_id;
    uint32_t timestamp;
    float temperature;
    float humidity;
    float light_level;
    uint8_t motion_detected;
    uint8_t battery_level;
} sensor_report_t;

volatile uint32_t system_time_ms = 0;
volatile uint8_t motion_flag = 0;

// 시스템 타이머
ISR(TIMER0_COMPA_vect) {
    system_time_ms++;
}

// 움직임 센서
ISR(INT0_vect) {
    motion_flag = 1;
}

void iot_node_init(void) {
    // UART 초기화
    uart_init_buffered();
    stdio_init();
    
    // ADC 초기화
    adc_init();
    
    // 시스템 타이머 (1ms)
    TCCR0A = (1 << WGM01);
    TCCR0B = (1 << CS01) | (1 << CS00);
    OCR0A = 249;
    TIMSK0 = (1 << OCIE0A);
    
    // 움직임 센서 인터럽트
    EICRA = (1 << ISC01);
    EIMSK = (1 << INT0);
    
    sei();
}

sensor_report_t read_sensors(void) {
    sensor_report_t report;
    
    report.node_id = NODE_ID;
    report.timestamp = system_time_ms;
    
    // 온도 센서 (LM35)
    uint16_t temp_raw = adc_read(0);
    report.temperature = (temp_raw * 5.0 / 1024.0) * 100.0;
    
    // 습도 센서 (가상)
    uint16_t humid_raw = adc_read(1);
    report.humidity = (humid_raw * 100.0) / 1023.0;
    
    // 광센서
    uint16_t light_raw = adc_read(2);
    report.light_level = (light_raw * 100.0) / 1023.0;
    
    // 움직임 센서
    report.motion_detected = motion_flag;
    motion_flag = 0;
    
    // 배터리 레벨 (전압 측정)
    uint16_t battery_raw = adc_read(3);
    float battery_voltage = (battery_raw * 5.0) / 1024.0;
    report.battery_level = (uint8_t)((battery_voltage / 5.0) * 100.0);
    
    return report;
}

void send_sensor_report(sensor_report_t* report) {
    printf("SENSOR_REPORT,%d,%lu,%.1f,%.1f,%.1f,%d,%d\r\n",
           report->node_id,
           report->timestamp,
           report->temperature,
           report->humidity,
           report->light_level,
           report->motion_detected,
           report->battery_level);
}

void iot_node_main(void) {
    uint32_t last_report = 0;
    
    printf("IoT Sensor Node %d started\r\n", NODE_ID);
    
    while(1) {
        // 정기 리포트
        if ((system_time_ms - last_report) >= REPORT_INTERVAL) {
            sensor_report_t report = read_sensors();
            send_sensor_report(&report);
            last_report = system_time_ms;
        }
        
        // 즉시 알림 (움직임 감지)
        if (motion_flag) {
            sensor_report_t report = read_sensors();
            printf("MOTION_ALERT,%d,%lu\r\n", NODE_ID, system_time_ms);
            send_sensor_report(&report);
            motion_flag = 0;
        }
        
        // 명령어 처리
        command_loop();
        
        _delay_ms(100);
    }
}

// IoT 게이트웨이 (Master)
void iot_gateway_main(void) {
    printf("IoT Gateway started\r\n");
    
    while(1) {
        char line[128];
        
        // 센서 노드로부터 데이터 수신
        if (read_line(line, sizeof(line))) {
            if (strncmp(line, "SENSOR_REPORT", 13) == 0) {
                parse_sensor_report(line);
            } else if (strncmp(line, "MOTION_ALERT", 12) == 0) {
                handle_motion_alert(line);
            }
        }
        
        // 웹 서버로 데이터 전송 (가상)
        upload_to_cloud();
        
        // 원격 명령 처리
        process_remote_commands();
        
        _delay_ms(1000);
    }
}

void parse_sensor_report(char* line) {
    // CSV 파싱 예제
    char* token = strtok(line, ",");
    
    if (strcmp(token, "SENSOR_REPORT") == 0) {
        uint8_t node_id = atoi(strtok(NULL, ","));
        uint32_t timestamp = atol(strtok(NULL, ","));
        float temperature = atof(strtok(NULL, ","));
        float humidity = atof(strtok(NULL, ","));
        float light = atof(strtok(NULL, ","));
        uint8_t motion = atoi(strtok(NULL, ","));
        uint8_t battery = atoi(strtok(NULL, ","));
        
        printf("Node %d: T=%.1f°C, H=%.1f%%, L=%.1f%%, M=%d, B=%d%%\r\n",
               node_id, temperature, humidity, light, motion, battery);
        
        // 데이터베이스 저장, 알림 처리 등
        store_sensor_data(node_id, timestamp, temperature, humidity, light, motion, battery);
        check_alert_conditions(node_id, temperature, humidity, battery);
    }
}

void check_alert_conditions(uint8_t node_id, float temp, float humid, uint8_t battery) {
    // 온도 경고
    if (temp > 35.0 || temp < 5.0) {
        printf("ALERT: Node %d temperature %.1f°C\r\n", node_id, temp);
        send_notification("Temperature alert", node_id);
    }
    
    // 배터리 경고
    if (battery < 20) {
        printf("ALERT: Node %d low battery %d%%\r\n", node_id, battery);
        send_notification("Low battery", node_id);
    }
    
    // 습도 경고
    if (humid > 80.0) {
        printf("ALERT: Node %d high humidity %.1f%%\r\n", node_id, humid);
        send_notification("High humidity", node_id);
    }
}

고급 UART 기법

고속 통신과 오류 처리

c

// 고속 UART 설정 (Double Speed 모드)
void uart_init_high_speed(uint32_t baud) {
    // Double Speed 모드 활성화
    UCSR0A = (1 << U2X0);
    
    // UBRR 계산 (Double Speed 모드)
    uint16_t ubrr = (F_CPU / (8UL * baud)) - 1;
    
    UBRR0H = (uint8_t)(ubrr >> 8);
    UBRR0L = (uint8_t)ubrr;
    
    // 9비트 모드 (패리티 포함)
    UCSR0B = (1 << TXEN0) | (1 << RXEN0) | (1 << RXCIE0) | (1 << UCSZ02);
    UCSR0C = (1 << UPM01) | (1 << UCSZ01) | (1 << UCSZ00);  // Even parity, 9비트
}

// 오류 감지 및 처리
ISR(USART_RX_vect) {
    uint8_t status = UCSR0A;
    uint8_t data = UDR0;
    
    // 오류 검사
    if (status & (1 << FE0)) {
        // Frame Error: 스톱 비트가 올바르지 않음
        printf("Frame Error detected\r\n");
        return;
    }
    
    if (status & (1 << DOR0)) {
        // Data OverRun: 이전 데이터를 읽기 전에 새 데이터 도착
        printf("Data Overrun detected\r\n");
        return;
    }
    
    if (status & (1 << UPE0)) {
        // Parity Error: 패리티 비트 오류
        printf("Parity Error detected\r\n");
        return;
    }
    
    // 정상 데이터 처리
    buffer_put(&rx_buffer, data);
}

// 플로우 컨트롤 (RTS/CTS)
void uart_flow_control_init(void) {
    // RTS 출력 설정 (PD4)
    DDRD |= (1 << PD4);
    PORTD |= (1 << PD4);  // RTS High (송신 중지 요청)
    
    // CTS 입력 설정 (PD5)
    DDRD &= ~(1 << PD5);
    PORTD |= (1 << PD5);  // 풀업
}

void uart_set_rts(uint8_t state) {
    if (state) {
        PORTD |= (1 << PD4);   // RTS High - 송신 중지 요청
    } else {
        PORTD &= ~(1 << PD4);  // RTS Low - 송신 허용
    }
}

uint8_t uart_get_cts(void) {
    return !(PIND & (1 << PD5));  // CTS Low이면 송신 허용
}

시리즈 마무리: 완전한 시스템

최종 통합 프로젝트

c

// 완전한 IoT 환경 모니터링 시스템
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include <string.h>

// 시스템 상태
typedef enum {
    SYS_INIT,
    SYS_NORMAL,
    SYS_ALERT,
    SYS_MAINTENANCE
} system_state_t;

volatile system_state_t system_state = SYS_INIT;
volatile uint32_t system_uptime = 0;

// 통합 센서 데이터
typedef struct {
    float temperature;
    float humidity; 
    float light_level;
    float pressure;
    uint8_t air_quality;
    uint8_t motion_detected;
    uint8_t door_open;
    uint8_t window_open;
} environment_data_t;

// 제어 출력
typedef struct {
    uint8_t hvac_mode;      // 0=OFF, 1=HEAT, 2=COOL, 3=AUTO
    uint8_t fan_speed;      // 0~100%
    uint8_t light_level;    // 0~100%
    uint8_t security_armed; // 0=DISARMED, 1=ARMED
    uint8_t alarm_active;   // 0=OFF, 1=ON
} control_outputs_t;

environment_data_t env_data = {0};
control_outputs_t outputs = {0};

// 시스템 초기화
void system_init(void) {
    // 하드웨어 초기화
    uart_init_buffered();
    stdio_init();
    adc_init();
    pwm_init();
    
    // 타이머 초기화 (1초 틱)
    TCCR1A = 0;
    TCCR1B = (1 << WGM12) | (1 << CS12) | (1 << CS10);
    OCR1A = 15624;  // 1초 (16MHz/1024)
    TIMSK1 = (1 << OCIE1A);
    
    // 인터럽트 초기화
    external_interrupt_init();
    
    sei();
    
    printf("\r\n=== Smart Environment Controller ===\r\n");
    printf("Firmware v1.0\r\n");
    printf("Initializing...\r\n");
    
    // 시스템 자가 진단
    if (self_test()) {
        system_state = SYS_NORMAL;
        printf("System ready!\r\n");
    } else {
        system_state = SYS_ALERT;
        printf("System error detected!\r\n");
    }
    
    printf("> ");  // 명령 프롬프트
}

// 1초 타이머
ISR(TIMER1_COMPA_vect) {
    system_uptime++;
    
    // 주기적 작업 스케줄링
    static uint8_t task_counter = 0;
    task_counter++;
    
    if (task_counter % 5 == 0) {    // 5초마다
        update_sensors();
    }
    
    if (task_counter % 10 == 0) {   // 10초마다
        update_controls();
    }
    
    if (task_counter % 60 == 0) {   // 1분마다
        send_status_report();
        task_counter = 0;
    }
}

void update_sensors(void) {
    // 온도/습도 센서
    env_data.temperature = read_temperature_sensor();
    env_data.humidity = read_humidity_sensor();
    
    // 광센서
    env_data.light_level = (adc_read(0) * 100.0) / 1023.0;
    
    // 기압 센서 (I2C - 가상)
    env_data.pressure = read_pressure_sensor();
    
    // 공기질 센서
    env_data.air_quality = (adc_read(2) * 100.0) / 1023.0;
    
    // 디지털 입력들
    uint8_t digital_inputs = PINC;
    env_data.motion_detected = !(digital_inputs & (1 << PC0));
    env_data.door_open = !(digital_inputs & (1 << PC1));
    env_data.window_open = !(digital_inputs & (1 << PC2));
}

void update_controls(void) {
    // 자동 온도 제어
    if (outputs.hvac_mode == 3) {  // AUTO 모드
        if (env_data.temperature > 25.0) {
            outputs.hvac_mode = 2;  // COOL
            outputs.fan_speed = (uint8_t)((env_data.temperature - 25.0) * 20);
        } else if (env_data.temperature < 20.0) {
            outputs.hvac_mode = 1;  // HEAT
            outputs.fan_speed = (uint8_t)((20.0 - env_data.temperature) * 20);
        } else {
            outputs.hvac_mode = 0;  // OFF
            outputs.fan_speed = 0;
        }
    }
    
    // 자동 조명 제어
    if (env_data.light_level < 30.0 && env_data.motion_detected) {
        outputs.light_level = 80;
    } else if (env_data.light_level > 70.0) {
        outputs.light_level = 0;
    }
    
    // 보안 시스템
    if (outputs.security_armed) {
        if (env_data.door_open || env_data.window_open || env_data.motion_detected) {
            outputs.alarm_active = 1;
            system_state = SYS_ALERT;
        }
    }
    
    // 하드웨어 출력 업데이트
    OCR0A = (outputs.fan_speed * 255) / 100;      // 팬 PWM
    OCR0B = (outputs.light_level * 255) / 100;    // 조명 PWM
    
    if (outputs.alarm_active) {
        PORTB |= (1 << PB5);  // 알람 LED
    } else {
        PORTB &= ~(1 << PB5);
    }
}

void send_status_report(void) {
    printf("\r\n=== Status Report ===\r\n");
    printf("Uptime: %lu seconds\r\n", system_uptime);
    printf("State: %s\r\n", get_state_string(system_state));
    
    printf("\r\nEnvironment:\r\n");
    printf("  Temperature: %.1f°C\r\n", env_data.temperature);
    printf("  Humidity: %.1f%%\r\n", env_data.humidity);
    printf("  Light: %.1f%%\r\n", env_data.light_level);
    printf("  Pressure: %.1f hPa\r\n", env_data.pressure);
    printf("  Air Quality: %d%%\r\n", env_data.air_quality);
    
    printf("\r\nInputs:\r\n");
    printf("  Motion: %s\r\n", env_data.motion_detected ? "YES" : "NO");
    printf("  Door: %s\r\n", env_data.door_open ? "OPEN" : "CLOSED");
    printf("  Window: %s\r\n", env_data.window_open ? "OPEN" : "CLOSED");
    
    printf("\r\nOutputs:\r\n");
    printf("  HVAC: %s\r\n", get_hvac_string(outputs.hvac_mode));
    printf("  Fan: %d%%\r\n", outputs.fan_speed);
    printf("  Light: %d%%\r\n", outputs.light_level);
    printf("  Security: %s\r\n", outputs.security_armed ? "ARMED" : "DISARMED");
    printf("  Alarm: %s\r\n", outputs.alarm_active ? "ACTIVE" : "OFF");
    
    printf("\r\n> ");
}

int main(void) {
    system_init();
    
    while(1) {
        // 명령어 처리
        command_loop();
        
        // 상태별 처리
        switch(system_state) {
            case SYS_NORMAL:
                // 정상 동작
                break;
                
            case SYS_ALERT:
                // 경고 상태 처리
                handle_alert_state();
                break;
                
            case SYS_MAINTENANCE:
                // 유지보수 모드
                break;
        }
        
        _delay_ms(100);
    }
    
    return 0;
}

시리즈 완주를 축하합니다! ㅠㅠㅠㅠ

여러분이 성취한 것들

완전한 마이크로컨트롤러 개발자가 되실 수 있습니다! (아직 멀었다!!! 퍽!)

근데, 진짜로 농담이 아니라 지금 이 시리즈 10편과 보충편을 쓰면서 느끼는 겁니다만, 여러분, 많은 걸 했습니다.

하드웨어 제어 마스터

  • GPIO의 모든 기능 (디지털 입출력, 풀업/풀다운)
  • ADC를 통한 아날로그 센서 처리
  • PWM을 이용한 정밀한 아날로그 출력
  • 타이머와 인터럽트를 활용한 실시간 제어

시스템 설계 능력

  • 다중 인터럽트 소스 관리
  • 실시간 멀티태스킹 구현
  • 안전한 데이터 공유와 동기화
  • 시스템 안정성과 오류 처리

통신과 네트워킹

  • UART를 통한 장치 간 통신
  • 프로토콜 설계와 구현
  • 다중 노드 네트워크 구축
  • IoT 시스템 통합

이제 무엇을 만들 수 있나요?

실제 제품 수준의 프로젝트들이 가능한 기초가 되었다고 생각합니다. 이정도도 정말 열심히 따라하면 나머진 일하고 경험하면서 더 배울 수 있습니다:

  • 스마트 홈 자동화 시스템
  • 산업용 모니터링 장비
  • 로봇 제어 시스템
  • IoT 센서 네트워크
  • 실시간 데이터 수집 장치
  • 정밀 제어 시스템

다음 학습 방향

더 깊이 있는 학습을 할 수 있는 기반이 되었죠. 제가 그랬죠? 이제 시작입니다.:

  • I2C/SPI 통신: 더 복잡한 센서와 디스플레이
  • 무선 통신: WiFi, Bluetooth, LoRa 모듈 활용
  • RTOS: FreeRTOS를 이용한 고급 멀티태스킹
  • 32비트 MCU: ARM Cortex-M 시리즈로 확장
  • PCB 설계: 자신만의 하드웨어 제작

마지막 인사

이 길고 예제 넘치는 글을 끝까지 읽어주셔서 정말 감사합니다.

처음 LED 하나 깜빡이던 것에서 시작해서, 이제는 완전한 IoT 시스템을 구축할 수 있는 수준까지 오셨습니다.

거의 강의자료 아니냐고 하실 분들이 있을텐데, 처음에는 강의자료였습니다. 설명을 더 길게 적고 하기가 힘들더군요. 실제로 일하고 들어와서 하려고 하니깐요. 그래서 저 코드들을 실제로 하나하나 따라해보는 걸 목표로 하고 있었습니다.

블로그 글로는 잘 이해 안되는 것들은, 제가 머리 비우고 나서 실제 영상을 찍어볼 계획입니다. (제 얼굴따윈 안나옵니다.)

그래도 이정도의 내용을 손에 넣은 분들이 프로젝트로써 하나씩 뭔가 만들어 질 거라고 생각합니다. 굳이 이런 것이 아니더라도 본인의 지식을 다시 볼 수 있는 계기가 되거나, 이 정보를 이용해서 각자만의 자료를 만들거나 해도 좋다고 생각합니다.

그럼, 다음에는 프로그래밍 글로 다시 돌아오겠습니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

이 사이트는 Akismet을 사용하여 스팸을 줄입니다. 댓글 데이터가 어떻게 처리되는지 알아보세요.