🦞

Arduino UnoをRustで動かして9軸加速度センサを扱う。

2022/12/14に公開

これはモダン言語によるベアメタル組込み開発 Advent Calendar 2022 15日目の記事です。


https://qiita.com/advent-calendar/2022/baremetal_embedded_modern_lang


今回の実装はこのリポジトリにあります。

https://github.com/kaaatsu32329/accelduino-rs


1. Topic

  • Rust
  • Arduino
  • Robotics

2. はじめに

2-1. 序文

もともとはマシン含むハードウェアを実装して、組込みRustでファームウェアを書き、制御や操作の部分まで全部Rustにした上で、Full Rust Roboticsと称した何かをしたかったのですが、計画性の無さから時間的に断念しました。なのでその要素であるセンサー周りの扱いについて記していこうと思います。いつか絶対に書く部分がすべてRustなロボットをやります。

2-2. やること

Rustを用いてArduinoのファームウェアを書き、9軸加速度センサの読み取りを行います。

9軸センサは家にあったBMX055を使います。

2-3. 雑記

1年前の2021年に読んだ組込みRustがきっかけでRustをやるようになったので、Wio-Terminalを使っても良かったですし、Raspberry Pi PicoSTM32も家に落ちているのでこれらを使う選択肢もありましたが、誰でも簡単に再現できたほうがいいかなということで、一番とっつきやすいイメージのあるArduino Unoを用いることにしました。

3. 実装

Arduino Unoに使われているはAVRマイコンであるので、avr-halというクレートを使います。このクレートの作者のRahix氏がGitHubに公開しているテンプレートを活用します。

cargo generate --git https://github.com/Rahix/avr-hal-template.git

を実行して作成します。

3-1. 依存関係と意図

こちらはCargo.tomlの中身を抜粋したものです。

Cargo.toml
...
[dependencies]
...
serde = { version = "1.0", default-features = false, features = ["derive"] }
serde-json-core = { version = "0.5", default-features = false, features = ["heapless"]}
quaternion-core = { version = "0.3", default-features = false, features = ["libm"]}
ufmt_float = { git = "https://github.com/tl8roy/ufmt_float" }

[dependencies.num-traits]
version = "0.2.15"
default-features = false
features = ["libm"]
...

依存関係にあるクレートについて触れます。panic-haltufmtnbembedded-halはデフォルトで入っているものなので割愛します。

  • serdeserde-json-core
    • シリアライズ/デシリアライズをするためのクレートです。今回は無くてもいいのですが、拡張していくときに利用する予定なので今回から導入します。と言ってもアトリビュートを追加しただけです。serde_json_coreの方はまだ使ってはいないですが一応書いてあります。
  • quaternion-core
    • 依存先を減らすためにも自分でQuaternionやVector3を実装しても良かったのですが、ちょっと面倒だったのと、このクレートはno_stdにも対応していたので採用しました。追々Madgwick Filterなどを実装したいので含めています。
  • num-traits
    • 数値型を扱うトレイトなどで、例えば浮動小数点型であればnum_traits::float::Floatをトレイト境界として記述します。するとマイコンによって数値型のビット数を変更する場合でも書き換えることなく移植、実装できます。便利なことにno_std環境であっても使えるので利用しています。
  • ufmt_float
    • デフォルトの依存先であるufmtのシリアル出力は数値型では整数のみなので、浮動小数点型についても扱えるように拡張したクレートです。処理した後の加速度センサの値を出力したいので用いています。小規模なクレートなので、これを用いずに自分で浮動小数点型にufmtuDisplayトレイトを継承させれば良いのですが、使いました。バグを見つけてpull requestを送りマージされたのでご愛敬ということで。

3-2. avr-halを用いたファームウェア実装

avr-hal(arduino-hal)はembedded-halをもとに実装されたAVRマイコン向けのクレートです。合わせてravedudeというavrdudeのRust版みたいなものもあります。

具体的にはGitHubに公開されているので、そちらを読んでいただければと思います。

ファームウェアのセットアップの部分だけ載せておきます。今回使うセンサはI2C通信を行うので、I2cについて初期化します。

main.rs
...
#[hal::entry]
fn main() -> ! {
    let dp = hal::Peripherals::take().unwrap();
    let pins = hal::pins!(dp);
    let mut serial = hal::default_serial!(dp, pins, 57600);

    let i2c = hal::I2c::new(
        dp.TWI,
        pins.a4.into_pull_up_input(),
        pins.a5.into_pull_up_input(),
        50000,
    );
    ...
}
...

加速度センサについては、具体的にはデータシート秋月のBMX055のページ、Arduino IDEのサンプルなどを参考にしました。

bmx055に関するデータなどを保持する構造体を定義します。具体的にI2cに関するものと各センサのデータです。

bmx055.rs
#[derive(Deserialize, Serialize)]
pub struct Bmx055 {
    #[serde(skip)]
    i2c: Option<I2c>,
    pub accl: Vector3<f32>,
    pub raw_accl: Vector3<i16>,
    pub gyro: Vector3<f32>,
    pub raw_gyro: Vector3<i16>,
    pub mag: Vector3<f32>,
    pub raw_mag: Vector3<i16>,
}

