音ゲー用QMK Firmwareの設定等

本記事は, 音ゲーでカスタムキーボードを使う際に重要なQMK Firmwareの設定について説明する.

環境

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_pksym_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 を単独で押下した場合:

次に asdf をそれぞれ1ms刻みで打鍵した場合:

単独で押下したときよりも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_pksym_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 及び sym_eager_pk を使用する際の注意として, コードサイズが若干大きくなり, 容量不足でコンパイル/書き込みに失敗する場合がある. その場合は最適化, 余計な機能を無効化して容量制限を解決すること. QMKでPro Micro用のファームを小さくするReducing firmware size in QMK – Thomas Baartが参考になる.

Scan Rateについて

Scan Rateについては, まともな設計のPCBであれば気にする必要はないと思う.

以下は私の持っているPCBについて, Scan Rateを計測した結果である:

PCBMCUMax Clock Rate (MHz)Debounce AlgorithmScan Rate (Hz, 入力なし)Scan Rate (Hz, ガチャガチャ時)
OG60RP2040133sym_eager_pk51114952
sym_defer_pk52385099
FM60-S/HSTM32F40184sym_eager_pk40903101
sym_defer_pk40003102
DZ60RGB-WKLATmega32U416sym_eager_pk20971775
sym_defer_pk20971781

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である. 詳しくは以下を参照:

市販のゲーミングキーボードの殆どはChord Splitではない.

実測値

QMKがChord Splitであることは, そうでない市販のゲーミングキーボードと比べて音ゲー的に不利であるように思われる(差を感じられるほど機械的に同時押しできるプレイヤーがどれだけいるかは一旦置いておこう).

しかしキーボードの入力遅延というやつは様々な要因が絡み合う複合的なものであり, 仕様上Chord Splitでないからといって実際に遅延しないことを意味するわけではない. 真の性能を知るには, 信頼できる実測値が必要だ.

朗報がある. 最近になって, RTINGSが一部のキーボードでChord Split Delayの計測を実施してくれた!

2023-12-07時点で公開されているデータのうち, 8k Chord Split Delayが10ms以下のキーボードを以下に纏めた(丁度Wooting 60HEが最後に入る形となった).

KeyboardPolling 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 Keyboard1,0008.16.51.24.9
Keychron K3 (Version 2)1,00016.415.62.05.2光学スイッチ
Corsair K70 MAX8,0004.4N/A4.56.8磁気スイッチ, ラピッドトリガー
Keychron C1 Pro/C2 Pro (QMK)1,0009.47.72.97.2STM32L432KB (80MHz)/STM32F402RC (84MHz)
Razer Huntsman V3 Pro [Mini, TKL]1,0003.4N/A2.37.4光学スイッチ, ラピッドトリガー
Razer BlackWidow1,0004.93.13.67.5
ErgoDox EZ (QMK)1,0002.90.92.97.5ATmega32U4 (16MHz), sym_eager_pr1, debounce time 30ms
Microsoft Bluetooth KeyboardN/AN/A20.71.27.7
Razer Huntsman1,0004.53.74.97.9光学スイッチ
Razer BlackWidow Elite1,0005.03.44.68.1
Mountain Everest Max1,00010.28.25.98.4
EVGA Z204,0002.21.45.58.6光学スイッチ
SteelSeries Apex Pro1,0004.1N/A5.98.7磁気スイッチ, ラピッドトリガー
Corsair K65 PRO MINI8,0000.80.05.18.7
ZSA Moonlander (QMK)1,00011.29.43.08.9STM32F303 (72MHz)
Corsair K70 PRO MINI WIRELESS8,0001.20.04.79.0
ASUS ROG Strix Flare II Animate8,0002.20.25.39.0
Razer BlackWidow V3 Pro1,0004.02.25.69.0
Corsair K70 RGB MK.21,0007.96.05.99.1
ROCCAT Vulkan II Mini Air1,0006.95.65.49.3
GLORIOUS GMMK1,00019.717.44.39.5
Razer Huntsman V28,0000.90.05.99.6光学スイッチ
Logitech G7151,0004.52.76.69.7
SteelSeries Apex 91,0003.02.06.89.7光学スイッチ
Keychron Q Pro Series [Q1 Pro, Q2 Pro, etc.] (QMK)1,0009.06.74.29.7STM32L432 (80MHz)
Razer BlackWidow V4 Pro8,0001.70.06.19.7
Razer BlackWidow V4 75%8,0001.60.04.59.8
Wooting two HE1,0001.9N/A5.99.8磁気スイッチ, ラピッドトリガー
IQUNIX F971,00015.713.85.39.9
Ducky Shine 71,00010.4N/A5.19.9
Wooting 60HE1,0001.8N/A4.710.0磁気スイッチ, ラピッドトリガー

この表から, 以下のことが読み取れる.

思うに, ゲーミングデバイスメーカーの言う"低遅延"とはSingle-key Latencyのことのみを指しており, (音ゲー的な)同時押しなどは最適化していない, というか考慮してすらいないのだろう. 実際音ゲー以外ではクソどうでもいいし, 音ゲーにしたって(まともな性能の範疇であれば)現実的には何の差もないだろうし, そこにリソース割いてもしょうもないというのはわかる.

結論: QMKでいい.

参考文献

脚注


  1. ErgoDox EZはマトリクスが90度回転している(rowが実質columnである)ため, sym_eager_pr であっても弊害が少ないことに注意. ↩︎