この記事では
「Raspberry Pi上で動かすためのプログラムをRustで書いてmacOS (Apple Silicon) 上でビルドする方法」
を解説する。最後におまけとしてLチカもやるよ🦀
RPiに限らず、「Rustのクロスコンパイルをやりたい」あるいは「crossの導入でちょっとハマった」という方の役にも立つかもしれない。
crossとは何か
今回お世話になるのが、Rustのクロスコンパイルを簡単にするcrossというプロジェクトだ。色々なターゲット用にDockerのコンテナとしてビルド環境一式を用意し、お手軽にクロスコンパイルができるのだ。
crossを使わずに cargo build でクロスコンパイルすることも可能なのだが、その場合、何らかの方法でローカルにツールチェーンを用意する必要がある。そのツールチェーンの準備方法がターゲットごとにまちまちであったり、自分の環境に対応していなかったり、情報がなかなか見つからなかったりして、高確率でハマる。心が折れる。なるべくcrossで楽をしよう。
ターゲットはどれを選ぶ?
とりあえず、Raspberry Pi 3以降はarmv7-unknown-linux-gnueabihfを選んでおけば動くかな? もっとCPUの性能を引き出せるものもあるかもしれない。詳しくはRPiのプロセッサ一覧を見て、合ってそうなものを選んでほしい。
準備する
事前に用意するものは
- Rust環境一式
- Docker Desktop
の2つ。Dockerは、crossを動かすために必要となる。
RPiデバイス上で実際に動かすところまでやりたい場合は、sshで入れるようにしておく。
Rustの開発環境とDockerが用意できたら、まず、crossをインストールする。コマンドラインから
cargo install cross
で入れられる。どこでやってもいい。
次に、rustupでツールチェーンを入れる。これはターゲットを追加するたびにやること。これも、どこでやってもいい。
rustup target add armv7-unknown-linux-gnueabihf
プロジェクトを用意する
Rustのプロジェクトを用意してみよう。
cargo init hello_cross
という具合に、好きな名前でプロジェクトを作し、プロジェクトのルートディレクトリに移動しておく。もしくは、既存プロジェクトを別ターゲット用にビルドするのでもいい。
ビルドする
crossは、基本的に「cargoのかわりにcrossと打てば、cargoコマンドでやる色々なことをcrossを使ってできるよ」という風に作られている。ビルドするなら、プロジェクトルートでこれを実行すればいい。
cross build --target armv7-unknown-linux-gnueabihf
問題がなければ、これでうまくいく。
指定したターゲットに対応したイメージを自動で docker pull してくるので、初回はかなり時間がかかる。コーヒーを飲みながら待とう。
もしも面倒なことになった場合は --verbose オプションを追加して実行すると詳しいメッセージが出る。
私はここでけっこうハマったので、次のセクションでいくつかの解決方法を書くので、うまくいかなかった人は参考にしてほしい。
うまくいかなかった場合、こうするといいかも?
ターゲットを指定してDocker Desktopも起動しているのにcrossがその通りに動いてくれないことがある。
- 指定したターゲットが無視されてしまう
- Dockerコンテナの導入・起動に失敗する
というパターンがある。
イメージが存在しているかどうかをチェックするには、Docker Desktopの “Images” のリストをチェックしてみる。ghcr.io/cross-rs/{{TARGET}}:{{VERSION}} が存在すれば、イメージの取得は成功している。

Dockerイメージを手動で入れてみる
crossが何らかの原因により自動でイメージを取得できなかった場合は、自分でdocker pullしてみると、以降、crossがそのイメージを適切に起動してビルドに成功するようになったりする。
$ docker pull ghcr.io/cross-rs/armv7-unknown-linux-gnueabihf:0.2.5
このあと普通にcrossコマンドを打つとうまくいったりする。
イメージの取得元は
ghcr.io/cross-rs/{{TARGET}}:{{VERSION}}
だ。{{VERSION}}のところはcrossのバージョンを入れる。バージョンのところは latest と書けばいいらしいが、うまくいかない場合は 0.2.5 のようにバージョンを明記する。crossのバージョンは cross version で調べられる。
Cross.tomlでDockerイメージを指定
プロジェクトルートにCross.tomlというファイルを用意する。crossの設定ファイルだ。このファイルで、使うべきイメージを明示する。たとえばこんな感じ。
[target.armv7-unknown-linux-gnueabihf]
image = "ghcr.io/cross-rs/armv7-unknown-linux-gnueabihf:0.2.5"
詳しくは、crossの設定ファイルについての公式ドキュメントを読むといい。
ちょっと古いバージョンのイメージを入れる
crossのバージョンと完全に一致しているイメージを入れなくても、ちょっと前のバージョンのイメージが動いたりする。最新版がうまくいかなかったら、少し古いものを見てみよう。ghcr.io/cross-rs/armv7-unknown-linux-gnueabihf:0.2.5 がダメだったら ghcr.io/cross-rs/armv7-unknown-linux-gnueabihf:0.2.4 という具合に。
実行
でき上がったバイナリは target/armv7-unknown-linux-gnueabihf/debug/<project_name> にあるので、これを何らかの手段でRPiデバイスに送り込もう。外付けのUSBメモリなどでもいいし、scpなら
scp ./target/armv7-unknown-linux-gnueabihf/release/hello_cross pi@<rpi_host_name>.local:~/
だ。バイナリを置いたら
./hello_cross
のようにコマンドラインから実行。
Hello, world!
と表示されるかな?
おまけ : RPiでRustでLチカ
ハードウェア周りをいじりたいと思ったら rppal というcrateを使うといい。
GPIO・I2C・PWM (sysfs経由)・SPI・UARTに対応している。
まずは、cargo.tomlの依存関係にrppalを追加
[dependencies]
rppal = "0.17"
main.rs のプログラム本体はこんな感じで。ピンは、物理的なピン番号ではなくGPIOの番号で指定する。たとえばGPIO12なら12だ。
use rppal::gpio::Gpio;
use std::{error::Error, thread, time::Duration};
const GPIO_LED: u8 = 12;
const INTERVAL: Duration = Duration::from_millis(500);
fn main() -> Result<(), Box<dyn Error>> {
let mut led = Gpio::new()?.get(GPIO_LED)?.into_output();
loop {
led.set_high();
thread::sleep(INTERVAL);
led.set_low();
thread::sleep(INTERVAL);
}
}
うまくいくと、指定したピンに接続したLEDが点滅する。