Rust no-std組み込み開発:ゼロからベアメタルファームウェアを構築する完全ガイド 2026

编程语言

Rust no-std組み込み開発:ゼロからベアメタルファームウェアを構築する完全ガイド 2026

組み込み開発の分野では、C言語が長らく支配的な地位を占めてきました。しかし、Rustエコシステムの成熟に伴い、安全で信頼性の高いベアメタルファームウェアを構築するためにCをRustに置き換えるチームが増えています。Rustの所有権システムはコンパイル時にメモリ安全性の脆弱性を排除し、no_std環境によりオペレーティングシステムなしのベアメタルハードウェア上でRustを実行できます。本ガイドでは、Rust組み込み開発の核心パターンをゼロから完全に解説します。

コア概念一覧

概念 説明 適用シーン
no_std Rust標準ライブラリを無効化、coreallocのみ使用 ベアメタル/RTOS環境
cortex-m-rt Cortex-Mランタイムエントリとリンカスクリプト ARM Cortex-MシリーズMCU
PAC ペリフェラルアクセスクレート、レジスタを直接マッピング 低レイヤドライバ開発
HAL ハードウェア抽象レイヤ、高レベルAPI提供 アプリケーション層開発
critical-section クリティカルセクション抽象、クロスプラットフォーム安全 割り込み安全コード
defmt ゼロコストログフレームワーク 組み込みデバッグ
RTIC 割り込みベースのリアルタイム並行フレームワーク リアルタイムシステム
embassy 非同期組み込みエグゼキュータ 高並行ペリフェラル操作

5つの主要な課題

  1. 環境構築が複雑:Rust組み込みツールチェーンの設定は煩雑で、クロスコンパイル、デバッガ接続、書き込みツールチェーンが全て必要。初心者は環境設定の段階で諦めることが多い
  2. ベアメタル起動プロセスが不透明:MCUの電源オンからmain()関数実行までの起動プロセス(ベクタテーブル、スタック初期化、データセクションコピー)は多くの開発者にとってブラックボックス
  3. ペリフェラルドライバ開発のハードルが高い:レジスタ操作、タイミング制御、DMA設定などの低レイヤ操作には、ハードウェアマニュアルとRustのunsafeセマンティクスの両方の理解が必要
  4. 割り込み処理でバグが発生しやすい:割り込みとメインループのデータ競合、優先度設定、ネストされた割り込みなどが高頻度のバグ源
  5. RTOS統合のベストプラクティスが不足:FreeRTOS/RT-ThreadとRustの相互運用、メモリ割り当て戦略、タスク間通信に成熟したソリューションがない

ステップバイステップ:5つのコアパターン

パターン1:no_std環境構築

実行環境:Rust 1.85+ / cortex-m target / probe-rs 0.24+

まずno_stdプロジェクトを作成し、クロスコンパイルターゲットを設定します:

# Rust組み込みターゲットをインストール
rustup target add thumbv7em-none-eabihf

# no_stdプロジェクトを作成
cargo new --lib embedded-firmware
cd embedded-firmware
# Cargo.toml
[package]
name = "embedded-firmware"
version = "0.1.0"
edition = "2021"

