サイコロが完全に止まれば、上面を調べるだけで出目は分かります。 では、最初に机へ触れた時点で、その後の跳ね方や転がり方まで計算して出目を予測できるでしょうか。

この試行のために製作しているのが、IoT Diceです。 BNO085で姿勢、角速度、線形加速度を測り、nRF5340からBluetooth Low EnergyでWebブラウザへ送ります。 本作は開発中で、現時点で動作するのは姿勢共有と3D表示までです。

BNO085の計測値をnRF5340からWebブラウザへ送り、Rapierで出目を予測するIoT Diceの構成

姿勢共有と出目予測の2モード

IoT Diceには、送信するデータとブラウザ側の処理が異なる二つのモードがあります。

モードサイコロから送るデータWebブラウザで行う処理
姿勢共有姿勢を表すクォータニオンThree.jsで3Dサイコロの向きを更新
シミュレーション(実装中)姿勢、角速度、線形加速度、並進速度、投擲状態、衝撃、確定した出目Rapierによる出目予測、的中判定、投擲データの保存

現在のコードでは、Webアプリで「シミュレーション」を有効にすると、ファームウェアが投擲状態の判定を開始し、ストリームへ角速度、線形加速度、並進速度を加えます。 衝撃と出目確定は、姿勢ストリームとは別のイベントとして直ちに送信します。

13 mm角の基板にnRF5340とBNO085を収める

基板外形は13 mm × 13 mmです。 2層基板の中央にnRF5340を搭載したISP2053-AX-RSモジュールとBNO085を配置し、書き込み用の6ピンコネクターと状態表示用LEDを載せました。

ファームウェアはnRF Connect SDK 3.4.0とZephyrを使い、nRF5340のアプリケーションコアで動作します。 BNO085とはSPIモード3、最大3 MHzで接続し、データ準備完了割り込みを待ってセンサーレポートを読み出します。

物理シミュレーション内のサイコロは一辺16 mmとしているため、13 mm角はシミュレーションモデルの寸法ではなく、内蔵基板の寸法です。

磁気センサーを使わない6軸姿勢推定

BNO085には磁気センサーを含む回転ベクトルもありますが、IoT DiceではGame Rotation Vectorを使います。 実装予定の内蔵電池や受電コイルが磁場を乱す可能性があり、投擲中の方位補正が姿勢へ不連続な変化を加えるためです。 出目の判定には重力方向に対する各面の向きが分かればよく、絶対方位は必要ありません。

ファームウェアは、次の三つのレポートを2.5 ms間隔で要求します。

  • Game Rotation Vector:磁気センサーを使わない姿勢クォータニオン
  • Calibrated Gyroscope:角速度
  • Linear Acceleration:重力成分を除いた線形加速度

2.5 msは400 Hzに相当しますが、実際の出力速度はBNO085が提供できる範囲に制限されます。 BLE通知はセンサーのサンプル数ではなく経過時間でペーシングし、姿勢表示と記録の更新頻度を平均約60 Hzにしています。

静止から出目確定までを4状態で追跡する

姿勢の表示だけなら、受信したクォータニオンを3Dモデルへ反映すれば足ります。 出目を予測するには、手の中で動き始めた時点、自由落下、机との最初の衝突、静止を区別しなければなりません。

ファームウェアは投擲を次の4状態で追跡します。

状態判定に使う量役割
REST角速度と線形加速度が300 ms継続して小さい速度積分の基準を0へ戻す
MOVING静止条件から外れた手から与えられた移動と回転を追う
FREEFALL線形加速度の大きさが重力加速度付近に3サンプル続けて入った落下時間を測る
TUMBLING20 m/s²を超える衝撃を検出した最初の着地から静止までを追う

並進速度は、線形加速度をクォータニオンで機体座標系からワールド座標系へ回転し、最後のRESTから数値積分して求めます。 これにより、自由落下だけでなく、手を離すまでに加えた速度も衝突時の初期条件へ含められます。

ただし、加速度の積分にはバイアスや姿勢誤差が蓄積します。 この速度は短い投擲区間のシミュレーション初期値であり、位置を長時間追跡するための推定値ではありません。

衝撃の3サンプル前から着地を計算する

机に当たった瞬間のサンプルを、そのままシミュレーションへ渡すわけにはいきません。 衝撃によって加速度と角速度が測定範囲へ達し、姿勢推定も乱れる可能性があるためです。

