Nucleo-F401RE + Ethernet(W5100) + MicroPythonでインターネット接続

stm32-w5100.jpg

MicroPythonが動くようになったNucleo-F401REですが、ESP32と比べるとネット接続がありません。
しかし、調べてみるとこのボードにArduino用のイーサネットシールドを接続できることが分かりました。

Adding ethernet connectivity to a STM32-Nucleo

実は、私も買ったまま使っていない(このパターン多いです)Arduino用イーサネットシールドを持っており、上記の記事と同じW5100というチップを使用しているものでした。

このチップはWIZnetというメーカーの製品で、単なるEthernetコントローラではなく、TCP/IPが実装されており、単体で通信ができます。
イメージとしてはESP8266のLANバージョンみたいなものでしょうか。
ESP8266よりもかなり以前からあるシリーズで、国内でもスイッチサイエンスさんなどで扱いがあります。

一方、MicroPythonについて調べてみると、このW5000シリーズがドライバとして組み込めることが分かりました。
ただ、現時点ではサポートされているのはW5200とW5500のみでした。

Ethernet Adapter for PyBoard Which? – MicroPython Forum

なおW5500とSTM32の組み合わせはごく最近バグフィックスされたようです。

drivers: WIP getting WizNet5500 working with stm32 port. by dpgeorge · Pull Request #3362 · micropython/micropython

残念ながらMicroPythonにはW5100のドライバは入っていませんでしたが、W5200やW5500のドライバを調べてみると、WIZnetが開発したドライバを利用していました。
そこで、WIZnetのGitHubをのぞいてみると、W5100のドライバも用意されていました。
また、これらチップの差分を吸収する抽象レイヤも用意されており、MicroPythonでもそれを使用しているようです。

ということで、「WIZnetのW5100用ドライバをMicroPythonへポーティングするのは簡単なのでは?」と思い、やってみることにしました。

利用したWIZnetのドライバは、現時点での最新版と思われるこちらのライブラリです。

Wiznet/ioLibrary_Driver: Create a repository of WIZnet ioLibrary.

ドライバの入っている「Ethernet」フォルダと、DNSなどのプロトコルを実装している「Internet」フォルダがありますが、Ethernetフォルダだけ使用しました。
MicroPythonではInternetフォルダのDNS実装を使っているようでしたので、最初はこちらも最新のものを使おうかと思ったのですが、最新のほうはコンパイルエラーが出る(というかバグが残っている)ので、使うのをやめました。

このEthernetフォルダを、micropython/drivers/wiznet5k/Ethernetに展開します。
そして、以下のように使用するチップの指定を変更します。

../../ioLibrary_Driver/Ethernet/wizchip_conf.h wizchip_conf.h
64c64
< #define _WIZCHIP_                      5500   // 5100, 5200, 5300, 5500
---
> #define _WIZCHIP_                      5100   // 5100, 5200, 5300, 5500

また、ドライバの抽象レイヤであるwizchip_conf.cは構造体の初期化のところで文法エラーが出るので修正します。

../../ioLibrary_Driver/Ethernet/wizchip_conf.c wizchip_conf.c
169,172c169,172
<       wizchip_cris_enter,
<       wizchip_cris_exit,
<       wizchip_cs_select,
<       wizchip_cs_deselect,
---
>       {wizchip_cris_enter,
>        wizchip_cris_exit},
>       {wizchip_cs_select,
>        wizchip_cs_deselect},
176,177c176,177
<       wizchip_bus_readdata,
<       wizchip_bus_writedata,
---
>       {{wizchip_bus_readdata,
> 	wizchip_bus_writedata}},

WIZnetチップはソケットのインタフェースが実装されています。
関数名はconnect、listenなどよく知られたAPIが使われていますが、もともとMicroPythonに組み込まれていたものに倣って、ソケット関連の関数名を変更するマクロを導入しました。

../../ioLibrary_Driver/Ethernet/socket.h socket.h
87a88,90
> // use this macro for exported names to avoid name clashes
> #define WIZCHIP_EXPORT(name) wizchip_ ## name
>
162c165
< int8_t  socket(uint8_t sn, uint8_t protocol, uint16_t port, uint8_t flag);
---
> int8_t  WIZCHIP_EXPORT(socket)(uint8_t sn, uint8_t protocol, uint16_t port, uint8_t flag);

以下略しますが、関数名socket, close, listen, connect, disconnect, send, recv, sendto, recvfrom, ctlsocket, setsockopt, getsockoptについて全て上記の変更を行います。
また、socket.cについても同様の変更を行います。
関数の宣言部だけでなく、内部でclose()を呼び出しているところがあるのでそこも変更が必要です。

また、&演算子の使い方でコンパイル時にエラーが出るので、以下を修正しました。

520,521c520,521
<    if((taddr == 0) && (getSn_MR(sn)&Sn_MR_MACRAW != Sn_MR_MACRAW)) return SOCKERR_IPINVALID;
<    if((port  == 0) && (getSn_MR(sn)&Sn_MR_MACRAW != Sn_MR_MACRAW)) return SOCKERR_PORTZERO;
---
>       if((taddr == 0) && ((getSn_MR(sn)&Sn_MR_MACRAW) != Sn_MR_MACRAW)) return SOCKERR_IPINVALID;
>       if((port  == 0) && ((getSn_MR(sn)&Sn_MR_MACRAW) != Sn_MR_MACRAW)) return SOCKERR_PORTZERO;

