hoboNicolaのPico-PIO-USB対応

概要

RP2040 on Raspberry Pi PICO

Pico-PIO-USB ライブラリを使ってUSB Host コントローラ無しで hoboNicolaアダプターを実現する。Pico-PIO-USBは、RP2040(Raspberry Pi PICO)のPIO(Programmable I/O) とGPIOポートを使ってUSBポート(デバイス/ホストのいずれか)を実現するライブラリで、これを使うことでRP2040を使ったArduinoボードは2つのUSBポートをもつことになる。

今までのhoboNicolaアダプターは、(mini) USB Host Shieldに載っているMAX3421EE CH9350L といったUSBホストコントローラとの接続や制御が不可欠だったが、Pico-PIO-USB (および Adafruit TinyUSBライブラリ)によりRP2040搭載マイコンボード単独で実装できるようになった。

RP2040を搭載しているマイコンボードならば特に品種は選ばないと思うが、Arduino-Pico が対応しているボードなら間違いないだろう。

今回は、Raspberry Pi PicoSeeed Studio XIAO RP2040 を使ってhoboNicola アダプターを実装した。その回路図やビルド方法、hoboNicolaライブラリ1.6.3版、プログラムの中身の話などを簡単にまとめた。参考のため、RP2040のデータシートはこちら

Pico-PIO-USB ライブラリに対応したメインスケッチおよびhoboNicolaLibrary1.6.3版は、こちらのページからダウンロードできます。今回のおもな対応内容は、examples/rp_hobo_nicola.ino に収めています。

ご注意

今回公開するバージョンにはいくつかの制限事項があります。この投稿の最後の方に書いてあるので、もしも同じように作ってみようと思った方は、確認したうえでどうするか判断してください。

ハードウェア

必要なものはRP2040搭載のマイコンボード、22Ωの抵抗2本、そして、キーボードを接続するためのUSB Type-Aレセプタクルのみである。

Raspberry Pi PICO版

以下は、Raspberry Pi PICOを使う場合。hoboNicolaアダプターとして使うときにはLEDが2つは欲しいので、GPIO6に増設している。

raspberry pi pico hoboNicola

SW1はリセット用で、RUN端子をLOWにするとマイコンにリセットがかかる。以下のハードウェアデザインガイドによると、RUN端子は内部でプルアップされているようなので、外付けのプルアップ抵抗は不要と判断した。

Pi PICOのKiCAD用シンボルは、RP2040ハードウェアデザインガイド(Hardware design with RP2040) の、Chapter 3. The VGA, SD card & audio demo boards for Raspberry Pi Pico and Raspberry Pi Pico W にリンクが埋め込まれている VGA-KiCAD.zip 内のものを利用している。この文献はPi PICOのようなマイコンボードを設計するための指針となっているので、とても安価なRP2040チップを使ったオリジナルボード作りの参考になる。ただ、PCB作って 0.4mmピッチのQFN56の手ハンダに挑戦するにはちょっと勇気が足りない。

ブレッドボードに実装

Pi PICOを使ったアダプターはテスト用としてブレッドボード上に組んだ。

Pico pio usb hoboNicola (pi pico)

ふつうのブレッドボードに組んだが、Pi PICOが大きいこともあって上のような配置になった。オンボードの緑色のLEDは接続中のキーボードを認識していることを示すREADY_LEDで、USB Aコネクタの脇に立てている青いLEDがNICOLA_LEDになる。

Pico pio usb hoboNicola (pi pico)

接続する必要がある端子も少ないので、こんな具合にあっさりできた。

おもな材料は以下のとおり(価格は秋月電子のホームページで調べたもの)。

品名 価格 秋月商品コード
Raspberry Pi PICO 730円
USBコネクタDIP化キット (Aメス) 120円 AE-USB-A-DIP
金属皮膜22Ω抵抗 200円(@100本) MFS25F22RB
リセット用タクトスイッチ 20円 TVBP06-B043CB-B
ふつうのブレッドボード 200円 BB-801
LED、ピンヘッダ、ジャンパワイヤなど 200円 いろいろ

