Rust を nRF52 で動かす

もう 12/27 なんですが [モダン言語によるベアメタル組込み開発 Advent Calendar 2022](https://qiita.com/advent-calendar/2022/baremetal_embedded_modern_lang) が空いていたので差し込んでみます。

とりあえず 1 歩目を踏み出すために、サンプルを Rust でちょっと改変してみました。

## セットアップ

```sh
# 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` で試しにビルドして書き込んでみます。

```sh
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"]` を指定します。

```toml
[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` は一旦無限ループにしておきます。

```rust
#![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` に置いておきます。

```c
#pragma once

#include <stdint.h>

uint32_t rust_interval();
```

Makefile にヘッダファイルのディレクトリとアーカイブのファイルを追加します。

```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` を呼び出してみましょう。

```diff
 #include "boards.h"
+#include "rcode.h"
```

```diff
-            nrf_delay_ms(500);
+            nrf_delay_ms(rust_interval());
```

もう一度ビルドして書き込んでみると、 LED の変化の間隔が変化します。 Rust で描いた関数の戻り値がインターバルに利用されるようになりました。

```sh
cd pca10040/blank/armgcc
make clean
make GNU_INSTALL_ROOT=""
make GNU_INSTALL_ROOT="" flash
```

## Rust から C を呼び出す

逆に　Rust のコードから C の関数を呼び出してみましょう。 `extern "C"` で C の関数を宣言して、 `unsafe`　で囲んで呼び出します。

```rust
    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` になっているため、別のコンパイル単位からは直接呼び出せません。

```c
void delay_ms_for_rust(uint32_t duration_ms)
{
    nrf_delay_ms(duration_ms);
}
```

もう一度ビルドして書き込むとまた点灯のタイミングが変わりました。

```sh
cargo build --target thumbv7em-none-eabihf
```

```sh
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 から呼び出すことができます。