Raspberry Pi Pico Wのハードウェアとソフトウェアを覗いてみる

2022年6月末、Raspberry Pi Pico (以下 Pico) に無線機能が搭載された [Raspberry Pi Pico W](https://www.raspberrypi.com/news/raspberry-pi-pico-w-your-6-iot-platform/) (以下 Pico W)が発表されました。
これまでは、Raspberry Pi Picoをネットに繋ぐには外部に何らかの通信モジュールを接続する必要がありましたが、基板上に無線チップが搭載されたことで、WiFi接続を簡単に利用できるようになりました。

**注: 2022年8月現在、Raspberry Pi Pico Wはまだ技術基準適合証明(いわゆる技適)が取得されていません。[スイッチサイエンス](https://www.switch-science.com/catalog/8171/)で技適取得後の発売を予定しているとのことなので、国内で無線機能を使うためには技適取得後に国内販売される商品を購入するか、海外から購入した場合は総務省の[技適未取得機器を用いた実験等の特例制度](https://exp-sp.denpa.soumu.go.jp/public/)による届け出(180日以内の実験等目的限定)が必要となります。**

今回、Pico Wを入手してみたので色々調べてみることにします。

# ハードウェア

## 外観

Picoで基板上にRaspberry Piのロゴが描いてあったあたりに無線通信チップ(Infineon CYW43439[^1])とアンテナが搭載されている他は、大きな違いはありません。ピン配列もPicoと全く同じでUSBもmicro-Bのまま、基板上にリセットボタンがないのも変わっていません。

[^1]: 余談ですが、Infineonの無線チップ製品は元々の開発元であるBroadcomのワイヤレス部門がCypressに買収されてそのCypressがInfineonに買収されてといった経緯をたどっているため、発売元はInfineonですが型番はCYWxxxxで同系列の旧製品はBCMxxxx型番だったりと、なかなか複雑なことになっています

![DSC_4228.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642667/aafbd569-387f-bd00-1367-f53374bd6195.jpeg)

Picoにリセットボタンがない問題についてはPIMORONIの [Captain Resetti](https://shop.pimoroni.com/products/captain-resetti-pico-reset-button) で解決することができたのですが、Pico Wでは部品配置が変わった影響で使えなくなっているので注意が必要です[^2]。

[^2]: PIMONORIのサイトには一応、「Pico Wの場合、Resettiを180度回転させてPico Wの基板からはみ出す向きに付けたり、基板の背面側に取り付けると使える」と注釈がありますが…(笑)。

私は手持ちのタクトスイッチをこんな感じで縦に取り付けて使うことにしました。

![DSC_4229.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642667/4b5757d5-b825-ff74-a3d0-4368cc0d1518.jpeg)
![DSC_4230.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642667/6c7175db-a397-4d8a-affc-986ceff819ba.jpeg)

## デバイス接続

Pico Wのピン配列図を見ると、LEDの接続先がGP25からWL_GPIO0に変わった[^3]以外、Picoとまったく変わっていません。

[^3]: このため、従来のPico用のLチカはPico Wでは動作しません。Pico SDKの [pico_cyw43_arch API](https://raspberrypi.github.io/pico-sdk-doxygen/group__pico__cyw43__arch.html) を用いて無線チップ CYW43439 のGPIOを操作する必要があります。参照: https://github.com/raspberrypi/pico-examples/blob/master/pico_w/blink/picow_blink.c

では無線チップはどこに接続されているかと [Raspberry Pi Pico W Datasheet](https://datasheets.raspberrypi.com/picow/pico-w-datasheet.pdf) 掲載の回路図を見てみると、以下のように CYW43439 からは WL_ON, WL_D, WL_CS, WL_CLK の4本の信号が出ていて、それぞれ RP2040 の GPIO 23, 24, 25, 29 に繋がっていることが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642667/07a118ef-e057-6ee2-f5dc-0fcdc0c452cb.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642667/80ac7ecb-8545-6936-3c34-3cf6faf8edc1.png)

これらのGPIOピンは元々Picoでも基板の外には出ていなかったもので、こうしたピンを無線チップ接続に使うことで、Picoからほとんど機能を削ることなく無線機能を追加しているわけです。

ところで、もう一度無線チップ周りの回路を見てみると、CYW43439から出ている BT_ で始まる信号線がどこにもつながっていません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642667/345ef6a3-fbba-63ed-2b50-2875763ee70a.png)

CYW43439 は WiFi と Bluetooth のコンボデバイスで、以下のブロック図の通り、WiFi 機能は WL_ で始まる信号線、Bluetooth 機能は BT_ で始まる信号線で制御するようになっているようです。ということは、現行のPico Wの回路だとBluetooth機能側は使えないのではないかと思うのですが…どうなんでしょう?

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642667/07fa97a1-9a79-e0ec-5b58-d15767f7317e.png)

Pico W発表時の[記事](https://gigazine.net/news/20220701-raspberry-pi-pico-w/)によると「将来のアップデートでBluetoothを有効にする可能性がある」とのことでしたが、ハード的に繋がっていないのでは有効にしようがないような…[^4]。

[^4]: CYW43439の内部的にはWiFi、Bluetoothともにチップ内のCortex-M3 CPUで制御されているので、実はWiFi側のインターフェースからBluetoothも制御できるようになっていたりしたら、可能なのかも知れませんが。

# ソフトウェア

## Pico SDKサンプルとビルド方法

Pico Wで追加された無線機能は、Pico W発表時点で既に [Raspberry Pi Pico SDK](https://raspberrypi.github.io/pico-sdk-doxygen/index.html) でサポート済みです。
詳細は [Connecting to the Internet with Raspberry Pi Pico W](https://datasheets.raspberrypi.com/picow/connecting-to-the-internet-with-pico-w.pdf) で説明されていますが、既にPico SDKの開発環境があるのであれば、[Pico SDK](https://github.com/raspberrypi/pico-sdk) と [Pico examples](https://github.com/raspberrypi/pico-examples) の最新のリポジトリをpullしてくれば利用可能になります(Pico SDKはgit submoduleの構成が以前と変わっているので、git cloneからやり直した方が楽かも知れません)。

Pico Wの無線機能を用いたサンプルは [pico-examples/pico_w](https://github.com/raspberrypi/pico-examples/tree/master/pico_w) 以下に用意されています。
従来のPico用サンプルとは異なり、cmake の際には "2.2 Building an SDK example" にあるように 
* PICO_BOARD に pico_w
* WIFI_SSID に接続先のWiFiルータのSSID
* WIFI_PASSWORD に接続先のWiFiルータのパスワード

を指定する必要があります。

```
$ mkdir build
$ cd build
$ cmake .. -DPICO_BOARD=pico_w -DWIFI_SSID="Your Network" -DWIFI_PASSWORD="Your password"
```

## Pico SDK内での無線機能サポート

Pico Wで追加された無線機能について、[Pico SDK](https://github.com/raspberrypi/pico-sdk) 内の具体的にどこでどのようにサポートされているのかを見てみます。

### CYW43439 - SPIインターフェース

* ソースコード
  * [pico-sdk/src/rp2_common/cyw43_driver](https://github.com/raspberrypi/pico-sdk/tree/master/src/rp2_common/cyw43_driver)

無線チップCYW43439との間の通信処理を、接続先となっているRP2040のGPIOを制御することで行います。
[CYW43439のドキュメント](https://www.infineon.com/dgdl/Infineon-CYW43439-Single-Chip-IEEE-802.11-b-g-n-MAC-PHY-Radio-with-Integrated-Bluetooth-5.0-Compliance-AdditionalTechnicalInformation-v03_00-EN.pdf?fileId=8ac78c8c7ddc01d7017ddd033d78594d) "4. WLAN System Interfaces" によると、WiFi機能のホストI/FとしてはSDIOまたはGeneric SPIのいずれかをサポートしているようですが、Pico Wでは Generic SPIを使用します。

RP2040は元々SPIインターフェースを2チャンネル持っていますが、Pico WのCYW43439はこれらのインターフェースを使えるような接続になっていないため、RP2040のPIO機能で「SPIプロトコルでの通信を行うプログラム」を動かすことで通信しています。
[pico-sdk/src/rp2_common/cyw43_driver/cyw43_bus_pio_spi.pio](https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/cyw43_driver/cyw43_bus_pio_spi.pio) の中の以下のPIOプログラムがそのコードです[^5]。

[^5]: ソースコード中にいくつかのPIOプログラムがありますが、そのうち1つしか使っていないようです

```
lp: out pins, 1             side 0
    jmp x-- lp              side 1
public lp1_end:
    set pindirs, 0          side 0
    nop                     side 1
lp2:
    in pins, 1              side 0
    jmp y-- lp2             side 1
public end:
```

sideset機能でクロック信号を送りながら、Xレジスタで指定したビット数だけデータを出力後、入出力の向きを入れ替えてYレジスタで指定したビット数だけデータを入力します。
通常、SPIは入力と出力で別々のピン(MISO, MOSI)を使用しますが、Pico Wでの接続ではこれらを同じGPIO (WL_D - GPIO24) に繋いでいるため、このような処理になっています。

[pico-sdk/src/rp2_common/cyw43_driver/cyw43_bus_pio_spi.c](https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/cyw43_driver/cyw43_bus_pio_spi.c) では、上記のPIOプログラムの設定や実行を行う、Pico Wのハードウェア構成固有の通信処理を実装して、そのインターフェースを次のCYW43439デバイスドライバに提供しています。

### CYW43439 デバイスドライバ

* ソースコード
  * [pico-sdk/lib/cyw43-driver](https://github.com/raspberrypi/pico-sdk/tree/master/lib)
  * submoduleの実体は [https://github.com/georgerobotics/cyw43-driver](https://github.com/georgerobotics/cyw43-driver/tree/195dfcc10bb6f379e3dea45147590db2203d3c7b) にあります

ここでは外部のリポジトリで提供されているCY43439ドライバをgit submoduleとして取り込んでいます。
ドライバ自体は特定のハードウェア、インターフェースに依存しないように書かれているようで、ホストインターフェースもSDIOとSPI両方のコードが用意されています。Pico WではこのうちSPI用のコードを、上記 [pico-sdk/src/rp2_common/cyw43_driver/cyw43_bus_pio_spi.c](https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/cyw43_driver/cyw43_bus_pio_spi.c) に繋ぎこんでいます。

このリポジトリには、デバイスドライバのソースコードの他にCYW43439のファームウェアバイナリも含まれています([ここ](https://github.com/georgerobotics/cyw43-driver/tree/195dfcc10bb6f379e3dea45147590db2203d3c7b/firmware))。Pico W向けにビルドするプログラムの中にもこのファームウェアが埋め込まれ、起動時の初期化の際にCYW43439に書き込まれるようになっています。

また、後述するlwIPとの繋ぎこみもこのドライバ内で行われます。lwIP内でのネットワークのリンク制御やIPパケット送受信などの処理でこのドライバが呼び出され、CYW43439が制御されるようになっています。

### Pico SDK APIサポート

* ソースコード
  * [pico-sdk/src/rp2_common/pico_cyw43_arch](https://github.com/raspberrypi/pico-sdk/tree/master/src/rp2_common/pico_cyw43_arch)

Pico SDKの [pico_cyw43_arch API](https://raspberrypi.github.io/pico-sdk-doxygen/group__pico__cyw43__arch.html) がここで実装されています。
ネットワーク処理ではメインのプログラム実行とは別にバックグラウンドでの処理が必要となります。Pico SDKはOSを使用していないため、そうした環境でバックグラウンド処理を行うために以下の3種類のライブラリを用意しています。

* pico_cyw43_arch_lwip_poll (PICO_CYW43_ARCH_POLL=1)
  * アプリケーションプログラムが実行中、明示的に `pico_cyw43_poll()` APIを呼び出すことでバックグラウンド処理を行います。仕組みとしてはもっとも単純ですが、アプリがループする箇所で必ずこのAPIを呼ばないと通信処理が正常に進まなくなります
* pico_cyw43_arch_lwip_threadsafe_background 
 (PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1)
  * タイマーを用いて定期的にアラームハンドラを呼び出し、その中でバックグラウンド処理を行います
* pico_cyw43_arch_lwip_sys_freertos (PICO_CYW43_ARCH_FREERTOS=1)
  * FreeRTOSを使用してバックグラウンド処理を別タスクとして実行します

### TCP/IPプロトコルスタック (lwIP)

* ソースコード
  * [pico-sdk/lib/lwip](https://github.com/raspberrypi/pico-sdk/tree/master/lib)
  * submoduleの実体は [https://github.com/lwip-tcpip/lwIP](https://github.com/lwip-tcpip/lwip/tree/239918ccc173cb2c2a62f41a40fd893f57faf1d6) にあります

Pico SDKでは、オープンソースの組み込み向けTCP/IPプロトコルスタックである[lwIP](https://savannah.nongnu.org/projects/lwip/lwIP)を用いています。
ネットワーク機能の初期化をPico SDK APIを用いて行った後、通信処理はlwIPの[API](https://www.nongnu.org/lwip/2_1_x/index.html)を用いて行うことになります。