秋月でまとめて買うならば、Raspberry Pi PICOベーシックセット (860円)の方がピンヘッダやUSBケーブルも付いていてお手軽かもしれない。

XIAO RP2040版

Seeed Studio XIAO RP2040を使う場合は以下のような接続になる。XIAOのKiCAD用シンボルはSeeed Stduio のサイトから取得できる。

xiao_pio_usb_hoboNicola

いずれも、RP2040のGPIO2にD+、GPIO3にD- を接続する構成とした (スケッチの修正で GPIOピンは変更できる。D- には、D+ につないだGPIO番号+1 のピンを接続すること)。

はんだ付けして実装

手持ちのユニバーサル基板、USB Type-Aレセプタクル、ピンソケットを使って作った。

Pico pio usb hoboNicola (xiao rp2040)

これを作ったときには、果たしてうまくいくんかいなという懐疑心があり、XIAOを無傷で回収できるようピンソケットを使った。

Pico pio usb hoboNicola (xiao rp2040)

XIAOにはLEDもリセットスイッチも載っているので、特に配置する部材もない。

もう一つの実装

しばらく前に、他のPCBといっしょにXIAO + MAX3421EE用のPCBも作っていたのだけど、しばらくは(数年は?) MAX3421EEを欲しい値段で入手できないだろうと思って、2箇所のハンダジャンパを埋めることで、Pico-PIO-USB + RP2040構成にできるような配線にしていた。回路は上に載せたものと同じになる。

Pico pio usb hoboNicola (xiao rp2040)
Pico pio usb hoboNicola (xiao rp2040)

XIAO RP2040の下には22Ωのチップ抵抗が2つ載っているだけ。

Pi PICOかXIAO RP2040か

Pi PICOと XIAO RP2040を比べると以下のような相違がある。

  • XIAOにはオンボードのリセットスイッチがあるが、Pi PICOにはない。
  • XIAOにはアノードコモンのRGB LED (実質的にLED3つ)とNeoPixel (WS2812B)が載っているが、Pi PICOのオンボードLEDは1つだけ。今回のhoboNicolaアダプターには、LEDが2つあったほうがよい。
  • Pi PICOにはパワーインジケータLED (PON LED)がないので、消費電流の面で1mAちょっと有利である。
  • XIAOのUSBはType-Cだが、Pi PICOはMicroB。同じMicroBでも、Pro Microコピー品のようなもげやすいものではない。

個人的には、XIAO RP2040の方が配線する手間も少なく今回の用途に適していると思う。

ビルド用のソフトウェアについて

ビルドはArduino IDE 1.8.19 (Windowsインストーラー版)で行った。そして、RP2040用のコアパッケージ(あるいはBoard Support Package)として、Earle F. Philhower, III氏による Raspberry Pi Pico Arduino core (Arduino-Pico)  を使う。hoboNicola 1.6.2版の話と重複するが、以下の手順で導入する。

Arduino-PICOの導入

Arduino IDEの環境設定/追加のボードマネージャのURL: に、 https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json   を追加してArduino IDEを再起動する。そして、ツール/ボードマネージャの入力フィールドに RP2040と入力することで Raspberry Pi Pico/RP2040 by Earle F. Philhower, III が表示される。

Arduino IDE Board Manager

今回は、バージョン2.5.2 でビルドと動作確認を行った。

また、hoboNicolaライブラリの他に以下のライブラリの導入が必須である。

いずれもZIP形式のファイルなので、Arduino IDEの スケッチ/ライブラリをインクルード/.ZIP形式のライブラリをインストール… を選択して導入する。

ビルド時の設定

Arduino IDEの ツール/ボード で Raspberry Pi RP2040 Boards (2.5.2) を選ぶとサポートするボード名がずらっと表示されるので、Raspberry Pi Pico または Seeed XIAO RP2040を選択する(以前は XIAOではなくXAIOだったが、いつの間にか修正されていた)。

