RustでRaspberry Pi Picoのベアメタルプログラミング
これはRust Advent Calendar 2023の10日目の記事です。
Raspberry Pi Picoとは
Raspberry Pi PicoはRaspberry Pi財団が開発しているマイコンボードで、よく知られているRaspberry Piとは異なり、Cortex-M0+という非常に小さいアーキテクチャのCPUを搭載しています。
具体的には通常のCortex-AシリーズにあったようなCPUモードがなく、MMUのようなページング機構もないため、Raspberry Piとは異なり、Linuxを動かすことはできません。
しかしながら、その分非常に省電力で動作し価格も安価です(秋月電子通商でこのブログ執筆時点で770円、無線機能がついたPico Wで1200円)。
ちょっとしたガジェットや自作キーボードのコントローラーに使う例などを見かけます。
RustでPicoを動かす
最もお手軽にPicoを動かしたいなら公式の出しているC/C++のSDKを使うのが楽です
Rustでもお手軽に動かすライブラリとして、rp-picoというものがあります。
これはRust Embeddedチームが開発したcortex-m-rtをベースにして、ランタイムの提供と各種ペリフェラルのラッパーを提供しています。
しかし今回はランタイムを自作したいと思ったので、これらの既存のライブラリをなるべく使わずにPicoを動かしてみました。
ブートローダー
Picoのブートプロセスはやや複雑で、Second Stage bootloaderというものを用いて自分のプログラムを起動する必要があります。
Second stage bootloaderは公式のSDKでも提供されていて、さらにこのブートローダーをRust向けにラップしたrp2040_boot2というものもあります。
今回作りたいのはランタイムであって、ブートプロセスについてはこちらのブートローダーを使うことにします。
ブートローダーまで完璧に自作したい方はRP2040のデータシートの2.8章を参照すると起動シーケンスについての解説があるのでそちらを参照してください。
ランタイム
ブートさえできれば、ランタイムの自作はThe embedonomiconや拙著のRustで始める自作組込みOS入門どおりにやればできます。
ただし、今回はPicoのブートローダーを組み込む必要があるのでまずリンカスクリプトにブートローダー用の領域を定義する必要があります。
MEMORY
{
/* ブートローダー用の領域 */
BOOT_LOADER : ORIGIN = 0x10000000, LENGTH = 0x100
FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
RAM : ORIGIN = 0x20000000, LENGTH = 256K
}
EXTERN(Reset);
ENTRY(Reset);
SECTIONS {
/* ### Boot loader */
.boot_loader ORIGIN(BOOT_LOADER) :
{
KEEP(*(.boot_loader*));
} > BOOT_LOADER
.vector_table ORIGIN(FLASH):
{
/* 以下省略 */
また、ライブラリからブートローダーをリンクさせるために以下をmain.rsに追加します。
#[link_section = ".boot_loader"]
#[used]
pub static BOOT_LOADER: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;
GPIO
プログラムを書き込んで動作確認するためにLEDを点滅させるプログラムを書きます。
LEDはGPIOを操作することで制御できます。25番ピンがボード内臓のLEDに接続されていますが、Pico Wの場合はこのLEDが無線モジュールに接続されているため制御がやや難しいです。
今回自分はPico Wを使うため、別の外付けのGPIOピンにLEDを接続して動作確認しました。
GPIOを使うには以下の操作が必要です
- Subsystem ResetによりIO_BANK0とPADS_BANK0をリセットしてリセットが完了するまで待つ
- IO_BANK0の対応するピンのCTRLレジスタでfunction selectを設定する
- PADS_BANK0の対応するピンのODビッド(output disable)をクリアする
- SIOのGPIO_OEレジスタで対応するピンを出力を有効にする
PADS_BANK0のODビットはリセット時にクリアはされているはずなのですが、念の為におこなっています
この設定が終わったあとはSIOのGPIO_OUTレジスタを操作することで出力を制御できます。
書き込み
elf2uf2-rs
というライブラリを使うと便利です。uf2というフォーマットに変換してRaspberry Pi Picoで実行できます。
.cargo/config
にrunnerを設定しておくとcargo run
でuf2ファイルを書き込むことができます。
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "elf2uf2-rs -d"
rustflags = [
"-C", "link-arg=-Tlink.ld",
]
[build]
target = "thumbv6m-none-eabi"
また、openocd
経由で書き込むことも出来ます。公式のフォークしたopenocdにはPico用の設定ファイルが含まれています。
こちらを以下のようにビルドします
./bootstrap
./configure --enable-ftdi --enable-sysfsgpio --enable-bcm2835gpio --enable-picoprobe
make
sudo make install
こうすると/usr/local/bin/openocd
以下にインストールされるので以下のコマンドで接続できます。
/usr/local/bin/openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -s tcl
Picoをデバッガにつなぐ方法はもう一台のPicoをデバッガとして使う方法が有名でよく使われているようです。
コード
以下が今回作成したコードです。Systickを使って1秒ごとにLEDを点滅させています。
2023-12-13 追記: 初期化コードが間違っていたので修正しました
おわりに
本当はprobe-rsとかも使う方法を模索したかったんだけどよくわからなかったにょ
参考
Discussion