Arduinoは確か倍精度の浮動小数点型が扱えなかった気がしたので、小数点型に関する実装はすべてf32を用いています。

3-3. Rustの特徴を用いる

申し訳程度に移植性や拡張性を少し意識して、Traitを用いた実装をしようと思います。

具体的にセンサー共通の振る舞いがなんだか思いつかなかったので、とりえあず初期化関数だけ持たせておきます。

Sensor trait
pub trait Sensor {
    fn init(&mut self);
}

加速度、角速度、磁気について似たようにしていますが、センサからデータを読み込むread_fooと得たデータを取得するget_fooを実装します。

Accl trait
use num_traits::Num;
use quaternion_core::Vector3;

pub trait Accl<N>
where
    N: Num,
{
    fn read_accl(&mut self);
    fn get_accl(&self) -> Vector3<N>;
}

これらを先にBmx055構造体に実装します。

impl Accl
impl Accl<f32> for Bmx055 {
    fn read_accl(&mut self) {
        let mut data = [0u8; 6];
        for i in DATA {
            let buf = [2 + i as u8; 1];
            self.i2c.as_mut().unwrap().write(ADDR_ACCL, &buf).unwrap();

            self.i2c
                .as_mut()
                .unwrap()
                .read(ADDR_ACCL, &mut data[i..i + 1])
                .unwrap();
        }

        for i in DOF {
            self.raw_accl[i] = ((data[2 * i + 1] as i16) * 256 + data[2 * i] as i16) / 16;
            self.accl[i] = (self.raw_accl[i] as f32) * ACCL_CORRECTION;
        }
    }

    fn get_accl(&self) -> Vector3<f32> {
        self.accl
    }
}

のようにしました。

3-4. 実行

cargo run --release

以下のように出力されれば成功です。加速度の単位は、[\rm{m} / \rm{s}^{2}]で、角速度は[\rm{rad}/\rm{s}]です。

Output
    Finished release [optimized + debuginfo] target(s) in 14.09s
     Running `ravedude uno -cb 57600 target\avr-atmega328p\release\accelduino-rs.elf`
       Board Arduino Uno
 Programming target\avr-atmega328p\release\accelduino-rs.elf => COM6

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.00s

avrdude: Device signature = 0x1e950f (probably m328p)
avrdude: erasing chip
avrdude: reading input file "target\avr-atmega328p\release\accelduino-rs.elf"
avrdude: writing flash (10696 bytes):

Writing | ################################################## | 100% 1.72s

avrdude: 10696 bytes of flash written
avrdude: verifying flash memory against target\avr-atmega328p\release\accelduino-rs.elf:

Reading | ################################################## | 100% 1.38s

avrdude: 10696 bytes of flash verified

avrdude done.  Thank you.

  Programmed target\avr-atmega328p\release\accelduino-rs.elf
     Console COM6 at 57600 baud
             CTRL+C to exit.

Start initializing
Finish sensor initializing
Start loop
ACCL -> X: 0.24412, Y: 0.11718, Z: 9.81382
GYRO -> X: 0.01304, Y: 0.00759, Z: 0.02083
ACCL -> X: 0.23436, Y: 0.11718, Z: 9.81382
GYRO -> X: 0.00146, Y: 0.00885, Z: 0.00139

ポートが見つからないなどの場合は、例えばWindowsであれば

config.toml
.cargo/config.toml
...
[target.'cfg(target_arch = "avr")']
runner = "ravedude uno -cb 57600 -P COM14"
...

のようにしてポートを指定してください。詳しくは

ravedude --help

を実行してください。他にもオプションを指定することができます。

3-5. 具体的なコード

具体的なものはこちらのGitHubのリポジトリに載せておきます。

4. 展望

センサ周りの扱いはなんとなく慣れたので、Madgwick Filterなどによるセンサーフュージョンとアクチュエータの制御などを実装していきたいと思います。合わせて他のマイコンへの移植もしてみようかと思います。

また、数値型におけるnum_traitsのように、センサの種類ごとにTraitを作って実装することで、embedded-halとそれを用いて実装すれば拡張性と移植性が遥かに向上するのでは、と思いついたりしたので模索してみたいです。すでにあるかもしれないですが。

今回、実装していてserde_jsonno_std版であるserde_json_coreを用いてjsonにしようとすると、コンパイルエラーを吐かれてしまいました。コンパイラーオプション周りに不都合があるのか不明ですが、上手くjsonにできるようにしていきたいと思います。(もしくは自分で書く。)GitHubのIssueに詳しく書いてあるので、もし分かる人がいたら教えてくださるとうれしいです。

5. 感想

Rustで組込みをやる界隈はわりと大きくてエコシステムも出来上がっている(気がする)ので、知識の乏しい自分のような人間でも、GitHubのコードやExampleを読めばあっさりと組込みRustが出来るようになっています。現在では組込みRustの書籍もあるので尚更です。先人たちに感謝です。

質問や指摘があれば是非、この記事のコメントやTwitterなどに送ってくれればと思います。

6. 更新歴

2022-12-14 公開


https://github.com/kaaatsu32329/accelduino-rs


Discussion