Arduino IDE Board selection.

CPU Speed:120MHzとする。TinyUSB + PICO-PIO-USBでUSBホスト機能を実現する場合、120MHzまたはオーバークロックの240MHzを選択する必要がある(クロックが高いほど消費電流も大きくなるので、240MHzは試していない)。

USB Stack: は、Adafruit TinyUSB を選択する。現状のhoboNicolaライブラリは、AVR以外はAdafruit TinyUSBライブラリに依存している。

シリアルポート: については、以下のように接続中のRP2040 (Raspberry Pi Picoと表示される)が見えるはずなのでそれを選択する。

Arduino IDE Serial Port selection

その他の設定は、初期値のままとしている。Flash Sizeについては将来的に変更が必要になるかもしれない。

hoboNicolaライブラリ1.6.3版について

hoboNicolaライブラリについては機能面での変更はほとんどなく、新たに対応したRaspberry Pi Pico をターゲットボードとしたときの対策が中心。変更点は以下のとおり。

  • ターゲットボードとしてRaspberry Pi Picoを選択したとき、ボード名未定義によるビルドエラーにならないよう、数カ所に #if defined(ARDUINO_RASPBERRY_PI_PICO) などのディレクトティブを追加した。
  • Raspberry Pi Pico用のLEDポートの定義を追加。
  • Seeed Stduio XIAO RP2040を選択したとき、ビルド時に定義されるシンボル名が、ARDUINO_SEEED_XAIO_RP2040 から ARDUINO_SEEED_XIAO_RP2040 に変更になったのに対応。どちらでもビルドは通るようにした。

rp_hobo_nicola.ino について

PICO_PIO_USBを使ったhoboNicolaのメインスケッチとして、rp_hobo_nicola.inoを作成した。TinyUSBに用意されているPICO_PIO_USBを使ったUSBホスト機能へのアクセスは、すべてこのスケッチ内におさめている。ライブラリファイルのexamplesディレクトリに入ってます。

トピックス的な事柄をいくつか。

デュアルコア関係

USBホスト機能はcore1

TinyUSBライブラリに含まれている Adafruit_USBH_HostクラスはPICO_PIO_USBで実現される2つ目のUSBポートを使ってUSBホスト機能を実現してくれる。2つ目のUSBポートは、ソフトウェアで(というか、PIOで) GPIOポートをコントロールすることで送受信を実現している Bit-Banging USB なので、他の処理と同じスレッドで動かすのはうまくない。そのためUSBホスト機能は、RP2040の2つのコアのうちの core1 で実行するようになっている。USBホスト以外の処理(今回は、hoboNicolaのUSBデバイス = HIDキーボードとしての処理)は、従来同様にcore0で動作する。

core1 での処理はUSBデバイス(HIDキーボード)の接続/切断の監視やHID入力および出力レポートのやり取りになる。従来のhoboNicolaアダプターでUSBホストコントローラー(MAX3421やCH9350L)に頼っていた部分を、同じマイコンのcore1が担うようになっている。

queueを使ってコア間でキー入力を受け渡す

core1が得たHID入力レポート(接続されているキーボードでのキー入力)は、従来のアダプターではSPIやUARTで受けていたが、今回は、2つのコアで共有する queue を介してcore0に渡すようにした (queueは、Pico SDKに用意されている便利なAPIの一つ)。

core1では、入力レポートを検出すると新たにオンになったキー、オフになったキーのコード (HID Usage ID)を生成してqueueに追加する。core0で動くメインループは、数msecごとにqueue をポーリングして新たなキーイベント(コードおよびオン・オフ情報)の有無を検出する。新しいキーイベントがあればhoboNicolaライブラリに渡して従来通りの処理を行う。つまり、ポーリングの対象が変わっただけで、従来のhoboNicolaのメインスケッチと変わらない。

queue(キュー)って何? というときはwikipediaのページを参照のこと。

core0とcore1の関係

