SPRESENSEでArduino Ethernetライブラリを動かしてみた

先日、[LTE-M拡張ボードを使った記事](https://qiita.com/baggio/items/028bf7a2a63a02e3737b) を投稿したばかりですが、今回は有線ネットワークの話です。Spresense上でArduino Ethernetライブラリを動かしてみました。本当はLTE拡張ボードを購入する前に既に動かしていたのですがライブラリ含めて公開しておきます。SpresenseはArduinoの上位互換でとても良い感じです。

# Ethernetモジュールの購入

Spresense拡張ボードがArduino UNOとピン互換なので、Arduino Ethernetシールドを購入しても良かったのですが、他に安価なモジュールがたくさん出ているのと、シールドのICSPコネクタがSpresenseにとっては邪魔だったのでコレを買いました。1494円也。探せばもっと安いものもありそうです。

W5500イーサネット ネットワークモジュール
https://www.amazon.co.jp/gp/product/B07H3Z4FNX

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432852/66ff16b3-8805-ba22-6444-28bdee88fd4c.png" width="40%"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432852/fbd13cfb-e88e-65ba-79a2-a447809f43ef.png" width="40%">

# SPRESENSE拡張ボードとW5500モジュールとの接続

W5500モジュールの動作電圧が3.3Vですが、5Vトレラントなので、3.3V/5Vのどちらでも動かせます。
下図は3.3Vとの接続例です。このときSpresense拡張ボードのジャンパ(JP1)も合わせて3.3Vに設定することを忘れずに。
![SPRESENSE-W5500.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432852/4c4a0a93-9360-e91a-3847-ed19812d3ca7.png)

# Ethernetライブラリの移植

EthernetライブラリとはArduinoIDEをインストールしたら自動で入っているアレですが、
本家のソースはこちらにあります。
https://github.com/arduino-libraries/Ethernet

Spresenseで動かすために、若干ライブラリに手を加える必要があったので変更したものをこちらにアップしました。
https://github.com/baggio63446333/Ethernet

もっとSpresenseに最適化されたコードに変更しても良かったのですが、
差分が明確になるようにパッチの当て易さを考慮した変更に留めています(2020/05/01現在)。
https://github.com/baggio63446333/Ethernet/pull/1/files

Arduino環境のlibrariesフォルダ以下にインストールしてください。他基板との互換性は担保しているので別のボードで動かすときもこちらのライブラリをそのまま使用して頂いて問題ありません。（不要になったらフォルダごと削除すれば何事も無かったことになります）

Spresense移植のポイントは、

- SPI_MODE0 ではなく、SPI_MODE3 を使用する
- SS (Slave Select) の制御はハードウェアが勝手にやってくれる
- W5500 への read/write はバッファリングしておいて、一度に SPI.transfer() する
- SPI.transfer() の転送中は割り込み禁止にする (追記2020/05/07)
 - Spresense Cameraを使ってIPカメラを作成しているときに割禁にする必要があることに気が付きました。長時間の割り込み禁止区間を設定できないリアルタイムなアプリケーションを作成している場合は SS を GPIO に割り当てて使用する方が良いかもしれません。Ether.begin() の前に Ethernet.init(9); のように PIN 番号を指定することで SS を GPIO に割り当てることができます。もしくは、やっぱりネットワーク処理はサブコアに割り当ててオフロードする方がメインコアのリアルタイム性も損なわないので良さそうです。IPカメラではサブコアに割り当てるように設計変更しました。

## Spresense SPIについて

Spresenseで使われているSPIは、SDKのソースをみるとARM PrimeCell PL022が使われていることが分かります。PrimeCellは、個人的にはかなり馴染みの深いハードウェアです。他にもSpresenseには UART(PL011)、DMAC(PL080) が搭載されてそうです（あくまでソースコードのレジスタマップを見てそう思ったというだけなので間違っているかもしれません）
これらPrimeCellはドライバがその辺に転がっているのでSpresense上でmbedなど別のOSも簡単に動かせそうな気がします。

PL022 を触ったことがある方はご存知かと思いますがまぁまぁクセが強くて、SSPFSSOUT 信号（いわゆる SS 信号）がソフト制御ではなくハードウェアによって自動的にLOW/HIGH制御されます。また、複数データを連続転送する際に Motorola SPI Format（いわゆる SPI_MODE）の選択によって挙動が変わります。

SPI_MODE0 や SPI_MODE2 の場合は、8/16bit送信するたびに SS 信号が毎回 HIGH に戻りますが、SPI_MODE1 や SPI_MODE3 の場合は SS が LOW のまま転送が続きます。W5500 と通信する場合は、SPI_MODE0 ではなく SPI_MODE3 を使用する必要があります。

詳しくは、テクニカルリファレンスマニュアルに載っています。
[ARM PrimeCell Synchronous Serial Port (PL022) テクニカルリファレンスマニュアル](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0194h/I1006921.html)

# Ethernet サンプルコード

examples コードの解説はこちらにまとまっています。
https://www.arduino.cc/en/Reference/Ethernet

|Example|説明|
|-------|---|
|LinkStatus|Linkステータスを表示する (Unknown or ON or OFF)|
|[DhcpAddressPrinter](https://www.arduino.cc/en/Tutorial/DhcpAddressPrinter)|DHCPを使ってIPアドレスを取得し、そのIPアドレスをシリアルに表示する。|
|[UdpNtpClient](https://www.arduino.cc/en/Tutorial/UdpNtpClient)|DHCPでIPアドレスを取得する。"time.nist.gov"へNTPパケットを送信し、時刻を取得してシリアルに表示する。|
|[ChatServer](https://www.arduino.cc/en/Tutorial/ChatServer)|事前にスケッチ上でIPアドレス、DNSサーバーアドレス、ゲートウェイ、サブネットマスクを設定しておく。Spresense側がtelnetサーバー。PCなどクライアントからtelnetで接続する。クライアントから入力した文字をサーバー側に送信、サーバー側で受信した文字列をシリアルに表示、クライアントにエコーバックする。|
|[DhcpChatServer](https://www.arduino.cc/en/Tutorial/DhcpChatServer)|DHCPによりIPアドレスを取得する。その他の動作は"ChatServer"と同じ。|
|AdvancedChatServer|事前にIPアドレス、DNSサーバーアドレス、ゲートウェイ、サブネットマスクを設定。最大8個のクライアントからの接続を受け付ける。その他の動作は"ChatServer"と同じ。|
|[WebClient](https://www.arduino.cc/en/Tutorial/WebClient)|DHCPでIPアドレスを取得する(失敗した場合は事前に設定したIPアドレス、DNSサーバーアドレスで動作する）。Spresense側がWebクライアント。Googleサーバーに対して、http://www.google.com/search?q=arduino のようにHTTP GETメソッドを送信する。レスポンス結果をシリアルに表示する。最後に受信データサイズと転送実効レートをシリアルに表示する。|
|[WebClientRepeating](https://www.arduino.cc/en/Tutorial/WebClientRepeating)|DHCPでIPアドレスを取得する(失敗した場合は事前に設定したIPアドレス、DNSサーバーアドレスで動作する）。Spresense側がWebクライアント。arduinoホームページに対して、http://www.arduino.cc/latest.txt でHTTP GETメソッドを繰り返し送信する。レスポンス結果をシリアルに表示する。|
|[WebServer](https://www.arduino.cc/en/Tutorial/WebServer)|事前にIPアドレスを設定する。Spresense側がWebサーバー。WebブラウザからアクセスするとanalogRead()した値が表示される。Spresenseの場合は、引数にA0~A5を指定する必要があるので analogRead(A0 + analogChannel) のように変更する。5秒でrefreshされる。（たまにHTTP/1.1の先頭の文字が欠けて表示が変になるので要デバッグ）<img width="375" alt="webserver.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432852/9ee5630f-87e1-55a2-03c9-26e8825afc06.jpeg">|
|[BarometricPressureWebServer](https://www.arduino.cc/en/Tutorial/BarometricPressureWebServer)|"WebServer"の応用で気圧センサーから取得した温度、気圧の値をWebブラウザに表示する。|
|[UDPSendReceiveString](https://www.arduino.cc/en/Tutorial/UDPSendReceiveString)|事前にIPアドレスを設定する。対向側は[Processing](https://processing.org/)を使ってUDPで通信する。Processing上でKeyを押すと"Hello World"をSpresenseへ送信する。Spresenseは受信すると"acknowledge"をProcessing側へ返す。|
|[TelnetClient](https://www.arduino.cc/en/Tutorial/TelnetClient)|事前にIPアドレス、サーバーアドレスを設定する。Spresenseがクライアント側となりサーバーに対してポート10002で接続する。シリアルモニタに入力した文字をサーバー側に送信する。|

ここまでは Ethernet が動作する普通のArduino互換ボードという感じなのですが、Spresense がスゴイのはマルチコア環境をもっているところ。Ethernet ライブラリを別コアで動作させることによってネットワーク処理を簡単にオフロードすることができます。メインコアはオーディオ動かしつつサブコアでネットワーク通信とか、メインコアでカメラボードを動かしつつサブコアでネットワークをつなげてIP監視カメラとか、今後チャレンジしてみたいと思います。