컴퓨터공학 300 주제 시리즈의 219번째 글이다. 전체 지도는 여기.

한 줄 요약

임베디드 시스템은 특정 기능을 위해 기기 안에 들어간 컴퓨터이고, IoT 는 그런 기기가 네트워크로 연결되어 데이터를 보내고 원격으로 제어되는 체계다. 핵심은 제한된 자원(메모리·전력) 안에서 센서를 읽고(입력), 판단하고, 액추에이터를 움직이는(출력) 일을 정해진 시간 안에 오래 안정적으로 하는 것이다.

왜 필요한가

웹·모바일 개발자에게도 임베디드는 가깝다. 스마트 플러그, 온습도계, 출입 카드 리더기, 공장 설비 센서가 보내는 데이터를 받는 쪽은 결국 웹 서버와 앱이다. 그리고 그 데이터는 기기 쪽 사정을 모르면 해석할 수 없다.

  • 센서 값이 튀는 것은 서버 버그인가, 센서 잡음인가.
  • 기기가 하루에 몇 번씩 재부팅되는 것은 네트워크 문제인가, 워치독 리셋인가.
  • 배터리 기기에 “1초마다 상태 전송”을 요구하면 배터리가 얼마나 버티는가.

일반 서버와 비교하면 차이가 크다. 메모리는 기가바이트가 아니라 킬로바이트 단위일 수 있고, 운영체제가 없거나 아주 작다. 디스크가 없을 수 있다. 장애가 나도 누가 가서 재부팅해 주지 않는다. 그래서 설계 습관이 다르다.

핵심 개념

마이크로컨트롤러와 마이크로프로세서

구분 마이크로컨트롤러(MCU) 애플리케이션 프로세서(SoC)
예 Arduino 보드의 AVR, ESP32, RP2040 라즈베리 파이의 ARM SoC, 스마트폰 칩
메모리 칩 안에 작은 RAM·플래시 외부 DRAM, 저장 장치
운영체제 없음(베어메탈) 또는 RTOS 리눅스 같은 범용 OS
부팅 전원 켜면 바로 수 초 이상
전력 아주 적음, 깊은 절전 모드 상대적으로 많음
적합 센서 읽기, 모터 제어, 배터리 기기 카메라 처리, 게이트웨이, 복잡한 앱

실제 IoT 시스템은 둘을 섞는다. 배터리로 도는 MCU 센서 여러 개가 무선으로 리눅스 게이트웨이에 데이터를 보내고, 게이트웨이가 클라우드나 서버로 전달하는 식이다.

입출력: 현실 세계와 만나는 핀

인터페이스 무엇 예
GPIO 디지털 입력·출력 (켜짐/꺼짐) 버튼, LED, 릴레이
ADC 아날로그 전압을 숫자로 온도·조도 센서, 배터리 전압
PWM 켜짐 비율로 평균 전력 조절 LED 밝기, 모터 속도, 서보
UART 두 선 비동기 직렬 통신 GPS 모듈, 디버그 콘솔
I2C 두 선(데이터·클록) 버스, 주소로 여러 장치 센서, 작은 디스플레이
SPI 클록·데이터 여러 선, 빠른 전송 SD 카드, 디스플레이

ADC 의 분해능은 비트 수로 말한다. 12비트 ADC 는 0~4095 의 값을 낸다. 같은 전압 범위를 더 많은 단계로 나눌수록 미세한 변화를 구분한다. 하지만 실제 값에는 언제나 잡음이 섞인다.

실행 모델: 슈퍼루프, 인터럽트, RTOS

가장 단순한 펌웨어 구조는 무한 반복하는 슈퍼루프다. Arduino 의 setup() 과 loop() 가 이 구조다.

void setup() { /* 핀 설정, 센서 초기화 */ }
void loop() {
  read_sensors();
  update_control();
  send_if_due();
}

버튼 누름처럼 놓치면 안 되는 사건은 인터럽트로 처리한다. 하드웨어가 신호를 감지하면 CPU 가 하던 일을 멈추고 인터럽트 처리 함수를 실행한다. 이 함수는 아주 짧아야 한다. 플래그만 세우고 실제 처리는 메인 루프에서 하는 것이 원칙이다.

할 일이 많아지면 RTOS(실시간 운영체제)를 쓴다. FreeRTOS 같은 RTOS 는 태스크, 우선순위 기반 스케줄링, 큐·세마포어를 제공한다. 여기서 “실시간”은 빠르다는 뜻이 아니라 마감 시간 안에 반응한다는 보장이 핵심이다. 6부 운영체제에서 본 스케줄링·동기화 개념이 아주 작은 규모로 그대로 나타난다.