Arduino-pico では、スケッチ内に void setup1() および void loop1() が定義されているとマルチコアを意図したスケッチだと判断し、multicore_launch_core1() を使ってcore1を開始するようになっている。

リセット後には必ずcore0が開始し、このコアで void main() が実行される。マルチコアのプログラムならば、main() 内で core1を使って main1() が開始しその中から setup1() と loop1() が実行される。core1を開始したあと、ふつうのArduinoでおなじみの void setup() と void loop() の実行が開始する。擬似コードで書くとだいたいこんな具合になっている。

setup()をsetup1()より先に実行したい

void main() を自前で用意すれば、先にsetup() を実行したあとでsetup1() を実行するなども可能だが、コアパッケージの更新時になにか追加されたりするとちょっとアレなので、main() はパッケージのもの (main.cpp) をそのまま利用した。

今回は、setup1()の先頭に delay(10);  を置くことで core1の進行をちょっと待たせることにし、その間にsetup()で LED用ポートの初期化やqueueの初期化などを行わせるようにしている。

TinyUSBのUSBホストサポート

TinyUSBのUSBホスト機能を利用する際には、 Adafruit_USBH_Host USBHost  を宣言し、インスタンスUSBHost task(); を(いずれかのコアの)メインループで実行する。USBHost 内で、USB HIDデバイスの接続、切断、データ転送といったことが検出されると対応のコールバック関数が呼び出される。rp_hobo_nicola.inoでは以下のコールバック関数を利用している。

void tuh_hid_mount_cb()

HIDデバイス(キーボード、マウスなど)が2つ目のUSBポートに接続され、そのデバイスが正しく認識できると呼び出される。

rp_hobo_nicola.inoでは、キーボードが認識されたことを示す READY_LEDの点灯、tuh_hid_parse_report_descriptor() を使ったレポートディスクリプタの解析(キーボードであることの確認とレポートIDの取得) を行っている。

READY_LEDは、Pi PICOならばオンボードLED(GPIO25)、XIAO RP2040はRGBLEDの青(GPIO25)に割り当てている (hobo_board_config.h)。

接続されているキーボードがコンポジットデバイス(Composite Device) の場合はレポートIDが0以外となるので、レポートディスクリプタからそれを得ておく必要がある (入力レポートの先頭にレポートIDが追加される)。

また、コンポジットデバイス構成のキーボードによっては、接続時にこのコールバックが複数回呼ばれ、その度ごとに異なるレポートディスクリプタが読み取れたりするので、Usage Pageが0x0001(Generic Desktop)で、Usageが0x06 (キーボード)のディスクリプタを見つける必要がある。

キーボード以外が接続されている場合、抜かれるまでREADY_LEDを点滅させるようにしてある。

void tuh_hid_umount_cb()

HIDデバイスがUSBポートから抜き去られたときに呼び出される。

rp_hobo_nicola.inoでは、READY_LEDの消灯とtuh_hid_mount_cb()でセットした情報の初期化を行っている。

void tuh_hid_report_received_cb()

接続中のHIDデバイスからの入力レポートを受けたときに接続時に得たレポートIDが0以外ならばコンポジットデバイス構成のキーボード、そうでないならば単なるキーボードとみなして入力レポートを解析し、新たなキーイベント(オンになったキー、オフになったキーの情報)を抽出し、queueに追加する。

このコールバックを受けるためには、毎回 tuh_hid_receive_report() を呼び出す必要がある。

キーボードLEDのコントロール

キーボードのLED (CapsLock, ScrLock, NumLock) の状態は、PCなどのホストからの出力レポートによって決まるので、これをUSBHost を使ってキーボードに伝える必要がある。

core0のhoboNicolaライブラリが受けたLED状態(8ビットデータ)を変数 kbd_led_state にコピーしておき、core1の loop1() 内でこの値を参照する。LED値が前回と異なっていれば tuh_hid_set_report()  を使ってキーボードに反映する。ループが回るごとにやるような仕事ではないので、約150msecに一度の頻度で kbd_led_state  のチェックと反映を行っている。

