M5StickS3 と M5Stack CoreS3 を、Windows/macOS/Linux 向けの BLE HID マウスとして使う PlatformIO プロジェクトです。両機種とも BMI270 のジャイロで空中マウスとして操作できます。 CoreS3 はタッチパッドも使用可能。StickS3 は物理ボタンとジャイロでクリック・ドラッグ・スクロールします。
pio run -e m5stack-sticks3
pio run -e m5stack-cores3CoreS3 は PlatformIO 標準の m5stack-cores3 定義(ESP32-S3、16 MB フラッシュ)を使用します。
M5StickS3 用の 8 MB ボード定義は boards/m5stack-sticks3.json に含めています。
書き込み後、PC の Bluetooth 設定でデバイスをペアリングしてください。
StickS3への書き込みでは環境を明示してください。 default_envs はCoreS3のままなので、
-e を省略するとCoreS3用ファームになります。
pio run -e m5stack-sticks3 -t uploadStickS3用のイメージは .pio\build\m5stack-sticks3\firmware.bin です。
診断画面の先頭には StickS3 BMI270 と表示されます。
| デバイス | 入力 | 操作 |
|---|---|---|
| M5StickS3 | BMI270 ジャイロ | 本体を旋回/先端を上下に回して相対移動。傾けたまま静止すると停止 |
| M5StickS3 | 前面 BtnA (GPIO11) | 短押し: 左クリック。450ms 長押し後、押している間だけ左ドラッグ |
| M5StickS3 | 側面 BtnB (GPIO12) | 短押し: 右クリック。300ms 以内にもう一度押すと中クリック |
| M5StickS3 | BtnB を350ms以上押したまま先端を上下 | カーソルを止め、上げる: 上スクロール、下げる: 下スクロール |
| M5StickS3 | 電源/RESET ボタン | 工場動作を維持。単押しはリセット。一時停止には使用しない |
| M5StickS3 | 強いシェイク | 空中操作の一時停止/再開 |
| CoreS3 | BMI270 ジャイロ | 空中で本体を回すとカーソルを相対移動 |
| CoreS3 | CoreS3 を強く振る | 空中マウスの一時停止/再開 |
| CoreS3 | 画面の 1 本指操作 | なぞる: カーソル移動、タップ: 左クリック、長押し: 右クリック |
| CoreS3 | 画面の 2 本指操作 | タップ: 右クリック、上下になぞる: ホイールスクロール |
| CoreS3 | 画面下部の LEFT/MIDDLE/RIGHT | M5Unified の仮想 BtnA/BtnB/BtnC。短押し: 各クリック、長押し: 対応ボタンを押し続ける |
| CoreS3 | HOLD / DRAG TOGGLE | 左ボタンを固定してドラッグ。もう一度タップして解除 |
| CoreS3 | マイク(既定で無効) | 大きな拍手・タップで左クリック |
StickS3 公式仕様 とインストール済み M5Unified 0.2.20 / M5GFX の実装を照合しています。StickC / StickC Plus の仕様とは異なります。
| 項目 | StickS3 の確認結果 |
|---|---|
| IMU | BMI270、6軸(3軸加速度+3軸ジャイロ)、I2C 0x68、SDA=47 / SCL=48 |
| 地磁気・タッチ | 内蔵地磁気センサーなし、タッチパネルなし。CoreS3 の BMM150 / タッチ処理は使わない |
| マイク | 搭載あり。MEMS マイク+ES8311。M5Unified に StickS3 用の M5.Mic 設定あり。本実装では無効 |
| 音声出力 | AW8737+8Ω/1W スピーカー。M5.Speaker 対応で、単なるブザーではない。本実装では無効 |
| プログラム用ボタン | 前面 BtnA=GPIO11、側面 BtnB=GPIO12、両方 active-low。BtnC は使わない |
| 電源/RESET | M5PM1 管理。標準操作は単押し=電源オン/リセット、2回=電源オフ、長押し=書き込みモード |
| 表示 | ST7789P3、135×240、タッチなし。M5GFX の offset_rotation=0、本実装は setRotation(0) の縦表示 |
| LED | PMIC管理の緑LEDあり。M5.Power.M5pm1.setLedEnLevel() で制御可能だが自動点滅もある。M5.Power.setLed() は非対応。状態表示には LCD を使用 |
ライブラリ側の根拠:
BtnA/B GPIO入力、
StickS3スピーカー設定、
StickS3マイク設定、
BMI270の機種別軸補正。
LCD寸法とタッチドライバを設定しないことは、インストール済み
M5GFX/src/M5GFX.cpp の board_M5StickS3 分岐でも確認できます。
画面を上向きにして、USB-C と反対側の Hat2-Bus(16ピン)端を PC へ向けます。 Grove/HY2.0-4P は先端ではなく USB-C と同じ端です。 公式外形図 と 公式 IMU 軸図 を参照してください。 画面の文字は Hat 側を上、USB 側を下にして読める縦向きにしています。表示回転は IMU 軸を変えません。
CoreS3 と ネイティブ軸が異なります。StickS3 の +X は USB-C 向き、+Y は画面右、
+Z は画面から外向きです。M5Unified の IMU_Class.cpp は board_M5StickS3 に軸回転を追加しません。
ライブラリのユーザー軸順を identity に設定し、AirMouse.h の stickToPointerFrame() で
加速度・ジャイロの 両方を共通座標へ変換してから校正・投影します。
共通 X(右) = ネイティブ Y
共通 Y(先端) = -ネイティブ X
共通 Z(画面外) = ネイティブ Z
| 動作(水平な画面) | StickS3 ネイティブジャイロ | 変換後/結果 |
|---|---|---|
| 先端を右へ旋回 | gz < 0 |
yaw<0、kCursorXSign=-1 なので X+ |
| 先端を上げる | gy > 0 |
pitch>0、kCursorYSign=-1 なので HID Y− |
| 先端方向を軸にひねるだけ | gx のみ |
yaw/pitch は0、移動しない |
| 傾けたまま静止 | 補正後の全ジャイロが0 | 移動・スクロールしない |
右手の法則で +Z 回転は先端を左へ、ネイティブ +Y 回転は先端 -X を上へ動かすため、この符号です。
ロール補償・加速度異常時の本体軸フォールバックも 変換後の座標で行います。
従来の加速度傾き差分・Y感度1.75倍・傾き平滑化は削除しました。共通の基本感度は両軸とも22です。
起動後は机などで約1秒静止し、Calibrating が消えるのを待ってください。
校正は **1回最大1.5秒、3回まで(通常のサンプリング中は最大約4.5秒)**です。
画面の Calibrating 1/3 1.5s は試行番号とその試行の残り時間です。ノイズや動きがあっても
タイマーを巻き戻しません。200サンプル以上・1秒以上を取得し、各軸の標準偏差が
ジャイロ0.8°/秒以下/加速度0.04g以下、平均角速度ノルム5°/秒以下、平均加速度が1±0.15gなら完了します。
3回とも失敗した場合は、取得済みの試行のうち加速度とジャイロの変動が小さい平均バイアスを使って
移動を開始し、CAL:WARN reset at rest を表示し続けます。選択スコアはジャイロ各軸分散の和
+100×加速度各軸分散の和+10×平均加速度ノルムの1gからの差です。
動き続けたままの校正値は不正確なことがあります。警告が出たら、本体を静止させてリセットすると再校正できます。
取得ゼロ・非有限値・加速度ゼロなどは成功扱いにせず、NO VALID DATA/NO SAMPLES を表示します。
データが回復すれば校正を再開します。CoreS3 の起動校正の条件は変更していません。
BtnA のドラッグは 押している間だけです。押し始めはカーソルを止め、450msでドラッグへ移行し、 さらに150msの押下振動抑制後に移動できます。離すと左ボタンを解放し、150ms停止します。 BtnB の単押しは、二度押し判定のため離してから300ms遅延します。二度押しでは右クリックを混ぜず、 中クリックを一度だけ送ります。短押し後に続けて長押しした場合、保留中の右クリックを取り消して スクロールします。接続が切れた場合は保留クリック・ドラッグを解除し、再接続後は押し直してください。
BtnB 長押し中は横移動も縦移動も送らず、バイアス補正/ロール補償済み pitch をホイールへ積分します。 先端を上げると正のホイール(通常は上スクロール)。感度は 0.35ステップ/度、 ポインタと同じ静止域・軽い平滑化を使い、ホイールには速度加速を掛けません。 小数を保持して8msごとに整数分を送り、モード切替時は残量を破棄します。 スクロール開始・終了およびすべてのクリック後に150ms停止します。
アプリは 内蔵BMI270と物理ボタンだけで動作し、HAT/Unitは不要です。
config.internal_imu=true、config.external_imu=false で M5.begin(config) を呼び、
M5Unified が初期化した M5.Imu/M5.In_I2C を使います。アプリでI2Cピンやアドレスを指定しません。
M5Unified 0.2.20 の根拠:
| 確認項目 | ソースと結果 |
|---|---|
| 内部バス | M5Unified.cpp のピン表は SCL,SDA の順なので、内部SCL48/SDA47で正しい。外部GroveのSCL10/SDA9とは別 |
| 初期化 | 内部I2C1の設定、internal_imu分岐、BMI270の0x69→0x68探索。同じドライバの選択済みアドレスにアクセス |
| 設定・単位 | IMU_Base.hppは8/32768 g、2000/32768 dps。Bosch定義でもAA/02/EA/00は400Hz・通常帯域・性能優先・±8g/±2000dps。設定と換算は一致しているため維持 |
| 新しいデータ | BMI270_Class.cppはINT_STATUS_1のaccel=0x80、gyro=0x40から鮮度マスクを返す。IMU_Class::updateの戻り値を使う。内部タイムスタンプの更新だけでは鮮度を保証しない |
| 磁気センサーなし | BMI270_Class.cpp のAUX初期化でBMM150が見つからなくてもPWR_CTRL=0x0Eで加速度・ジャイロは有効。磁気データを待つ処理はない |
| 初期化結果の弱点 | BMI270_Class.cppは初期化完了の判定結果を失敗として返し切らない。アプリはisEnabled/typeに加えCHIP_ID=0x24、INTERNAL_STATUSのinit_ok、ACC/GYR電源、ERR_REGのfatal/configビットを確認する |
旧StickS3の停止条件として、厳しい校正条件(0.25°/秒、0.015g、1±0.06g)を満たせず
calibrated=false のまま無期限に待つ経路を数値テストで再現しました。
これを上記の期限付き校正に置き換えています。ただし、個々の実機がこの条件で止まっていたかは
実機の取得値なしには断定できません。ピン誤りやレンジ不一致が見つかったわけではありません。
TARGET_M5STICKS3 の専用タスクで2ms周期に読み、鮮度マスク付きのgyroと30ms以内のaccelだけを処理します。
I2C処理が遅れたときは追いつくための連続ループを避け、最低1tick待ってボタン/BLE側へCPUを渡します。
I2C読み取り・画面描画・BLE送信・シリアル出力は状態mutexの外で行います。
初期化エラー時も診断画面とBLEボタンクリックを動かし、IMU移動だけを無効にします。
起動時の一時停止はOFFで、3gのシェイク・150msのクリック凍結・BLE接続判定は従来どおりです。
操作状態は200ms、詳細診断は1秒ごとに更新します。変更のある行だけを低優先度で描画します。 間隔の最小/最大値と最長処理時間は起動以降の値です。値は次の意味です。
| 表示 | 意味 |
|---|---|
StickS3 BMI270 OK, ID, @, TYPE |
対象機種、データ取得状態、チップID(24)、選択アドレス(68)、M5Unifiedのtype番号 |
I2C, SDA, SCL |
M5Unifiedの実際の内部バス/ピン |
IN, ER, P, CFG |
起動時のINTERNAL_STATUS/ERR_REG/PWR_CTRLと4設定バイト(正常例01/00/0E、AA/02/EA/00)。AUX/FIFOエラーだけでは加速度・ジャイロを停止しない |
BLE, Hz, G, A |
BLE接続状態、有効サンプルHz、gyro/accelそれぞれの実取得Hz(直近約1秒) |
X, Y, Z と各行の B |
ソフトウェアバイアスを引く前のネイティブ角速度と、その軸に戻したバイアス(両方dps) |
A:...g, Age |
加速度ノルム、最後の有効サンプルからの経過ms。取得なしはGATEで判別。静止時は概ね1g |
Paused, Freeze, GATE |
一時停止と短時間凍結を個別表示。GATEは取得不良・校正・BLE待ち・PAUSED・FREEZE・READYのどれが出力を止めているかを示す |
A, B, DRAG, CLICK |
物理押下、ドラッグ保持、直近のクリック |
S:min/max ms |
有効IMUサンプルの実取得間隔。400HzのODRを2msポーリングするため、通常も2ms/4ms程度が混在する |
R:min/max ms ...Hz |
空中マウスの送信判定周期。静止・未接続時も計測。目標8ms/125Hz |
T:min/max ms, TX |
非ゼロ移動をBLEライブラリへ渡した間隔と回数。静止時は送らないためTの最大値には停止時間も入る。PC側受信の保証ではない |
Read, Lock |
最長IMU読み取り時間(プリエンプト時間を含む)/状態mutex待ち時間(µs) |
Notify, Buttons |
最長BLE move呼出時間/ボタン更新+クリック処理時間(µs) |
LCD, FMT |
最長1行描画/状態整形時間(µs)。どちらも入力処理と独立した低優先度タスク上 |
Skip S/R |
IMU/送信の期限を1周期以上過ぎ、スキップした周期数 |
Drop D/S |
送信判定が16ms超遅れた/サンプルが未取得または30ms超古いため移動を破棄した判定回数。Sは起動・故障中にも増える |
Limit |
±127の2レポート分を超える整数残量を制限した回数。通常の小さな動きでは増えない |
原因としてソースと決定的テストで確認できたのは、旧版の LCD更新とHID送信が同じloopにあったことです。
abb5717のdrawStatusとloop
では200msごとに135×240画面全体を消して約30行を直接描画していました。
インストール済み M5GFX/src/M5GFX.cpp:2881-2897 のStickS3設定はSPI3/40MHz。
lgfx/v1/panel/Panel_LCD.cpp:251-260 は矩形の全画素を送信し、同ファイル:68-89と
lgfx/v1/platforms/esp32/Bus_SPI.cpp:350-353 は転送完了を待ちます。
16bit画素なら 画面消去だけで最低12.96ms(135×240×16÷40MHz)かかり、
8msの送信周期を超えます。文字転送とCPU処理はさらに加算されます。
CoreS3の src/main.cpp は小さい状態領域だけを更新し、この大量診断描画はありません。
旧 reportMotion は遅れた時刻を次の基準にし、その間IMUタスクが積分した移動量をまとめて送っていました。
13msのLCD停止を注入するホストテストでも送信間隔の穴と大きい次フレームを再現できます。
また旧状態mutexはI2C通信全体を囲んでおり、読み取り遅延も送信を待たせ得ました。
実機で何msかかっていたか、BLE無線側にも遅延があるかは未測定であり、これだけが個体上の原因とは断定しません。
現在のStickS3では次のように分離しています。このタイミング対策では感度・フィルタ・軸・校正・操作方法を変更していません。
| 処理 | 実行場所・動作 |
|---|---|
| IMU取得 | Core 1、優先度2の専用タスク。400Hz設定を2ms期限でポーリング、鮮度マスクと実測dtで従来の共通計算を実行 |
| ボタンとHID | Core 1、優先度3の専用タスク。ボタン5ms、移動送信判定8ms。クリック・ドラッグ・移動のBLE呼出を1タスクに集約して共有レポートバッファの競合を防止 |
| 画面とログ | Arduino loop(Core 1、優先度1)。135×8の小さいRAM描画バッファで、1ループにつき変更された1行だけ転送。運用中のfillScreenはなし |
| 排他 | 状態と数値計算の短いmutexだけ。StickS3のM5.updateはGPIO11/12のボタン更新で、タッチなし・PMICポーリング無効なのでI2Cと排他する必要がない |
内部I2Cの所有者はIMUタスクだけです。M5Unifiedの追加センサー・外部バスは使用しません。
インストール済みArduinoの sdkconfig.h は1ms RTOS tick、Bluetooth controller/BluedroidはCore 0です。
期限管理は1ms程度の起床ずれを吸収し、それを超えた遅延は次周期を再設定します。
過去の期限を連続実行して追いつこうとはせず、各入力タスクはループごとに最低1tick休みます。
送信が16ms超止まった場合や最新IMUが30ms超古い場合は蓄積移動を破棄し、復帰直後に古い移動を噴き出しません。 通常は小数と±127超過分を保持しますが、極端な高速回転では各軸の整数残量を ±254(2レポート分) に制限します。 整数の超過だけを落として小数は残し、無制限に尾を引く動きを防止します。CoreS3の処理は変更していません。
BLEライブラリは両機種で同一です。BleMouse::moveはsetValue+notifyで、
Arduinoの BLECharacteristic::notify(true) はindicationの確認応答待ちをしません。
機種別の接続間隔設定もなく、今回は推測によるBLEパラメータ変更はしていません。
ホストの接続イベント周期・無線混雑・実際の通知配送結果は、上記のアプリ側時刻だけでは証明できません。
実機では pio run -e m5stack-sticks3 -t upload で明示的に書き込み、机上で校正してから接続します
(この変更の検証では書き込みを実施していません)。小さく一定方向に回している間に、
GATE:READY、Hz/G/A、Rの間隔と125Hz付近の周期、Skip R/Drop Dの増加有無を確認してください。
400Hzの設定値ではなく実取得Hzを見ます。少なくとも200Hz以上の取得を目標とします。
画面更新に同期してRの最大間隔やDrop Dが増えないこと、静止・クリック・ドラッグ・Bスクロールが従来どおりであることも確認します。
異常が残る場合は、静止してリセットした後の短い連続操作で S/R/T、Read/Lock/Notify/LCD、
Skip/Drop/Limit を含む画面を記録すると、取得・アプリ送信・それ以降の問題を切り分けられます。
Tの最大値だけでは、単なる手の停止と通信遅延を区別できません。
工場動作(単押し=電源オン/リセット、二度押し=電源オフ、長押し=書き込みモード)を維持します。
追加のPMICレジスタ書込み・電源ボタン設定・BtnPWR IRQ処理はありません。
M5Unified の通常のボード初期化は使用しますが、config.pmic_button=false として
電源キーのイベントポーリングを無効にしています。一時停止/再開には 3g以上を連続2サンプル
検出するシェイクを使い、画面に PAUSED を表示します。再受付は静かな状態400msと
1.2秒のクールダウン後です。
共通の全ジャイロ定数は後述の表にあります。以下は StickS3 のみの設定です。
| ファイル | 定数 | 既定値 |
|---|---|---|
include/StickButtons.h |
kDragHoldMs |
450ms |
include/StickButtons.h |
kScrollHoldMs |
350ms |
include/StickButtons.h |
kDoubleClickMs |
300ms(最初の解放から次の押下まで) |
include/StickButtons.h |
kDebounceMs |
10ms |
src/StickMouse.cpp |
kButtonPollUs |
5000µs |
src/StickMouse.cpp |
kStatusIntervalMs |
200ms |
src/StickMouse.cpp |
kDiagnosticsIntervalMs |
1000ms |
src/StickMouse.cpp |
kClickIndicatorMs |
300ms |
src/StickMouse.cpp |
kDisplayRotation |
0(縦135×240) |
src/StickMouse.cpp |
kDisplayBrightness |
90 |
src/StickMouse.cpp |
kMotionCore |
1 |
src/StickMouse.cpp |
kImuPriority, kHidPriority |
2, 3(LCDのloopは1) |
include/StickTiming.h |
kWakeToleranceUs |
1000µs |
include/StickTiming.h |
kMaxReportGapUs |
16000µs |
include/StickTiming.h |
kMaxPendingCounts |
254(各軸、整数分) |
include/AirMouse.h |
kScrollStepsPerDegree |
0.35 |
include/AirMouse.h |
kScrollSign |
+1 |
include/StickCalibration.h |
kMinimumDurationUs |
1000000µs |
include/StickCalibration.h |
kAttemptDurationUs |
1500000µs |
include/StickCalibration.h |
kMinimumSamples |
200 |
include/StickCalibration.h |
kMaximumAttempts |
3 |
include/StickCalibration.h |
kGyroStdDevDps |
0.8°/秒(各軸) |
include/StickCalibration.h |
kAccelStdDevG |
0.04g(各軸) |
include/StickCalibration.h |
kGravityToleranceG |
0.15g |
include/StickCalibration.h |
kMaximumMeanRateDps |
5.0°/秒 |
画面を上向きにして、先端(画面上辺の +Y 方向)を PC へ向ける、テレビのリモコンの持ち方です。
CoreS3 の USB-C は側面にあるため、「USB-C と反対側」ではなく 画面上辺を基準にしてください。
軸の基準はコネクタや LCD の表示回転ではなく、
公式 IMU 軸図 の
+X: 画面右、+Y: 画面上辺、+Z: 画面から外へ、という右手系です。
起動時は机上などで約 1 秒静止させ、Calibrating... Keep still! が消えてから操作してください。
動き・分散が大きい場合は自動で校正をやり直します。校正中もタッチパッドは利用できます。
| 動作(画面が水平の場合) | ジャイロの符号 | カーソル |
|---|---|---|
| 先端を右へ旋回 | gz < 0 |
右 (X+) |
| 先端を左へ旋回 | gz > 0 |
左 (X-) |
| 先端を上げる | gx > 0 |
上 (Y-) |
| 先端を下げる | gx < 0 |
下 (Y+) |
| 先端方向を軸にひねるだけ | gy のみ |
移動なし |
| 傾けたまま静止 | バイアス補正後の角速度ゼロ | 停止 |
右手の法則では +Z 回転は先端 +Y を左へ、+X 回転は先端を上へ動かします。
したがって X・Y 両方の既定符号は -1 です。持ち方・個体差で逆になる軸だけ符号定数を反転できます。
カーソル位置・速度を加速度計の傾きから作ることはありません。加速度計は姿勢の基準と静止判定に
だけ使います。静止時の加速度は「下向きの重力」ではなく上向きの支持力なので、正規化した
低域通過後の値を up とします。バイアスを引いたジャイロ omega に対して次の投影を行います。
forward = (0, 1, 0)
horizontal = normalize(forward - dot(forward, up) * up)
right = cross(horizontal, up)
yaw = dot(omega, up)
pitch = dot(omega, right)
dx = kCursorXSign * kPixelsPerDegree * filtered_yaw * dt_seconds
dy = kCursorYSign * kPixelsPerDegree * filtered_pitch * dt_seconds
これにより手首をロールさせても旋回は左右、先端の上下動は上下になります。
急なロールでは姿勢基準の低域通過(時定数 150ms)の追従遅れがあり、瞬時の完全補償ではありません。
加速度が 0.8~1.2g から外れる場合はその値で姿勢を更新せず、yaw=gz / pitch=gx の本体軸へ
一時的に戻します。先端がほぼ鉛直で水平投影を定義できない場合も同じです(BODY AXES 表示)。
縦持ち以外を禁止する旧判定や連続傾きモードはありません。
角速度の 1°/秒以下は静止域とし、その外側 1°/秒は連続的な二次曲線で立ち上げます。 以降は線形、レート平滑化は軽い 1 ポール(alpha=0.65)だけです。静止域へ入るとフィルタの余韻を 切ります。両機種とも 速度依存のカーソル加速はありません。 ノイズ処理後の角速度を実測dtで積分し、固定感度 22 HID カウント/度 を掛けます。 手を速く動かしたからといって感度が上がることはなく、ノイズ処理後の同じ角変位には同じ移動量を出します。 入力速度に関係なくカーソルを一定速度で動かす方式ではありません。
静止域・ソフトニー・フィルタ開始時の過渡応答は維持しているため、特に遅い動きや短い動きでは、 生の角変位が同じでも移動量が完全には一致しません。HIDの±127制限やStickS3の残量制限・ 欠落時の破棄も従来どおりです。固定感度はノイズ処理後の値に対するもので、これらを無効にはしません。 また、画面上の画素数はOS側の設定にも依存します。Windowsの 「ポインターの精度を高める」(Enhance pointer precision) が有効だと、 ファームウェアで加速しなくてもOS側のポインター加速が残ります。比較する際はこの設定にも注意してください。 本プロジェクトはPCの設定を変更しません。
BMI270 を 加速度・ジャイロとも 400Hz に設定して読み戻し確認し、専用タスクが 2ms ごとに
M5.Imu.update() を呼びます。新しいジャイロデータのマスクを確認してから同一バッチの
getImuData() を読み、取得時の micros() 差分で積分します。getAccelData() / getGyroData()
を別々に呼んで二重更新することはありません。画面には直近 1 秒の実取得 Hz も表示します。
CoreS3ではM5.update()(タッチ/PMIC)とIMUの共用I2Cアクセスをmutexで排他します。
StickS3のGPIOボタン・取得・送信・描画は上記の専用タスク構成です。
空中マウスの HID 移動レポートは約 8ms ごとです。浮動小数で移動量を蓄積し、整数部分だけを ±127 に制限して送信します。小数と制限を超えた分は次回へ残します。 StickS3のみ、長い送信遅延・古いサンプル・極端な残量には上記の破棄/上限を適用します。 一時停止・接続切断・入力欠落時には蓄積分を破棄し、復帰時の飛びを防ぎます。 BLE 通信と OS を含む実際の遅延・実サンプルレートは実機で確認してください。
起動時のジャイロオフセットは M5Unified の保存値/単発校正に頼らず、新しい平均値で置き換えます。 この校正値は RAM のみに保持し、毎回の起動で取得し直します。加速度計・磁気センサーの保存校正は保持します。 以降のバイアス追従は角速度が 1.5°/秒未満で加速度が安定した状態が 0.5 秒続いた場合だけ、 30 秒の時定数でゆっくり行います。低速の一定回転とバイアスは完全には区別できないため、 校正中は必ず本体を静止させてください。
画面に触れている間と仮想ボタン押下中は 空中マウスだけを停止し、離してからも 150ms 停止します
(AIR:FREEZE)。その間もタッチ移動、クリック、スクロールは有効です。空中でドラッグしたい場合は
HOLD / DRAG をオンにして指を離してください。保持中のクリックでドラッグ固定を解除しないようにしています。
空中操作を止めたいときは強い短いシェイクで AIR:PAUSE にします。再度のシェイクで再開します。
3g 以上を連続 2 サンプル検出した場合にだけ切り替え、0.4 秒の静かな状態と 1.2 秒のクールダウンを
満たすまで再受付しません。通常のポインティングや長く振り続ける動作で連続切替しない設計です。
再開シェイク後も 0.4 秒は移動を凍結します。
CoreS3 の BMM150 の補正済み磁気方位は引き続き MAG に表示しますが、カーソルの計算には一切使いません。
磁石や金属による磁場の変化は空中マウスの方向を変えません。
include/AirMouse.h を変更して再ビルドします。角速度は度/秒、移動感度は HID カウント/度です。
移動計算は両機種で共通です。ここで感度や符号を変更すると両方に適用されます。
以下の kCalibration* はCoreS3の起動校正用で、StickS3は上記 StickCalibration.h の条件を使います。
固定感度への変更は両機種の空中ポインタに適用されます。CoreS3のタッチ操作は変更していません。
| 定数 | 既定値 | 用途 |
|---|---|---|
kPixelsPerDegree |
22.0 | ノイズ処理後の両軸の固定感度(速度依存倍率なし) |
kCursorXSign, kCursorYSign |
-1, -1 | yaw→X、pitch→Y の符号 |
kRateDeadzoneDps |
1.0 | 静止域 |
kRateKneeDps |
1.0 | 静止域の外側のソフトニー幅 |
kRateFilterAlpha |
0.65 | 軽い平滑化(0.5~1.0) |
kGravityTimeConstantS |
0.15秒 | 姿勢基準の低域通過時定数 |
kGravityToleranceG |
0.20g | 重力方向を信頼する 1g からの許容差 |
kMinHorizontalProjection |
0.10 | 鉛直付近の投影の下限 |
kCalibrationDurationUs |
1000000 | 起動時校正の最短時間 |
kCalibrationSamples |
400 | 校正の最小サンプル数 |
kCalibrationMaxRateDps |
5.0 | 校正中の最大角速度ノルム |
kCalibrationGyroStdDevDps |
0.25 | 校正時の各軸標準偏差上限 |
kCalibrationAccelStdDevG |
0.015g | 校正時の加速度各軸標準偏差上限 |
kSteadyGravityToleranceG |
0.06g | CoreS3起動校正・両機種のバイアス追従の静止判定 |
kSteadyAccelDeltaG |
0.025g | 追従時の加速度サンプル間差の上限 |
kBiasTrackingMaxRateDps |
1.5 | 追従を許す補正後角速度ノルム |
kBiasStillTimeUs |
500000 | 追従前の静止継続時間 |
kBiasTimeConstantS |
30.0秒 | バイアス追従時定数 |
kImuPollMs |
2ms | 専用タスクのポーリング周期(ODR は 400Hz 固定) |
kReportIntervalUs |
8000 | 空中マウスの HID 移動送信間隔 |
kMaxSampleGapUs |
30000 | これ以上の入力欠落は移動破棄/エラー表示 |
kTouchFreezeUs |
150000 | 画面操作/クリック後の移動凍結時間 |
kShakeThresholdG |
3.0g | シェイクしきい値 |
kShakeSamples |
2 | しきい値以上の連続サンプル数 |
kShakeCooldownUs |
1200000 | 次のシェイクまでの最短時間 |
kShakeQuietUs |
400000 | 再受付に必要な静かな時間/シェイク後の凍結 |
CoreS3 の src/main.cpp の kTouchPollUs=5000 はタッチ更新周期、
kStatusIntervalMs=200 は画面の状態更新周期です。
BMI270 の設定は ACC_CONF=0xAA, ACC_RANGE=0x02, GYR_CONF=0xEA, GYR_RANGE=0x00
(400Hz、通常帯域/性能優先、±8g、±2000°/秒)です。
タッチ操作の既定値は従来どおり kTrackpadSensitivity=1.55、
kScrollSensitivity=0.18、kClickThresholdMs=220、kVirtualButtonHoldMs=450、
kTouchMovementThresholdPixels=4、kScrollMovementThresholdPixels=2。
画面下部の高さは kVirtualButtonBarHeight=30、kHoldToggleHeight=30 画素です。
マイクは kEnableClapClick=false、kClapPeakThreshold=9000、
kClapCooldownMs=500、kClapSampleCount=64 です。
上部の緑枠はタッチパッドです。1 本指でカーソルを動かし、2 本指で上下になぞるとスクロールします。
その下の黄色行は左ドラッグ固定の切り替え、最下段 3 分割は M5Unified の仮想 BtnA、BtnB、BtnC
です。仮想ボタンは M5Unified の setTouchButtonHeight() で有効化しているため、画面 UI とライブラリの
ボタン状態が一致します。
constexpr float kTrackpadSensitivity = 1.55f;
constexpr int kTouchMovementThresholdPixels = 4;
constexpr float kScrollSensitivity = 0.18f;
constexpr uint32_t kVirtualButtonHoldMs = 450U;誤操作を避けるため既定では無効です。有効にする場合は kEnableClapClick を true にしてください。
M5Unified の M5.Mic.record() で短い PCM ブロックを読み、最大振幅がしきい値以上のときだけクリックします。
マイクとスピーカーは同じ音声経路を共有するため、このファームウェアは拍手クリック有効時にスピーカーを
無効化します。
constexpr bool kEnableClapClick = false;
constexpr int kClapPeakThreshold = 9000;誤クリックがある場合は kClapPeakThreshold を上げてください。
- BLE 接続が切れている間は PC に操作を送信しません。
- CoreS3 の磁気方位は、磁石、金属、ケーブルなどの影響を受けます。
MAG表示が不安定なときは、 M5Unified の IMU 例を使って BMM150 を校正してください。 - 実機の保持癖で空中マウスの方向が合わない場合は、符号定数で軸ごとに補正してください。
- CoreS3 仕様・IMU 軸図
- StickS3 仕様・IMU 軸図・ボタン標準操作
- StickS3 M5PM1・音声・電源構成
- Bosch BMI270 のレジスタ定義
- M5Unified: https://github.com/m5stack/M5Unified
- ESP32-BLE-Mouse: https://github.com/T-vK/ESP32-BLE-Mouse
M5Unified 0.2.20 の src/utility/IMU_Class.cpp では CoreS3 の BMI270 の加速度/ジャイロ軸は
変更せず、BMM150 の Y/Z だけ反転します。本ファームは setAxisOrder(X+,Y+,Z+) を明示し、
LCD 表示の回転に依存しません。src/utility/imu/BMI270_Class.cpp のデータ準備完了マスクと
IMU_Base.hpp の 8g/2000°/秒の換算係数も使用しています。
tests/air_mouse_test.cpp は外部テストライブラリを使わないホスト用 C++ テストです。
実ファームと同じ AirMouse.h に、両機種の方向・ロール・同等移動、静止校正の再試行、静止傾き、
バイアス追従、ソフトニー、可変 dt、小数・±127 超過分の保持、クリック凍結、切断、シェイク、
時刻周回、ホイール積分を入力します。StickButtons.h も単押し・二度押し・長押し・
切断中のクリック破棄・ボタン保持中の再接続・時計周回を同じ実装で検査します。
StickCalibration.h の期限付き校正も同じ実装で実行します。旧無期限停止の再現、
ノイズ許容、3試行後の最良平均選択、取得ゼロ/非有限値、復帰、時刻周回、回転し続けた場合の
警告付き復帰を検査します。固定感度の検査を含む20グループです。
5~300°/秒・両符号・斜め移動・2~5.5msのdtで、ノイズ処理後の角変位に対する倍率が22で
一定であることを確認します。同じ10度の入力を異なる速度/dtで動かす検査では、
維持した静止域とフィルタ過渡応答による損失を明示的に計算し、期待値と比較します。
これらの固定感度検査ではレポートを飽和させず、小数残量も含めます。
実機のI2C信号やBLE受信を代替するテストではありません。
g++ -std=c++14 -Wall -Wextra -Werror -pedantic tests\air_mouse_test.cpp -o air_mouse_test.exe
.\air_mouse_test.exe
Remove-Item air_mouse_test.exetests/stick_timing_test.cpp はStickS3専用の期限管理・レポート取り出しと共通の実際の移動計算を使う
7グループの回帰テストです。13msの描画停止が旧構成では送信間隔と移動量を乱し、新構成の独立した
送信判定では影響しないこと、60msのI2C停止、期限スキップ、時刻周回、古い移動の破棄、小数保持、
過大残量の制限、操作凍結と再接続を検査します。時間は仮想時計による入力で、
ESP32の実スケジューラや無線配送の測定ではありません。
g++ -std=c++14 -Wall -Wextra -Werror -pedantic tests\stick_timing_test.cpp -o stick_timing_test.exe
.\stick_timing_test.exe
Remove-Item stick_timing_test.exe