W5100のドライバは全く変更する必要はありません。ただ、インクルードパスの都合でw5100.hを少しだけ修正します。

../../../../../ioLibrary_Driver/Ethernet/W5100/w5100.h w5100.h
44c44
< #include "wizchip_conf.h"
---
> #include "../wizchip_conf.h"

また、application/dns/フォルダのdns.hも同様に1行だけ修正します。

diff dns.c dns.c~
57c57
< #include "../../Ethernet/socket.h"
---
> #include "../../ethernet/socket.h"

ここでMicroPythonの内部構造を少し書いておきます。
STM32関連の実装は「ports/stm32」に集められています。
networkモジュールはmodnetwork.cで実装されていますが、種類の異なるNICごとの実装は別ファイルになります。
WIZnet5Kシリーズのための実装はmodnwwiznet5k.cにあり、この実装はSTM32のHALライブラリを介してSPIをたたいてWIZnet5Kのデバイスと通信しています。

WIZnet5KドライバのAPIが、もともとMicroPythonに含まれていたものと若干変わっているので、これらのドライバを呼び出すmodnwwiznet5k.c側の修正も必要になりました。
具体的には、SPIのコールバック関数として、1バイトずつの送受信を行うものが新たに必要になりましたので追加しました。
また、W5000シリーズのレジスタの呼び名が若干変更になっています。

diff modnwwiznet5k.c modnwwiznet5k.c.org
41,42c41,42
< #include "Ethernet/wizchip_conf.h"
< #include "Ethernet/socket.h"
---
> #include "ethernet/wizchip_conf.h"
> #include "ethernet/socket.h"
74,87c74
< STATIC uint8_t wiz_spi_read(void) {
<   uint8_t c;
<     HAL_StatusTypeDef status = HAL_SPI_Receive(wiznet5k_obj.spi, &c, 1, 5000);
<     (void)status;
<     return c;
< }
<
< STATIC void wiz_spi_write(uint8_t c) {
<     HAL_StatusTypeDef status = HAL_SPI_Transmit(wiznet5k_obj.spi, &c, 1, 5000);
<     (void)status;
< }
<
< //STATIC void wiz_spi_read(uint8_t *buf, uint32_t len) {
< STATIC void wiz_spiburst_read(uint8_t *buf, uint16_t len) {
---
> STATIC void wiz_spi_read(uint8_t *buf, uint32_t len) {
92,93c79
< //STATIC void wiz_spi_write(const uint8_t *buf, uint32_t len) {
< STATIC void wiz_spiburst_write(uint8_t *buf, uint16_t len) {
---
> STATIC void wiz_spi_write(const uint8_t *buf, uint32_t len) {
381d366
<     reg_wizchip_spiburst_cbfunc(wiz_spiburst_read, wiz_spiburst_write);
424c409
<             printf(" %02x", WIZCHIP_READ(WIZCHIP_OFFSET_INC(WIZCHIP_SREG_BLOCK(sn), i)));
---
>             printf(" %02x", WIZCHIP_READ(WIZCHIP_SREG_ADDR(sn, i)));

最後にMakefileです。
こちらもW5200指定になっていたのでその修正と、インクルードパスの修正をします。

diff Makefile Makefile.org
295,297c295,297
< 	Ethernet/W5100/w5100.c \
< 	Ethernet/wizchip_conf.c \
< 	Ethernet/socket.c \
---
> 	ethernet/w5200/w5200.c \
> 	ethernet/wizchip_conf.c \
> 	ethernet/socket.c \

以上で移植作業は完了です。
makeします。

cd micropython/ports/stm32/
make BOARD=NUCLEO_F401RE CROSS_COMPILE=~/gcc-arm-none-eabi-5_4-2016q3/bin/arm-none-eabi- MICROPY_PY_WIZNET5K=1
cd build-NUCLEO_F401RE/
cp firmware.hex /mnt/hgfs/share/
#このあとST-Link Utilityで書き込み

実際にNucleo-F401REとW5100でインターネットアクセスしてみました。
結線は下図のようになります。

stm32-w5100.png

Adding ethernet connectivity to a STM32-Nucleoにも書かれていますが、このイーサネットシールドはSPIの信号が、Nucleoボードが持っていない6ピンの端子に出ているため、配線に工夫が必要です。
私のボードは6ピン端子の足元の部分に線が露出していたので、ICクリップを使って接続しました。

ethernet-shield-spi.jpg

まずはESP32のときと同様、アスキーアートSTAR WARSに接続してみます。
SPIオブジェクトがmachine.SPIだとクラスが違うというエラーが出てしまい、pyb.SPIを使う必要がありました。
このへんはMicroPythonの問題だと思います。

ちょっとカクカクしますが、一応表示されました。

starwars.png

以下の記事によると、W5kシリーズの通信速度は、最新のW5500でDMAを使用したとき4.4Mbps、使用しないとき1.7Mbpsくらい出るようです。

STM32F401REでSPI DMAを利用してW5500の送受信性能改善 | WIZnet JP Blog

HTTPでの通信も試してみました。
これまたESP32のときと同じネタです。
今回はLCDは使わず、とりあえず通信できるかどうかの確認です。
WIZNET5Kオブジェクトはreadline()が実装されていなかったので、コードは若干変わっています。

REPLの中で呼び出すと結果が表示されます。

>>> main()
2017-10-13
JPY/USD: 112.18
>>> 

コメント