[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
panic-halt = "1.0"
defmt = "0.3"
defmt-rtt = "0.4"

[profile.release]
opt-level = "s"
lto = true
codegen-units = 1

[[bin]]
name = "embedded-firmware"
path = "src/main.rs"
// src/main.rs
#![no_main]
#![no_std]

use cortex_m_rt::entry;
use panic_halt as _;
use defmt::info;

#[entry]
fn main() -> ! {
    info!("Hello from no_std Rust!");

    loop {
        cortex_m::asm::delay(8_000_000); // 約1秒遅延(8MHzクロック)
    }
}
# .cargo/config.toml
[build]
target = "thumbv7em-none-eabihf"

[target.thumbv7em-none-eabihf]
runner = "probe-rs run --chip STM32F411CEUx"

rustflags = [
    "-C", "link-arg=-Tlink.x",
    "-C", "link-arg=--nmagic",
    "-C", "link-arg=-Tdefmt.x",
]

パターン2:Cortex-Mベアメタル起動

起動プロセスを理解・カスタマイズし、ベクタテーブルとリンカスクリプトを習得:

// src/startup.rs
//! カスタムスタートアップコード - MCUの電源オンからmainまでの全過程を理解

use core::arch::global_asm;

// ベクタテーブル:MCU電源オン後最初に読み取られるデータ構造
// Flashの先頭アドレスに配置、初期スタックポインタと割り込みベクタを含む
global_asm!(
    ".section .vector_table, \"a\"",
    ".global _vector_table",
    "_vector_table:",
    "    .word _estack           // 初期スタックポインタ(RAM末尾を指す)",
    "    .word Reset             // Resetハンドラ",
    "    .word NMI               // NMIハンドラ",
    "    .word HardFault         // HardFaultハンドラ",
    "    .word 0                 // MemManage(Cortex-M7専用)",
    "    .word 0                 // BusFault",
    "    .word 0                 // UsageFault",
    "    .word 0                 // Reserved",
    "    .word 0                 // Reserved",
    "    .word 0                 // Reserved",
    "    .word 0                 // Reserved",
    "    .word SVCall            // SVC呼び出し",
    "    .word DebugMonitor      // Debug Monitor",
    "    .word 0                 // Reserved",
    "    .word PendSV            // PendSV(コンテキストスイッチ)",
    "    .word SysTick           // SysTickタイマ",
);

/// Resetハンドラ:MCU電源オンまたはリセット後に実行
/// 責務:.dataセクション初期化、.bssセクションゼロクリア、main呼び出し
#[no_mangle]
pub unsafe extern "C" fn Reset() -> ! {
    // 1. .dataセクション初期化:FlashからRAMに初期化データをコピー
    extern "C" {
        static mut _sdata: u32;
        static mut _edata: u32;
        static _sidata: u32;
    }

    let mut src = &_sidata as *const u32;
    let mut dst = &mut _sdata as *mut u32;
    while dst < &mut _edata as *mut u32 {
        dst.write_volatile(src.read_volatile());
        src = src.add(1);
        dst = dst.add(1);
    }

    // 2. .bssセクションをゼロクリア
    extern "C" {
        static mut _sbss: u32;
        static mut _ebss: u32;
    }

    let mut bss_dst = &mut _sbss as *mut u32;
    while bss_dst < &mut _ebss as *mut u32 {
        bss_dst.write_volatile(0);
        bss_dst = bss_dst.add(1);
    }

    // 3. Rust main関数を呼び出し
    extern "Rust" {
        fn main() -> !;
    }
    main()
}

/// HardFaultハンドラ - 組み込みで最も重要な例外処理
#[no_mangle]
pub unsafe extern "C" fn HardFault() -> ! {
    let scb = cortex_m::peripheral::SCB::ptr();
    let hfsr = (*scb).hfsr.read();
    defmt::error!("HardFault! HFSR = {:08x}", hfsr);

    let psp = cortex_m::register::psp::read();
    if psp != 0 {
        let stack_frame = psp as *const u32;
        defmt::error!("R0  = {:08x}", *stack_frame.offset(0));
        defmt::error!("R3  = {:08x}", *stack_frame.offset(3));
        defmt::error!("R12 = {:08x}", *stack_frame.offset(4));
        defmt::error!("LR  = {:08x}", *stack_frame.offset(5));
        defmt::error!("PC  = {:08x}", *stack_frame.offset(6));
    }

    loop {}
}

fn NMI() {}
fn SVCall() {}
fn DebugMonitor() {}
fn PendSV() {}
fn SysTick() {}

パターン3:ペリフェラルドライバ開発

PACとHALに基づくペリフェラルドライバ開発、GPIOとUARTを例に:

// src/drivers/led.rs
//! LEDドライバ - HALベースのGPIOラッパー

use embedded_hal::digital::OutputPin;
use cortex_m::delay::Delay;

/// LEDドライバ構造体
pub struct Led<PIN: OutputPin> {
    pin: PIN,
    active_low: bool,
}

impl<PIN: OutputPin> Led<PIN> {
    /// 新しいLEDインスタンスを作成
    /// - `pin`: GPIO出力ピン
    /// - `active_low`: trueはLowアクティブ(コモンアノードLED)
    pub fn new(pin: PIN, active_low: bool) -> Self {
        let mut led = Self { pin, active_low };
        led.off(); // 初期化時にLEDをオフ
        led
    }

    /// LEDをオン
    pub fn on(&mut self) {
        if self.active_low {
            self.pin.set_low().ok();
        } else {
            self.pin.set_high().ok();
        }
    }

    /// LEDをオフ
    pub fn off(&mut self) {
        if self.active_low {
            self.pin.set_high().ok();
        } else {
            self.pin.set_low().ok();
        }
    }

    /// LED呼吸効果(ソフトウェアPWM)
    pub fn breathe(&mut self, delay: &mut Delay, cycles: u32) {
        const STEPS: u32 = 100;
        for _ in 0..cycles {
            for i in 0..STEPS {
                self.on();
                delay.delay_us(i);
                self.off();
                delay.delay_us(STEPS - i);
            }
            for i in (0..STEPS).rev() {
                self.on();
                delay.delay_us(i);
                self.off();
                delay.delay_us(STEPS - i);
            }
        }
    }
}
// src/drivers/uart.rs
//! UARTドライバ - HALベースのシリアル通信ラッパー

use embedded_hal::serial::{Read, Write};
use nb::block;

/// UARTエラータイプ
#[derive(Debug, defmt::Format)]
pub enum UartError {
    Framing,
    Noise,
    Overrun,
    Parity,
    BufferFull,
}

/// リングバッファ - 割り込み受信用
pub struct RingBuffer<const N: usize> {
    buffer: [u8; N],
    head: usize,
    tail: usize,
    full: bool,
}

impl<const N: usize> RingBuffer<N> {
    pub const fn new() -> Self {
        Self {
            buffer: [0u8; N],
            head: 0,
            tail: 0,
            full: false,
        }
    }

    pub fn push(&mut self, byte: u8) -> bool {
        if self.full {
            return false;
        }
        self.buffer[self.head] = byte;
        self.head = (self.head + 1) % N;
        if self.head == self.tail {
            self.full = true;
        }
        true
    }

    pub fn pop(&mut self) -> Option<u8> {
        if self.is_empty() {
            return None;
        }
        let byte = self.buffer[self.tail];
        self.tail = (self.tail + 1) % N;
        self.full = false;
        Some(byte)
    }

    pub fn is_empty(&self) -> bool {
        self.head == self.tail && !self.full
    }

    pub fn len(&self) -> usize {
        if self.full {
            return N;
        }
        if self.head >= self.tail {
            self.head - self.tail
        } else {
            N - self.tail + self.head
        }
    }
}

/// UART通信インターフェース
pub struct UartDriver<SERIAL> {
    serial: SERIAL,
    rx_buffer: RingBuffer<256>,
}

impl<SERIAL, E> UartDriver<SERIAL>
where
    SERIAL: Read<u8, Error = E> + Write<u8, Error = E>,
    E: defmt::Format,
{
    pub fn new(serial: SERIAL) -> Self {
        Self {
            serial,
            rx_buffer: RingBuffer::new(),
        }
    }

    /// 非ブロッキング読み取り(割り込み受信データ)
    pub fn read_byte(&mut self) -> Option<u8> {
        self.rx_buffer.pop()
    }

    /// 割り込み内呼び出し:受信バイトをバッファに格納
    pub fn on_rx_interrupt(&mut self) {
        match self.serial.read() {
            Ok(byte) => {
                if !self.rx_buffer.push(byte) {
                    defmt::warn!("UART RX buffer overflow!");
                }
            }
            Err(nb::Error::WouldBlock) => {}
            Err(nb::Error::Other(_)) => {
                defmt::error!("UART read error");
            }
        }
    }

    /// ブロッキング送信バイト
    pub fn write_byte(&mut self, byte: u8) {
        block!(self.serial.write(byte)).ok();
    }

    /// ブロッキング送信文字列
    pub fn write_str(&mut self, s: &str) {
        for byte in s.bytes() {
            self.write_byte(byte);
        }
    }

    /// 基礎シリアルの所有権を解放
    pub fn free(self) -> SERIAL {
        self.serial
    }
}

パターン4:割り込み処理

安全な割り込み処理パターン、データ競合を回避:

// src/interrupts.rs
//! 安全な割り込み処理 - cortex_m::interruptとatomicを使用

use core::sync::atomic::{AtomicU32, AtomicBool, Ordering};
use cortex_m::interrupt::{self, Mutex};
use core::cell::RefCell;
use stm32f4xx_hal::pac::{self, interrupt};

/// 方式1:アトミック操作 - 最もシンプルな割り込み安全ソリューション
static Systick_COUNT: AtomicU32 = AtomicU32::new(0);
static BUTTON_PRESSED: AtomicBool = AtomicBool::new(false);

/// 方式2:Mutexで保護された共有リソース
type SharedSerial = Mutex<RefCell<Option<pac::USART1>>>;
static SHARED_SERIAL: SharedSerial = Mutex::new(RefCell::new(None));

/// 方式3:cortex_m::singleton! コンパイル時シングルトン保証
cortex_m::singleton!(
    static SHARED_BUFFER: [u8; 128] = [0; 128];
);

/// SysTick割り込み - システムティック
#[interrupt]
fn SysTick() {
    Systick_COUNT.fetch_add(1, Ordering::Relaxed);

    if Systick_COUNT.load(Ordering::Relaxed) % 1000 == 0 {
        defmt::trace!("1 second elapsed");
    }
}

/// 外部ボタン割り込み - GPIO割り込み
#[interrupt]
fn EXTI0() {
    unsafe {
        let exti = &*pac::EXTI::ptr();
        exti.pr.write(|w| w.pr0().set_bit());
    }

    BUTTON_PRESSED.store(true, Ordering::Release);
}

/// USART1割り込み - シリアル受信割り込み
#[interrupt]
fn USART1() {
    interrupt::free(|cs| {
        let mut serial = SHARED_SERIAL.borrow(cs).borrow_mut();
        if let Some(ref mut usart1) = serial.deref_mut() {
            if usart1.sr.read().rxne().bit_is_set() {
                let byte = usart1.dr.read().bits() as u8;
                defmt::info!("RX: {:02x}", byte);
            }
        }
    });
}

/// メインループで割り込みフラグをチェック
pub fn process_interrupt_flags() {
    if BUTTON_PRESSED.swap(false, Ordering::AcqRel) {
        defmt::info!("Button pressed!");
    }

    let tick = Systick_COUNT.load(Ordering::Acquire);
    if tick > 0 && tick % 5000 == 0 {
        defmt::info!("5 seconds elapsed, tick = {}", tick);
    }
}

use core::ops::DerefMut;

パターン5:RTOS統合

RustとFreeRTOS/embassyの統合ソリューション:

// src/rtos_embassy.rs
//! Embassy非同期エグゼキュータ - Rustネイティブの"RTOS"ソリューション

use embassy_executor::Spawner;
use embassy_stm32::{
    gpio::{Level, Output, Speed},
    usart::Uart,
    time::Hertz,
    Config,
};
use embassy_time::{Duration, Timer};

/// Embassy非同期タスク:LED点滅
#[embassy_executor::task]
async fn blink_led(mut led: Output<'static, embassy_stm32::gpio::PA5>) {
    let mut counter = 0u32;
    loop {
        led.set_high();
        Timer::after(Duration::from_millis(500)).await;
        led.set_low();
        Timer::after(Duration::from_millis(500)).await;
        counter += 1;
        defmt::info!("Blink #{}", counter);
    }
}

/// Embassy非同期タスク:シリアルエコー
#[embassy_executor::task]
async fn uart_echo(mut uart: Uart<'static, embassy_stm32::usart::USART2>) {
    use embassy_stm32::usart::Error;
    loop {
        let mut buf = [0u8; 1];
        match uart.read(&mut buf).await {
            Ok(_) => {
                let _ = uart.write(&buf).await;
            }
            Err(Error::Framing) => defmt::error!("UART framing error"),
            Err(_) => defmt::error!("UART error"),
        }
    }
}

/// Embassy非同期タスク:センサ読み取り
#[embassy_executor::task]
async fn sensor_reader() {
    let mut values = [0u16; 64];
    let mut idx = 0;
    loop {
        Timer::after(Duration::from_secs(1)).await;

        let value = read_adc_channel(0);
        values[idx % 64] = value;
        idx += 1;

        if idx % 10 == 0 {
            let avg: u16 = values[..10].iter().sum::<u16>() / 10;
            defmt::info!("Sensor avg: {}", avg);
        }
    }
}

fn read_adc_channel(_ch: u8) -> u16 {
    42
}

/// メインエントリ - Embassyランタイム
#[embassy_executor::main]
async fn main(spawner: Spawner) {
    let config = Config::default();
    let p = embassy_stm32::init(config);

    let led = Output::new(p.PA5, Level::Low, Speed::Low);

    let uart_config = embassy_stm32::usart::Config::default()
        .baudrate(Hertz(115200));
    let uart = Uart::new(
        p.USART2, p.PA3, p.PA2,
        uart_config,
    ).unwrap();

    spawner.spawn(blink_led(led)).ok();
    spawner.spawn(uart_echo(uart)).ok();
    spawner.spawn(sensor_reader()).ok();

    defmt::info!("Embassy RTOS started!");

    loop {
        Timer::after(Duration::from_secs(5)).await;
        defmt::info!("Main loop heartbeat");
    }
}
// src/rtos_freertos.rs
//! FreeRTOS + Rust統合 - freertos-rustクレートを使用

use freertos_rust::{CurrentTask, Delay, FreeRtosAllocator, Task, TaskPriority};

#[global_allocator]
static ALLOC: FreeRtosAllocator = FreeRtosAllocator;

fn led_task(_pv: *mut core::ffi::c_void) {
    loop {
        toggle_led();
        Delay::new(500);
    }
}

fn sensor_task(_pv: *mut core::ffi::c_void) {
    loop {
        let value = read_sensor();
        defmt::info!("Sensor: {}", value);
        Delay::new(1000);
    }
}

pub fn start_freertos_tasks() {
    Task::new()
        .name("LED")
        .stack_size(256)
        .priority(TaskPriority(2))
        .start(led_task)
        .expect("Failed to create LED task");

    Task::new()
        .name("Sensor")
        .stack_size(512)
        .priority(TaskPriority(1))
        .start(sensor_task)
        .expect("Failed to create Sensor task");

    freertos_rust::start_scheduler();
}

fn toggle_led() { /* ハードウェア固有 */ }
fn read_sensor() -> u16 { 42 }

よくある落とし穴

落とし穴1:#![no_std]の忘れによるリンクエラー

// ❌ 間違い:no_std宣言がない
use std::println; // 標準ライブラリ全体を取り込んでしまう!

// ✅ 正しい:クレートルートで宣言必須
#![no_std]
#![no_main]

原因:Rustはデフォルトで標準ライブラリをリンクしますが、組み込みターゲットにはOSサポートがありません。宣言を忘れると大量の未定義シンボルのリンクエラーが発生します。

落とし穴2:割り込み内でのブロッキング関数呼び出し

// ❌ 間違い:割り込み内で時間のかかる操作
#[interrupt]
fn USART1() {
    let data = blocking_read_sensor(); // 数msブロックする可能性!
    process_data(data);
}

// ✅ 正しい:割り込み内はフラグ設定のみ、メインループで処理
#[interrupt]
fn USART1() {
    DATA_READY.store(true, Ordering::Release);
}

loop {
    if DATA_READY.swap(false, Ordering::AcqRel) {
        let data = blocking_read_sensor();
        process_data(data);
    }
}

落とし穴3:共有可変状態の保護なし

// ❌ 間違い:生の共有可変静的変数
static mut BUFFER: [u8; 64] = [0; 64];

#[interrupt]
fn DMA1_STREAM0() {
    unsafe { BUFFER[0] = 42; } // データ競合!
}

// ✅ 正しい:MutexまたはAtomicを使用
use core::sync::atomic::AtomicU8;
static BUFFER_HEAD: AtomicU8 = AtomicU8::new(0);

落とし穴4:PACレジスタ操作でのunsafe忘れ

// ❌ 間違い:安全なAPIを使わずPACレジスタに直接アクセス
let gpioa = unsafe { &*pac::GPIOA::ptr() };
gpioa.bsrr.write(|w| w.bits(1)); // 型安全なAPIを使用していない

// ✅ 正しい:PACの型安全なAPIを使用
let gpioa = unsafe { &*pac::GPIOA::ptr() };
gpioa.bsrr.write(|w| w.bs5().set_bit()); // コンパイル時チェック

落とし穴5:スタックオーバーフローの未検出

// ❌ 間違い:大きな配列をスタックに直接配置
fn process() {
    let buffer = [0u8; 4096]; // STM32F103は20KBのRAMしかない!
}

// ✅ 正しい:静的バッファまたはヒープ割り当てを使用
static BUFFER: cortex_m::singleton!(Buffer = [0u8; 4096]) = [0; 4096];

use embedded_alloc::Heap;
#[global_allocator]
static HEAP: Heap = Heap::empty();

エラートラブルシューティング表

エラーメッセージ 原因 解決策
error: language item required, but not found: eh_personality no_stdランタイムサポート不足 main.rs#![no_main]panic-halt依存を追加
error: linking with cc failed リンカスクリプト不足 rustflagsに-C link-arg=-Tlink.xを追加
error[E0152]: duplicate lang item 複数クレートが同じlang itemを定義 stdno_stdのpanic handlerが同時に参照されていないか確認
region FLASH overflowed ファームウェアがFlash容量を超過 LTO有効化、codegen-unitsを1に減少、opt-level=s使用
region RAM overflowed RAM不足 スタック/ヒープサイズ削減、静的割り当てでヒープ割り当てを代替
HardFault at 0x08001234 ヌルポインタ参照またはスタックオーバーフロー defmtでフォールトレジスタを出力、スタックサイズ確認
panic at src/main.rs:42 実行時panic panic-haltの代わりにpanic-probeでコールスタック取得
error: cannot find -lprobe_rs probe-rs未インストール cargo install probe-rs-tools
OpenOCD connection failed デバッガ接続失敗 ST-Linkドライバ、USBケーブル、openocd.cfg設定を確認
error[E0277]: the trait bound is not satisfied HAL traitが実装されていない ターゲットMCUがHALクレートでサポートされているか確認

高度な最適化

1. defmtで従来のログを代替

// defmtはリリースモードでゼロオーバーヘッド
// コンパイル時フォーマット、ランタイムコストなし
defmt::info!("Sensor value: {}", value);
defmt::debug!("Buffer: {[u8; 4]}", &buf[..4]);
defmt::error!("HardFault HFSR={:08x}", hfsr);

2. cortex_m::singleton!で静的リソース管理

// コンパイル時シングルトン保証、ランタイムチェック不要
cortex_m::singleton!(
    static SHARED_STATE: SharedState = SharedState::new();
);

3. RTICでゼロコスト割り込み駆動アーキテクチャを実現

#[rtic::app(device = stm32f4xx_hal::pac)]
mod app {
    use stm32f4xx_hal::prelude::*;

    #[shared]
    struct Shared {
        sensor_value: u16,
    }

    #[local]
    struct Local {
        led: PA5<Output<PushPull>>,
    }

    #[init]
    fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
        let mut led = cx.device.GPIOA.split().pa5.into_push_pull_output();
        led.set_high();

        (
            Shared { sensor_value: 0 },
            Local { led },
            init::Monotonics(),
        )
    }

    #[task(binds = TIM2, shared = [sensor_value], local = [led])]
    fn timer_interrupt(cx: timer_interrupt::Context) {
        let sensor_value = cx.shared.sensor_value;
        let led = cx.local.led;

        *sensor_value.lock(|v| *v = read_adc());
        led.toggle();
    }
}

