Blog

自作キーボードシリーズ(4):自作キーボードのアナログスティックで物理フリック入力

弊社では業務PCとしてノートPCが支給されますが、外付けキーボードやマウスを利用したい方は追加支給してもらうこともできます。ですが自分好みのキーボード・マウスを利用したい方は、各人の責任で持ち込んで利用することも認められています。
その流れで私は自作したキーボードを業務PCに接続して使っていますが、そのキーボードを作成した際の知見を少しまとめました。

アナログジョイスティックについて

アナログジョイスティックは主に縦横4方向を直感的に操作するためのデバイスのうちon/offを表す2値スイッチではなく、方向の量をアナログ的に検出できるものです。
ぶっちゃけ日頃からよく見かけるゲームのコントローラーについていて自キャラを移動させるときに使うやつです。

最近はゲーム機の修理用で単体で利用できるモジュールが手ごろな価格で販売されています(例:サイトAサイトBサイトC)ので、その中からキーボード基板に固定しやすく、設置面積が小さく、高さが低いものとして写真の中で黄色枠で囲ったものを選びました。
(K-Silverと言うメーカのJP19の互換品のようです)

これは元々両手に分かれて使うあのコントローラーで使われているものを基板で転用しやすくするために端子の形を変えたものではないでしょうか。端子の形を電子工作で扱いやすい通常のスルーホール端子に変えると同時に、基板に固定するための突起が付いているところがポイント高いです。
キーボードに取り付ける際、マウスだけの使い方では勿体ないと思い、出来れば折角4方向を指示できるのでカーソルキーの入力として利用したいと考えました。

qmkのアナログスティック

私は自作キーボードでqmk_firmware(Quantum Mechanical Keyboard)というオープンソースのファームウェアとその派生物を使っています。qmkでアナログジョイスティックを使う一般的な方法は、ゲームコントローラーのアナログスティックとする方法と、ポインティングデバイスとしてマウスカーソルを移動させるために使う2種類です。
カーソルキーとして使うにはスティックを倒しこんだ際にキーをONに、戻したり逆方向に倒した時にOFFにする必要があり、既存のプログラムでは対応できません。
動きを変えるには全面的に差し替えてしまうと今度はポインティングデバイスとして利用するのが難しくなる懸念があります。
このため、ポインティングデバイスのロジックが呼び出すアナログスティックの状態確認部分に手を入れることにします。
rules.mkで POINTING_DEVICE_DRIVER=custom を指定してからカスタムドライバの関数を定義します。

動作方針

自作キーボードは使用中に動作モードを変更する手段としてキーを押したときに入力される文字を変更するためのキーマップがあります。キーマップではキースイッチそれぞれを押した際にどのキーを入力したことにするかを割り当てるために文字コードに似たキーコードという物を使います。
そしてキーボードでマウスの代わりをするためにUSBを使ってPCに送ることはせず、qmk内部で異なる処理をしてマウスの動作を発生させるマウスキーと呼ばれる機能があり、専用のキーコードが存在します。

今回ジョイスティックをカーソルキーの代わりに使うと決めましたが、マウスの代わりにマウスカーソルを操作したくなることもあるかもしれませんので、マウスキーが指示された場合はマウスとして動くことにします。

ということで、下記2つの方針とします

  1. スティックを倒した時は各方向に指示されたキーコードを発生させる
  2. その方向に定義されたキーコードがポインティングデバイスの移動移動を扱う場合は倒し込み角度を返す

qmkでデバイスドライバなど機能拡張を行う場合は、qmkのソースコード本体で(weak)宣言されている関数と同名の関数を定義することで差し替えることができます。
今回はquantum/pointing_device/pointing_device.h を参考にしてポインティングデバイスを利用する際に使われる下記の関数を定義しました。

  • pointing_device_driver_init()では、スティックで利用するアナログ入力のピンを初期化ます
  • pointing_device_driver_get_report()では、上記ピンの電圧を確認してスティックの倒しこみを検出し、スティックの向きとキーマップの定義内容から実行する動作をqmkに伝えます
  • pointing_device_task_user()では、簡易的なドリフト対策をします

元々のqmkではquantum/pointing_device/pointing_device.c と drivers/sensors/analog_joystick.c の2層構造でしたが、両方のレイヤで加工が必要になってうまくまとめられなかったので quantum 側の関数でドライバー側の処理もしてしまうことにしてしまいました。

