Arduino UnoをRustで動かして9軸加速度センサを扱う。
これはモダン言語によるベアメタル組込み開発 Advent Calendar 2022 15日目の記事です。
今回の実装はこのリポジトリにあります。
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 Pico
やSTM32
も家に落ちているのでこれらを使う選択肢もありましたが、誰でも簡単に再現できたほうがいいかなということで、一番とっつきやすいイメージのある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の中身を抜粋したものです。
...
[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-halt
、ufmt
、nb
、embedded-hal
はデフォルトで入っているものなので割愛します。
-
serde
とserde-json-core
- シリアライズ/デシリアライズをするためのクレートです。今回は無くてもいいのですが、拡張していくときに利用する予定なので今回から導入します。と言ってもアトリビュートを追加しただけです。
serde_json_core
の方はまだ使ってはいないですが一応書いてあります。
- シリアライズ/デシリアライズをするためのクレートです。今回は無くてもいいのですが、拡張していくときに利用する予定なので今回から導入します。と言ってもアトリビュートを追加しただけです。
-
quaternion-core
- 依存先を減らすためにも自分でQuaternionやVector3を実装しても良かったのですが、ちょっと面倒だったのと、このクレートは
no_std
にも対応していたので採用しました。追々Madgwick Filterなどを実装したいので含めています。
- 依存先を減らすためにも自分でQuaternionやVector3を実装しても良かったのですが、ちょっと面倒だったのと、このクレートは
-
num-traits
- 数値型を扱うトレイトなどで、例えば浮動小数点型であれば
num_traits::float::Float
をトレイト境界として記述します。するとマイコンによって数値型のビット数を変更する場合でも書き換えることなく移植、実装できます。便利なことにno_std
環境であっても使えるので利用しています。
- 数値型を扱うトレイトなどで、例えば浮動小数点型であれば
-
ufmt_float
- デフォルトの依存先である
ufmt
のシリアル出力は数値型では整数のみなので、浮動小数点型についても扱えるように拡張したクレートです。処理した後の加速度センサの値を出力したいので用いています。小規模なクレートなので、これを用いずに自分で浮動小数点型にufmt
のuDisplay
トレイトを継承させれば良いのですが、使いました。バグを見つけてpull requestを送りマージされたのでご愛敬ということで。
- デフォルトの依存先である
avr-hal
を用いたファームウェア実装
3-2. 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
以下のように出力されれば成功です。加速度の単位は、[
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
...
[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_json
のno_std
版であるserde_json_core
を用いてjsonにしようとすると、コンパイルエラーを吐かれてしまいました。コンパイラーオプション周りに不都合があるのか不明ですが、上手くjsonにできるようにしていきたいと思います。(もしくは自分で書く。)GitHubのIssueに詳しく書いてあるので、もし分かる人がいたら教えてくださるとうれしいです。
5. 感想
Rustで組込みをやる界隈はわりと大きくてエコシステムも出来上がっている(気がする)ので、知識の乏しい自分のような人間でも、GitHubのコードやExampleを読めばあっさりと組込みRustが出来るようになっています。現在では組込みRustの書籍もあるので尚更です。先人たちに感謝です。
質問や指摘があれば是非、この記事のコメントやTwitterなどに送ってくれればと思います。
6. 更新歴
2022-12-14 公開
Discussion