Open5

メモ: ZMKでPMW3610のXY Swap・Invert設定がリセットされる問題に対処する

kot149kot149

背景

ZMKキーボードファームウェアとPMW3610光学センサーを用いたトラックボール付きキーボードを使用している。
ドライバーには以下を使用。
https://github.com/badjeff/zmk-pmw3610-driver

このドライバーには、XYをswapしたり、XYそれぞれ方向をinvertする設定がある。
https://github.com/badjeff/zmk-pmw3610-driver/blob/14d39b7a6944d1014544a2382707b14724c22f49/Kconfig#L22-L29

キーボードの使用中にPMW3610の基板を物理的に外して再度付け直すと、これらの設定がリセットされる問題が発生する。意図的に付け外ししなくても、振動で一瞬コンスルーの接触が切れるだけでも同じ現象が起こるようで、稀にXYが逆になって困っていた。
キーボードを再起動すれば解消するが、特に無線接続時はいちいち時間がかかるのでできればやりたくない。

kot149kot149

原因究明・仮説

ドライバーでswapやinvertがどのように実装されているか見てみると、単にソフトウェアでswap、invertしているだけだった。これはコンパイル時に確定するので、動作中に無効になることは考えにくい。
https://github.com/badjeff/zmk-pmw3610-driver/blob/14d39b7a6944d1014544a2382707b14724c22f49/src/pmw3610.c#L426-L436

PMW3610のデータシートを見てみると、ハード側でも同じ機能が提供されており、RES_STEPレジスタ(0x85)に所定の値を書き込むことで設定可能となっている。
レジスタは8ビットで、上位7ビット目がSWAPXY、6ビット目がINV_X、5ビット目はINV_Y、下位0~4ビットがCPIを200で割った値を入れることになっている。
SWAPXYINV_XINV_Yの値はいずれも0が無効、1が有効。

ドライバーでそのレジスタをどうしているか見てみると、CPIだけ設定していて、他は常に0(無効)になっている。
https://github.com/badjeff/zmk-pmw3610-driver/blob/14d39b7a6944d1014544a2382707b14724c22f49/src/pmw3610.c#L112-L131

(ちなみに、このbadjeff版のドライバーの他に、inorichi版Zephyrに含まれるドライバーの計3種類が存在するが、いずれもハード側のSWAP機能は使用していない。まあ、ソフトで簡単に実装できるものをわざわざレジスタに書き込むとかしなくても、ということなのだろうか。)

この設定がハード側のデフォルト値と異なっているから、基板再接続によりリセットされると反転していしまうのではないかと推測できる。
そこで、データシートに記載のデフォルト値を見てみると、0x86 = 0b1000_0110、つまり、SWAPXYは有効、INV_Xは無効、INV_Yは無効、CPIは1200のようだ。
(PMW3610のデータシートにはフルのデータシートとフルではないデータシートが存在するが、0x86はフルではない方に書かれている値で、フルの方には0x06と書かれている。実際の挙動からすると、フルではない方(0x86)が正しいと思われる。)

よって、以下の仮説が立てられる。

仮説: SWAPXY有効、INV_X無効、INV_Y無効となるようにRES_STEPレジスタ(0x85)に値を書き込めば、基板が再接続されても反転しなくなる。

kot149kot149

検証

ソフトで制御するのではなく、レジスタに値を書き込むことでswapとinvertを行うようにドライバーを書き換えてみた。

    uint8_t value = (cpi / 200);

    // Set swap and invert bits in RES_STEP register
#if IS_ENABLED(CONFIG_PMW3610_SWAP_XY)
    value |= (1 << 7);
#endif
#if IS_ENABLED(CONFIG_PMW3610_INVERT_X)
    value |= (1 << 6);
#endif
#if IS_ENABLED(CONFIG_PMW3610_INVERT_Y)
    value |= (1 << 5);
#endif

そして、ハード側のデフォルト値と一致するように、

CONFIG_PMW3610_SWAP_XY=y
CONFIG_PMW3610_INVERT_X=n
CONFIG_PMW3610_INVERT_Y=n

と設定した。

その結果実際のマウスセンサーの向きと異なっている場合は、ZMKのInput Processorを用いてソフト側で制御すればよい。
CPIは普段使用している800のままにした。

これで再度基板を付け外ししてみたところ、XYの反転が起こらなくなった
XとYのinvertの方を有効化(=y)してみると、XYの反転は起こらないままinvertだけ反転した。
よって、仮説は正しかったと判断できる。

CPIもリセットされるのでは(データシートによればデフォルト値は1200で、800より大きく増えるはず)と思ったが、こちらは体感変わっていない。

kot149kot149

これからのアクション

ドライバーを編集したのをプルリクするとか、ZMKのDiscordに投げるとかしようとも考えたのだが、ZMKがv0.4(Zephyr v4.1)のリリースが控えていることを考慮して一旦様子見することにした。
というのも、Zephyrに含まれるPMW3610ドライバーがZMK v0.4(Zephyr v4.1)では使用可能になるからだ。これまでZMKではzmk-pmw3610-driverをモジュールとして外付けしていたが、それとdevicetreeのcompatible名が衝突するため、このまま両立はできない。Zephyrのドライバーを使うのが主流になるのか、名前の衝突を回避した上でzmk-pmw3610-driverを使い続けるのか、どちらに転ぶか見届けてからにしたい。

もしZephyrの方が主流になった場合は好都合で、Zephyrのドライバーは一度レジスタの値を読み取ってから、invert・CPIの部分だけ書き換えてレジスタに書き戻すことで、デフォルト値を上書きしないようになっている。よって、ドライバーを書き換えずとも、invertを有効にしない限り反転問題は発生しないはずだ。
https://github.com/zephyrproject-rtos/zephyr/blob/2dd8570eecea0bae4a11ab30dc42695ec20e723c/drivers/input/input_pmw3610.c#L444-L457

または、根本的に接続が切れないようにするという解決策もある。
自分はイスのアームレストにキーボードを設置しているので、イスを立ったり座ったりすると振動が発生してしまう。普通に机の上に置いていれば問題は滅多に起こらないはずだ。
また、使用しているキーボードではPMW3610基板をコンスルーで接続しているが、別に基板を頻繁に付け外しする必要があるわけではないので、はんだ付けしてしまっても構わないだろう。

kot149kot149

2025/09/06にRES_STEPレジスタを使用してswap・invertするようにドライバーが修正された (差分) 。この新しいバージョンのドライバーを用いる場合は、ドライバーをいじらなくても、以下のような設定にすることで再接続時のリセットを回避できる。

&trackball {
    cpi = <800>;
    swap-xy;
};

(invert-x, invert-y, CONFIG_PMW3610_SWAP_XY, PMW3610_INVERT_*は指定しない。