잡음과 떨림을 다루는 습관

  • 필터링: 센서 값은 이동 평균이나 중앙값 필터로 다듬는다.
  • 디바운싱: 기계식 버튼은 눌리는 순간 수 밀리초 동안 켜짐/꺼짐이 여러 번 튄다. 일정 시간 안정된 뒤에만 상태를 바꾼다.
  • 히스테리시스: “28도 넘으면 팬 켜기” 하나로 제어하면, 온도가 28도 근처에서 오르내릴 때 릴레이가 계속 딸깍거린다. 켜는 기준(28.5도)과 끄는 기준(27.5도)을 다르게 둔다. 가정용 온도 조절기도 같은 원리다.

오래 혼자 버티기

  • 워치독 타이머: 정해진 시간 안에 프로그램이 “살아 있다” 신호(feed)를 주지 않으면 하드웨어가 기기를 리셋한다. 무한 루프·교착에 빠진 기기를 스스로 되살린다.
  • 전력 관리: 배터리 기기는 대부분의 시간을 깊은 절전 상태로 보내고, 잠깐 깨어 측정·전송한 뒤 다시 잔다. 무선 전송이 가장 비싼 동작인 경우가 많아, 전송 빈도가 배터리 수명을 좌우한다.
  • OTA 업데이트: 현장에 깔린 기기를 원격으로 업데이트하는 기능이다. 업데이트 도중 전원이 끊겨도 벽돌이 되지 않도록 두 개의 펌웨어 영역(A/B)을 두고, 새 펌웨어가 정상 부팅을 확인한 뒤에만 전환하는 방식을 쓴다.

보안

IoT 기기는 오래, 사람의 관리 없이 네트워크에 붙어 있다. NIST IR 8259 는 IoT 기기 제조사가 출시 전후에 해야 할 기본 보안 활동을 정리하고, 짝이 되는 NIST IR 8259A 는 기기가 기본으로 갖춰야 할 보안 기능의 기준선(core baseline)을 정의한다. 실무에서는 다음이 출발점이다.

  • 모든 기기에 같은 기본 비밀번호를 쓰지 않는다.
  • 통신은 TLS 로 암호화하고, 기기별 인증 정보를 쓴다.
  • 서명된 펌웨어만 설치되게 한다.
  • 기기를 일반 업무망과 분리된 네트워크 구간에 둔다.

직접 해 보기

실제 보드 없이, 슈퍼루프 안에서 잡음 낀 12비트 ADC 온도 값을 읽어 팬 릴레이를 제어하는 상황을 흉내 낸다. 순진한 단일 기준 제어와 “이동 평균 + 히스테리시스” 제어의 릴레이 전환 횟수를 비교한다.

import math, random
from collections import deque

random.seed(3)

def read_adc(t):
    """12비트 ADC(0~4095) 로 읽은 온도 센서 값이라고 가정: 실제 온도 + 잡음."""
    true_c = 27 + 2.5 * math.sin(t / 40)                  # 천천히 오르내리는 실제 온도
    raw = int((true_c / 50) * 4095 + random.gauss(0, 60))  # 0~50도를 0~4095 로
    return max(0, min(4095, raw)), true_c

def to_celsius(raw):
    return raw / 4095 * 50

window = deque(maxlen=8)          # 이동 평균 창
fan_naive = fan_hyst = False
toggles_naive = toggles_hyst = 0
ON, OFF = 28.5, 27.5              # 히스테리시스: 켜는 기준과 끄는 기준을 다르게

for tick in range(400):           # 슈퍼루프: 100ms 마다 한 바퀴라고 가정
    raw, true_c = read_adc(tick)
    c = to_celsius(raw)
    window.append(c)
    avg = sum(window) / len(window)

    # 순진한 제어: 잡음 낀 값으로 28도 하나만 비교
    want = c > 28
    if want != fan_naive: fan_naive, toggles_naive = want, toggles_naive + 1

    # 개선한 제어: 평균값 + 히스테리시스
    if not fan_hyst and avg > ON:   fan_hyst, toggles_hyst = True, toggles_hyst + 1
    elif fan_hyst and avg < OFF:    fan_hyst, toggles_hyst = False, toggles_hyst + 1

    if tick % 80 == 0:
        print(f"t={tick/10:5.1f}s raw={raw:4d} 측정={c:5.2f}℃ 평균={avg:5.2f}℃ 실제={true_c:5.2f}℃")

print(f"팬 릴레이 전환 횟수: 순진한 제어 {toggles_naive}회, 평균+히스테리시스 {toggles_hyst}회")

실행 결과(난수 시드를 고정해 재현 가능):

