Skip to content
Published on

組み込みシステム・IoT完全ガイド: MCUからエッジAIまで

Authors

1. 組み込みシステムの概要

組み込みシステム(Embedded System)とは、特定の機能を実行するためにハードウェアとソフトウェアが統合された専用コンピューティングシステムです。洗濯機、自動車のエンジン制御ユニット(ECU)、スマートサーモスタット、医療機器など、私たちの身の回りにある数多くの製品に内蔵されています。

マイクロコントローラ(MCU)vs マイクロプロセッサ(MPU)

項目MCUMPU
CPUシンプル・低消費電力高性能・複雑
メモリ内蔵Flash/RAM外付けDRAMが必要
OSなし or RTOSLinux, Android等
消費電力数mW数W
STM32, AVR, ESP32Raspberry Pi, i.MX8

主要MCUプラットフォーム

  • STM32(STマイクロエレクトロニクス): ARM Cortex-Mベース、産業用途に広く採用。HALライブラリとCubeMX GUIツールを提供。
  • AVR(Microchip/Atmel): Arduino UNOの基盤MCU。入門者に親しみやすいプラットフォーム。
  • ESP32(Espressif): デュアルコアXtensa LX6にWiFiとBLEを内蔵。IoTプロジェクトに最適。
  • RP2040(Raspberry Pi): デュアルコアARM Cortex-M0+に独自のPIO(プログラマブル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, ST-Link

2. Cによるハードウェア制御

組み込みCプログラミングの核心は、ハードウェアレジスタを直接操作するか、メーカー提供のHAL(ハードウェア抽象化レイヤ)ライブラリを活用することです。

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(パルス幅変調)機能で、サーボモータ制御、LED輝度調節、DCモータ速度制御を実現します。

// 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モジュール、Bluetoothモジュールとの接続に主に使われます。

// 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送受信(Flash ID読み取り)
uint8_t tx_data[] = {0x9F, 0x00};
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バス

自動車・産業機器で使われる堅牢な差動シリアル通信プロトコルです。

// 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;
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(リアルタイムオペレーティングシステム)

RTOSは決定論的なタイミングを保証するOSです。複数のタスクを優先度に従ってスケジューリングし、リアルタイム応答が求められるシステムに不可欠です。

FreeRTOSの基礎

FreeRTOSは世界で最も広く使われているオープンソースRTOSです。Amazonが管理し、STM32、ESP32、Arduinoなど多くのプラットフォームをサポートします。

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "queue.h"

// LEDブリンクタスク
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クロックFlashRAM特徴
UNO R3ATmega328P16MHz32KB2KB定番入門用
NanoATmega328P16MHz32KB2KB小型フォームファクタ
Mega 2560ATmega256016MHz256KB8KB多数のI/Oピン
DueSAM3X8E84MHz512KB96KB32ビットARM
UNO R4RA4M148MHz256KB32KB最新世代

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製の強力な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

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接続:', wlan.ifconfig())

def on_message(topic, msg):
    print('Topic:', topic, 'Message:', msg)

connect_wifi('MySSID', 'MyPassword')

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/control')

while True:
    raw     = adc.read()
    voltage = raw * 3.3 / 4095
    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 WiFi(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モデル比較

モデルCPURAM特徴
Pi Zero 2WARM Cortex-A53 quad 1GHz512MB超小型・WiFi/BLE搭載
Pi 4 Model BARM Cortex-A72 quad 1.8GHz1〜8GB汎用高性能
Pi 5ARM Cortex-A76 quad 2.4GHz4〜8GB最新世代
Pi Pico 2RP2350 dual Cortex-M33520KBマイクロコントローラ

GPIO制御とサーボモータ

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)

# 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}度")
        time.sleep(1)
finally:
    pwm.stop()
    GPIO.cleanup()

gpiozeroライブラリ

from gpiozero import LED, Button, DistanceSensor
import time

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"距離: {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 -- フルIoTスタック
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_details[0]['shape'])   # [1, 224, 224, 3]
print("入力型:", 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)

    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"ラベル: {labels[top_idx]}, スコア: {output[0][top_idx]}")
    print(f"推論時間: {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

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_name}, 形状: {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 to CHW
img_array = np.expand_dims(img_array, axis=0)   # バッチ次元追加

result = session.run([output_name], {input_name: img_array})
print("出力形状:", result[0].shape)

エッジAIプラットフォーム比較

プラットフォームベンダーAI性能消費電力特徴
Jetson Orin NanoNVIDIA40 TOPS7〜15WCUDAオンチップGPU
Coral Dev BoardGoogle4 TOPS約2W専用Edge TPU
RK3588ボードRockchip6 TOPS NPU約8Wコスパ優秀
Hailo-8 M.2Hailo26 TOPS2.5WPCIe拡張モジュール
Pi 5 + AI HAT+RPi Foundation26 TOPS約5WPiエコシステム対応

10. IoT通信プロトコルとクラウド

MQTT

MQTT(Message Queuing Telemetry Transport)はIoT向けに最適化された軽量のpub/subプロトコルです。ブローカーが発行者と購読者を分離し、スケーラブルで効率的なメッセージングを実現します。

# 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"接続コード: {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 2.xにセンサーデータを書き込む
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)

