📡
Rust + Raspberry Pi Pico :“cargo run”一発でLチカプログラムを書き込むまで
目標
- 開発環境を整えること
- なるべく楽に開発を進められるように
- 将来性を加味してEmbassyの活用を行う
- Lチカまで行う
TL;DR
MacにRustクロスコンパイル環境を入れ、cargo generate
でEmbassyテンプレートを作成。
.cargo/config.toml
のrunnerをelf2uf2-rs -d
に設定。
cargo run --release
ラズパイのボリュームにドラッグ&ドロップなどが必要なくなって、プロジェクトの作成から、ラズパイの書き込みまで、快適に開発が続けられます。
環境構築
構成
- Mac OS 15.5
- Homebrew
- Rust
- Rustup
- cargo-generate
- elf2uf2-rs
- Rustターゲット
- thumbv6m-none-eabi (RP2040 用)
- Raspberry Pi Pico H
ツールチェーンの導入
ツールチェーンとは、Rustでいうビルドやコンパイルなどに必要な諸々すべてを指すらしい。
# Rust + cross コマンド
brew install rustup # 既に入っていればスキップ
rustup default stable
rustup target add thumbv6m-none-eabi # Cortex-M0+ 用 :contentReference[oaicite:1]{index=1}
# プロジェクト生成と転送ツール
cargo install cargo-generate
cargo install elf2uf2-rs # UF2 自動転送
諸々ちゃんと入っているかの確認。私の環境では下記の様な感じになります。
# Rustのコンパイラ
% rustc --version
rustc 1.87.0 (17067e9ac 2025-05-09)
# ビルドターゲット Picoの開発は`thumbv6m-none-eabi`があればOK
% rustup target list --installed
aarch64-apple-darwin
thumbv6m-none-eabi
% cargo install --list
cargo-generate v0.23.3:
cargo-generate
elf2uf2-rs v2.1.1:
elf2uf2-rs
プロジェクトの作成からLチカ
プロジェクト作成
lulfというembassy関連のテンプレートを頻繁に更新している方のリポジトリからクローンします。不プロジェクト名は自由に変えてください。
cargo generate --git https://github.com/lulf/embassy-template.git --name pico_blink
そうすると下記のようなフォルダ構成になります。
pico_blink/
├─ .cargo/
│ └─ config.toml ← ★runner を差し替える
├─ src/
│ └─ main.rs ← ★点滅サンプルが入っている
├─ Cargo.toml
├─ memory.x (2 MB Flash / 264 kB RAM の Pico 標準)
├─ rust-toolchain.toml (nightly-embedded 最新が pin 済み)
├─ build.rs
└─ README.md
今回はcargo run --release
コマンド一発で書き込みまで進めてもらうことが目的なので、.cargo/config.toml
を修正します。
# ==== Runner ====
[target.thumbv6m-none-eabi]
runner = "elf2uf2-rs -d" # ← probe-rs から置き換え
# ==== Build target ====
[build]
target = "thumbv6m-none-eabi"
# ==== defmt ログ ====
[env]
DEFMT_LOG = "info"
テンプレではrunner = "probe-rs run --chip {{ probe_chip }}"
になっていますが、
いまのところデバッグプローブ無しで十分なので、UF2直転送に。詳細はまたどこかで。
Lチカするプログラム
src/main.rs
を各。プログラムの本体はこんな感じ。
src/main.rs
#![no_std] // 標準ライブラリ(std)をリンクしない ── 組み込みではメモリ節約の定番
#![no_main] // 通常の main() を使わず、独自のエントリポイントを用意
// --- 依存クレートの導入(use)-----------------------------------------------
use {
embassy_executor::Spawner, // 非同期タスクを起動するためのハンドラ
embassy_rp::gpio::{Level, Output}, // RP2040 の GPIO 周り(LED 制御)
embassy_time::{Duration, Timer}, // 非同期タイマー
defmt_rtt as _, // ログ用(RTT経由)※ 下線は「未使用警告を抑止」
panic_probe as _, // パニック時に最小限の情報を出力
};
// --- エントリポイント ---------------------------------------------------------
#[embassy_executor::main] // 「async main」を生成し、RTOS 風にスケジューリングするマクロ
async fn main(_spawner: Spawner) {
// ① RP2040 ペリフェラル初期化(クロック / GPIO など)
let p = embassy_rp::init(Default::default());
// ② ボード上のユーザー LED(GP25)を出力 Low で初期化
let mut led = Output::new(p.PIN_25, Level::Low);
// ③ メインループ(async/await でノンブロッキング)
loop {
led.set_high(); // 点灯
Timer::after(Duration::from_millis(500)).await; // 0.5 秒スリープ
led.set_low(); // 消灯
Timer::after(Duration::from_millis(500)).await; // 0.5 秒スリープ
}
}
Rust初心者向けポイント解説
個人的にわからなかったことをメモ。
- #![no_std] / #![no_main] – 裸メタル環境の定番。OS もヒープも使わない極小バイナリを生成します。
- embassy_executor::main – async fn main をタスク化し、簡易 RTOS のようにスケジューリングしてくれるマクロ。
- embassy_rp::init() – ボード固有のクロック・GPIO 初期化を一括で実行。
- Output::new() – GPIO を出力モードへ。第 2 引数は起動時レベル(High/Low)。
- Timer::after(...).await – 非同期で遅延。await 中は CPU を他タスクに譲るためブロッキングしません。
- defmt_rtt & panic_probe – println! の代わりに RTT 経由でログを吐き、パニック時も極小サイズで情報を取得可能。
ラズパイに書き込む
ここまで来ると、シンプルにコマンド一発で書き込める。
- BOOTSEL ボタンを押しながら Pico H を接続(初回のみ)
- ここからはコマンド一発
cargo run --release
#elf2uf2-rs が pico_blink.uf2 を生成
#/Volumes/RPI-RP2 または /Volumes/NO NAME を探して自動コピー
#コピー完了後 Pico が再起動 → LED 点滅 → 成功
これでRaspberry Pi Picoに内蔵されているLED(25Pin)が点滅する。
おまけ
今回は後段で楽をしたいので、コマンド一発で書き込めるようにしました。一方で下記が一般的な書き込みのプロセスらしい。
手順 | コマンド/操作 |
---|---|
① |
cargo build --release で UF2 を生成 |
② |
target/thumbv6m-none-eabi/release/<APP>.uf2 を確認 |
③ | Pico を BOOTSEL 接続 → RPI-RP2 ドライブ出現 |
④ | UF2 ファイルを ドラッグ&ドロップ |
⑤ | 自動でドライブが Eject され、Pico がリセットしてプログラムが動作 |
次回は「音を出そう①:PWMで音を出す + デバッグプローブを使ってみる」を学びながら書いていきます。
Discussion