NuttX for Raspberry Pi PicoのUSBデバイスサポート

Raspberry Pi PicoのUSBポートは、BootROMの機能によるファームウェア書き込みに使われる他、Pico SDKではMSC他いくつかのデバイスクラスで動作したり、microPythonではシリアルコンソールとして使われたりしています。
今回、NuttX for Raspberry Pi PicoでもようやくUSBが使えるようになりました。

現在、

- CDC/ACM (Communication Device Class Abstract Control Model - シリアルポート)
- MSC (Mass Storage Class - マスストレージ)
- CDC/ACM と MSC のコンポジットデバイス

の3種類のデバイスクラスでの動作を確認しています。

# RP2040のUSBデバイスコントローラ

Raspberry Pi PicoのSoCであるRP2040は、USB フルスピード(12Mbps)のホスト/デバイス両対応のコントローラを持っています。
このコントローラはどうやらRP2040の独自IPのようなのですが、Pico SDKなどは [TinyUSB](https://github.com/hathach/tinyusb) というオープンソースの組み込みシステム向けUSBスタックを利用して、この中にRP2040対応を取り込んで使っています。

NuttXはTinyUSBとは異なる独自のUSBスタックを持っているので、RP2040 DatasheetのUSBコントローラに関する記述やTinyUSBのRP2040対応実装を参考に、USBデバイスコントローラのドライバを実装しました。

# NuttX コンフィグレーション

USBのサポートに伴い、新たに以下のコンフィグレーションが追加されています。

- `raspberrypi-pico:usbnsh`
  - 最小構成の `raspberrypi-pico:nsh` の、UARTシリアルコンソールの代わりにUSB CDC/ACMをコンソールデバイスとして使用します
  - これまでRaspberry Pi PicoでNuttXを使うには、UARTコンソールをPCなどに繋ぐために別途USB - シリアル変換基板が必要でしたが、このコンフィグレーションで変換基板なしでの利用も可能になりました
- `raspberrypi-pico:usbmsc`
  - USB MSCとCDC/ACMを利用可能にしたコンフィグレーションです
  - コンソールには従来通りUARTシリアルコンソールを使用します。MSCのストレージには、SPIで接続したmicroSDカードを使用します。接続は以前の記事「[NuttX for Raspberry Pi PicoでmicroSDカードを読み書き (SPI接続)](https://qiita.com/yunkya2/items/3eefddb73f1fa0709cb3)」と同じです
  - NuttX起動時にはUSBデバイスはまだ有効になっていません。MSCとCDC/ACMはそれぞれ、NuttShellの以下のコマンドで接続を制御できます
     - `msconn` / `msdis` (MSCの接続、解除)
     - `sercon` / `serdis` (CDC/ACMの接続、解除)
- `raspberrypi-pico:composite`
  - `usbmsc` は MSC と CDC/ACM のどちらかの排他利用ですが、このコンフィグレーションはこれら両方の複合(コンポジット)デバイスを提供します
  - 起動後、NuttShellの以下のコマンドで接続を制御できます。
     - `conn` / `disconn` (MSC+CDC/ACMの接続、解除)

更に、これまでUARTコンソールを使っていた `raspberrypi-pico:displaypack` もCDC/ACMコンソールに変更することで、[Pico Display Pack](https://shop.pimoroni.com/products/pico-display-pack)を直接Raspberry Pi Picoに挿しても使えるようになりました。
(参考: [NuttX for Raspberry Pi PicoでPico Display Packを使う](https://qiita.com/yunkya2/items/2c73efe937e1c83caf2a))

# ビルドと起動

ビルドの方法は通常通りです。`raspberrypi-pico:usbnsh` をビルドしてみます。

```
$ git clone https://github.com/apache/incubator-nuttx.git nuttx
$ git clone https://github.com/apache/incubator-nuttx-apps.git apps
$ cd nuttx
$ ./tools/configure.sh raspberrypi-pico:usbnsh
$ make
```

以下のように、USBコンソールで使えるようになりました。

https://twitter.com/yunkya2/status/1405910782617952259

[Pico Display Pack](https://shop.pimoroni.com/products/pico-display-pack) や [Tiny2040](https://shop.pimoroni.com/products/tiny-2040) も USBコンソールで使ってみます。

https://twitter.com/yunkya2/status/1405910876977201156

# 未サポートの機能

- 他のデバイスクラス
  - Pico SDK (Tiny USB) ではUSB AudioやHID、MIDIなども使えるのですが、NuttXにはまだこれらのクラスドライバの実装がないようです
- USBホストサポート
  - RP2040のUSBコントローラにはUSBホスト機能もあってTiny USBでも利用可能ですが、NuttXではまだサポートしていません。Raspberry Pi PicoをUSBホストとして使うにはOTGケーブルが必要な他、Pico自身の電源を別途供給する必要もあってUSBデバイスより若干面倒だったりします
- USB MSCのRP2040ワークアラウンド
  - 「未サポート」とはちょっと違いますが、現在のRP2040 USBドライバではバルクエンドポイントをストールさせた後に回復する処理がうまく動いていないため、この機能が必要なMSCのクラスドライバに専用のワークアラウンド[^1]が入っています。
  - MSCとしての動作には特に問題はないのですが、やはり専用のワークアラウンドが入っているというのはあまり気持ちのよいものではないので、いずれは直したいところです[^2]。

[^1]: 具体的には、MSCの内部で使われるSCSIプロトコルのやり取りで、USBホストから未サポートのフラグが指定された場合やホストの要求するデータ長より短いデータを返す場合などに、本来の実装では一旦エンドポイントをストールさせることでホストにエラーを通知するのですが、RP2040の場合だけストール状態に入れず単に無視するようにしています (TinyUSBのMSCクラスドライバもストールに入らずこのような動作をしているため、それに合わせた)。
[^2]: データシートにあまり記述がなくてエンドポイントの正しいストールへの入り方/出方がよく分からないのと、既存の実装であるTinyUSBもストールに入ることがないので参考にすることができず、なかなか難しいです…。