MicroPythonでのUSBキーボード入力の調査

ここ1ヶ月ほど、Raspberry PiベアメタルMicroPythonにUSBキーボード入力を追加してみたいと思っていろいろ調べています。

用途として、
(1)USBキーボードを単純にREPLのための入力手段としてのみ使う
(2)モジュールを用意してMicroPythonのスクリプトから汎用的に使えるようにする
の2つが考えられ、当然のことながら後者のほうが応用が利きますが、APIを設計する必要があります。

考えてみると、そもそも私はこれまでMicroPythonでUSBを使ったことがありませんでした。
そこでまずはUSBデバイスモードでの利用方法を調べたのが前回の記事でした。

一方、USBホストモードについてフォーラムで情報を探してみたところ、実験的な実装がSTM32にはあることが分かりました。
元来MCU向けであるMicroPythonのUSB機能は、もっぱらUSBデバイスを作成するための機能が整備されており、USBホストはプライオリティが低いようです。

USB host mode · Issue #212 · micropython/micropython

ちょっといじってみましたが、STMHALの最新ライブラリに追随していないのか、リンクエラーになってしまってUSBホストモードを有効にした状態でビルドすることはできませんでした。

ですので、どのような実装になっているのか、ports/stm32配下のソースコードを調べてみた結果をメモしておきます。

●条件付きコンパイル
関連しそうなスイッチは以下の通りです。

mphalport.c:#ifdef USE_HOST_MODE
usb.c:#if defined(USE_HOST_MODE)
Makefile:#USBHOST_DIR=usbhost
boards/XXX/mpconfigboard.h:#define MICROPY_HW_ENABLE_USB       (1)

●REPLでのUSBキーボード入力
以下のように、コードはありますが無効化されています。

int mp_hal_stdin_rx_chr(void) {
    for (;;) {
#if 0
#ifdef USE_HOST_MODE
        pyb_usb_host_process();
        int c = pyb_usb_host_get_keyboard();
        if (c != 0) {
            return c;
        }
#endif
#endif

●USBホストモードへの変更
pyb.usb_mode()の実装(usb.c)で以下のように処理されています。

#if defined(USE_HOST_MODE)
if (strcmp(mode_str, "host") == 0) {
    pyb_usb_host_init();
} 

●USBホストモードの実装
usb.cの中に

// code for experimental USB OTG support

#ifdef USE_HOST_MODE

で始まる部分があり、

void pyb_usb_host_init(void);
void pyb_usb_host_process(void);
uint pyb_usb_host_get_keyboard(void);

の3つの関数が定義されていますが、実際にはpyb_usb_host_get_keyboardはTODO扱いになっています。

というわけで、現状の仮の実装としては

・pyb.usb_mode(“host”) を実行するとUSBホストモードが有効に(pyb_usb_host_init()が呼ばれる)
・以降は、REPLループの中でpyb_usb_host_process()とpyb_usb_host_get_keyboard()が呼ばれる

となっています。
usb.cの中に書かれているコメントに、MicroPythonにおけるUSBの実装方針があったので引用しておきます。

// MicroPython bindings for USB

/*
Philosophy of USB driver and Python API: pyb.usb_mode(...) configures the USB
on the board.  The USB itself is not an entity, rather the interfaces are, and
can be accessed by creating objects, such as pyb.USB_VCP() and pyb.USB_HID().
We have:
pyb.usb_mode()          # return the current usb mode
pyb.usb_mode(None)      # disable USB
pyb.usb_mode('VCP')     # enable with VCP interface
pyb.usb_mode('VCP+MSC') # enable with VCP and MSC interfaces
pyb.usb_mode('VCP+HID') # enable with VCP and HID, defaulting to mouse protocol
pyb.usb_mode('VCP+HID', vid=0xf055, pid=0x9800) # specify VID and PID
pyb.usb_mode('VCP+HID', hid=pyb.hid_mouse)
pyb.usb_mode('VCP+HID', hid=pyb.hid_keyboard)
pyb.usb_mode('VCP+HID', pid=0x1234, hid=(subclass, protocol, max_packet_len, polling_interval, report_desc))
vcp = pyb.USB_VCP() # get the VCP device for read/write
hid = pyb.USB_HID() # get the HID device for write/poll
Possible extensions:
pyb.usb_mode('host', ...)
pyb.usb_mode('OTG', ...)
pyb.usb_mode(..., port=2) # for second USB port
*/

USBホストモードのAPIとしてはusb_mode(‘host’)が唯一のものであり、接続されたUSBデバイスを使うためのAPIはありません。
ですので、APIは独自に設計せざるを得ない感じですが、USB_HID()などの既存のUSBデバイス向けAPIと衝突するのは望ましくないので、例えば

(1)usb_mode() は共通。現在のモードを返せるようにしておく(pyb.usb_mode()を呼ぶと’host’が返る)
(2)USB_HCD()でホストコントローラのオブジェクトを返す
(3)ホストコントローラオブジェクト経由でデバイスとの通信を行う

みたいな感じかなあと思います。

各種デバイスクラスをどこまでCで実装するかは考えどころですが、Low SpeedのHIDの場合はMicroPythonで様々なデバイスを実装できるほうが、応用が利く気がします。
MicroPythonで実装した場合に、マウスのように比較的更新速度が高いデバイスで速度が追いつくかどうかは気になりますが、これは実験してみないとなんとも言えないですね。

コメント