本記事は, 音ゲーでカスタムキーボードを使う際に重要なQMK Firmwareの設定について説明する.
環境
- qmk 0.23.1
- qmk_cli 1.1.2
NKROを有効にする
QMKはデフォルトでNKROを有効にしないため, rules.mk
で設定する:
NKRO_ENABLE = yes
または info.json
でも設定可能である:
"features": {
"nkro": true
},
また, NKROを有効化しただけでは起動時に有効になるわけではないので config.h
で設定する:
#define FORCE_NKRO
Polling Rate 1,000 Hzであることを確認する
現在, QMKはPolling Rate 1,000 Hzがデフォルトとなっているため, 特に手を加える必要はない(#15352).
しかし工場出荷の段階で古いQMKが焼かれていて125 Hzで動作している場合がある. その場合は最新のQMKをビルドして焼き直すこと.
OLEDを使わない
OLEDは遅延を生じるため使ってはならない.
どうしてもOLEDを使いたい場合は, OLEDの更新を一時的に"完全に"止められるようコードに手を加える必要がある(QMK Firmware を色々改造して最強の Corne Cherry を仕立てる - 壁ツェーンを参照).
デバウンスアルゴリズムは sym_eager_pk
か sym_defer_pk
を使用する
一度のメカニカルスイッチの押下は, 実際にはバウンス(ON/OFFの繰り返し)を伴っており, 最終的に入力が安定するまでに一定の時間を必要とする. このバウンスへの対処をデバウンスと呼ぶ.
デバウンスの実装については色々と対立軸があるが, QMKの入力遅延において特に重要なDefer/Eagerとタイマーの共有について取り上げる.
Defer vs Eager
Defer Debounce
Defer Debounceは従来使われてきたアルゴリズムである. キーの状態変化を検知した後, 一定時間キーの状態が変化することがなければレポートを行う. バウンスによって状態の変化が検出される度にタイマーがリセットされるため, 入力が安定してから一定時間待機してレポートを行うことになる. 外来的なノイズへの耐性を持つが, 入力の安定を待ち, 更に所定の時間待機する必要があるため, 遅延が生じる("メカニカルスイッチの遅延"と呼ばれているものはほぼこれである). 入力の安定を待つ必要があるが, これは必ずしも一定ではない(キースイッチの個体差・状態によって変わったりする)ため, 遅延に一貫性がない問題もある. また, 状態が悪いスイッチの場合はバウンスを除去しきれずチャタリングが発生する.
Eager Debounce
Eager Debounceは最近主流になってきた[要出典]アルゴリズムである. キーの状態変化を即座にレポートし, それ以降の変化(バウンス)を一定時間無視することによってデバウンスを実現する. Defer Debounceと違い, 即座にレポートするため遅延を生じないが, 外来的なノイズへの耐性がない. ただし, 遅延を気にせずデバウンス時間を大きくできるため(後述するタイマーをキー毎に持つことが前提), 状態が悪いスイッチのバウンスへの耐性は強いといえる.
タイマー共有の粒度
タイマーについて, キーボード全体で共有, 行毎に共有, キー毎に一つのタイマーを持つ実装がある.
"タイマーの共有"と聞いて不穏なものを感じたと思うが, 実際これは滅茶苦茶まずい.
Defer Debounceの場合
タイマーをキーボード或いは行全体で共有, デバウンス時間5ms (QMKのデフォルト), キースイッチは常に2msのバウンスがある場合にどうなるか考えてみよう (スキャンやその他の遅延を一切考慮していないクッソ雑な机上論であると断っておく).
まず a を単独で押下した場合:
- 0ms: a 押下
- 2ms: a のバウンスが終了し, タイマーがリセットされる
- 7ms: タイマーがゼロになり, a の押下が送信される
次に asdf をそれぞれ1ms刻みで打鍵した場合:
- 0ms: a 押下
- 1ms: s 押下
- 2ms: d 押下. a のバウンスは終了するが他のキーのバウンスがあるためタイマーはリセットされる.
- 3ms: f 押下. a, s のバウンスは終了しているが他のキーのバウンスがあるためタイマーはリセットされる.
- 4ms: a, s, d のバウンスは終了しているが, f のバウンスがまだ終了していないためタイマーがリセットされる
- 5ms: ようやく a s d f 全てのバウンスが終了し, タイマーがリセットされる
- 10ms: タイマーがゼロになり, a の押下が送信される(後述するがQMKはChord Splitであるため1キーずつしかレポートを送信できない)
- 11ms: s 送信
- 12ms: d 送信
- 13ms: f 送信
単独で押下したときよりも3ms遅延してしまい, 一貫性がない.
実際のところ, 数ms刻みで入力をするような場面なんてほぼないし, debounce timeが小さく設定されている場合, 別に問題となることはないのだろう. しかし例外がある. 音ゲーだ.
音ゲーでは多キー同時押しをしてそれぞれ0~3ms程度の間隔で叩いてるなんてことは当たり前にある. 更に言えば音ゲーマーは特定のキーを異常に酷使し数百万〜数千万打鍵することも普通なので, スイッチの状態は悪くバウンスは長いものと考えられる. 新品かつまともな品質のスイッチの場合, バウンス時間は1msにも満たないらしい(オシロスコープを使って実際に確認したわけではないのでメーカーの言い分の鵜呑みになる)が, 音ゲーで酷使されていれば2-3msくらいのバウンスが生じていても何もおかしくないし, そんな状態では遅延は大きく, 一貫性も低くなってしまう.
Eager Debounceの場合
これはウダウダ理屈を語るより実際どうなるのかを見た方が早い. sym_eager_pr
, debounce time 25msで焼いてイベントログを確認してみると, 以下のようになる.
% wev ... [14: wl_keyboard] key: serial: 81223; time: 47068089; key: 38; state: 1 (pressed) sym: a (97), utf8: 'a' [14: wl_keyboard] key: serial: 81224; time: 47068114; key: 39; state: 1 (pressed) sym: s (115), utf8: 's' [14: wl_keyboard] key: serial: 81225; time: 47068158; key: 39; state: 0 (released) sym: s (115), utf8: '' [14: wl_keyboard] key: serial: 81226; time: 47068182; key: 38; state: 0 (released) sym: a (97), utf8: '' [14: wl_keyboard] key: serial: 81227; time: 47070181; key: 38; state: 1 (pressed) sym: a (97), utf8: 'a' [14: wl_keyboard] key: serial: 81228; time: 47070206; key: 39; state: 1 (pressed) sym: s (115), utf8: 's' [14: wl_keyboard] key: serial: 81229; time: 47070240; key: 39; state: 0 (released) sym: s (115), utf8: '' [14: wl_keyboard] key: serial: 81230; time: 47070264; key: 38; state: 0 (released) sym: a (97), utf8: '' [14: wl_keyboard] key: serial: 81231; time: 47071728; key: 39; state: 1 (pressed) sym: s (115), utf8: 's' [14: wl_keyboard] key: serial: 81232; time: 47071753; key: 38; state: 1 (pressed) sym: a (97), utf8: 'a' [14: wl_keyboard] key: serial: 81233; time: 47071794; key: 38; state: 0 (released) sym: a (97), utf8: '' [14: wl_keyboard] key: serial: 81234; time: 47071818; key: 39; state: 0 (released) sym: s (115), utf8: '' [14: wl_keyboard] key: serial: 81235; time: 47072738; key: 38; state: 1 (pressed) sym: a (97), utf8: 'a' [14: wl_keyboard] key: serial: 81236; time: 47072763; key: 39; state: 1 (pressed) sym: s (115), utf8: 's' [14: wl_keyboard] key: serial: 81237; time: 47072824; key: 39; state: 0 (released) sym: s (115), utf8: '' [14: wl_keyboard] key: serial: 81238; time: 47072848; key: 38; state: 0 (released) sym: a (97), utf8: '' [14: wl_keyboard] key: serial: 81239; time: 47073581; key: 38; state: 1 (pressed) sym: a (97), utf8: 'a' [14: wl_keyboard] key: serial: 81240; time: 47073606; key: 39; state: 1 (pressed) sym: s (115), utf8: 's' [14: wl_keyboard] key: serial: 81241; time: 47073644; key: 39; state: 0 (released) sym: s (115), utf8: '' [14: wl_keyboard] key: serial: 81242; time: 47073669; key: 38; state: 0 (released) sym: a (97), utf8: '' [14: wl_keyboard] key: serial: 81243; time: 47074350; key: 38; state: 1 (pressed) sym: a (97), utf8: 'a' [14: wl_keyboard] key: serial: 81244; time: 47074375; key: 39; state: 1 (pressed) sym: s (115), utf8: 's' [14: wl_keyboard] key: serial: 81245; time: 47074414; key: 39; state: 0 (released) sym: s (115), utf8: '' [14: wl_keyboard] key: serial: 81246; time: 47074438; key: 38; state: 0 (released) sym: a (97), utf8: ''
お分かりいただけただろうか…… 常に2キー目が25ms(デバウンス時間分)遅延するのである! (1ms未満のずれで24msに見えている場合もある) タイマーを共有している都合, Eager Debounceの"即座にレポートし, その後一定時間入力を無視する"特性が他のキーに作用しているというわけだ. これでは同時押しどころか, ディレイも, というか何もかもまともに叩けるわけがない.
QMKのデバウンス実装
QMKのデバウンス実装について, 各実装の利点・欠点を表に纏めた:
実装 | 利点 | 欠点 | 備考 |
---|---|---|---|
sym_defer_g | ノイズ耐性 | デバウンス遅延がある, 遅延の一貫性が低い, 品質の悪い軸への耐性が低い | リソースの消費量が最も少なく, 明示的に指定しない場合これが使われる |
sym_defer_pr | ノイズ耐性 | デバウンス遅延がある, 遅延の一貫性が低い, 品質の悪い軸への耐性が低い | |
sym_defer_pk | ノイズ耐性, 遅延の一貫性が比較的高い | デバウンス遅延がある, 品質の悪い軸への耐性が低い | |
sym_eager_pr | 単一キーの押下ではデバウンス遅延がない | 同一行の同時押し時に2キー目が必ずデバウンス時間分遅延する, ノイズ耐性がない | ErgoDox EZのようにマトリクスが90度回転している場合は弊害が少ない |
sym_eager_pk | デバウンス遅延がない, 遅延の一貫性が高い, 品質の悪い軸への耐性が高い | ノイズ耐性がない | |
asym_eager_defer_pk | (よくわからん) | (よくわからん) |
音ゲー的には, というか音ゲーに限らず何であっても, sym_eager_pk
か sym_defer_pk
の二択である.
基本的にはデバウンス遅延がなく遅延の一貫性が高い sym_eager_pk
を選択すべきだが, 何か問題があった場合は次善策として sym_defer_pk
を使おう.
一般的なキーボード基板のインピーダンスと使用環境では外来的なノイズを気にする必要はないと思うが.
(敢えて sym_eager_pk
でも sym_defer_pk
でもなく asym_eager_defer_pk
を使うべき場面というのがあるのかよくわからない. 詳しい人いたら教えていただけると助かります)
設定方法
デバウンスアルゴリズム及びデバウンス時間は info.json
で設定可能である:
"build": {
"debounce_type": "sym_eager_pk"
},
"debounce": 25,
デバウンス時間は,
sym_defer_pk
の場合: 5msは長すぎるので3msでいい. チャタリングが生じる場合はスイッチの方を換装しよう.sym_eager_pk
の場合: 連打とLNに影響が出ない範囲で可能な限り大きくしてよい. 私は25msに設定しているが, もう少し大きくても大丈夫なはず.
sym_defer_pk
及び sym_eager_pk
を使用する際の注意として, コードサイズが若干大きくなり, 容量不足でコンパイル/書き込みに失敗する場合がある.
その場合は最適化, 余計な機能を無効化して容量制限を解決すること. QMKでPro Micro用のファームを小さくするとReducing firmware size in QMK – Thomas Baartが参考になる.
Scan Rateについて
Scan Rateについては, まともな設計のPCBであれば気にする必要はないと思う.
以下は私の持っているPCBについて, Scan Rateを計測した結果である:
PCB | MCU | Max Clock Rate (MHz) | Debounce Algorithm | Scan Rate (Hz, 入力なし) | Scan Rate (Hz, ガチャガチャ時) |
---|---|---|---|---|---|
OG60 | RP2040 | 133 | sym_eager_pk | 5111 | 4952 |
sym_defer_pk | 5238 | 5099 | |||
FM60-S/H | STM32F401 | 84 | sym_eager_pk | 4090 | 3101 |
sym_defer_pk | 4000 | 3102 | |||
DZ60RGB-WKL | ATmega32U4 | 16 | sym_eager_pk | 2097 | 1775 |
sym_defer_pk | 2097 | 1781 |
DZ60RGB-WKLでも, 負荷の高い sym_eager_pk
, sym_defer_pk
で安定してScan Rate 1,000 Hz以上を維持できており, 実用上まず問題はないだろう.
Chord Splitについて
キーの状態変化をレポート毎に1つずつしか送信できない実装をChord Splitと呼ぶ. Polling Rate 1,000 HzかつChord Splitである場合, 8キーを完全に同時押しした場合は最初に送信されるキーと最後に送信されるキーとで理論上7msの遅延が生じる.
今のところ, QMKはChord Splitである. 詳しくは以下を参照:
- Keys at the same time · Issue #4904 · qmk/qmk_firmware
- Draft: [Core] Register multiple key events/presses per USB report by KarlK90 · Pull Request #12686 · qmk/qmk_firmware
市販のゲーミングキーボードの殆どはChord Splitではない.
実測値
QMKがChord Splitであることは, そうでない市販のゲーミングキーボードと比べて音ゲー的に不利であるように思われる(差を感じられるほど機械的に同時押しできるプレイヤーがどれだけいるかは一旦置いておこう).
しかしキーボードの入力遅延というやつは様々な要因が絡み合う複合的なものであり, 仕様上Chord Splitでないからといって実際に遅延しないことを意味するわけではない. 真の性能を知るには, 信頼できる実測値が必要だ.
朗報がある. 最近になって, RTINGSが一部のキーボードでChord Split Delayの計測を実施してくれた!
2023-12-07時点で公開されているデータのうち, 8k Chord Split Delayが10ms以下のキーボードを以下に纏めた(丁度Wooting 60HEが最後に入る形となった).
Keyboard | Polling Rate (Hz) | Single-key Latency (Wired, ms) | Single-key Latency (PCB Estimated, ms) | 4k Chord Split Delay (ms) | 8k Chord Split Delay (ms) | Notes |
---|---|---|---|---|---|---|
8BitDo Retro Mechanical Keyboard | 1,000 | 8.1 | 6.5 | 1.2 | 4.9 | |
Keychron K3 (Version 2) | 1,000 | 16.4 | 15.6 | 2.0 | 5.2 | 光学スイッチ |
Corsair K70 MAX | 8,000 | 4.4 | N/A | 4.5 | 6.8 | 磁気スイッチ, ラピッドトリガー |
Keychron C1 Pro/C2 Pro (QMK) | 1,000 | 9.4 | 7.7 | 2.9 | 7.2 | STM32L432KB (80MHz)/STM32F402RC (84MHz) |
Razer Huntsman V3 Pro [Mini, TKL] | 1,000 | 3.4 | N/A | 2.3 | 7.4 | 光学スイッチ, ラピッドトリガー |
Razer BlackWidow | 1,000 | 4.9 | 3.1 | 3.6 | 7.5 | |
ErgoDox EZ (QMK) | 1,000 | 2.9 | 0.9 | 2.9 | 7.5 | ATmega32U4 (16MHz), sym_eager_pr 1, debounce time 30ms |
Microsoft Bluetooth Keyboard | N/A | N/A | 20.7 | 1.2 | 7.7 | |
Razer Huntsman | 1,000 | 4.5 | 3.7 | 4.9 | 7.9 | 光学スイッチ |
Razer BlackWidow Elite | 1,000 | 5.0 | 3.4 | 4.6 | 8.1 | |
Mountain Everest Max | 1,000 | 10.2 | 8.2 | 5.9 | 8.4 | |
EVGA Z20 | 4,000 | 2.2 | 1.4 | 5.5 | 8.6 | 光学スイッチ |
SteelSeries Apex Pro | 1,000 | 4.1 | N/A | 5.9 | 8.7 | 磁気スイッチ, ラピッドトリガー |
Corsair K65 PRO MINI | 8,000 | 0.8 | 0.0 | 5.1 | 8.7 | |
ZSA Moonlander (QMK) | 1,000 | 11.2 | 9.4 | 3.0 | 8.9 | STM32F303 (72MHz) |
Corsair K70 PRO MINI WIRELESS | 8,000 | 1.2 | 0.0 | 4.7 | 9.0 | |
ASUS ROG Strix Flare II Animate | 8,000 | 2.2 | 0.2 | 5.3 | 9.0 | |
Razer BlackWidow V3 Pro | 1,000 | 4.0 | 2.2 | 5.6 | 9.0 | |
Corsair K70 RGB MK.2 | 1,000 | 7.9 | 6.0 | 5.9 | 9.1 | |
ROCCAT Vulkan II Mini Air | 1,000 | 6.9 | 5.6 | 5.4 | 9.3 | |
GLORIOUS GMMK | 1,000 | 19.7 | 17.4 | 4.3 | 9.5 | |
Razer Huntsman V2 | 8,000 | 0.9 | 0.0 | 5.9 | 9.6 | 光学スイッチ |
Logitech G715 | 1,000 | 4.5 | 2.7 | 6.6 | 9.7 | |
SteelSeries Apex 9 | 1,000 | 3.0 | 2.0 | 6.8 | 9.7 | 光学スイッチ |
Keychron Q Pro Series [Q1 Pro, Q2 Pro, etc.] (QMK) | 1,000 | 9.0 | 6.7 | 4.2 | 9.7 | STM32L432 (80MHz) |
Razer BlackWidow V4 Pro | 8,000 | 1.7 | 0.0 | 6.1 | 9.7 | |
Razer BlackWidow V4 75% | 8,000 | 1.6 | 0.0 | 4.5 | 9.8 | |
Wooting two HE | 1,000 | 1.9 | N/A | 5.9 | 9.8 | 磁気スイッチ, ラピッドトリガー |
IQUNIX F97 | 1,000 | 15.7 | 13.8 | 5.3 | 9.9 | |
Ducky Shine 7 | 1,000 | 10.4 | N/A | 5.1 | 9.9 | |
Wooting 60HE | 1,000 | 1.8 | N/A | 4.7 | 10.0 | 磁気スイッチ, ラピッドトリガー |
この表から, 以下のことが読み取れる.
- 仕様上Chord SplitであるQMKとそうでない市販のゲーミングキーボードについて, Chord Split Delayに有意差はない.
- Polling Rate 8,000 HzはSingle-key Latencyについては有意差を示すが, (少なくとも現状市場にあるものについて)Chord Split Delayは差がなく, 音ゲー的には無価値である.
- ATmega32U4のErgoDox EZでもQMK仕様のほぼ理論値が出ており, RP2040やSTM32 familyなどの高性能なMCUを積む意味は薄い.
- QMKについて, Defer/Eager Debounceで概ね机上論通りの差がSingle-key Latencyに出ている.
- 「光学スイッチはデバウンス遅延がなくて従来のメカニカルスイッチより高速」などという広告は詐欺である.
思うに, ゲーミングデバイスメーカーの言う"低遅延"とはSingle-key Latencyのことのみを指しており, (音ゲー的な)同時押しなどは最適化していない, というか考慮してすらいないのだろう. 実際音ゲー以外ではクソどうでもいいし, 音ゲーにしたって(まともな性能の範疇であれば)現実的には何の差もないだろうし, そこにリソース割いてもしょうもないというのはわかる.
結論: QMKでいい.
参考文献
- QMK Firmware
- qmk/qmk_firmware: Open-source keyboard firmware for Atmel AVR and Arm USB families
- Keyboard Reviews - RTINGS.com
脚注
ErgoDox EZはマトリクスが90度回転している(rowが実質columnである)ため,
sym_eager_pr
であっても弊害が少ないことに注意. ↩︎