スリープ時の動作について

RP2040をhoboNicolaのようなのんびりした装置に使うと、消費電流の大きさがとても気になってしまう。miniUHSやCH9350Lと組み合わせた際は、クロック50MHzに落とすことで改善したが、今回は120MHz必須である。

Pi PICOでのおおよその消費電流は、キーボードをつないだ状態で、キーボードの消費電流+25mAほど、キーボードを抜くと23mAほどだった (READY LEDも消灯する)。なお、ここでの電流は、PC側USBのVBUS電流をモニタして得ている。

PCをスリープさせたときは、キーボード自体をサスペンドさせることで消費電流を減らしたいのだが、 TinyUSBのUSBホスト機能には、接続されているUSBデバイス(キーボード)をサスペンドさせるAPIがない(そのうちできるのかも)。では自前でと思っていろいろとやってみたが、今のところ正しい方法(?)でサスペンドさせることができない。しょうがないので、以下のようにした。

  1. core0のメインループでPC側のUSBのサスペンド(スリープまたはオフ)を検出したら、multicore_reset_core1(); を実行してcore1の実行を止めてしまう。
  2. 結果として、2つ目のUSBポートに接続されているキーボードへのSOFの送出が止まるので、キーボードはサスペンドする。 この状態での消費電流は20mA程度になった。
  3. core0のメインループでUSBがリジュームしたことを検出したら、watchdog_reboot(0, 0, 10); によって全体にリセットをかけて、すべて最初からやり直す。

core1が停止すると、 約3mAほど消費電流が減るようである。キーボードサスペンドによる省電流効果はキーボードに依存するが、ほとんどのキーボードの消費電流は1mA未満となる。

スリープからの復帰時にcore1のみを再始動させようとしたが、うまくいかず全体をリセットすることにした。core1の実行が停止しても、USBHostやPico_Pio_USBライブラリの動作状態(変数の値など)を元に戻せないので、完全にリセットして出直すよりほかなかった。

1と2について、CH9350LのRESET端子をアクティブ化してSOF を停止したのと同じやり方になる。

core1のリセットによるキーボードのサスペンドは、設定のK (KEYBOARD SUSPEND) が有効の場合にのみ動作する。無効の場合は、キーボードやマイコンボードのLEDを消灯するだけで、特に省電流の処理はない。また、いずれの場合もキーボードの打鍵によるリジューム(スリープからの復帰)はできない 。

シングルコア動作

蛇足になるが、USBHost のtask() をcore0で回すことで、シングルコア動作とすることもできる。その際には、setup1() と loop1() の名前をいずれも変更し ( たとえば、usbh_setup() と usbh_loop() )、それらを setup()とloop()から呼び出せばよい。queue を使う必要もなくなるが、そのままでも問題なく動作する (hoboNicolaのようなキーボード入力を処理するだけのプログラムだから)。

ただ シングルコア動作の場合、USBホスト機能だけ停止する手段がないので、スリープ時にキーボードをサスペンドさせることができない。

設定の保存について

hoboNicola用のいろいろな設定の記憶には、Arduino-Picoに用意されているフラッシュメモリを使ったEEPROM(エミュレーション)ライブラリを使っている。現状、一つの項目書き換えるごとに watchdog_reboot(0, 0, 10); を使ってリセットをかけている。こんなことをやる必要はないと思うのだが、EEPROMライブラリのcommit() にある rp2040.idleOtherCore(); あるいは rp2040.resumeOtherCore(); によってcore1のUSBHostとしての動作が停止してしまうようなので、あっさり全体リセットすることにした。

Pi PICOやXIAO RP2040は、おそらく WinBondのNORフラッシュメモリ (WS25Q16JV…) または互換品を使っていると思うのだが、それらのデータシートには、セクタごとに10万回以上のプログラム/イレーズサイクルを実施できますよ、と書いてある。一項目ごとにフラッシュ書込みしていても、すぐに寿命ってこともないだろうと判断した。