4. embassyで非同期並行を実現

#[embassy_executor::task]
async fn task_a() {
    loop {
        do_something().await;
        Timer::after(Duration::from_millis(100)).await;
    }
}

#[embassy_executor::task]
async fn task_b() {
    loop {
        another_thing().await;
        Timer::after(Duration::from_secs(1)).await;
    }
}
// 各タスクは数十バイトのスタックのみ必要(コルーチン状態機械)

5. Flash最適化戦略

[profile.release]
opt-level = "z"          # 速度よりサイズを最適化
lto = true               # リンク時最適化、未使用コードを削除
codegen-units = 1        # 単一コンパイルユニット、最適な最適化
strip = true             # デバッグシンボルを削除
panic = "abort"          # unwindの代わりにabort、サイズ削減

# 典型的な最適化結果:
# 最適化前:128KB
# 最適化後:32KB(75%削減)

比較分析

特徴 Embassy RTIC FreeRTOS+Rust ベアメタルポーリング
メモリオーバーヘッド 極低(コルーチン) 極低 高(タスクごとに独立スタック) 最低
学習曲線 中程度 中程度 高い 低い
並行モデル async/await 割り込み優先度 プリエンプティブマルチタスク なし
エコシステム成熟度 ★★★★ ★★★ ★★ ★★★★★
デバッグ利便性 ★★★★ ★★★★ ★★★ ★★★★★
リアルタイム性保証 ソフトリアルタイム ハードリアルタイム ハードリアルタイム 実装依存
Rustネイティブ ❌(C相互運用)
適用シーン IoT/センサ モータ制御 複雑マルチタスク 単純制御

