はじめに
Wi-Fi を使わずに、Pico と同じ形の基板 1 枚で有線 LAN に出る。WIZnet の W55RP20-EVB-Pico は、そのためのボードです。この記事のゴールは、箱から出して USB と LAN ケーブルをつなぎ、DHCP で IP をもらい、PC から ping が通り、TCP でデータを送って同じものが返ってくるところまで。そこへ行く前に確かめたいのは 2 つです。中で RP2040 と Ethernet チップがどうつながっているのか、そして「ハードウェア TCP/IP」と呼ばれるチップの仕組みを、手元のコードが本当に使っているのか。だからこの記事では、内部配線と TCP/IP の役割分担を図で押さえてから、Arduino と公式 C 例題の 2 つの経路で同じ最初の一歩を通します。
センサーノードや Web サーバー、速度の比較といった応用は、次の記事以降で扱います。ここは基礎だけです。
- W55RP20 は RP2040 と W5500 を 1 パッケージにした SiP。Pico と同じピン配置だが、使えない GPIO がある
- RP2040 と W5500 は SPI でつながっていて、そのピンがハードウェア SPI の割り当てに合わないので PIO で SPI を作っている
- 同じ基板に 2 つの使い方 がある。チップの TCP/IP を使う(公式 C 例題)か、生フレームだけ受け取ってソフトウェアで処理する(Arduino の arduino-pico)か
- 最初の一歩は DHCP → ping → TCP エコー。両方の経路で手順を示す
🧩 W55RP20-EVB-Pico は何者か
W55RP20-EVB-Pico。RJ45 の隣に W55RP20 が 1 チップで載り、USB-C の手前に BOOTSEL と RUN の 2 つのボタンが並ぶ。シルクの 20・21・40 はピン番号
RP2040 と W5500 を 1 パッケージにした「ioNIC」
W55RP20 は、Raspberry Pi の RP2040(Pico のマイコン)と、WIZnet の有線 LAN コントローラ W5500 を 1 つのパッケージに収めたチップです。WIZnet はこの系統を ioNIC(Internet Offload Network Interface Chip)と呼んでいて、W55RP20 はその第 1 弾として 2024 年 11 月に出ました(スイッチサイエンスマガジンの WIZnet 寄稿)。
RP2040 と W5500 を 1 つの外形にまとめた SiP(System in Package)なので、ソフトウェアから見ると「RP2040 の GPIO に W5500 が SPI でつながっている」状態そのものです。Pico に W5500 のモジュールを配線したのと同じ構成が、最初からパッケージの中で済んでいる、と考えるとわかりやすいです。
評価ボードの W55RP20-EVB-Pico は、これを Pico と同じ外形・同じピン配置の基板に載せ、RJ45 コネクタと USB Type-C を付けたものです。
| 項目 | 仕様(WIZnet Docs・スイッチサイエンス商品ページ) |
|---|---|
| マイコン | RP2040 相当(Cortex-M0+ デュアル・最大 133 MHz・SRAM 264 KB) |
| フラッシュ | 2 MB(パッケージ内蔵) |
| Ethernet | W5500 相当(10/100BASE-T・PHY 内蔵・オートネゴシエーション) |
| ハードウェア TCP/IP | TCP・UDP・ICMP・IPv4・ARP・IGMP・PPPoE |
| ソケット | 8 本を同時に使える。送受信バッファは合計 32 KB |
| コネクタ | RJ45(PoE 対応)・USB Type-C・3 ピン SWD |
| PoE | 別売の WIZPoE-P1 をスルーホールに付ける(ピンソケットは未実装) |
Pico と同じところ、違うところ
外形とピン配置は Pico と同じですが、内部で W5500 に使っているぶん、外に出ている GPIO は減っています。
| Raspberry Pi Pico | W55RP20-EVB-Pico | |
|---|---|---|
| 外に出ない GPIO | GP23〜25(基板内で使用) | GP17・GP20〜25 |
| 電源モード切替 | GP23(SMPS) | GP16(DC/DC モード選択) |
| VBUS 検出 | GP24 | GP18 |
| オンボード LED | GP25 | GP19 |
| VSYS/3 の測定 | GP29(ADC3) | GP29(ADC3)で同じ |
| USB | Micro-USB | USB Type-C |
ボードの機能に使っている GP16・GP18・GP19 も含めると、汎用 GPIO として気兼ねなく使えるのは GP0〜15 と GP26〜28 の 19 本と考えるのが安全です。Zephyr のボード定義も、ピンヘッダとして定義しているのはこの 19 本だけです。
Pico で LED_BUILTIN(GP25)や GP20〜22 を使っていたコードは、そのままでは動きません。GP25 は W5500 のリセット、GP20〜23 は W5500 の SPI につながっています。LED は GP19 です。
40 ピンのうち GPIO と GND 以外の 7 本(VBUS・VSYS・3V3_EN・3V3・ADC_VREF・AGND・RUN)は Pico と同じ意味です。3V3_EN を GND に落とすと 3.3 V が止まり、RP2040 と W5500 の両方の電源が落ちる点だけ、Pico より影響範囲が広くなっています。
「Pico + W5500 モジュール」と比べて何がうれしいか
これまで Pico で有線 LAN をやるなら、W5500 のモジュールを SPI で配線するのが定番でした。W55RP20-EVB-Pico はそれを 1 枚にしたものです。比べると次のようになります。
| Pico + W5500 モジュール | W55RP20-EVB-Pico | |
|---|---|---|
| 配線 | SPI 4 本+INT・RST・電源をジャンパで | 0 本(パッケージ内で完結) |
| 面積 | Pico + モジュール 2 枚ぶん | Pico 1 枚ぶん |
| 自由に使える GPIO | 約 20 本(Pico の 26 本から SPI・INT・RST の 6 本を引く) | 19 本(GP0〜15・GP26〜28)で、ほぼ同じ |
使える GPIO の数はほとんど変わらないので、うれしいのは配線と面積です。SPI の配線の取り違えや、ジャンパ線の接触不良で W5500 が見えない、という定番のつまずきが最初から起きません。
🔗 RP2040 と W5500 はどうつながっているか
内部配線:SPI 4 本+INT・RST
パッケージの中で、RP2040 の GPIO20〜25 が W5500 につながっています。ピンの割り当ては、公式 C 例題(WIZnet-PICO-C)の port/ioLibrary_Driver/inc/wizchip_spi.h、arduino-pico の w55rp20.h、Zephyr のボード定義で一致しています。
(SPI を実装)"] G["RP2040 の
GPIO24・GPIO25"] S["W5500
SPI スレーブ"] IR["W5500
INTn・RSTn"] PHY["W5500
MAC / PHY"] P0 -- "CS・SCK・MOSI
(GPIO20・21・23)" --> S S -- "MISO(GPIO22)" --> P0 IR -- "INTn → GPIO24" --> G G -- "GPIO25 → RSTn" --> IR S --> PHY end PHY --- RJ["RJ45"]
| RP2040 の GPIO | W5500 側 | 向き |
|---|---|---|
| GPIO20 | CS(チップセレクト) | RP2040 → W5500 |
| GPIO21 | SCK(クロック) | RP2040 → W5500 |
| GPIO22 | MISO(W5500 からのデータ) | W5500 → RP2040 |
| GPIO23 | MOSI(W5500 へのデータ) | RP2040 → W5500 |
| GPIO24 | INTn(割り込み) | W5500 → RP2040 |
| GPIO25 | RSTn(リセット) | RP2040 → W5500 |
なぜハードウェア SPI ではなく PIO なのか
RP2040 にはハードウェアの SPI が 2 つありますが、どの GPIO にどの信号を出せるかは決まっています。GPIO20〜23 に出せるのは SPI0 だけで、その割り当ては次のとおりです(arduino-pico の SPI.cpp が持つ有効ピン表・RP2040 の GPIO 機能表)。
| 信号 | SPI0 で出せる GPIO(20〜23 の範囲) | W55RP20 の配線 | 合うか |
|---|---|---|---|
| RX(MISO) | GPIO20 | GPIO22 | 合わない |
| CSn | GPIO21 | GPIO20 | (CS は GPIO 操作で代用できる) |
| SCK | GPIO22 | GPIO21 | 合わない |
| TX(MOSI) | GPIO23 | GPIO23 | 合う |
SCK と MISO が入れ替わった形になっているので、ハードウェア SPI ではこの配線を駆動できません。そこで PIO(RP2040 のプログラマブル I/O)のステートマシンで SPI の波形を作っています。PIO は任意の GPIO に任意のタイミングで信号を出せるので、ピンの割り当てに縛られません。
一次でも確かめられます。WIZnet-PICO-C の wizchip_spi.h は W55RP20 のときだけ USE_PIO を定義し、arduino-pico の w55rp20.h は WIZNET_PIO_SPI_*_PIN という名前でピンを定義しています。Zephyr のボード定義には「W55RP20 は W5500 に PIO ベースの SPI を使うので、ハードウェア SPI(spi0)は無効にする」というコメントがあります。
RP2040 に 2 ブロック(ステートマシン合計 8 個)載っている、小さな命令セットで I/O の波形を作る専用ハードウェアです。CPU とは独立に動くので、SPI・UART・WS2812 の信号などを、CPU の時間を使わずに任意のピンで出せます。W55RP20 では、このうち 1 つを W5500 との SPI に使っています。
SPI でやりとりしている中身:アドレス・制御・データ
W5500 の SPI は、1 回の通信(CS が Low の間)が 3 つの部分でできています(ioLibrary_Driver の w5500.h・w5500.c)。
CS --+ +--
+----------------------------------------------------+
MOSI [ Address 16bit ][ Control 8bit ][ Data N byte ... ]
|BSB[4:0]|RWB|OM[1:0]|
| 部分 | 中身 |
|---|---|
| Address(2 バイト) | 読み書きするレジスタやバッファのオフセット |
| Control(1 バイト) | BSB=どのブロックか(共通レジスタ/ソケット n のレジスタ/送信バッファ/受信バッファ)・RWB=読み 0/書き 1・OM=長さの決め方(00 なら CS を戻すまで続ける) |
| Data(N バイト) | 書き込む値、または読み出した値 |
BSB は、共通レジスタが 0、ソケット n のレジスタが 1+4n、送信バッファが 2+4n、受信バッファが 3+4n です。たとえば W5500 の版番号レジスタ(VERSIONR・オフセット 0x0039)を読むと 0x04 が返ってきます。公式 C 例題は起動時にこの値を確かめていて、SPI がうまく通らないと ACCESS ERR : VERSION != 0x04 を出して止まります。SPI の配線が見えないぶん、この 1 行が「RP2040 と W5500 が話せているか」の最初の手がかりになります。
⚙️ ハードウェア TCP/IP(TOE)とは
チップが TCP/IP を処理し、マイコンは「ソケット」に読み書きするだけ
ネットワークの通信には、何層もの手順があります。相手の MAC アドレスを調べる ARP、IP パケットの組み立て、ping に応える ICMP、届いたか確かめて再送する TCP、送りっぱなしの UDP。普通のマイコンでは、これらをソフトウェア(TCP/IP スタック、たとえば lwIP)が CPU の時間とメモリを使って処理します。
W5500 は、この処理をチップの中の回路で行います。WIZnet はこれを TOE(TCP/IP Offload Engine)、日本語で「ハードウェア TCP/IP」と呼んでいます。マイコン側から見えるのは ソケット と呼ばれる 8 本の窓口だけで、やることは次の程度です。
- ソケット 0 を「TCP・ポート 5000」で開く(レジスタに書く)
- 「待ち受けて」と命令する
- 接続されたら、受信バッファに溜まったデータを SPI で読む
- 返したいデータを送信バッファに SPI で書き、「送って」と命令する
再送やウィンドウ制御、ARP の問い合わせ、ping への応答は、チップが勝手にやります。送受信バッファは送信 16 KB・受信 16 KB の合計 32 KB で、8 本のソケットに 1・2・4・8・16 KB 単位で割り振ります(既定は各 2 KB)。
もう 1 つの使い方:MACRAW + ソフトウェアスタック
W5500 には、TCP/IP の処理を使わずに イーサネットフレームをそのままマイコンに渡すモード(MACRAW) もあります。この場合、ARP も IP も TCP も、マイコン側のソフトウェアスタックが処理します。W5500 は「PHY 付きの LAN の口」として働くだけになります。
socket / send / recv"] --> A2["SPI"] A2 --> A3["W5500 の中で
TCP・UDP・IP
ARP・ICMP"] A3 --> A4["MAC / PHY
→ LAN"] end subgraph B["MACRAW + lwIP"] direction TB B1["アプリ
WiFiClient など"] --> B2["RP2040 上の lwIP が
TCP・UDP・IP
ARP・ICMP"] B2 --> B3["SPI(生フレーム)"] B3 --> B4["W5500 は
MAC / PHY だけ
→ LAN"] end A ~~~ B
同じ基板で、経路によって使い方が違う
ここが基礎として押さえておきたいところです。W55RP20-EVB-Pico を Arduino(arduino-pico)で使うと、W5500 のハードウェア TCP/IP は使われていません。
arduino-pico 6.3.1 の lwIP_w55rp20 ライブラリは、ヘッダに「original sources: W5500MacRaw」と書かれていて、begin() の中でソケット 0 を MACRAW モードで開き、16 KB ずつのバッファを全部ソケット 0 に割り当てています。TCP/IP は RP2040 上の lwIP が処理します。一方、公式の C 例題(WIZnet-PICO-C)は WIZnet の ioLibrary を使い、ハードウェアソケットで通信します。
| Arduino(arduino-pico 6.3.1) | 公式 C 例題(WIZnet-PICO-C) | |
|---|---|---|
| W5500 の使い方 | MACRAW(生フレーム) | ハードウェアソケット(TOE) |
| TCP/IP を処理する場所 | RP2040 上の lwIP | W5500 の中 |
| アプリの書き方 | WiFiClient・WiFiServer(Wi-Fi と同じ API) |
socket()・listen()・send()・recv() |
| 同時接続の上限 | lwIP の設定しだい | ソケット 8 本 |
どちらが良い・悪いではありません。arduino-pico の方式は Pico W の Wi-Fi と同じ API で書けるので、Wi-Fi 用のコードをほぼそのまま有線に持ってこられます。ハードウェアソケットの方式は、RP2040 の CPU とメモリを TCP/IP に使わずに済みます。WIZnet には、lwIP の API のまま TOE と MACRAW を切り替えられる WIZnet-PICO-LWIP-TOE-C という例題集もあります。どちらが速いか、CPU をどれだけ使うかは、応用編で測ります。
W5500 の売りはハードウェア TCP/IP なのに、いちばん手軽な Arduino の経路では、それを使わずに PHY 付きの口として使っています。逆に言えば、W55RP20 は「RP2040 + 有線 LAN の口」としても「TCP/IP を肩代わりしてくれるチップ」としても使える、ということです。Wi-Fi のコードを有線に持ってきたいのか、RP2040 の CPU をアプリに全部使いたいのかで、経路を選べます。
🛠️ 開発環境を整理する
この記事で実際に通すのは、(a) Arduino IDE と (b) VS Code + 公式 C 例題の 2 つです。
| 経路 | 使うもの | 向いている人 |
|---|---|---|
| (a) Arduino | Arduino IDE + arduino-pico 6.3.1 | Pico や Pico W を Arduino で触っている人 |
| (b) C/C++ | VS Code + Raspberry Pi Pico 拡張 + WIZnet-PICO-C | Pico SDK で書く人・ハードウェアソケットを使いたい人 |
| (c) そのほか | MicroPython・CircuitPython・Zephyr | 公式の例題やボード定義がある(この記事では扱わない) |
(a) Arduino IDE + arduino-pico
-
Arduino IDE の「ファイル → 基本設定 → 追加のボードマネージャの URL」に次を足す
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json -
ボードマネージャで「Raspberry Pi Pico/RP2040/RP2350」(earlephilhower 版)を入れる。この記事では 6.3.1 を使います
-
「ツール → ボード」で WIZnet W55RP20-EVB-Pico を選ぶ
-
例題は「ファイル → スケッチ例 → lwIP_w55rp20 → WiFiClient-W55RP20」にあります
ライブラリ lwIP_w55rp20 はコアに同梱されているので、別途入れるものはありません。
(b) VS Code + WIZnet-PICO-C
-
VS Code に Raspberry Pi Pico 拡張を入れる(Pico SDK とツールチェーンは拡張が用意します) 拡張の入れ方・blink での動作確認・Import の手順は Pico SDK の環境構築の記事にまとめています
-
例題を submodule ごと取得する
git clone --recurse-submodules https://github.com/WIZnet-ioNIC/WIZnet-PICO-C.gitlibraries/の下に ioLibrary_Driver・mbedtls・pico-sdk が submodule として入ります。--recurse-submodulesを付けないと、これらが空のフォルダになります -
ルートの
CMakeLists.txtでボードを選ぶ。取得した時点(main・2026-10-06 版)では W6300_EVB_PICO2 が有効になっているので、次のように書き換えます# set(BOARD_NAME W6300_EVB_PICO2) set(BOARD_NAME W55RP20_EVB_PICO)W55RP20_EVB_PICO を選ぶと、
_WIZCHIP_=W5500と、ピン定義(PIO SPI)が自動で選ばれます -
VS Code の Raspberry Pi Pico 拡張でこのフォルダをプロジェクトとして読み込み、ビルドする。
build/examples/dhcp_dns/などに.uf2ができます
UF2 の書き込み方(BOOTSEL を押しながら USB をつなぎ、RPI-RP2 ドライブにコピー)は Pico と同じです。UF2 の仕組みは自作ロジアナ #2 で書いています。
🚀 最初の一歩:DHCP → ping → TCP エコー
ここからが手順です。家庭のルーターのように DHCP サーバーがある LAN を前提にしています。
USB-C をつなぐ"] --> B["② (a) スケッチを書き込み
DHCP の IP と MAC を見る"] B --> C["③ PC から ping"] C --> D["④ (a) TCP エコー
PC から送って返ってくるか"] D --> E["⑤ (b) 公式 C 例題で
dhcp_dns と loopback"] E --> F["⑥ LED を見る"]
① LAN ケーブルと USB-C をつなぐ
ルーターやスイッチングハブから LAN ケーブルを RJ45 に、PC から USB-C をつなぎます。電源は USB から取ります。
USB だけをつないで LAN ケーブルを挿していない間は、RJ45 の LED は点きません。LAN ケーブルを挿すと、緑が点灯し、オレンジが点滅します。LED の見分け方は⑥で扱います。
USB をつなぐと、PC には COM ポート(USB シリアルデバイス)が 1 つ現れます。USB の ID(VID 2E8A・PID 000A)から見ると、出荷時に入っているのは Pico SDK で作られたファームです。シリアルを開いても何も出てきません。
② DHCP で IP をもらい、IP と MAC をシリアルに出す(Arduino)
arduino-pico の例題 WiFiClient-W55RP20 は外部のサーバーにつなぎに行くので、LAN の中だけで完結するように、DHCP で IP をもらって表示し、TCP 5000 番で受けたデータをそのまま返すスケッチにしました。④の TCP エコーも、このスケッチ 1 本で試せます。
// W55RP20-EVB-Pico: DHCP で IP を取り、TCP 5000 番でエコーする(arduino-pico 6.3.1)
#include <W55RP20lwIP.h>
Wiznet55rp20lwIP eth(20); // CS = GPIO20(基板の内部配線)
WiFiServer server(5000); // TCP エコー用のポート
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 5000) {} // USB シリアルを開くまで最大 5 秒待つ
if (!eth.begin()) { // MAC 省略 = チップ固有 ID から生成
Serial.println("W5500 not found");
while (true) delay(1000);
}
Serial.print("DHCP");
while (!eth.connected()) { // IP が付くまで待つ
Serial.print('.');
delay(500);
}
Serial.println();
uint8_t mac[6];
eth.macAddress(mac);
Serial.printf("MAC : %02X:%02X:%02X:%02X:%02X:%02X\n",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
Serial.print("IP : "); Serial.println(eth.localIP());
Serial.print("MASK: "); Serial.println(eth.subnetMask());
Serial.print("GW : "); Serial.println(eth.gatewayIP());
server.begin();
Serial.println("TCP echo server on port 5000");
}
void loop() {
WiFiClient client = server.accept();
if (!client) return;
Serial.print("client: "); Serial.println(client.remoteIP());
while (client.connected()) {
int n = client.available();
if (n > 0) {
uint8_t buf[128];
n = client.read(buf, min(n, (int)sizeof(buf)));
client.write(buf, n); // 受け取った分をまとめて返す
}
delay(50); // lwIP のポーリング(既定 20 ms)が走る間を空ける
}
client.stop();
Serial.println("closed");
}
loop() の delay(50) は飾りではありません。これを抜くと、エコーの途中から基板が ping にも応えなくなります。理由は④の後で説明します。
ボード「WIZnet W55RP20-EVB-Pico」を選び、Arduino IDE から書き込みます。BOOTSEL ボタンを押す必要はありません。 IDE がシリアルポートを 1200 bps で開き直す合図で基板が書き込みモードに入り、RPI-RP2 ドライブが一瞬現れて、UF2 が自動でコピーされます。スケッチの大きさは約 115 KB(フラッシュの 5%)、グローバル変数は約 68 KB(RAM の 26%)です。
出荷時のファーム(Pico SDK 製)と arduino-pico では、USB の製品 ID(PID)が違います。出荷時は 000A、arduino-pico のスケッチを書き込むと 1029 になり、Windows は別のデバイスとして扱って COM ポートの番号を振り直します(筆者の PC では COM12 → COM13)。書き込み後にシリアルモニタが開けないときは、「ツール → ポート」で新しい番号を選び直してください。
シリアルモニタを 115200 bps で開くと、次のように表示されます(IP などは筆者の LAN の例です)。
DHCP.....
MAC : 3A:38:36:3A:00:19
IP : 192.168.0.53
MASK: 255.255.255.0
GW : 192.168.0.1
TCP echo server on port 5000
LAN ケーブルを挿した状態でリセットすると、eth.begin() から 0.5 秒(点 1 つ)で IP が付きました。LAN ケーブルを挿していないと、DHCP の後ろの点が 500 ms ごとに増え続け、ケーブルを挿すと IP が付いて先へ進みます。W5500 not found は LAN ケーブルがなくても出ません。チップの初期化は SPI の先の W5500 と話せれば通るからです。
このスケッチと④の PowerShell は、GitHub の ramtuc/wiznet-pico-basics(MIT ライセンス)に置いています。スケッチは arduino/w55rp20-evb-pico/dhcp_tcp_echo/、PowerShell は tools/tcp_echo_client.ps1 です。
Arduino(arduino-pico): eth.begin() に MAC を渡さないと、RP2040 の固有 ID(フラッシュのユニーク ID)から 6 バイトを作ります。先頭バイトの下位 2 ビットを「ローカル管理・ユニキャスト」に設定するので、MAC の 2 文字目(先頭バイトの下の桁)は 2・6・A・E のどれかになります。基板ごとに違う値になり、書き込み直しても変わりません。決まった値にしたいときは eth.begin(mac) で渡します。
上の表示例の 3A:38:... も 2 文字目が A です。スケッチを 5 回書き込み直しても MAC は変わらず、ルーターも毎回同じ IP を配りました。
公式 C 例題(WIZnet-PICO-C): ソースに 00:08:DC:12:34:56 が書いてあります。00:08:DC は WIZnet のベンダー番号です。同じ例題を書き込んだ基板を 2 枚同じ LAN につなぐと MAC がぶつかるので、そのときは末尾を変えてください。
③ PC から ping
シリアルに出た IP に、PC から ping を打ちます。
ping 192.168.x.x
返事をしているのは、Arduino の経路では RP2040 上の lwIP、公式 C 例題の経路では W5500 の中の回路です。同じ ping でも、応えている場所が違います。 それは数字にそのまま出ました。同じ基板・同じ LAN で 10 発ずつ打った結果です((b) は⑤の dhcp_dns を動かしている間)。
| 経路 | 応えている場所 | RTT(最小/平均/最大) | TTL |
|---|---|---|---|
| (a) Arduino(MACRAW + lwIP) | RP2040 上の lwIP | 2/13/21 ms | 255 |
| (b) 公式 C 例題(ハードウェアソケット) | W5500 の回路 | 10 発すべて 1 ms 未満 | 128 |
どちらも損失は 0 です。(a) の RTT が 2〜21 ms と幅広いのは、④の後で説明する lwIP の 20 ms ごとのポーリングのためと考えられます。ping が W5500 に届いても、RP2040 が次にポーリングするまで返事は作られません。その待ち時間は 0〜20 ms のどこかになり、RTT の幅とおおむね重なります。(b) では W5500 がその場で返事を作るので、RP2040 の都合を待ちません。
TTL も違います。255 は lwIP の既定値です(lwIP の opt.h で IP_DEFAULT_TTL が 255、ping の返事に使う ICMP_TTL もこれに従う)。(b) で返ってきた 128 は、W5500 が返した値です。Windows の ping は返事の TTL を表示するので、どちらが応えているかを TTL で見分けられます。
④ TCP エコー(Arduino)
PC から TCP 5000 番につないで文字を送り、同じ文字が返ってくれば TCP の往復ができています。Windows なら、次を tcp_echo_client.ps1 という名前で保存して PowerShell から実行します。ファイルは UTF-8(BOM 付き)で保存してください。 Windows PowerShell 5.1 は BOM なしの UTF-8 を cp932 として読むため、日本語コメントの次の行が消え、接続できているのに返事が空になります(PowerShell 7 では起きません)。
# TCP エコーの確認 (Windows PowerShell 5.1 / PowerShell 7)
# 使い方: .\tcp_echo_client.ps1 192.168.x.x "hello W6300"
# このファイルは UTF-8 (BOM 付き) で保存してある。BOM なしで保存し直すと Windows PowerShell 5.1 が
# 日本語コメントを cp932 として読み、コメントの直後の行が消えて返事が空になる (PowerShell 7 は問題なし)
param(
[string]$ip = "192.168.x.x", # シリアルに出た IP (W55RP20 / W6300)
[string]$text = "hello" # 送る文字列 (記事では hello W55RP20 / hello W6300)
)
$c = New-Object System.Net.Sockets.TcpClient($ip, 5000)
$s = $c.GetStream()
$s.ReadTimeout = 3000 # 3 秒返事がなければエラーで止める (ms)
$msg = [Text.Encoding]::ASCII.GetBytes("$text`n")
$s.Write($msg, 0, $msg.Length)
$buf = New-Object byte[] 64
$got = ""
while ($got -notmatch "`n") { # 改行が返ってくるまで読み続ける (loop)
$n = $s.Read($buf, 0, $buf.Length)
if ($n -le 0) { break } # 相手が接続を切ったら終わり (EOF)
$got += [Text.Encoding]::ASCII.GetString($buf, 0, $n)
}
$got
$c.Close()
日本語コメントの末尾を (ms)・(loop) のような ASCII で終えているのも同じ理由で、BOM なしで保存されてしまっても行が消えないようにしています。VS Code なら右下のエンコード表示から「UTF-8 with BOM」で保存し直せます。
実行は、IP と送る文字列を引数で渡します(IP はシリアルに出た値)。
.\tcp_echo_client.ps1 192.168.x.x "hello W55RP20"
スクリプトの実行が実行ポリシーで止められる場合は、powershell -ExecutionPolicy Bypass -File .\tcp_echo_client.ps1 192.168.x.x "hello W55RP20" の形にすると、その 1 回だけ実行できます。
hello W55RP20 と表示されれば成功です。筆者の環境では 21 ms で返り、続けてもう 1 行送ると 41 ms で返りました。スクリプトが最後に $c.Close() で切ると、シリアルに client: <PC の IP> に続いて closed が出ます。2 回続けて実行しても同じように返り、その後の ping も通ります。
Read() を 1 回呼ぶだけにしないのは、TCP には「1 回の送信=1 回の受信」という区切りがないからです。返事が 2 つのパケットに分かれて届くと、1 回目の Read() は先に着いた分だけを返します。そこで、行の終わり(改行)が来るまで読み続けます。読み残しがあるまま Close() すると、Windows は普通の切断(FIN)ではなく RST(強制切断)を送るので、その意味でも読み切ってから閉じます。
Linux や macOS なら nc 192.168.x.x 5000 でつなぎ、打った行がそのまま返ってくるかを見ます。Test-NetConnection 192.168.x.x -Port 5000 は「つながるか」だけを確かめる用途に使えます(データの往復までは見ません)。
arduino-pico の有線 Ethernet は、lwIP を割り込みではなく 20 ms 周期のポーリングで動かしています。ドキュメント(docs/ethernet.rst)には「By default it will run every 20ms」とあり、libraries/lwIP_Ethernet/src/LwipEthernet.cpp にも _pollingPeriod = 20 があります。
落とし穴は、ユーザーのコードが lwIP を呼ぶたびに、このポーリングが先送りされることです。同じファイルのコメントに「It causes ethernet_timeout_reached to not be called when user code does frequent lwip calls」とあります。client.connected() や client.available() も lwIP の呼び出しなので、これを休みなく呼ぶループでは、受信の処理(相手からの切断・ARP・ping)が一度も走りません。
while (client.available()) client.write(client.read()); を休みなく回す書き方では、実際に次のようになりました。
- 1 バイト目の
hだけが返り、残りが返らない - PC が切っても
closedが出ない - その間は ARP にも ping にも応えない(基板が止まったように見える)。ループを抜けると元に戻る
ループの 1 周ごとにポーリング周期より長く休む delay(50) を入れると直ります。delay(1) では直りませんでした。1 バイトずつ write() するのもやめ、受け取った分をまとめて返します。arduino-pico の WiFiClient は Nagle アルゴリズムが既定で有効なので(WiFiClient.cpp の defaultNoDelay = false)、1 バイトずつ書くと、先の 1 バイトの ACK が来るまで次を送らずに溜めます。PC 側の遅延 ACK と重なると、細切れの送信が待ち合いになります。
ポーリングの周期は lwipPollingPeriod(ms) で変えられますが、短くしすぎると Ethernet の処理が間に合わず固まる、とドキュメントにあります。
もう 1 つの書き方は、1 行受けて返したら、サーバー側から切る 1 往復型です。client.readStringUntil('\n') で 1 行読み、client.println() で返して client.stop() します。arduino-pico の WiFi ライブラリにある WiFiServer の例題も、1 行受けて返したら接続を終える形です。ループで lwIP を呼び続けないので同じ問題が起きず、この形でも 2 回続けて返り、ping も通りました。その代わり、1 回の接続で送れるのは 1 行だけです。
⑤ 同じことを公式 C 例題で(ハードウェアソケット)
今度は W5500 のハードウェアソケットで同じことをします。WIZnet-PICO-C の 2 つの例題を順に使います。Import とビルドの手順は Pico SDK の環境構築の記事の④にあります。RP2040 の基板は Windows では Zadig を入れないと拡張の Run が使えないので、ここではビルドしてできた .uf2 を BOOTSEL のドライブにコピーして書き込みました。
dhcp_dns(examples/dhcp_dns/wizchip_dhcp_dns.c)は、DHCP で IP をもらって表示し、www.wiznet.io の IP を DNS で引きます。取得した時点のソースでは .dhcp = NETINFO_DHCP になっているので、そのままビルドして wizchip_dhcp_dns.uf2 を書き込み、USB の COM ポートをシリアル端末で開いてリセットします(例題の出力は USB シリアルに出る設定です)。
dhcp_dns の出力(シリアル端末は Tera Term)。MAC はソースに書かれた 00:08:DC:12:34:56、IP は DHCP でもらった値(192.168.0.x は筆者の LAN の例)。最後の 2 行が www.wiznet.io を DNS で引いた結果
起動直後の [PIO SPI CLOCK SPEED : 50.00 MHz] (sys=200.00 MHz) は、この例題が RP2040 を 200 MHz で動かし、PIO で作った SPI を 50 MHz で回していることを示しています。続いて DHCP client running の後に MAC・IP・サブネットマスク・ゲートウェイ・DNS サーバーが並び、DHCP leased time : 7200 seconds(リース 2 時間)、DHCP success と出ます。DNS は DNS success で、www.wiznet.io は 183.111.138.249 に引けました。SPI の節で触れた ACCESS ERR : VERSION != 0x04 は出ず、RP2040 と W5500 は SPI で話せています。
loopback(examples/loopback/wizchip_loopback.c)は、TCP 5000 番で受けたデータを返すサーバーです。こちらは 固定 IP(既定は 192.168.11.2)なので、自分の LAN に合わせて書き換えてからビルドします。
static wiz_NetInfo g_net_info = {
.mac = {0x00, 0x08, 0xDC, 0x12, 0x34, 0x56}, // MAC address
.ip = {192, 168, 11, 2}, // ← LAN 内の空いている IP に
.sn = {255, 255, 255, 0}, // Subnet Mask
.gw = {192, 168, 11, 1}, // ← ルーターの IP に
.dns = {8, 8, 8, 8}, // DNS server
// ...
};
ここでは筆者の LAN に合わせて、IP を 192.168.0.250、ゲートウェイを 192.168.0.1 に書き換えました(どちらも筆者の LAN の例です)。
loopback の起動表示。network configuration が static(固定 IP)になり、書き換えた IP とゲートウェイが出る。DNS はソースのまま 8.8.8.8
書き込んだら、④と同じ PowerShell(IP の引数を固定 IP に変える)で 5000 番に送ります。hello W55RP20 が 12 ms で返り、もう一度実行すると 1 ms で返りました。loopback は接続が来てもシリアルには何も出さないので、成否は PowerShell 側で見ます。その後の ping も通ります。
⑥ LED を見る
RJ45 の 緑はリンク、オレンジは通信のたびに点滅します。チップの横に並ぶ緑の LED 4 つは W55RP20 の LED 用のピン(SPD・LINK・DUP・ACT)につながっていて、ユーザー LED は別にある GPIO19 の LED です(回路図 V1.00)。
💡 こういうことにも使える(応用編の予告)
- 有線のセンサーノード: Wi-Fi が届きにくい場所の温湿度を MQTT で送る(自宅センサー基盤 #3・#4 の受け側にそのままつながる)
- 小さな Web サーバー: ブラウザから GPIO の状態を見る・切り替える
- Modbus TCP: 工場や設備で使われる産業用プロトコルの端末にする
- PoE 給電の機器: WIZPoE-P1 を付けて LAN ケーブル 1 本で電源も取る
🔧 トラブルシューティング
| 症状 | 見るところ | 試すこと |
|---|---|---|
| RJ45 の LED が点かない | ケーブル・ハブのポート | 別のケーブル・ポートで試す。PC で同じケーブルがリンクするか確かめる |
| DHCP の点が止まらない | ルーターの DHCP・リンク LED | 固定 IP で試す(Arduino は eth.config(IPAddress(192,168,1,50), IPAddress(192,168,1,1), IPAddress(255,255,255,0)); を begin() の前に) |
W5500 not found / ACCESS ERR |
ボードの選択・BOARD_NAME | Arduino はボード「WIZnet W55RP20-EVB-Pico」、C 例題は BOARD_NAME W55RP20_EVB_PICO になっているか |
| ping は通るが TCP が返らない | PC のファイアウォール・ポート番号 | ポート 5000 で合っているか。同じ IP の機器が他にないか(MAC・IP の重複) |
| エコーの途中から ping も通らなくなる | loop() の受信ループ |
ループが休まず lwIP を呼び続けている。1 周ごとに delay(50) を入れる(④の後の解説) |
| PowerShell の返事が空行だけ(接続も ping も通る) | .ps1 の保存形式 |
BOM なしの UTF-8 だと Windows PowerShell 5.1 が日本語コメントの次の行を落とす。UTF-8(BOM 付き)で保存し直すか、PowerShell 7 で実行する |
| PowerShell に最初の数文字しか出ない | PC 側の読み方 | Read() 1 回で終えていないか。改行が来るまで読むループにする |
| 書き込み後にシリアルモニタが開けない | COM ポートの番号 | 書き込むと USB の PID が変わり番号が変わる。「ツール → ポート」で選び直す |
| BOOTSEL でドライブが出ない | USB ケーブル | 充電専用ケーブルでないか。BOOTSEL を押したまま USB を挿す |
| 途中でリセットする・不安定 | USB の給電 | PC 直結の USB ポートやセルフパワーのハブにつなぎ替える |
✅ まとめ
- W55RP20-EVB-Pico は、RP2040 と W5500 を 1 パッケージにした W55RP20 を載せた Pico 互換ボード。外に出る GPIO は GP0〜15・GP26〜28 が中心で、LED は GP19
- RP2040 と W5500 は SPI(CS 20・SCK 21・MISO 22・MOSI 23)と INT 24・RST 25 でつながる。SCK と MISO がハードウェア SPI の割り当てに合わないので、PIO で SPI を作っている
- 同じ基板に 2 つの使い方がある。Arduino(arduino-pico)は MACRAW + lwIP、公式 C 例題(WIZnet-PICO-C)はハードウェアソケット
- 最初の一歩は DHCP → ping → TCP エコー。両方の経路で同じ確認ができる
次の一手は、この 2 つの経路を同じ基板で比べることです。スループット、RP2040 の CPU 使用率、コードの長さがどう変わるかを、応用編で測ります。
関連記事
- RP2040 自作ロジックアナライザの立ち上げ【自作ロジアナ #2】:同じ RP2040 の基板を UF2 で立ち上げた記録。BOOTSEL と UF2 の仕組みはこちら
- RP2040で16chロジックアナライザを自作【自作ロジアナ #1】:RP2040 を自分で基板に載せるところから
- I2C・SPI・UART の違い|比較表と4つの質問で選べるマイコン通信規格:RP2040 と W5500 をつないでいる SPI の基本
- ESP32 シリーズの選び方|S3・C3・C6・H2・P4 の違いと使い分け:Wi-Fi でつなぐなら、こちらの選択肢
- 自宅MQTTサーバーをDockerで構築【自宅センサー基盤 #3】:有線のセンサーノードを作ったときの受け側
- Grafanaで作る自宅環境ダッシュボード【自宅センサー基盤 #4】:集めた値を見る側
- ramtuc/wiznet-pico-basics(GitHub):この記事のスケッチ(
arduino/w55rp20-evb-pico/dhcp_tcp_echo/)と TCP エコーの PowerShell(tools/tcp_echo_client.ps1)
参考
- W55RP20-EVB-Pico(WIZnet Docs):仕様・内部使用 GPIO・追加 7 ピン(2026-10-31 取得)
- W55RP20-EVB-Pico - W55RP20評価基板(スイッチサイエンス SKU 10015):2024-11-12 発売(2026-10-31 取得)
- WIZnet W55RP20: コンパクトで高効率なIoT用Ethernetチップ(スイッチサイエンスマガジン・WIZnet 寄稿):2024-11-12
- WIZnet-ioNIC/WIZnet-PICO-C(GitHub):main 2026-10-06。
port/ioLibrary_Driver/inc/wizchip_spi.h・CMakeLists.txt・examples/loopback・examples/dhcp_dns(2026-10-31 取得) - WIZnet-ioNIC/WIZnet-PICO-LWIP-TOE-C(GitHub):lwIP の API で TOE と MACRAW を切り替える例題集
- W55RP20-EVB-Pico 回路図 V1.00(Wiznet/Hardware-Files-of-WIZnet・GitHub):2024-11-15。RJ45 の LED と W55RP20 の LINKLED/ACTLED、基板の LED(SPDLED・LINKLED・DUPLED・ACTLED・GPIO19 のユーザー LED)
- Wiznet/ioLibrary_Driver(GitHub):
Ethernet/W5500/w5500.h(SPI フレーム・BSB・バッファ) - earlephilhower/arduino-pico(GitHub):6.3.1(2026-10-23)。
libraries/lwIP_w55rp20・libraries/SPI/src/SPI.cpp・libraries/lwIP_Ethernet/src/LwipIntfDev.h・libraries/lwIP_Ethernet/src/LwipEthernet.cpp(ポーリング周期)・docs/ethernet.rst(Adjusting LWIP Polling)・libraries/WiFi/src/WiFiClient.cpp(Nagle の既定)・同梱 lwIP のsrc/include/lwip/opt.h(IP_DEFAULT_TTL) - W55RP20-EVB-Pico(Zephyr Project ボード定義):ピンヘッダ・PIO SPI・LED(GPIO19)
- W5500(WIZnet Docs):データシート