なお、設定内容を表示したとき最後に出てくる数字(下図では 56) は、フラッシュメモリへの書込み回数を表している。

SAMD21版ではFlashStorageライブラリを使っているので、スケッチの書込み時に記憶内容が消え、書込み回数も0に戻ってしまうが、RP2040では大丈夫。

ビルドサイズなど

ビルドしたときのプログラムサイズやメモリ使用量は以下のとおり。

XIAO RP2040

Pi PICO

XIAOの方がスケッチサイズが若干大きい。

既知の問題点、制限事項、注意点など

今回のハードウェア、ソフトウェアを使ったほぼNICOLAキーボード用アダプターについて、分かっている問題点、制限事項など。

問題点と制限事項

  1. キーボードによっては、接続しても認識されないことがある (tuh_hid_mount_cb() が呼ばれない) 。手持ちのキーボードのうちの6種類のキーボードは意図通りに動いたものの、1台だけ認識されないものがあった (SANWAサプライSKB-E3U)。このキーボードは MAX3421EEやCH9350Lを使ったアダプターでは問題なく動いているが、今回のアダプターではなぜか認識されない。調査中です。
  2. USBハブを内蔵し、キーボードデバイスがハブの配下にあるようなキーボードの場合、今回のソフトウェアでは認識されないと思います。そのうち直す予定。
  3. PCやハブに接続済で動作中のアダプターに後からキーボードを接続した場合、キーボードが認識されないことがあるが、RP2040をリセットすれば認識する。リセットスイッチがない場合、PCやハブ側のケーブルを差し直すことになる。なにかタイミング的なもののような気がするので、この項目も解消したいところ。

一般的な注意点

  1. Raspberry Pi PICOに初めてArduinoスケッチを書き込む場合、シリアルデバイスとして認識されていないので、BOOTSELボタンを押したままリセットする必要がある。そうすることでWindows Explorer にRPI-RP2ドライブが現れるはずなので、ユーザー名/AppData/Temp/arduino_build_xxxxx フォルダ内にできている rp_hobo_nicola.ino.uf2 ファイルをこのドライブにドロップしてやるとスケッチが書き込まれる。
  2. Arduino IDEにPi PICOがシリアルポートとして表示されなかったり、BOOTSEL + リセット操作をしたにも関わらず PCにRPI-RP2ドライブが現れず「USBデバイスが認識されません」となることがあった。しょうがないのでPCを再起動してみたところ解決した。
  3. この問題のもう一つの解決方法は、Pi PICOのUSBケーブルをつないでいるUSBハブのポートを変更してやること。別のポートに差し直すと、再起動しなくてもシリアルポートもでてくるし、RP2ドライブも出現してくれた。

きょうのまとめ

Pico_Pio_USBライブラリによってUSBポートを追加できる、という話題は今年の春あたりからキャッチしていたのだけど、USBホストコントローラを使ったhoboNicolaアダプターが一段落するまで後回しにしてきて、ようやく試してみることができた。

余談として、RP2040という型番のうち数字部分は内蔵コア数や実装SRAM容量、不揮発メモリ容量などを表している(詳しくは、最初にリンクを載せた650ページほどのデータシートを参照)。
USBホストコントローラを使ったhoboNicolaアダプターに使うならば、架空のRP1020  (シングルコア、SRAM32K、その代わり省エネ版)で十分だろう。Pico_PIO_USB版の実装にはデュアルコアが必須だが。

消費電流については、通常動作時についてはUSBホストコントローラ版より数mA多いかなという程度(USBホストコントローラの動作電流がないので)。スリープ時の消費電流という面では、SAMD21ボードとUSBホストコントローラ(MAX3421EE、CH9350L)の組み合わせが勝っているようだ。キーボードを使っている時間より電源オンでスリープさせている時間の方が長いので、こちらを減らす方が本筋だろう。

Pico-PIO-USB ライブラリや、これを使ったAdafruit_TinyUSBライブラリも今後更新されていくと思うので、こちらもそれに合わせて更新を続けていくことになると思います。