[自作キーボード]RP2040, QMKで分割キーボードに対応する

目次

Abstract

  • RP2040, QMK環境で分割キーボードに対応してみた
  • RP2040環境では左右間の通信にシリアル通信を用いる
    • 半二重と全二重の2種類が選択可能

Introduction

前回まででLCD対応の方は一旦止めて、今回は分割キーボードに対応してみます。 やり方は、QMKのドキュメントに記載があるのでその通りに実施します。

コードの方は前回のLCD対応までで色々いじったので、まっさらにしてから実施

参考: QMK Split Keyboard

Preparation

コードを書き始める前に、分割キーボードに使う通信方式などの選択があるので先にそちらを確認する

左右間の通信方式

  • QMKでは左右間の通信方式はI2CSerialの2種類
  • RP2040(ARM)の場合は場合は、Serialしか使えないみたいなのでSerialを使用する

Serial Driverの通信方式

  • 参考
  • シリアルドライバの種類は3種類
    • Bitbang
      • ソフトウェアでUARTをエミュレートする方式で、通信用に使用するGPIOは好きなものを使用できる
        • 下のUSARTの場合は、MCU側でUART用に定義されているGPIOを使用する必要有り
      • ただし、効率悪いので専用のハードウェアがあるならそちらを使うべきとのこと
      • また、ARM(RP2040含む)の場合は、WS2812(フルカラーLED)のドライバと一緒に使うと問題が発生するとのことなので、使用しない方が良さげ
    • USART Half-duplex(半二重)
      • VCC, GND, TX/RXの3線式
      • 左右間の送受信を1本で共用で行う
      • 外付けのプルアップ抵抗が必要
    • USART Full-duplex(全二重)
      • VCC, GND, TX, RXの4線式
      • 左右間の送受信を2本で別々に行う
  • ドキュメント内では半二重か全二重がおすすめとあるので、今回は全二重のUSART Full-duplexを使います

【補足】半二重か全二重か

最終的にキーボードとして作るときは、左右間をつなぐケーブルとジャックにも影響するのでそちらも踏まえて検討する必要有り。また全二重の場合は左右間でTX, RXをクロスさせる必要があるので、左右の基盤を別設計で入れ替えるか、ケーブル側でクロスさせる必要が有る。

4極よりは3極のケーブルの方が出回っているので、ケーブル自作する気が無いなら3極(半二重)で作った方がケーブルの選択肢が増えそう。


Serial Driverのサブシステムの選択

  • 参考
  • 以下の3種類の形式が選択可能
    • SERIAL
      • MCUがこの形式に対応している場合は、この形式を使用しろとのこと
      • MCUが対応しているかどうかは、そのMCUのmcuconf.hを見てxxx_SERIAL_USART0みたいなdefineがあるかを確認すれば良い(xxxはMCU名)
        • RP2040は非対応
    • SIO (Single-cycle IO)
      • MCUが↑のSERIALドライバに対応してない場合は、こちらを使えとのこと
      • MCUが対応しているかどうかは、そのMCUのmcuconf.hを見てxxx_SIO_USE_USART0みたいなdefineがあるかを確認すれば良い
    • PIO (Programable IO)
      • RP2040のみ選択可能な機能
      • 特徴は以下
        • メリット
          • どのGPIOピンでもシリアルのTX,RXピンとして使用可能
          • 半二重通信で必要となる外部プルアップ抵抗が必要ない
            • RP2040に内蔵されている内部プルアップを使用するため
        • デメリット
          • RP2040で使えるPIOは2系統のみ
            • 便利な機能ではあるので、このシリアル通信で使うか他の用途に残しておくかは設計次第

RP2040の場合は、SIOかPIOが選択できます。今回は全二重でピンアサインの問題も無いのででSIOを使用します


回路図

  • 今回の検証で作成するキーボードの回路図は以下
    • RP2040で使えるUARTのTX,RXのデフォルトのGP0,GP1に設定されているので、UART用のピンはそのままそれを使います
  • 2つの1x4(1row, 4col)のキーボードを繋ぎます
    • 左右のキーボードのピンアサインはそれぞれ別々にしています
      • 最終的に作成するキーボードで、別々のピンアサインにするかもなので別々にアサインして問題にならないかも合わせて検証