まとめ

Rust組み込み開発は「実験段階」から「プロダクション対応」へ移行しています。2026年現在、no_stdエコシステムは十分に成熟しています:

  • 環境構築:probe-rsが書き込みとデバッグ体験を統一、cargo embedで一発完了
  • ベアメタル起動:cortex-m-rtが起動詳細をカプセル化、ただし低レイヤの原理理解はトラブルシューティングの鍵
  • ペリフェラルドライバ:PAC→HAL→アプリケーションの3層アーキテクチャで安全性と効率を両立
  • 割り込み処理:アトミック操作、Mutex、RTICの3方式で異なる複雑さの要件をカバー
  • RTOS統合:EmbassyがRustネイティブソリューションとして第一選択になりつつある、FreeRTOSは既存プロジェクトの移行に適する

選択の推奨:新規プロジェクトはEmbassyを優先検討、ハードリアルタイム性が求められるシーンはRTIC、既存のFreeRTOSコードベースはfreertos-rustで段階的移行。

オンラインツール推薦

  • /ja/json/format - JSONフォーマッター、シリアルプロトコルデータのデバッグに必須
  • /ja/dev/curl-to-code - HTTPリクエストからコード変換、IoTデバイスAPIデバッグに最適
  • /ja/encode/hash - ハッシュ計算ツール、ファームウェアチェックサム生成に
  • /ja/text/diff - テキスト差分ツール、レジスタ設定のバージョン間比較に

ブラウザローカルツールを無料で試す →

#Rust no-std#嵌入式开发#裸机固件#ARM Cortex#嵌入式Rust#2026#编程语言