t=  0.0s raw=2216 측정=27.06℃ 평균=27.06℃ 실제=27.00℃
t=  8.0s raw=2520 측정=30.77℃ 평균=29.55℃ 실제=29.27℃
t= 16.0s raw=2105 측정=25.70℃ 평균=25.40℃ 실제=25.11℃
t= 24.0s raw=2093 측정=25.56℃ 평균=25.92℃ 실제=26.30℃
t= 32.0s raw=2421 측정=29.56℃ 평균=29.45℃ 실제=29.47℃
팬 릴레이 전환 횟수: 순진한 제어 76회, 평균+히스테리시스 4회

같은 온도 변화인데 순진한 제어는 40초 동안 릴레이를 76번 껐다 켰고, 개선한 제어는 4번만 바꿨다. 실제 릴레이는 전환 횟수만큼 수명이 줄고, 모터는 잦은 기동에 열이 난다. 측정값 한 개는 실제 온도와 1도 넘게 차이 날 때도 있지만, 8개 평균은 실제 값에 훨씬 가깝다.

실제 보드에서는 MicroPython 으로 거의 같은 코드를 쓸 수 있다. 아래는 MicroPython 문서의 machine 모듈 API 를 쓴 형태이며, 핀 번호는 보드마다 다르다(여기서는 실행하지 않았다).

from machine import ADC, Pin, WDT
import time

adc = ADC(Pin(26))           # read_u16() 은 0~65535 로 스케일된 값을 준다
fan = Pin(15, Pin.OUT)
wdt = WDT(timeout=2000)      # 2초 안에 feed() 가 없으면 리셋
while True:
    raw = adc.read_u16()
    # ... 평균·히스테리시스 계산 후 fan.value(1) 또는 fan.value(0)
    wdt.feed()
    time.sleep_ms(100)

현업에서는

  • “데이터가 이상해요”의 절반은 기기 쪽: 서버 대시보드에 튀는 값이 보이면, 기기에서 필터링을 했는지, 시계가 맞는지(RTC·NTP), 재부팅 직후 첫 값인지부터 확인한다. 기기가 보내는 메시지에 펌웨어 버전과 부팅 후 경과 시간을 넣어 두면 원인 추적이 쉬워진다.
  • 현장 기기 업데이트: OTA 는 단계적으로 배포한다. 일부 기기에 먼저 올려 재부팅·오류율을 본 뒤 넓힌다. 웹 서비스의 카나리 배포와 같은 생각이다.
  • 홈랩의 IoT 게이트웨이: 집 안 센서들을 무선으로 모으는 게이트웨이와, 그 데이터를 받는 메시지 브로커·시계열 DB 를 홈랩 k3s 클러스터에 두는 구성이 흔하다. 이때 센서 기기들은 일반 PC·서버와 다른 네트워크 구간에 두고, 브로커 접근은 인증으로 제한한다. 기기 쪽 통신 프로토콜로는 다음 글의 MQTT 가 가장 널리 쓰인다.
  • 시간이 곧 사양: “버튼을 누르면 200ms 안에 문이 열린다” 같은 요구는 평균이 아니라 최악의 경우로 검증한다. 임베디드에서 지연은 기능의 일부다.

확인 문제

  1. 마이크로컨트롤러와 리눅스가 도는 애플리케이션 프로세서의 차이를 메모리·운영체제·전력 관점에서 설명하라.
  2. 인터럽트 처리 함수를 짧게 유지해야 하는 이유는?
  3. RTOS 의 “실시간”은 무엇을 보장한다는 뜻인가.
  4. 온도 제어에서 히스테리시스를 두는 이유는?
  5. 워치독 타이머는 어떤 상황에서 기기를 살리는가.

풀이

  1. MCU 는 칩 안의 작은 RAM·플래시로 OS 없이(또는 RTOS 로) 돌고 전력 소모가 매우 적다. 애플리케이션 프로세서는 외부 DRAM·저장 장치와 리눅스 같은 범용 OS 를 쓰며 전력을 더 쓴다.
  2. 인터럽트 처리 중에는 메인 흐름과 다른 인터럽트가 지연되므로 길어지면 다른 사건을 놓치거나 타이밍이 깨진다. 플래그만 세우고 처리는 메인 루프에서 한다.
  3. 빠르다는 뜻이 아니라, 정해진 마감 시간 안에 반응하는 것을 예측 가능하게 보장한다는 뜻이다.
  4. 기준값 근처에서 값이 오르내릴 때 출력이 계속 켜졌다 꺼지는 떨림(채터링)을 막기 위해서다.
  5. 프로그램이 무한 루프·교착·멈춤으로 정해진 시간 안에 신호를 주지 못할 때, 하드웨어가 자동으로 리셋해 사람 개입 없이 복구한다.

더 읽을거리 (References)