- Authors

- Name
- Youngju Kim
- @fjvbn20031
1. 임베디드 시스템 개요
임베디드 시스템(Embedded System)은 특정 기능을 수행하기 위해 하드웨어와 소프트웨어가 결합된 전용 컴퓨팅 시스템입니다. 세탁기, 자동차 엔진 제어 유닛(ECU), 스마트 온도조절기, 의료 기기 등 우리 주변의 수많은 기기에 내장되어 있습니다.
마이크로컨트롤러(MCU) vs 마이크로프로세서(MPU)
| 항목 | MCU | MPU |
|---|---|---|
| CPU | 단순, 저전력 | 고성능, 복잡 |
| 메모리 | 내장 Flash/RAM | 외부 DRAM 필요 |
| 운영체제 | 없거나 RTOS | Linux, Android 등 |
| 소비 전력 | 수 mW | 수 W |
| 예시 | STM32, AVR, ESP32 | Raspberry Pi, i.MX8 |
주요 MCU 플랫폼
- STM32 (ST Microelectronics): ARM Cortex-M 기반, 산업용으로 널리 사용. HAL 라이브러리와 CubeMX 개발 도구 제공.
- AVR (Microchip/Atmel): Arduino UNO의 기반 MCU. 입문자에게 친숙한 플랫폼.
- ESP32 (Espressif): 듀얼코어 Xtensa LX6, WiFi+BLE 내장. IoT 프로젝트에 최적.
- RP2040 (Raspberry Pi): 듀얼코어 ARM Cortex-M0+, PIO(Programmable I/O) 기능이 독특.
- PIC (Microchip): 산업 현장에서 오랜 역사를 가진 MCU.
개발 환경
- IDE: STM32CubeIDE, MPLAB X, Arduino IDE, PlatformIO
- 컴파일러: GCC (arm-none-eabi-gcc), LLVM/Clang
- 디버거: JTAG, SWD (Serial Wire Debug), OpenOCD, J-Link
2. C 언어로 하드웨어 제어
임베디드 C 프로그래밍의 핵심은 하드웨어 레지스터를 직접 제어하거나 HAL(Hardware Abstraction Layer) 라이브러리를 활용하는 것입니다.
GPIO 제어
GPIO(General Purpose Input/Output)는 디지털 입출력을 제어하는 가장 기본적인 인터페이스입니다.
// STM32 HAL 라이브러리를 사용한 GPIO 제어
#include "stm32f4xx_hal.h"
// LED 토글 (핀 PA5)
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(500);
// 버튼 읽기 (핀 PC13, 활성 LOW)
if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_RESET) {
// 버튼이 눌린 경우 처리
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
}
레지스터 직접 조작
HAL 없이 레지스터를 직접 제어하는 방법은 더 빠르고 코드 크기가 작습니다.
// 레지스터 직접 조작 (STM32F4)
// GPIOA 클록 활성화
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// PA5를 출력으로 설정 (MODER 레지스터)
GPIOA->MODER &= ~(0x3 << (5 * 2)); // 비트 클리어
GPIOA->MODER |= (0x1 << (5 * 2)); // 출력 모드 설정
// PA5 HIGH 출력
GPIOA->BSRR = (1 << 5);
// PA5 LOW 출력
GPIOA->BSRR = (1 << (5 + 16));
volatile 키워드의 중요성
컴파일러 최적화로 인해 하드웨어 레지스터 접근이 무시될 수 있습니다. volatile로 이를 방지합니다.
// 잘못된 예: 컴파일러가 최적화로 제거할 수 있음
uint32_t *reg = (uint32_t *)0x40020000;
*reg = 0x01;
// 올바른 예: volatile 사용
volatile uint32_t *reg = (volatile uint32_t *)0x40020000;
*reg = 0x01;
// ISR에서 사용하는 전역 변수도 volatile 필요
volatile uint8_t button_pressed = 0;
3. 인터럽트와 타이머
인터럽트 서비스 루틴 (ISR)
인터럽트는 하드웨어 이벤트(버튼 입력, 타이머 오버플로우, 통신 수신 등)가 발생할 때 CPU가 현재 작업을 중단하고 ISR을 실행하는 메커니즘입니다.
// EXTI 인터럽트 콜백 (HAL 방식)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == GPIO_PIN_13) {
button_pressed = 1;
// ISR 내에서는 최소한의 작업만 수행
// HAL_Delay() 등 블로킹 함수 사용 금지
}
}
// 타이머 인터럽트 콜백 (1ms마다 호출)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2) {
system_tick++;
}
}
PWM 출력
타이머의 PWM(Pulse Width Modulation) 기능으로 서보모터, LED 밝기 조절, 모터 속도 제어를 구현합니다.
// PWM 시작 (TIM3 채널 1)
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
// 듀티 사이클 50% 설정 (ARR = 999이면 CCR = 500)
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 500);
// 서보모터 제어 함수 (0~180도)
void servo_set_angle(uint8_t angle) {
// 1ms~2ms 펄스폭을 타이머 카운터값으로 변환
uint32_t pulse = 500 + (angle * 1000 / 180);
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, pulse);
}
4. 직렬 통신 프로토콜
UART/USART
비동기 직렬 통신. 디버그 출력, GPS 모듈, 블루투스 모듈 연결에 주로 사용됩니다.
// UART로 문자열 전송
char msg[] = "Hello, UART!\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), 100);
// UART 수신 (인터럽트 방식)
uint8_t rx_byte;
HAL_UART_Receive_IT(&huart2, &rx_byte, 1);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART2) {
rx_buffer[rx_index++] = rx_byte;
HAL_UART_Receive_IT(&huart2, &rx_byte, 1);
}
}
SPI 통신
마스터-슬레이브 동기 통신. 고속 데이터 전송이 필요한 디스플레이, SD카드, ADC에 사용됩니다.
// SPI로 데이터 송수신
uint8_t tx_data[] = {0x9F, 0x00}; // Flash ID 읽기 명령
uint8_t rx_data[2];
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); // CS LOW
HAL_SPI_TransmitReceive(&hspi1, tx_data, rx_data, 2, 100);
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); // CS HIGH
I2C 통신
2선식(SDA, SCL) 버스 통신. 여러 기기를 같은 버스에 연결 가능. 센서류에 주로 사용됩니다.
// I2C로 MPU6050 가속도계 읽기
#define MPU6050_ADDR 0xD0 // 7비트 주소 0x68, 쓰기 0xD0
#define ACCEL_XOUT_H 0x3B
uint8_t data[6];
HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, ACCEL_XOUT_H,
I2C_MEMADD_SIZE_8BIT, data, 6, 100);
int16_t accel_x = (data[0] << 8) | data[1];
int16_t accel_y = (data[2] << 8) | data[3];
int16_t accel_z = (data[4] << 8) | data[5];
// 실제 가속도 (g단위) = raw / 16384.0 (±2g 설정시)
float ax = accel_x / 16384.0f;
CAN Bus
자동차와 산업용 장비에서 사용하는 강인한 차동 직렬 통신 프로토콜입니다.
// CAN 메시지 전송 (STM32 HAL)
CAN_TxHeaderTypeDef tx_header;
uint8_t tx_data[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
uint32_t tx_mailbox;
tx_header.StdId = 0x123; // CAN ID
tx_header.IDE = CAN_ID_STD; // 표준 프레임
tx_header.RTR = CAN_RTR_DATA; // 데이터 프레임
tx_header.DLC = 8; // 데이터 길이
HAL_CAN_AddTxMessage(&hcan1, &tx_header, tx_data, &tx_mailbox);
5. RTOS (Real-Time Operating System)
RTOS는 결정론적(Deterministic) 타이밍을 보장하는 운영체제입니다. 여러 태스크를 우선순위에 따라 스케줄링하며, 실시간 응답이 요구되는 시스템에 필수적입니다.
FreeRTOS 기초
FreeRTOS는 가장 널리 사용되는 오픈소스 RTOS입니다. Amazon이 관리하며 STM32, ESP32, Arduino 등 다양한 플랫폼을 지원합니다.
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "queue.h"
// 태스크 함수 정의
void vTaskLED(void *pvParameters) {
while(1) {
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
vTaskDelay(pdMS_TO_TICKS(500)); // 500ms 대기 (CPU 양보)
}
}
void vTaskSensor(void *pvParameters) {
QueueHandle_t xQueue = (QueueHandle_t)pvParameters;
float temperature;
while(1) {
temperature = read_temperature_sensor();
xQueueSend(xQueue, &temperature, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
QueueHandle_t xTempQueue = xQueueCreate(10, sizeof(float));
// 태스크 생성 (함수명, 태스크명, 스택크기, 파라미터, 우선순위, 핸들)
xTaskCreate(vTaskLED, "LED", 128, NULL, 1, NULL);
xTaskCreate(vTaskSensor, "Sensor", 256, xTempQueue, 2, NULL);
vTaskStartScheduler(); // RTOS 스케줄러 시작 (이 줄 이후로 돌아오지 않음)
while(1);
}
세마포어와 뮤텍스
SemaphoreHandle_t xMutex;
SemaphoreHandle_t xSemaphore;
// 뮤텍스로 공유 자원 보호
xMutex = xSemaphoreCreateMutex();
void vTaskA(void *pvParameters) {
while(1) {
if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
// 임계 구역: 공유 자원 접근
shared_data++;
xSemaphoreGive(xMutex);
}
}
}
// 바이너리 세마포어로 ISR-태스크 동기화
xSemaphore = xSemaphoreCreateBinary();
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
6. Arduino 생태계
Arduino는 임베디드 입문자를 위한 오픈소스 플랫폼입니다. 간단한 API와 방대한 라이브러리 생태계가 특징입니다.
Arduino 보드 비교
| 보드 | MCU | 클록 | Flash | RAM | 특징 |
|---|---|---|---|---|---|
| UNO R3 | ATmega328P | 16MHz | 32KB | 2KB | 기본 입문용 |
| Nano | ATmega328P | 16MHz | 32KB | 2KB | 소형 폼팩터 |
| Mega 2560 | ATmega2560 | 16MHz | 256KB | 8KB | 많은 핀 수 |
| Due | SAM3X8E | 84MHz | 512KB | 96KB | 32비트 ARM |
| UNO R4 | RA4M1 | 48MHz | 256KB | 32KB | 최신 버전 |
DHT22 + I2C LCD 예제
#include <DHT.h>
#include <LiquidCrystal_I2C.h>
#define DHTPIN 2
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
Serial.begin(9600);
dht.begin();
lcd.init();
lcd.backlight();
lcd.print("IoT Weather Box");
delay(2000);
lcd.clear();
}
void loop() {
float temp = dht.readTemperature();
float hum = dht.readHumidity();
if (isnan(temp) || isnan(hum)) {
Serial.println("DHT read error!");
return;
}
lcd.setCursor(0, 0);
lcd.print("Temp: ");
lcd.print(temp, 1);
lcd.print(" C ");
lcd.setCursor(0, 1);
lcd.print("Hum: ");
lcd.print(hum, 1);
lcd.print(" % ");
Serial.print("T="); Serial.print(temp);
Serial.print(" H="); Serial.println(hum);
delay(2000);
}
7. ESP32 & WiFi/BLE IoT
ESP32는 Espressif Systems의 강력한 IoT MCU입니다. 듀얼코어 240MHz, WiFi 802.11 b/g/n, BLE 4.2/5.0을 내장하며 가격 대비 성능이 탁월합니다.
MicroPython으로 MQTT IoT 구현
# MicroPython ESP32 MQTT 센서 데이터 전송
import time
import network
from umqtt.simple import MQTTClient
from machine import Pin, ADC
# WiFi 연결
def connect_wifi(ssid, password):
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
wlan.connect(ssid, password)
while not wlan.isconnected():
time.sleep(0.5)
print('WiFi connected:', wlan.ifconfig())
# MQTT 메시지 수신 콜백
def on_message(topic, msg):
print('Topic:', topic, 'Message:', msg)
if topic == b'device/led' and msg == b'on':
led.value(1)
connect_wifi('MySSID', 'MyPassword')
# ADC로 온도 센서 읽기 (NTC 서미스터)
adc = ADC(Pin(34))
adc.atten(ADC.ATTN_11DB)
client = MQTTClient('esp32_sensor', 'broker.hivemq.com', port=1883)
client.set_callback(on_message)
client.connect()
client.subscribe(b'device/led')
while True:
raw = adc.read()
voltage = raw * 3.3 / 4095
# 간단한 온도 변환 (실제는 NTC 특성 곡선 필요)
temp_approx = (voltage - 0.5) * 100
payload = '{:.1f}'.format(temp_approx)
client.publish(b'sensor/temperature', payload.encode())
client.check_msg()
time.sleep(5)
ESP-IDF (C/C++ 기반)
// ESP-IDF WiFi 연결 예제
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
#define WIFI_SSID "MyNetwork"
#define WIFI_PASS "MyPassword"
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI("WiFi", "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
}
}
void wifi_init_sta(void) {
nvs_flash_init();
esp_netif_init();
esp_event_loop_create_default();
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, NULL);
esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_event_handler, NULL);
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASS,
},
};
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
esp_wifi_start();
}
8. Raspberry Pi & Linux 임베디드
Raspberry Pi는 완전한 Linux 운영체제를 실행하는 싱글보드 컴퓨터(SBC)입니다. 임베디드와 일반 컴퓨팅의 경계에 있는 강력한 플랫폼입니다.
Raspberry Pi 시리즈 비교
| 모델 | CPU | RAM | 특징 |
|---|---|---|---|
| Pi Zero 2W | ARM Cortex-A53 quad 1GHz | 512MB | 초소형, WiFi/BLE |
| Pi 4 Model B | ARM Cortex-A72 quad 1.8GHz | 1~8GB | 범용 고성능 |
| Pi 5 | ARM Cortex-A76 quad 2.4GHz | 4~8GB | 최신 고성능 |
| Pi Pico 2 | RP2350 dual Cortex-M33 | 520KB | 마이크로컨트롤러 |
GPIO 제어 및 서보모터
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM) # BCM 핀 번호 사용
GPIO.setup(18, GPIO.OUT) # GPIO18을 출력으로 설정
# 50Hz PWM 서보모터 제어
pwm = GPIO.PWM(18, 50)
pwm.start(7.5) # 중립 위치 (90도)
def set_servo_angle(angle):
"""서보모터 각도 설정 (0~180도)"""
duty = 2.5 + (angle / 180.0) * 10.0
pwm.ChangeDutyCycle(duty)
time.sleep(0.3)
try:
for angle in [0, 45, 90, 135, 180]:
set_servo_angle(angle)
print(f"Angle: {angle} degrees")
time.sleep(1)
finally:
pwm.stop()
GPIO.cleanup()
gpiozero 라이브러리
from gpiozero import LED, Button, Servo, DistanceSensor
from signal import pause
import time
# LED와 버튼 연결
led = LED(17)
button = Button(4)
# 버튼 이벤트 핸들러
button.when_pressed = led.on
button.when_released = led.off
# 초음파 거리 센서 (HC-SR04)
sensor = DistanceSensor(echo=24, trigger=23)
while True:
dist = sensor.distance * 100 # cm 단위
print(f"Distance: {dist:.1f} cm")
if dist < 20:
led.blink(on_time=0.1, off_time=0.1)
time.sleep(0.5)
Docker 컨테이너 활용
# Raspberry Pi용 Python IoT 앱 Dockerfile
FROM python:3.11-slim-bullseye
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "sensor_server.py"]
# docker-compose.yml
version: '3'
services:
sensor-app:
build: .
privileged: true # GPIO 접근을 위해 필요
volumes:
- /dev:/dev
restart: unless-stopped
influxdb:
image: influxdb:2.7
ports:
- '8086:8086'
volumes:
- influx_data:/var/lib/influxdb2
grafana:
image: grafana/grafana:latest
ports:
- '3000:3000'
volumes:
- grafana_data:/var/lib/grafana
volumes:
influx_data:
grafana_data:
9. 엣지 AI (Edge AI)
엣지 AI는 클라우드가 아닌 로컬 기기에서 AI 추론을 실행하는 기술입니다. 낮은 지연 시간, 오프라인 동작, 프라이버시 보호, 대역폭 절감이 주요 장점입니다.
TensorFlow Lite (TFLite)
TFLite는 모바일 및 임베디드 기기용으로 최적화된 경량 ML 프레임워크입니다.
# TFLite 이미지 분류 추론 (Raspberry Pi)
import tflite_runtime.interpreter as tflite
import numpy as np
from PIL import Image
import time
# 모델 로드
interpreter = tflite.Interpreter(
model_path='mobilenet_v2_1.0_224_quant.tflite',
num_threads=4 # 멀티스레드 추론
)
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
# 입력 정보 출력
print("Input shape:", input_details[0]['shape']) # [1, 224, 224, 3]
print("Input dtype:", input_details[0]['dtype']) # uint8 (양자화 모델)
def classify_image(image_path, labels):
img = Image.open(image_path).convert('RGB').resize((224, 224))
input_data = np.expand_dims(np.array(img), axis=0)
# uint8 양자화 모델의 경우 정규화 불필요
interpreter.set_tensor(input_details[0]['index'], input_data)
start = time.time()
interpreter.invoke()
elapsed = (time.time() - start) * 1000
output = interpreter.get_tensor(output_details[0]['index'])
top_idx = np.argmax(output[0])
print(f"Label: {labels[top_idx]}, Score: {output[0][top_idx]}")
print(f"Inference time: {elapsed:.1f}ms")
# 레이블 파일 로드
with open('labels.txt') as f:
labels = [line.strip() for line in f.readlines()]
classify_image('test.jpg', labels)
ONNX Runtime
# ONNX Runtime으로 커스텀 모델 실행
import onnxruntime as ort
import numpy as np
from PIL import Image
# 세션 생성 (CPU/GPU 선택 가능)
session = ort.InferenceSession(
'model.onnx',
providers=['CPUExecutionProvider']
)
# 입출력 정보 확인
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
input_shape = session.get_inputs()[0].shape
print(f"Input: {input_name}, Shape: {input_shape}")
# 이미지 전처리
img = Image.open('test.jpg').resize((224, 224))
img_array = np.array(img).astype(np.float32) / 255.0
img_array = np.transpose(img_array, (2, 0, 1)) # HWC -> CHW
img_array = np.expand_dims(img_array, axis=0) # 배치 차원 추가
# 추론
result = session.run([output_name], {input_name: img_array})
print("Output shape:", result[0].shape)
Edge AI 플랫폼 비교
| 플랫폼 | 제조사 | AI 성능 | 소비전력 | 특징 |
|---|---|---|---|---|
| Jetson Orin Nano | NVIDIA | 40 TOPS | 7~15W | CUDA GPU 내장 |
| Coral Dev Board | 4 TOPS | ~2W | Edge TPU 전용 | |
| RK3588 | Rockchip | 6 TOPS NPU | ~8W | 가성비 우수 |
| Hailo-8 M.2 | Hailo | 26 TOPS | 2.5W | PCIe 확장 모듈 |
| Pi 5 + AI HAT+ | RPi Foundation | 26 TOPS | ~5W | 라즈베리파이 에코계 |
10. IoT 통신 프로토콜 & 클라우드
MQTT
MQTT(Message Queuing Telemetry Transport)는 IoT에 최적화된 경량 pub/sub 메시지 프로토콜입니다. 브로커를 통해 발행자(Publisher)와 구독자(Subscriber)가 통신합니다.
# Python Paho-MQTT 클라이언트 예제
import paho.mqtt.client as mqtt
import json
import time
BROKER = "broker.hivemq.com"
PORT = 1883
TOPIC_PUB = "home/sensor/living_room"
TOPIC_SUB = "home/control/#"
def on_connect(client, userdata, flags, rc):
print(f"Connected with code: {rc}")
client.subscribe(TOPIC_SUB)
def on_message(client, userdata, msg):
payload = json.loads(msg.payload.decode())
print(f"Topic: {msg.topic}, Payload: {payload}")
client = mqtt.Client("python_sensor_01")
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER, PORT, keepalive=60)
client.loop_start()
while True:
data = {
"temperature": 23.5,
"humidity": 60.2,
"timestamp": int(time.time())
}
client.publish(TOPIC_PUB, json.dumps(data))
time.sleep(10)
InfluxDB + Grafana 데이터 시각화
# InfluxDB에 센서 데이터 저장
from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS
INFLUX_URL = "http://localhost:8086"
INFLUX_TOKEN = "your_token_here"
INFLUX_ORG = "myorg"
INFLUX_BUCKET = "iot_sensors"
client = InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG)
write_api = client.write_api(write_options=SYNCHRONOUS)
# 데이터 포인트 작성
point = (
Point("environment")
.tag("location", "living_room")
.tag("device", "esp32_01")
.field("temperature", 23.5)
.field("humidity", 60.2)
.field("co2_ppm", 650)
)
write_api.write(bucket=INFLUX_BUCKET, record=point)
AWS IoT Core
AWS IoT Core는 수십억 개의 IoT 기기를 안전하게 연결하고 관리할 수 있는 완전관리형 클라우드 서비스입니다. X.509 인증서 기반 mTLS로 보안을 보장하며, AWS Lambda, DynamoDB, S3 등 AWS 서비스와 통합됩니다.
11. 임베디드 보안
OTA (Over-the-Air) 펌웨어 업데이트
보안 OTA의 핵심은 서명 검증입니다. 서명되지 않은 펌웨어를 거부하여 악성 코드 설치를 방지합니다.
// ESP-IDF OTA 업데이트 예제
#include "esp_ota_ops.h"
#include "esp_https_ota.h"
void ota_task(void *pvParameter) {
esp_https_ota_config_t ota_config = {
.http_config = &(esp_http_client_config_t){
.url = "https://myserver.com/firmware.bin",
.cert_pem = server_cert_pem, // 서버 인증서 검증
},
};
esp_err_t ret = esp_https_ota(&ota_config);
if (ret == ESP_OK) {
esp_restart(); // 성공시 재부팅
} else {
ESP_LOGE("OTA", "Update failed: %s", esp_err_to_name(ret));
}
vTaskDelete(NULL);
}
MCU에서 AES 암호화
// mbedTLS AES-128 CBC 암호화 (STM32/ESP32)
#include "mbedtls/aes.h"
void aes_encrypt_data(uint8_t *plaintext, uint8_t *ciphertext,
uint8_t *key, uint8_t *iv, size_t len) {
mbedtls_aes_context ctx;
mbedtls_aes_init(&ctx);
mbedtls_aes_setkey_enc(&ctx, key, 128); // 128비트 키
uint8_t iv_copy[16];
memcpy(iv_copy, iv, 16); // IV는 암호화 과정에서 변경됨
mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_ENCRYPT, len,
iv_copy, plaintext, ciphertext);
mbedtls_aes_free(&ctx);
}
보안 설계 원칙
- 최소 권한 원칙: 필요한 기능만 활성화, 미사용 인터페이스 비활성화
- 보안 부트(Secure Boot): 부팅 시 펌웨어 서명 검증
- 플래시 암호화: 플래시 메모리에 저장된 코드/데이터 암호화
- RDP(Read-Out Protection): JTAG/SWD를 통한 펌웨어 덤프 방지
- 난수 생성기(TRNG): 암호화 키 생성에 하드웨어 RNG 사용
- 타임아웃 및 워치독: 응답 없는 상태 자동 복구
12. 퀴즈
Q1. MCU와 MPU의 가장 큰 차이점은 무엇인가요?
정답: MCU(마이크로컨트롤러)는 CPU, 메모리(Flash, RAM), 주변장치(UART, SPI, I2C, ADC 등)가 하나의 칩에 통합되어 있습니다. 반면 MPU(마이크로프로세서)는 CPU 코어에 집중하며, 외부 메모리와 주변장치가 별도로 필요합니다.
설명: MCU는 저전력, 소형, 저비용이 요구되는 임베디드 애플리케이션에 적합하고, MPU는 Linux 같은 복잡한 OS를 실행해야 하는 고성능 애플리케이션에 적합합니다.
Q2. RTOS에서 세마포어와 뮤텍스의 차이점은?
정답: 뮤텍스(Mutex)는 **소유권(Ownership)**이 있어 잠근 태스크만 열 수 있으며, 우선순위 역전(Priority Inversion) 방지를 위한 우선순위 상속 메커니즘이 있습니다. 세마포어는 소유권이 없어 다른 태스크나 ISR도 신호를 줄 수 있습니다.
설명: 뮤텍스는 공유 자원의 상호 배제(Mutual Exclusion)에, 세마포어는 태스크 간 동기화나 자원 카운팅에 사용합니다. ISR에서 태스크로 이벤트를 알릴 때는 바이너리 세마포어를 사용합니다.
Q3. I2C와 SPI 통신 방식의 장단점을 비교하세요.
정답:
- I2C 장점: 2선(SDA, SCL)만 필요, 여러 기기를 같은 버스에 연결 가능 (주소로 구분), 배선이 간단
- I2C 단점: SPI보다 느림(최대 3.4Mbps), 풀업 저항 필요, 오픈 드레인 방식으로 고속 불리
- SPI 장점: 매우 빠름(수십 Mbps), 풀 듀플렉스 통신, 하드웨어 구현 단순
- SPI 단점: 기기마다 CS 핀 필요(핀 수 증가), 주소 개념 없음
설명: 저속 센서(MPU6050, BMP280 등)에는 I2C, 고속 전송이 필요한 디스플레이, SD카드, SPI Flash에는 SPI를 주로 사용합니다.
Q4. 임베디드 시스템에서 volatile 키워드가 중요한 이유는?
정답: 컴파일러는 최적화 과정에서 변수 값이 코드 내에서만 변경된다고 가정합니다. 하지만 하드웨어 레지스터, ISR에서 변경되는 변수, DMA 버퍼 등은 프로그램 흐름 외부에서 값이 바뀔 수 있습니다. volatile 키워드는 컴파일러에게 이 변수를 최적화하지 말고 매번 메모리에서 읽으라고 지시합니다.
설명: volatile 없이 ISR에서 플래그를 설정해도 메인 루프가 캐시된 값을 읽어 인식하지 못하는 버그가 발생할 수 있습니다. 이는 디버거 없이 발견하기 어려운 클래식한 임베디드 버그입니다.
Q5. 엣지 AI에서 모델 양자화(Quantization)를 사용하는 이유는?
정답: 양자화는 모델의 가중치와 활성화값을 32비트 부동소수점(FP32)에서 8비트 정수(INT8)로 변환하는 기법입니다. 이를 통해 모델 크기를 약 4배 줄이고, 추론 속도를 2~4배 향상시키며, 메모리 사용량과 전력 소비를 크게 줄일 수 있습니다.
설명: 임베디드 기기는 메모리(수 MB)와 연산 능력이 제한적입니다. TFLite의 INT8 양자화 모델은 Raspberry Pi나 Cortex-M MCU 같은 저사양 기기에서도 실시간 추론이 가능합니다. 단, 정확도가 약간 감소할 수 있습니다 (일반적으로 1~2% 이내).