USBキーボードを SBC につなぐ KKBD-USB その2 ~Lチカからジャンパー&UART&USBホストまで~

前回のおさらいと今回の話
前回の記事では、KKBD-USB の概要と要件定義まわりの話をしました。USB キーボードを Raspberry Pi Pico(RP2040)に繋ぎ、TinyUSB でキー入力を受けて UART で SBC へ流す、という基本的な考え方でした。
今回はいよいよ実装の話です。Phase 1 から Phase 3 まで、段階的に積み上げながら進めました。実装計画では最初から「一気に全部作るのはやめよう」と決めていて、Lチカ → ジャンパー読取 + UART → USB ホスト基盤、と細かく区切りました。各 Phase で実機動作確認を挟みながら進めるつもりでした。実際、この段階分けが何度か助かりました。
Phase 1: ビルド環境構築・Lチカ
Pico SDK をセットアップする
Pico SDK はホームディレクトリに clone するのが基本です。KKBD-USB のリポジトリ内に入れると誤コミットの温床になるので、外に出します。
`git clone -b 1.5.1 https://github.com/raspberrypi/pico-sdk.git \
--recurse-submodules ~/pico-sdk
export PICO_SDK_PATH=$HOME/pico-sdk`
CMake 4.x で盛大にハマった
最初のつまずきがここでした。手元の環境が CMake 4.x だったのですが、ビルドしようとすると途中でこんなエラーが出て止まります。
`CMake Error at CMakeLists.txt:1 (cmake_minimum_required):
Compatibility with CMake < 3.5 has been removed from CMake.`
Pico SDK 1.5.1 に同梱されている TinyUSB のサブビルド(pioasm / elf2uf2)が古い cmake_minimum_required を宣言しているため、CMake 4 が弾くという問題です。
最初に「CMakeLists.txt に set(ENV{CMAKE_POLICY_VERSION_MINIMUM} 3.5) を書けばいいか」と試してみたんですが、これが効かないんです。CMakeLists.txt 内で set(ENV{...}) を書いても、ビルドフェーズで外部プロジェクトのサブ cmake が起動するタイミングには伝播しないことがわかりました。
解決策は単純で、シェルで export するのが正解です。
`export CMAKE_POLICY_VERSION_MINIMUM=3.5
cmake -S . -B build -G Ninja
cmake --build build`
毎回手で書くのも面倒なので、scripts/build.sh にまとめました。CMake のバージョンを自動検出して 4.x なら自動で export するようにしています。
`export PICO_SDK_PATH=$HOME/pico-sdk
./scripts/build.sh # 通常ビルド
./scripts/build.sh --clean # build/ 削除して再ビルド`
Lチカ確認
Phase 1 の受け入れ条件はシンプルで「LED が 500ms 周期で点滅すること」です。書き込んだら Pico の緑 LED がチカチカし始めました。Pico SDK ビルドパスが通ったことが確認できました。
Phase 2: ジャンパー読取と UART 送信
GPIO 10〜13 をプルアップ入力で初期化
KKBD-USB はボーレートと行末コードをジャンパーピンで選択します。JP1〜JP4 はそれぞれ GPIO 10〜13 に対応し、gpio_pull_up() で内蔵プルアップを有効にします。OPEN 時は High、SHORT(GND 接続)で Low になります。
物理状態と論理ビットの反転
「ジャンパーを繋いだら機能 ON」という直感的な動作にしたかったので、論理ビットは SHORT=1 に反転しています。gpio_get() が 1(OPEN)のとき論理 0、0(SHORT)のとき論理 1 として扱います。

decode 関数をホスト側でテストできるように分離
config_decode_baudrate() と config_decode_line_ending() は純粋関数として実装し、KKBD_HOST_TEST マクロで Pico SDK 依存コードを除外できるようにしました。Pico 実機がなくても decode ロジックのユニットテストが通せます。
“KKBD-USB Ready” で動作確認
USB-シリアル変換アダプタ(3.3V TTL 対応品)を GPIO 0 (UART0 TX) に繋いでシリアル端末で確認しました。9600 / 19200 / 38400 / 115200 bps の 4 パターン全部で KKBD-USB Ready が 1 秒毎に届くことを確認して合格です。
Phase 3: USB ホスト基盤(TinyUSB)
tusb_config.h の設定
TinyUSB のホスト設定は tusb_config.h に書きます。CFG_TUH_HID=4(HID 最大 4 インターフェース)、CFG_TUH_HUB=0(USB ハブ非対応)、BOARD_TUH_RHPORT=0(Pico の USB ポート 0)が主要な設定です。HID インスタンスを 4 つ確保しているのは、キーボード 1 台でも複数の HID インターフェースを持つことがあるためです。

USB OTG ケーブルの罠
Pico の Micro-B コネクタは VBUS ピンが「入力」設計です。USB ホストとして動かす場合、キーボードに VBUS(5V)を出力しないといけませんが、普通の OTG ケーブルには給電回路がありません。これで最初「接続しても何も起きない」とハマりました。
対策は 2 パターンあります。方式 A は「給電パススルー機能付き OTG ケーブル」を使う方法(購入時に「VBUS パススルー」と書いてあるものを選ぶ)。方式 B は Pico のピン 39(VSYS)にショットキーダイオード経由で外部 5V を供給する方法です。VBUS(ピン 40)に直接入れると PC への逆流の危険があるので VSYS 経由が正解です。
給電パススルー OTG ケーブルに変えたらあっさり認識されました。
tuh_hid_mount_cb / tuh_hid_umount_cb でログ確認
キーボードを繋いだらこんなログが出てきました。
`[USB] TinyUSB host initialized
KKBD-USB v0.1 (Phase 3) - Waiting for USB keyboard...
[USB] Keyboard connected (addr=1, instance=0)`
接続/切断を繰り返しても毎回ログが出ることを確認して合格です。
マルチ HID キーボードの予期せぬ収穫
実機で使ったキーボードが複数の HID インターフェースを持っていて(Boot Keyboard + メディアキー用 Generic HID など)、こんなログも出てきました。
`[USB] Non-keyboard HID ignored (proto=0)`
本来は USB マウスを別途用意して「マウスは無視される」ことを確認する TC-305 でしたが、このキーボードのマルチインターフェース構成がそのまま代替テストになってくれました。フィルタが正しく動いていることが意図せず確認できてしまいました。
次回予告
Phase 1〜3 で「USB キーボードを接続したら接続ログが出る」ところまで来ました。次回 Part 3 では、Phase 4〜6 の実装(キー入力本実装・修飾キー・キーリピート・LED・マニュアル整備)について書きます。
関連リンク
- GitHub: https://github.com/kuninet/KKBD-USB
- Issue #9 (Phase 1): https://github.com/kuninet/KKBD-USB/issues/9
- Issue #10 (Phase 2): https://github.com/kuninet/KKBD-USB/issues/10
- Issue #11 (Phase 3): https://github.com/kuninet/KKBD-USB/issues/11
- Raspberry Pi Pico SDK: https://github.com/raspberrypi/pico-sdk
- TinyUSB: https://github.com/hathach/tinyusb
- USB 簡単ホスト(OTG 給電の参考): https://q61.org/blog/2021/06/09/easyusbhost/