Rust で Raspberry Pi Pico の組み込み開発

まえがき

Rust で組み込み開発(※趣味レベル)をやっていたこともあったのですが、内容をほとんど忘れたので覚書として残そうかなと思います。

開発の効率なども考えて、今回は SWD (Serial Wired Debug) の機能を使い、下記の要素を取り入れて開発環境を準備していきます。

  • ランナーとして probe-run を使ってファームウェアを書き込む
  • defmt でホスト上にログを出力する

なお、私の手元で確認したことを記載するつもりですが、お試しいただく時期や環境によって動作しないこともあるかと思いますので、そのときはイイ感じに読み替えたり試行錯誤してもらえればと思います。

用意するもの

名称備考
開発ホストM1 MacBook Pro 2020 / macOS 12.6 Monterey
Rust1.64.0
ブレッドボード適当なやつ x1
Raspberry Pi Pico開発ターゲット
Seeeduino XIAOデバッグアダプタ用に使用
LED何色でもいいです x1
抵抗330Ω x1
ジャンパワイヤー5 本ぐらい。

ちなみに、LED や抵抗に関しては、マイコンボード上の据え付けのものも利用できるので、準備がない方はそちらでもいいと思います。

環境情報

使用する Rust 言語のバージョンはこんな感じ。

$ rustup --version
rustup 1.24.3 (2021-05-31)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.64.0 (a55dd71d5 2022-09-19)`

$ rustc --version
rustc 1.64.0 (a55dd71d5 2022-09-19)

$ cargo --version
cargo 1.64.0 (387270bc7 2022-09-16)

次に使用するツールチェーンと各種ツール類をインストールします。

$ rustup target install thumbv6m-none-eabi
$ cargo install flip-link probe-run

今回は Raspberry Pi Pico を開発ターゲットにするので、使用されている RP2040 コア (Arm Cortex-M0+デュアルコア@133MHz) に合うように thumbv6m-none-eabi を選択しています。

追加でインストールしているツール類については、ざっくりと下記のとおりです。

  • knurling-rs/flip-link: スタックオーバーフローを防止するためにメモリ配置をいい感じにしてくれるやつ。
  • knurling-rs/probe-run: デバッグアダプタ経由でターゲットに書き込んだりホスト上で実行ログを表示させたりできるタスクランナー。

デバッグアダプタ

今回、ターゲットにするのが Raspberry Pi Pico の ARM プロセッサということなので、SWD (Serial Wired Debug) を活用してデバックをかんたんにしていきたいので、どのご家庭にもある Seeeduino XIAO に DAPLink ファームウェアを焼いて使っていきます。

詳細な手順は Seeed Studio XIAO SAMD21 を使用して DAPLink デバイスを構築する - Seeed Wiki にある通り、ファームウェアをダウンロード、XIAO をブートローダーモードに切り替え、uf2 ファイルをドラッグ&ドロップして終わりです。

結線の方法についてはこんな感じです。

XIAO 物理ピンPico 物理ピン
09 (D9)<->SWDIO
10 (D10)<->SWCLK
12 (GND)<->GND
13 (5v)<->1 (VSYS)

プロジェクトの作成

まずは、Cargo コマンドでプロジェクト用のディレクトリを作成して行きます。

$ cargo init ras-pico-tutorial
$ cd ras-pico-tutorial

今回は、Raspberry Pi Pico 用の BSP (Board Support Package) があるのでそれを使ってプログラミングしていこうかなと思います。

$ cargo add rp-pico cortex-m cortex-m-rt embedded-hal defmt defmt-rtt
  • rp-pico: Raspberry Pi Pico 用の BSP クレート。
  • cortex-m: Cortex-M の低レベルアクセスのクレート。
  • cortex-m-rt: Cortex-M のランタイムライブラリのクレート。
  • rust-embedded/embedded-hal: rust 用の Hardware Abstraction Layer (HAL) のクレート
  • defmt: 組み込み用の高効率なログフレームワークのクレート。
  • defmt-rtt: RTT(Real-Time Transfer) 上で defmt ログを送信するためのクレート。

次にビルド時のターゲットやランナーの指定を設定ファイルに書いておきます。 必須ではないのですがファイルを作っておくとコマンドが短くなって楽になります。

.cargo/config.toml

[build]
target = "thumbv6m-none-eabi"

[target.thumbv6m-none-eabi]
runner = "probe-run --chip RP2040"

rustflags = [
  "-C", "linker=flip-link",
  "-C", "link-arg=--nmagic",
  "-C", "link-arg=-Tlink.x",
  "-C", "link-arg=-Tdefmt.x",
  "-C", "inline-threshold=5",
  "-C", "no-vectorize-loops",
]

[env]
DEFMT_LOG = "debug"

続いて、VS Code の設定ファイルも作成しておくと、コードのフォーマッティングや文法警告などの機能が使えるので設定しておくといいと思います。 ちなみに、拡張機能としては rust-analyzer をインストールしてあります。

.vscode/settings.json

{
  "rust-analyzer.checkOnSave.allTargets": false,
  "rust-analyzer.checkOnSave.extraArgs": ["--target", "thumbv6m-none-eabi"],
  "editor.formatOnSave": true
}

つづいて、RP2040 用のメモリマップファイル?を用意します。

memory.x

MEMORY {
    BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
    FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
    RAM   : ORIGIN = 0x20000000, LENGTH = 256K
}

EXTERN(BOOT2_FIRMWARE)

SECTIONS {
    /* ### Boot loader */
    .boot2 ORIGIN(BOOT2) :
    {
        KEEP(*(.boot2));
    } > BOOT2
} INSERT BEFORE .text;

それではメインのソースコードです。LED は GPIO28 につなぐ想定です。(内蔵 LED 使う人は)

#![no_std]
#![no_main]

// エントリポイント用アトリビュート
use cortex_m_rt::entry;

// デバッグ出力用
use defmt::*;
use defmt_rtt as _;

// パニックハンドラ
use panic_probe as _;

// Board Support Package
use rp_pico as bsp;

use bsp::hal::{
    clocks::{init_clocks_and_plls, Clock},
    pac,
    sio::Sio,
    watchdog::Watchdog,
};

// GPIO 制御用
use embedded_hal::digital::v2::OutputPin;

// エントリポイント
#[entry]
fn main() -> ! {
    // スタート
    info!("Program start.");

    // シングルトンオブジェクトを取得
    let mut pac = pac::Peripherals::take().unwrap();
    let core = pac::CorePeripherals::take().unwrap();

    // Watchdog ドライバーの取得と、クロックの初期化
    let mut watchdog = Watchdog::new(pac.WATCHDOG);

    let clocks = init_clocks_and_plls(
        bsp::XOSC_CRYSTAL_FREQ,
        pac.XOSC,
        pac.CLOCKS,
        pac.PLL_SYS,
        pac.PLL_USB,
        &mut pac.RESETS,
        &mut watchdog,
    )
    .ok()
    .unwrap();

    // ディレイオブジェクトの生成
    let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());

    // GPIO ピンの初期化
    let sio = Sio::new(pac.SIO);

    let pins = bsp::Pins::new(
        pac.IO_BANK0,
        pac.PADS_BANK0,
        sio.gpio_bank0,
        &mut pac.RESETS,
    );

    let mut led_pin = pins.gpio28.into_push_pull_output();

    // メインループ
    loop {
        // 出力ピンをハイに
        info!("high");
        led_pin.set_high().unwrap();
        delay.delay_ms(500);

        // 出力ピンをローに
        info!("low");
        led_pin.set_low().unwrap();
        delay.delay_ms(500);
    }
}

実行

$ cargo run

こんな感じで L チカ ができました!

参考文献

© 2021 czu.jp