そこで、ファームウェアは直近8サンプルの姿勢、角速度、線形加速度、積分速度をリングバッファへ保持します。 衝撃を検出すると、3サンプル前の姿勢、角速度、並進速度を取り出します。 400 Hzで取得できている場合は、衝突のおよそ7.5 ms前に相当します。

衝撃パケットには、これらの初期条件に加え、自由落下時間とセンサー飽和回数を格納します。 ジャイロが飽和した投擲は、ブラウザ上でも予測の信頼性が低いデータとして表示します。

Rapierによる出目予測の試作

現在のブラウザ側コードは、衝撃パケットを受け取ると、RapierのWebAssembly版で32回の剛体シミュレーションを実行する構成です。 一回目は計測値と基準パラメーターをそのまま使い、残りは反発係数、摩擦係数、並進速度、角速度を少しずつ変えます。 計測誤差や机との接触条件を一つの軌道へ固定しないためです。

シミュレーション上の形状は、一辺16 mm、角丸半径0.8 mmの立方体です。 時間刻みは1 ms、計算時間は最大3秒とし、120 ms継続して速度が小さくなった時点で静止と判定します。

16 mmの剛体は、メートル単位を前提とした物理エンジンの既定許容値に対して小さすぎます。 そのため、長さ、重力加速度、並進速度を10倍し、時間と角速度は変えない相似変換を使っています。

最後に32回の出目を数え、最も多かった面を予測値として表示します。 同時に1から6までの比率も残すため、単一の数字だけでなく、予測がどの程度割れているかも確認できます。

六面の校正と投擲データの記録

基板の取り付け角度が数度ずれると、固定したXYZ軸と実際の面は一致しません。 IoT Diceでは、各面を上にして静置し、Webアプリ上で1から6のボタンを押して面の方向を登録します。 出目は、静止時の上方向と登録済みの六つの方向を内積で比較して判定します。

サイコロが静止すると、ファームウェアは姿勢、投擲中の最大角速度、最大線形加速度、飽和回数をRESULTパケットとして送ります。 ブラウザは実際の出目と予測を比較し、的中数を更新します。

現在のコードは、各投擲の衝撃条件、確定した出目、予測分布、衝撃前2秒から静止までのストリームをIndexedDBへ保存します。 保存したデータをJSONとして書き出し、反発係数や摩擦係数を実測データへ合わせられるところまで完成させる予定です。

用途ごとに分けたBLEパケット

すべてのデータを同じ長さで送り続けると、姿勢共有だけの利用でも通信量が増えます。 そこで、一つのGATT Characteristic上で先頭の種別を変え、用途ごとにパケットを分けました。

種別長さ内容
POSE22 byte時刻と姿勢クォータニオン
STREAM58 byte姿勢、角速度、線形加速度、並進速度、投擲状態
IMPACT50 byte衝突直前の姿勢、角速度、並進速度、落下時間、飽和回数
RESULT32 byte静止姿勢、最大角速度、最大線形加速度、飽和回数

通常モードではPOSEだけを通知し、シミュレーションモードではSTREAMへ切り替えます。 IMPACTとRESULTは間引かず、状態遷移が起きた時点で通知します。

Web Bluetoothで接続する3D表示

WebアプリはTypeScript、Three.js、Viteで構成し、物理計算にRapierを使っています。 インストールは不要で、ChromeまたはEdgeから公開ページを開き、「サイコロを接続」を押してBLEデバイス名diceを選択します。 Web Bluetoothを使うため、ページはHTTPSまたはlocalhostから開く必要があります。

電池充電とファームウェア更新の課題

本体をケースへ収めたまま使うには、コネクターを挿さずに内蔵電池を充電できる無線給電回路が必要です。 受電コイル、充電回路、電池を基板と筐体へ組み込む設計が残っています。

ファームウェアは現在、書き込み用コネクターから更新します。 完成形ではケースを開けずに更新できるよう、BLE経由のファームウェア更新機能を追加する必要があります。

出目予測の検証課題

出目予測は投擲結果を必ず当てるものではありません。 公開リポジトリには予測精度を評価した投擲回数や的中率が含まれていないため、精度を数値では示せません。

予測を外す主な要因は、角速度と加速度の飽和、加速度積分のドリフト、机とサイコロの反発係数および摩擦係数の差、最初の接触位置の誤差です。 現在のシミュレーションパラメーターは初期値であり、投擲記録から同定した値ではありません。

予測と実際の出目を一投ごとに同じ形式で残せるようになれば、どの条件で誤差が増えるかを調べられます。

回路図、EAGLE基板データ、nRF Connect SDK用ファームウェア、WebアプリをGitHubで公開しています。