回路図

↓回路図通りに実際に作ったキーボード

 ※2x2のキーボードに見えるが、1x4の5ピン仕様のキーボード(せんごく電気のジャンクコーナーで1枚100円。。)


Method

準備で諸々が決まったので、実際にコーディングしていきます。 実際にやるのは、シリアルドライバの設定とスプリットキーボードの設定の2点です。

1. 新規でキーボードファイルを作成する

  • new-keyboardコマンドで新規にキーボードフォルダを作成する
    • 今回はrp2040splitという名前のキーボードにする
(venv) user@user:~$ qmk new-keyboard
~~
Keyboard Name? cepst/rp2040split
~~
(venv) user@user:~/qmk/qmk_firmware$

2. シリアルドライバの設定をする

シリアルドライバの設定は、rules.mk, config.h, halconf.h, mcuconf.hの4箇所で行います

rules.mkの編集

  • シリアルドライバーに何を使うかの設定を行います
    • 通常はusartを指定しますが、RP2040の場合はvendorを指定する必要があります
  • 編集後のrules.mkは以下
SERIAL_DRIVER = vendor

config.hの編集

  • シリアル通信の方式、シリアル通信に使うピンの定義を行います
    • 今回は全二重通信を使うのでSERIAL_USART_FULL_DUPLEXを設定
    • UARTに使うピンは以下で設定
      • SERIAL_USART_DRIVER, SERIAL_USART_TX_PIN, SERIAL_USART_RX_PIN
  • 編集後のconfig.hは以下
#pragma once

/* serial driver setting */
#define SERIAL_USART_FULL_DUPLEX
#define SERIAL_USART_DRIVER SIOD0
#define SERIAL_USART_TX_PIN GP0
#define SERIAL_USART_RX_PIN GP1

halconf.h

  • シリアルドライバのサブシステムを有効化します
    • 今回はSIOを使うので、HAL_USE_SIOを有効化
  • 編集後のhalconf.hは以下(デフォルトは無いのでファイルを作成して追記する)
#pragma once

/* serial driver setting */
#define HAL_USE_SIO TRUE

#include_next <halconf.h>

mcuconf.h

  • RP2040のUARTはUART0, UART1の2系統があるので、どちらを通信にSIOに使うかを設定する
    • 今回はUART0を使うのでRP_SIO_USE_UART0 TRUEを設定
      • デフォルトのRP2040のmcuconf.h側ではUART0, UART1のどちらもFALSEで#defineされているので、一度#undefしてから#defineする
  • 編集後のmcuconf.hは以下(デフォルトは無いのでファイルを作成して追記する)
#pragma once

#include_next <mcuconf.h>

#undef RP_SIO_USE_UART0
#define RP_SIO_USE_UART0 TRUE

3. 分割キーボードの設定をする

分割キーボードとしての設定は、info.json, keymap.cの2箇所で行います

info.json

  • 分割キーボードの有効化、分割キーボードのキーマトリクスに使用するピン、レイアウトの3箇所を変更します
  • 分割キーボードの有効化
    • split - enabledをtrueに変更します
  • 分割キーボードのキーマトリクスに使用するピンの定義
    • 左手側
      • 通常通り、ルート直下のmatrix_pinsの中に記載する
    • 右手側
      • split - matrix_pins - rightタグの中に記載する
    • 補足
      • 左右のキーマトリクスのサイズは一致している必要が有るので注意
        • 例. 左手が2x2なら、右手側も2x2にする必要がある
          • 左右どちらかで使用しない行列のピンがある場合はNO_PINを設定する
  • レイアウトの定義
    • layoutタグに書くキーマトリクスの定義は左右を縦方向に結合したものを記載する
      • イメージとしては以下の図

分割キーボードのキーマトリクスの定義イメージ。(layoutタグの中の定義は左右を縦方向に結合したものを使う) image

編集後のinfo.jsonは以下