クラウドIoTサービス

  • AWS IoT Core: 完全マネージド型、数十億台のデバイスをサポート。X.509証明書ベースのmTLSで安全な接続を実現。Lambda、DynamoDB、S3と統合。
  • Azure IoT Hub: エンタープライズ向けデバイス管理。デバイスツインやダイレクトメソッドが利用可能。
  • Google Cloud IoT: Pub/Sub、BigQuery、Vertex AIとの統合でエンドツーエンドのMLパイプラインを構築。

11. 組み込みセキュリティ

OTA(Over-the-Air)ファームウェア更新

セキュアOTAの核心は署名検証です。署名されていないファームウェアを拒否し、悪意あるコードのインストールを防止します。

// ESP-IDF HTTPS 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);
}

セキュリティ設計原則

  1. 最小権限の原則: 必要な機能のみ有効化、未使用インタフェースは無効化
  2. セキュアブート(Secure Boot): 起動時にファームウェア署名を検証
  3. フラッシュ暗号化: フラッシュメモリに保存されたコード・データを暗号化
  4. 読み出し保護(RDP): JTAG/SWDによるファームウェアダンプを防止
  5. ハードウェアRNG(TRNG): 暗号化鍵生成にオンチップ真性乱数生成器を使用
  6. ウォッチドッグタイマー: 無応答状態を自動復旧

12. 確認クイズ

Q1. MCUとMPUの根本的な違いは何ですか?

正解: MCU(マイクロコントローラ)はCPU、メモリ(FlashとRAM)、周辺機器(UART、SPI、I2C、ADC等)が1チップに統合されています。一方、MPU(マイクロプロセッサ)はCPUコアに特化し、外付けメモリや周辺チップが別途必要です。

解説: MCUは低消費電力・小型・低コストが求められる組み込みアプリケーションに適しています。MPUはLinuxのような複雑なOSを動かす必要があるゲートウェイ、HMIパネル、メディアデバイスに適しています。

Q2. RTOSにおけるセマフォとミューテックスの違いは?

正解: ミューテックスには**所有権(Ownership)**があり、ロックしたタスクだけがアンロックできます。また優先順位逆転を防ぐ優先順位継承機能を備えています。バイナリセマフォには所有権がなく、別のタスクやISRでもシグナルを送れます。

解説: ミューテックスは共有リソースの相互排他(Mutual Exclusion)に使います。バイナリセマフォはタスク間のイベント同期、特にISRからタスクへの通知に使います。ISR内でミューテックスを使うのは誤りであり、デッドロックの原因になります。

Q3. I2CとSPIのトレードオフを比較してください。

正解:

  • I2Cの利点: 2線(SDA+SCL)のみ、同一バスに複数機器を接続可能(7ビットアドレスで識別)、配線がシンプル
  • I2Cの欠点: SPIより低速(最大約3.4Mbps)、プルアップ抵抗が必要、オープンドレイン方式
  • SPIの利点: 非常に高速(数十Mbps)、全二重通信、電気的にシンプル
  • SPIの欠点: 機器ごとにCS(チップセレクト)ピンが必要でピン数が増える、アドレス概念なし

解説: 低速センサー(MPU6050、BMP280等)には配線の簡便さからI2Cを、TFTディスプレイ、SDカード、SPIフラッシュなど高速転送が必要な機器にはSPIを使います。

Q4. 組み込みCプログラミングでvolatileキーワードが重要な理由は?

正解: コンパイラは変数の値がプログラムの通常の実行パスでしか変化しないと仮定します。そのため値をレジスタにキャッシュして、メモリからの再読み込みを省略することがあります。しかしハードウェアレジスタ、ISRで更新される変数、DMAバッファはプログラムフロー外部から値が変わります。volatile修飾子はコンパイラにこれらの変数を最適化しないよう指示します。

解説: volatileなしでISR内でフラグを立てても、メインループがレジスタにキャッシュされた値を読み続け、変更を認識しないバグが発生します。これは組み込みシステム開発における典型的で発見が難しいバグの一つです。

Q5. エッジAIにおいてモデル量子化(Quantization)を使う理由は?

正解: 量子化はモデルの重みと活性化値を32ビット浮動小数点(FP32)から8ビット整数(INT8)に変換する手法です。これによりモデルサイズが約4分の1になり、推論速度が2〜4倍向上し、メモリ使用量と消費電力を大幅に削減できます。

解説: 組み込みデバイスはメモリ(数MB)と演算リソースが極めて限られています。TFLiteのINT8量子化モデルであれば、Raspberry PiやCortex-M MCUといった低スペック機器でもリアルタイム推論が可能です。精度の低下は一般的にFP32モデルと比較して1〜2%以内に収まり、多くの実用用途では許容範囲内です。


参考資料