実装

※コード全文は GitHub に置きました

処理のトリガは通常のマウス操作と同じく pointing_device_driver_get_report() が呼び出されることでトリガにします。この関数が呼び出されるたびに毎回マウスレポートを報告してしまうと過剰すぎるために間隔をあける必要があります。
timer_elapsed() は引数で示した時刻から経過時間を返すので、これがインターバル時間(ANALOG_JOYSTICK_READ_INTERVAL)を超えるまではスキップするようにしておきます。
次に analog_joystick_read() を呼び出してスイッチとして確認と動作を実行した上で処理が残ったポインターのアナログ的移動を取得します。

analog_joystick_read() は既存のqmkでは drivers/sensors/analog_joystick.c で行う処理のレイヤですが、キーマップを確認して操作を依頼するという上位レイヤの処理が入ります。
(qmkの流儀に従うなら返り値として戻す方が好ましいでしょう)

倒しこみ量のアナログ値をスイッチとして判定する際、境界値の前後でon/offを割り当てるとチャタリングと似た症状が発生してしまいます。
このためにonにする値とoffに戻す値を分けて設定します。


ジョイスティック1本に対して必要な情報は、3種あります

  • 確認するアナログピン2本 ※1
  • アナログピンのニュートラル位置 ※2
  • 4方向の仮想的なマトリクス位置 ※3

その他に、内部データと状態保存のために下記3種の情報が必要でした

  • マウスカーソルのための倒しこみと速度の対応表 ※4
  • マウスホイールのための倒しこみと速度の対応表 ※5
  • 仮想キースイッチの状態マップ ※6

関数全体は以下のようになります。

処理の流れとしては下記の様になっています。

  • ※a スティックごとに縦横のそれぞれ2回ずつ処理にする
  • >※b アナログ値を取得
  • >※c -100 ~ 100 の割合値に加工
  • >※d アナログ値ごとに正の方向、負の方向の2回処理にする
  • >>※e キーコードを確認してマウスカーソル関連だったら返答用reportに記録
  • >>※f 通常キーで前回onだった場合、キーコードが設定された方向を正として、off境界以下になっていたらキーを離したアクションを発生して記憶
  • >>※g 通常キーで前回offだった場合、キーコードが設定された方向を正として、on境界値以上になっていたらキー入力アクションを発生して記憶

こうすることで、通常時はカーソルキーだけれどレイヤーを変更するとマウスカーソルやホイール操作ができるようになったり、上下はマウスホイールで左右はブラウザバックなどと言った自由なキー割り当てが可能になりました。

さて、これを升目状に並べたら物理フリックキーボードが出来上がるわけですが、それはそれで大変そうなので将来の課題にさせてください。

ドリフト対策

今回アナログジョイスティックをあまり品質が良いものでないものを選んでしまったため、乱暴に扱った時などに値がおかしいことが頻繁に発生しました。これは携帯ゲーム機などでドリフト現象などと呼ばれているものです。これをプログラムで対処しようと思います。
簡易的なアプローチは2つあり、一つはホームポジションの0とする範囲を広げること。二つ目として異常を検出して修正することです。
ここでは2つ目のアプローチについて説明します。

ドリフトとはホームポジションがずれてしまい、操作をしていなくても一定の固定の操作が続いていると誤認してしまう状態ですので、ドライバーからの応答が一定時間続いたらドリフトと判断することにします。今回は3秒続いたらドリフトと判断してホームポジションを今の位置に変更しています。

この3秒ルールでドリフトに対してイラつくことが少なくなりました。
実際にはこの処理だけで全てのドリフトに対処できないため、特定の操作でポジションリセットを行っています。

おわりに

自作キーボードのファームウェアはソフトウェアと電子回路、機械的な事柄まで幅広い知識が身に着けられるのでソフトウェアエンジニアとして幅を広げるにはいい課題ではないでしょうか。
キーボードと言う毎日使うデバイスですので、活動の結果がQOLに直結する点も素晴らしいと思います。
この記事で興味を持って一人でも多くの仲間が現れることを期待しています。

ニフティでは、
さまざまなプロダクトへ挑戦する
エンジニアを絶賛募集中です!
ご興味のある方は以下の採用サイトより
お気軽にご連絡ください!

ニフティに興味をお持ちの方は
キャリア登録をぜひお願いいたします!

connpassでニフティグループに
参加いただくと
イベントの
お知らせが届きます!