{
    "manufacturer": "cepst",
    "keyboard_name": "cepst/rp2040split",
    "maintainer": "cepst",
    "bootloader": "rp2040",
    "diode_direction": "COL2ROW",
    "features": {
        "bootmagic": true,
        "command": false,
        "console": false,
        "extrakey": true,
        "mousekey": true,
        "nkro": true
    },
    "matrix_pins": {
        "cols": ["GP2", "GP3", "GP4", "GP5"],
        "rows": ["GP6"]
    },
    "processor": "RP2040",
    "url": "",
    "usb": {
        "device_version": "1.0.0",
        "pid": "0x0000",
        "vid": "0xFEED"
    },
    "split": {
        "matrix_pins": {
            "right": {
                "cols": ["GP19", "GP18", "GP17", "GP16"],
                "rows": ["GP20"]
            }
        }
    },
    "layouts": {
        "LAYOUT_ortho_1x4x2": {
            "layout": [
                {"matrix": [0, 0], "x": 0, "y": 0},
                {"matrix": [0, 1], "x": 1, "y": 0},
                {"matrix": [0, 2], "x": 2, "y": 0},
                {"matrix": [0, 3], "x": 3, "y": 0},
                {"matrix": [1, 0], "x": 0, "y": 1},
                {"matrix": [1, 1], "x": 1, "y": 1},
                {"matrix": [1, 2], "x": 2, "y": 1},
                {"matrix": [1, 3], "x": 3, "y": 1}
            ]
        }
    }
}

keymap.c

  • info.jsonのlayoutで定義したレイアウトに沿ってキーコードを定義します
    • 今回は左右で1x4(1行4列)を使っているので、2x4(2行4列)のキーボードとしてキーコードを定義する
#include QMK_KEYBOARD_H

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    [0] = LAYOUT_ortho_1x4x2(
        KC_1,    KC_2,    KC_3,     KC_4,
        KC_5,    KC_6,    KC_7,     KC_8
    )
};

4. コンパイルして書き込む

  • 上記の通りコードを変更して、コンパイルを実施
  • 特に問題なくコンパイルできた
  • 書き込みは出来上がったファームウェアを、左右それぞれのRaspberry Pi Picoに書き込む
    • 左右は同じファームウェアを書き込むことになります

Result

  • 分割キーボード用のファームウェアを書き込んだ後の動作確認
  • 左右のキーが問題無く動作した
  • 今回は左右を判別させる設定をしていないので、左手側にUSBケーブルを差さないと正しく動作しません
    • キーボードのどちらが右か左かを設定させる方法についてはAppendixで記載する

Conclusion

左右分割のキーボードを作成してみました。わりと単純な作業で分割キーボードが実現できて良いですね。ただ、今回は左右で同じファームウェアを使っていますが、左右でそれぞれLCDをつけて、左右それぞれで異なる画像を表示するとかなったときは、別々にファームウェアを作成する必要が出てきますね。そこらへんどうするかも後々考える必要が出てきそう。。。

次回は、ロータリーエンコーダやフルカラーLEDなどの対応をしていく予定です。



Appendix

分割キーボードの左右を決める方法

  • 参考
  • 左右の設定方法については上記に詳しく記載がある
  • 主に以下の4種類
    • SPLIT_HAND_PIN
      • 任意のピンを設定して、そのピンのLOW/HIGHの状態で左右を決める
        • HIHGだと左、LOWだと右の判定になる
      • ピン数にあまりが有るならこの方法が手っ取り早い
    • SPLIT_HAND_MATRIX_GRID
      • キーマトリクスの未使用部分を使用して判定する方法
    • EEPROM
      • eepromに左右の情報を書き込み、起動時にその値を読むことで判定する方法
    • MASTER_RIGHT, MASTER_LEFT
      • USBケーブルが常に片側のキーボードに繋がれる状況で使うもの
      • USBケーブルが繋がれている方を右手 or 左手と判定する
      • 常に左手に繋がれる場合はMASTER_LEFT、常に右側に繋がれるならMASTER_RIGHT
        • デフォルトはMASTER_LEFTに設定されている
          • そのため、上記の左右設定を何もしてないと、左手側にUSBケーブルを指したときだけ正しく動作する挙動になる

左右対称のキーボードやピン数に余裕がある場合はSPLIT_HAND_PINを使って、ピン数に余裕が無いとか左右非対称でUSBケーブル指す方決まってるときの場合にはMASTER_RIGHT/LEFTを使う感じかな。