もう 12/27 なんですが モダン言語によるベアメタル組込み開発 Advent Calendar 2022 が空いていたので差し込んでみます。
とりあえず 1 歩目を踏み出すために、サンプルを Rust でちょっと改変してみました。
セットアップ
# thumbv7em-none-eabihf をターゲットにできるように追加
rustup target add thumbv7em-none-eabihf
# SDK を展開したディレクトリに移動
cd ~/nRF5_SDK_17.1.0_ddde560
# 作業用ディレクトリを作って移動
mkdir projects/app
cd !:1
# blinky のサンプルをコピー
cp -r ../../examples/peripheral/blinky .
# VSCode で開く
code blinkey
とりあえずの動作確認
まずはサンプルそのままで動くことを確認します。 nRF52-DK を USB でつないだら pca10040/blank/armgcc
、 nRF52840-DK なら pca10056/blank/armgcc
で試しにビルドして書き込んでみます。
cd pca10040/blank/armgcc
# 手元で試す程度なので Homebrew や apt/dnf とかで入れた
# PATH の arm-none-eabi-gcc を使ってビルド
make GNU_INSTALL_ROOT=""
# 一旦 DK にかかれているものを消す
make GNU_INSTALL_ROOT="" erase
# ビルドしたバイナリを書き込む
make GNU_INSTALL_ROOT="" flash
開発ボード上の 4 つの LED が順番に点灯・消灯を繰り返します。今回はここに Rust を差し込んでみたいと思います。
Rust のライブラリを作成
Rust で静的リンクライブラリ(.a
ファイル)を作成してくっつけてみます。
lib/rcode/Cargo.toml
に crate-type = ["staticlib"]
を指定します。
[package]
name = "rcode"
version = "0.1.0"
authors = ["Your Name <address@example.com>"]
edition = "2021"
[lib]
name = "rcode"
crate-type = ["staticlib"]
[dependencies]
lib/rcode/src/lib.rs
には no_std
と no_mangle
を付けます。 panic_handler
は一旦無限ループにしておきます。
#![no_std]
mod rcode {
#[no_mangle]
pub extern fn rust_interval() -> u32 {
1000
}
#[panic_handler]
fn on_panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
}
cargo build --target thumbv7em-none-eabihf
でビルドします。
ビルドが成功すると、 target/thumbv7em-none-eabihf/debug/librcode.a
にアーカイブが作成されます。
C から Rust を呼び出す
C から呼び出す用のヘッダはこんなかんじで lib/rcode/include/rcode.h
に置いておきます。
#pragma once
#include <stdint.h>
uint32_t rust_interval();
Makefile にヘッダファイルのディレクトリとアーカイブのファイルを追加します。
INC_FOLDERS += \
(略)
$(PROJ_DIR)/lib/rcode/include \
LIB_FILES += \
$(PROJ_DIR)/lib/rcode/target/thumbv7em-none-eabihf/debug/librcode.a
main.c
で rcode.h
を include
して rust_interval
を呼び出してみましょう。
#include "boards.h"
+#include "rcode.h"
- nrf_delay_ms(500);
+ nrf_delay_ms(rust_interval());
もう一度ビルドして書き込んでみると、 LED の変化の間隔が変化します。 Rust で描いた関数の戻り値がインターバルに利用されるようになりました。
cd pca10040/blank/armgcc
make clean
make GNU_INSTALL_ROOT=""
make GNU_INSTALL_ROOT="" flash
Rust から C を呼び出す
逆に Rust のコードから C の関数を呼び出してみましょう。 extern "C"
で C の関数を宣言して、 unsafe
で囲んで呼び出します。
extern "C" {
fn delay_ms_for_rust(x: u32);
}
#[no_mangle]
pub extern fn rust_interval() {
unsafe {
delay_ms_for_rust(100)
}
}
呼び出される C の関数はこんな感じです。 nrf_delay_ms
は static inline
になっているため、別のコンパイル単位からは直接呼び出せません。
void delay_ms_for_rust(uint32_t duration_ms)
{
nrf_delay_ms(duration_ms);
}
もう一度ビルドして書き込むとまた点灯のタイミングが変わりました。
cargo build --target thumbv7em-none-eabihf
cd pca10040/blank/armgcc
make clean
make GNU_INSTALL_ROOT=""
make GNU_INSTALL_ROOT="" flash
まとめ
-
--target thumbv7em-none-eabihf
で静的リンクライブラリを作成することで、 nRF52 上でも Rust の関数を C から呼び出すことができます。 -
extern "C"
とunsafe
を利用することで、 C の関数を Rust から呼び出すことができます。