Rustでmicrobitのシェイク検出コードを書く
加速度センサーの読み取り値をシステムコンソールに出力することには、すでに成功しています。みなさんはmicrobitをさまざまな方向に傾けて、値がどのように変化するかを確認してみたと思います。それはそれで面白いのですが、単に値を表示するだけではあまりわくわくしません。この章では、micro:bitを振ると音を鳴らすプログラムを書きます。
テンプレートからプロジェクトを作成する
このプロジェクトでは、microbit-bsp(Embassyあり)を使用します。テンプレートを使って新しいプロジェクトを生成するには、次のコマンドを実行します。
cargo generate --git https://github.com/ImplFerris/mb2-template.git --rev 3d07b56
-
プロジェクト名の入力を求められたら、"accelerometer-print" のような名前を入力します。
-
async を使うかどうかを尋ねられたら、"true" を選択します。
-
"BSP" または "HAL" のどちらを使うかを尋ねられたら、"BSP" を選択します。
Cargo.tomlを更新する
Cargo.tomlファイルを開き、次の行を追加します。
lsm303agr = { version = "1.1.0", features = ["async"] }
static_cell = { version = "2" }
シェイクを検出するには?
シェイクを検出するには、まず加速度センサーの読み取り値の大きさを計算する必要があります。大きさを計算する式は次のとおりです。
$$ \text{magnitude} = \sqrt{x^2 + y^2 + z^2} $$
計算例
1つのサンプル読み取り値を使って、その大きさを計算してみましょう。
サンプル値: x = 81, y = -105, z = -1018
#![allow(unused)] fn main() { x = 81 => x^2 = 6561 y = -105 => y^2 = 11025 z = -1018 => z^2 = 1036324 magnitude^2 = 6561 + 11025 + 1036324 = 1053910 magnitude = sqrt(1053910) ≈ 1026.6 }
もう1つのサンプル読み取り値を見てみましょう: x:-875, y:-567, z:-143
#![allow(unused)] fn main() { x^2 = (-875)^2 = 765625 y^2 = (-567)^2 = 321489 z^2 = (-143)^2 = 20449 magnitude^2 = 765625 + 321489 + 20449 = 1107563 magnitude = sqrt(1107563) ≈ 1052.4 }
Microbitを振っているときの読み取り値
さらにいくつかのサンプル読み取り値を試して、その大きさを計算してみてください。静止時のほとんどの値は1000に近いはずです。では、前のプログラムを実行して加速度センサーの値を表示してみましょう。今回は、micro:bitをしっかり振って、読み取り値を観察してください。
以下は、デバイスを振っているときに私が記録したサンプルの1つです: x:-1265, y:-2029, z:1657
#![allow(unused)] fn main() { x^2 = (-1265)^2 = 1600225 y^2 = (-2029)^2 = 4116841 z^2 = (1657)^2 = 2745649 magnitude^2 = 1600225 + 4116841 + 2745649 = 8462715 magnitude = sqrt(8462715) ≈ 2909.07 }
おお、今度は大きさがほぼ3000です!これは静止時の値から大きく跳ね上がっており、強いシェイクが起きていることをはっきり示しています。
コードでシェイクを検出するには、しきい値(たとえば 2000)を決めて、計算した大きさと比較します。大きさがこのしきい値より大きければ、シェイクが発生したと判断できます。
Embedded Rust (#![no_std]) で平方根(sqrt)を計算する方法
sqrt() 関数は、no_std 環境ではデフォルトでは利用できません。libm のようなcrateを使えば no_std 向けの数学関数を利用できますが、ここではもっと簡単で効率のよい方法を使います。
平方根を計算する代わりに、しきい値を二乗して、大きさの二乗と直接比較できます。たとえば、しきい値が 2000 なら、これを二乗して 4,000,000 にします。次に、この値と大きさの二乗を比較します。
#![allow(unused)] fn main() { // これの代わりに: 2909.07 > 2000 // こうします: 8462715 > 4000000 }
シェイクを検出するコード
しきい値の比較ロジックをRustコードにしてみましょう。以下の関数は、x、y、z の加速度センサーの読み取り値を受け取り、シェイクが検出された場合に true を返します。
#![allow(unused)] fn main() { const SHAKE_THRESHOLD_MG: i32 = 2000; const SHAKE_THRESHOLD_SQUARED: i64 = (SHAKE_THRESHOLD_MG * SHAKE_THRESHOLD_MG) as i64; fn detect_shake(x: i32, y: i32, z: i32) -> bool { let mag_sq = x as i64 * x as i64 + y as i64 * y as i64 + z as i64 * z as i64; mag_sq > SHAKE_THRESHOLD_SQUARED } }
完全なコード
デバイスの振り方や求める感度に応じて、しきい値やディレイのタイミングを調整する必要があるかもしれません。
#![no_std] #![no_main] use defmt::info; use embassy_nrf::{self as hal, pwm::SimplePwm, twim::Twim}; use hal::twim; use defmt_rtt as _; use embassy_executor::Spawner; use embassy_time::{Delay, Timer}; use lsm303agr::{AccelMode, AccelOutputDataRate, Lsm303agr}; use microbit_bsp::{ Microbit, speaker::{NamedPitch, Pitch, PwmSpeaker}, }; use static_cell::ConstStaticCell; #[panic_handler] fn panic(panic_info: &core::panic::PanicInfo) -> ! { info!("{:?}", panic_info); loop {} } hal::bind_interrupts!(struct Irqs { TWISPI0 => twim::InterruptHandler<hal::peripherals::TWISPI0>; }); const SHAKE_THRESHOLD_MG: i32 = 2000; const SHAKE_THRESHOLD_SQUARED: i64 = (SHAKE_THRESHOLD_MG * SHAKE_THRESHOLD_MG) as i64; fn detect_shake(x: i32, y: i32, z: i32) -> bool { let mag_sq = x as i64 * x as i64 + y as i64 * y as i64 + z as i64 * z as i64; mag_sq > SHAKE_THRESHOLD_SQUARED } #[embassy_executor::main] async fn main(_spawner: Spawner) -> ! { // let p = embassy_nrf::init(Default::default()); let board = Microbit::default(); static RAM_BUFFER: ConstStaticCell<[u8; 16]> = ConstStaticCell::new([0; 16]); let twim_config = twim::Config::default(); let twim0 = Twim::new( board.twispi0, Irqs, board.i2c_int_sda, board.i2c_int_scl, twim_config, RAM_BUFFER.take(), ); let mut speaker = PwmSpeaker::new(SimplePwm::new_1ch(board.pwm0, board.speaker)); let mut sensor = Lsm303agr::new_with_i2c(twim0); sensor.init().await.unwrap(); sensor .set_accel_mode_and_odr( &mut Delay, AccelMode::HighResolution, AccelOutputDataRate::Hz50, ) .await .unwrap(); loop { if sensor.accel_status().await.unwrap().xyz_new_data() { let data = sensor.acceleration().await.unwrap(); let x = data.x_mg(); let y = data.y_mg(); let z = data.z_mg(); if detect_shake(x, y, z) { // info!("SHAKE => x:{}, y:{}, z:{}", x, y, z); speaker.start_note(Pitch::Named(NamedPitch::A4)); Timer::after_millis(100).await; speaker.stop(); } } Timer::after_millis(50).await; } }
Quick startプロジェクトをクローンする
私が作成した quick startプロジェクトをクローンして、プロジェクトフォルダへ移動し、実行できます。
git clone https://github.com/ImplFerris/microbit-projects
cd microbit-projects/bsp-embassy/shake-detect
書き込み - Run Rust Run
あとはコードをmicro:bitに書き込んで、実際に動かしてみるだけです。
プロジェクトフォルダから次のコマンドを実行します。
#![allow(unused)] fn main() { cargo run }
では、microbitを振ってみてください。シェイクが検出されると音が鳴るはずです。
応用例
シェイク検出でほかに何ができるでしょうか? 楽しくて創造的な可能性がたくさんあります!
いくつかアイデアを紹介します。
-
サイコロを振る: 振るたびに 1 から 6 のランダムな数を生成します。
-
色やアニメーションを変えるために振る: micro:bitが振られるたびに、LEDパターンを更新したり新しいフレームを表示したりします。
-
ゲームを操作するために振る: シンプルなゲームで、ジャンプ、移動、アクションのトリガーなどの入力としてシェイクを使います。
-
歩数計: 小さく繰り返されるシェイクを検出して、基本的な歩数計のように歩数を数えます。