bleについて
差分
このページの2つのバージョン間の差分を表示します。
次のリビジョン | 前のリビジョン | ||
bleについて [2024/12/09 02:30] – 作成 araki | bleについて [2024/12/22 04:52] (現在) – [コード片] araki | ||
---|---|---|---|
行 101: | 行 101: | ||
' | ' | ||
' | ' | ||
- | | + | |
- | 0, 0, 0, | + | 28, 31, 30, |
}, | }, | ||
{ | { | ||
行 109: | 行 109: | ||
| | ||
| | ||
- | | + | |
- | 0, 0, 0, | + | 28, 31, 30, |
}, | }, | ||
{ | { | ||
行 117: | 行 117: | ||
'#', | '#', | ||
' | ' | ||
- | | + | |
- | 0, 0, 0, | + | 28, 31, 30, |
}, | }, | ||
}; | }; | ||
行 370: | 行 370: | ||
ハイハイスクールアドベンチャーには必要ないが、キーのオートリピートはあると便利な機能です。 | ハイハイスクールアドベンチャーには必要ないが、キーのオートリピートはあると便利な機能です。 | ||
- | キーブレイクが発生すれば、notification が発生するのですが、押しっぱなしでは何も発生しません。 | + | [[フルーツフィールド]]をM5 Stackで遊ぼうと思ったら、ないと、REM君の移動にキーをたくさん連打しないといけません。 |
+ | |||
+ | キーボード側でキーブレイクが発生すれば、notification が発生するのですが、押しっぱなしでは何も発生しません。 | ||
+ | 一定時間押しっぱなしだったら、キーボード側でキーブレイク処理を勝手に挿入して、疑似的にキーを連打しているかのようにしてくれればこちらが何もしなくてもいいのですが((実は、CardputerをBLEキーボードにするアプリは一定間隔で、勝手にキーブレイクを挿入してくるので、試してませんが、勝手にキーリピートされると思われます。))現実はそんなに甘くありません。 | ||
なので、タイマー割り込みを使って、押しっぱなしのキーを拾ってキューイングすることにします。 | なので、タイマー割り込みを使って、押しっぱなしのキーを拾ってキューイングすることにします。 | ||
+ | |||
+ | == タイマー割り込みの実装 == | ||
ESP32には4つのタイマーがあり、それぞれに割り込みを設定できます。 | ESP32には4つのタイマーがあり、それぞれに割り込みを設定できます。 | ||
+ | < | ||
+ | hw_timer_t *timer = nullptr; | ||
+ | |||
+ | void IRAM_ATTR | ||
+ | onTimer() | ||
+ | { | ||
+ | // 割り込み処理のコード | ||
+ | } | ||
+ | |||
+ | ... | ||
+ | |||
+ | void | ||
+ | setup() | ||
+ | { | ||
+ | auto cfg = M5.config(); | ||
+ | cfg.clear_display = true; | ||
+ | M5.begin(cfg); | ||
+ | Serial.begin(115200); | ||
+ | if ((timer = timerBegin(0, | ||
+ | { | ||
+ | timerAttachInterrupt(timer, | ||
+ | timerAlarmWrite(timer, | ||
+ | timerAlarmEnable(timer); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | </ | ||
+ | |||
+ | 要するに、タイマー割り込みがかかるたびに onTimer() が呼び出されますってことです。 | ||
+ | 最初の timerBegin(0, | ||
+ | タイマーは0-3の四本あります。 | ||
+ | dividerは、クロックのチックをいくつで1とみなすかって話です。 | ||
+ | ESP32のシステムのクロックは80MHzだそうですので、80で割って、1usを1チックにするってことです。 | ||
+ | |||
+ | これが、この後の timerAlarmWrite(timer, | ||
+ | |||
+ | 勿論、もっと頻度上げることもできますが、割り込み処理がいい感じに終わる範囲でないと意味がないですし、キーボードのリピート処理のデザインとしては、「大体0.5秒押しっぱなしだったら、以後0.1秒ごとにそのキーが押されているものとみなす」としたいので、0.1秒間隔で割り込んでくれていれば十分実用の範囲なのです。 | ||
+ | |||
+ | == リピート処理 == | ||
+ | |||
+ | あとは、onTimer() の中身を書くことになります。 | ||
+ | |||
+ | といっても、大したことはしていません。 | ||
+ | そもそも、今の手抜き実装は、後から来た Notification で渡されたものと比較して、新しく押されたキーがないか確認するために、直近の Notification で持ってきたキーコードデータを保持しています。 | ||
+ | |||
+ | なので、新しい Notification が来ないまま0.5秒たったのなら、それはもうオートリピートすればいいでしょうっていう判断ができるので、あたかも保持しているキーコードが押されたかのような処理を組めば完了です。 | ||
+ | |||
+ | 割り込み間隔を0.1秒に設定した天才的ひらめき((そうか?))により、リピート開始後の「以後0.1秒ごとにキーが押されているものとする」の部分はほぼ何もしないでもいいくらいに簡単に実現しています。 | ||
+ | |||
+ | Notification が来たところからカウントが始まるので、カウンターを一つ新設します。 | ||
+ | |||
+ | < | ||
+ | static volatile int keyboardCount = 0; | ||
+ | </ | ||
+ | |||
+ | 既存のNotification処理しているところで、これを 0 にするコードを足します。 | ||
+ | |||
+ | < | ||
+ | keyboardCount = 0; | ||
+ | keyboard_t *newKeyReport = (keyboard_t*)pData; | ||
+ | </ | ||
+ | |||
+ | のように、pDataの処理をする前あたりにでも入れておけばいいでしょう。 | ||
+ | |||
+ | 割り込み側では、カウントアップ処理とリピートが始まったらリピート処理を書きます。 | ||
+ | |||
+ | < | ||
+ | void IRAM_ATTR | ||
+ | onTimer() | ||
+ | { | ||
+ | if (!connected || keybuf.size() > 30 || keyboardCount++ < 5) // every 0.5sec | ||
+ | { | ||
+ | return; | ||
+ | } | ||
+ | int buflen = 6; | ||
+ | uint8_t *buf = keyboardReport.k2.keys; | ||
+ | uint8_t | ||
+ | if (keyboardBufferType == 1) | ||
+ | { | ||
+ | buflen = 10; | ||
+ | buf = keyboardReport.k1.keys; | ||
+ | mod = keyboardReport.k1.modifiers; | ||
+ | } | ||
+ | for (int i = 0 ; i < buflen ; i++) | ||
+ | { | ||
+ | uint8_t c = buf[i]; | ||
+ | if (c == 0) continue; | ||
+ | if (keybuf.size() > 30) break; | ||
+ | keybuf.push(((uint16_t)mod << 8)|c); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 一応、リピートできるけれど、無制限にやるとメモリ食い尽くすので、30文字バッファにたまったら止めるようにしています。 | ||
+ | (keybuf.size() > 30 などのチェックです。) | ||
+ | トリガーがかかるのは、カウンターが5になったときです。 | ||
+ | なので、それ未満なら何もせずに戻ります。 | ||
+ | キーボードが未接続の時も何もする必要はありません。 | ||
+ | バッファが一杯でも同じです。 | ||
+ | リピートが始まったら、あとはカウンターは常に 5以上なので、割り込みが発生する0.1秒ごとにリピート処理されます。 | ||
+ | カウンターがオーバーフローした場合の処理を入れていませんが、32bitでも2500日くらい押し続けないと溢れないので、実用上問題はないと思っています。頑張って2500日押し続けてオーバーフローしたら教えてください。 | ||
+ | 直します。 | ||
+ | |||
+ | === キーが押されているか? === | ||
+ | |||
+ | まあ、[[フルーツフィールド]]については、オートリピートでもゲームになりますが、結局ゲームの場合、キーが押されているかどうかを読んで、動作させる必要性があります。 | ||
+ | |||
+ | 実際、PC-6001mkII版だって、キーが押されているかどうかを調べるシステムコールを使って動作させているので、BLE版だってそうさせたい。 | ||
+ | |||
+ | 実装は簡単なのですが、どういう実装にするのかが問題。 | ||
+ | |||
+ | PC-6001mkII版のように、上下左右+SPC+ESC+何か二つ、をuint8_tのビットマップにパックするのも考えたんですが、ビットマップに畳み込んで、ビットマップを展開してとかやるんだったら、BLEの通信バッファの方を直接スキャンしても大差ないだろうということで、 | ||
+ | < | ||
+ | キーコード, | ||
+ | </ | ||
+ | のペアの配列を用意して、そこに定義されているキーが押されているかどうかをチェックして、押されてたら文字コードの方を返すようにしました。 | ||
+ | |||
+ | < | ||
+ | static uint8_t _keymap[] = { | ||
+ | 0x50, 28, | ||
+ | 0x4f, 29, | ||
+ | 0x52, 30, | ||
+ | 0x51, 31, | ||
+ | 0x2c, ' ', | ||
+ | 0x29, 27, | ||
+ | 0x16, ' | ||
+ | 0x1f, ' | ||
+ | 0, 0 | ||
+ | }; | ||
+ | |||
+ | bool | ||
+ | keyscan(uint8_t &code) | ||
+ | { | ||
+ | for (int i = 0 ; _keymap[2 * i] != 0 ; i++) | ||
+ | { | ||
+ | if (_keyboard-> | ||
+ | { | ||
+ | code = _keymap[2 * i + 1]; | ||
+ | return true; | ||
+ | } | ||
+ | } | ||
+ | return false; | ||
+ | } | ||
+ | </ | ||
bleについて.1733711405.txt.gz · 最終更新: 2024/12/09 02:30 by araki