AstroCamp/衛星開発ゼミ
電子工作からネットワークへ
TCP/IPの基礎と
Pico WによるWiFi通信
Raspberry Pi Pico C++ SDK で、PC上のPythonスクリプトと通信する
Week5
AGENDA
本日お話しすること
00導入シリアル通信とネットワーク通信の違い
01TCP/IPの基礎階層モデル・WiFiの守備範囲・IPアドレスとポート・TCP/UDP
02Pico WでWiFi通信ハード構成・ソフトウェアスタック・C++コードの要点
03PCと通信する構成の選択・Pythonサーバとの接続・動作確認
TCP/IPの基礎とPico WによるWiFi通信
02
導入
TCP/IPは「ネットワーク」を前提にした通信規格
シリアル通信:1本の線を決まった機器間で独占
ネットワーク:多数の機器と共有された網
共有ゆえに起きること
同じ電波を何台もの機器が使う
↓
隣の機器を識別して、電波を分け合う
リンク層(WiFi)
共有ゆえに起きること
相手は他人の網の先にいる
↓
住所で相手を決めて、経路をたどる
インターネット層(IP)
共有ゆえに起きること
落ちる・遅れる・混ざる
↓
宛先のプログラムを分け、確実さを決める
トランスポート層(TCP/UDP)
TCP/IPの基礎とPico WによるWiFi通信
03
01
TCP/IPの基礎
通信の仕事を役割ごとに分けて、「どこへ」「どう届けるか」を決める共通ルール。
1.1 階層モデル
通信の仕事は4つの層に分業されている
アプリケーション層
「何を話すか」— メッセージの中身と意味
HTTP / 自作の形式
トランスポート層
「どう届けるか」— 確実に届ける/軽く送る
TCP / UDP
インターネット層
「どこへ届けるか」— 住所を頼りに経路を選ぶ
IP(IPアドレス)
リンク層
「隣まで運ぶ」— 電波や電線で物理的に伝える
WiFi / Ethernet
各層は自分の仕事だけを行い、残りは下の層に任せる。だから電波の仕様を知らなくても通信プログラムが書ける。
TCP=Transmission Control Protocol/UDP=User Datagram Protocol/IP=Internet Protocol
TCP/IPの基礎とPico WによるWiFi通信
05
1.2 リンク層:WiFiとは
WiFiが運ぶのは「電波が直接届く1区間」だけ
インターネット層(IP)
どの機器まで届けるかを決め、区間をつなぎ合わせる — 端から端までを受け持つ
Pico W
マイコン
WiFi(電波)
WiFiルータ(AP)
AP=Access Point。2つのリンクを中継する
有線LAN・光
PC・その先の機器
電波が直接届かない相手
リンク層① WiFi = 今日の主役
リンク層② 別の技術でもう1区間
どの区間にもリンク層がある。ルータより先もIPが直接運ぶのではなく、有線LAN(Local Area Network)や光といった別のリンク層が1区間ずつ運ぶ。区間をどうつなぐかを決めているのがIP。
「WiFiに接続」=網の輪に参加する手続き。リンク層の話。あとで出てくるTCPの「接続」とは層も目的も別物。
TCP/IPの基礎とPico WによるWiFi通信
06
1.3 インターネット層:IPアドレスとポート
住所(IPアドレス)と部屋番号(ポート)で相手を指定する
IPアドレス = どの機器か。IP(Internet Protocol)。LAN内では 192.168.x.x がルータから自動で配られる(DHCP=Dynamic Host Configuration Protocol)。
ポート番号 = どのプログラムか。0〜65535。同じPCの中のプログラムを区別する。
2つのペアで宛先が決まる。「192.168.1.10 の 5000番」のように組で指定する。
PC192.168.1.10= 住所
:5000Pythonスクリプト(今日の宛先)
:80Webサーバ
:22SSH
ポート = 部屋番号。同じ住所でも部屋ごとに住人(プログラム)が違う。
TCP/IPの基礎とPico WによるWiFi通信
07
1.4 トランスポート層:なぜ要るのか
シリアルの「当たり前」は、網の中では成り立たない
届かないかもしれない。ルータは混雑するとパケットを捨てる。IPは「努力して運ぶ」だけで保証しない(ベストエフォート)。
順番が入れ替わるかもしれない。経路は1本ではない。後から送ったパケットが先に着くこともある。
機器に届いても「誰宛てか」分からない。IPアドレスが指すのは機器まで。中のどのプログラム宛てかはIPの管轄外。
この3つの穴を埋めるのがトランスポート層 — ポートで宛先プログラムを指定し、届け方の品質を選ぶ
TCP/IPの基礎とPico WによるWiFi通信
08
1.5 トランスポート層の例:TCPとUDP
穴を全部埋めるTCP、埋めずに軽さを取るUDP
TCP
Transmission Control Protocol|つないでから話す — 電話
最初に接続の手続きをする
届いたか確認し、欠けたら再送
送った順に届く(順序保証)
その分、手間と遅延が増える
UDP
User Datagram Protocol|いきなり送る — はがき
接続の手続きなしで即送信
届いたかどうかは分からない
順番が入れ替わることもある
軽くて速い。仕組みが単純
「接続」に注意 — WiFiの接続はリンク層(網への参加。TCP・UDP共通の前提)、TCPの接続はトランスポート層(プログラム同士の合意。TCPだけ)
TCP/IPの基礎とPico WによるWiFi通信
09
1.6 クイズ
Q. この用途、TCPとUDPどちらを選ぶ?
①
温度センサの値を1秒ごとにPCへ送り続ける
TCP / UDP ?
②
設定ファイルをPicoへ転送する(1バイトも欠けてはいけない)
TCP / UDP ?
③
動作ログをPCへ送る
TCP / UDP ?
ヒント:欠けたら困るか? 遅れたら困るか?
TCP/IPの基礎とPico WによるWiFi通信
10
1.6 クイズの答え
A. 欠けて困るならTCP、鮮度が命ならUDP
① センサ値 → UDPが定番。1秒後に新しい値が来る。1個の欠けより、再送で古い値が遅れて届く方が困る。
② ファイル転送 → TCP一択。1バイト欠けても壊れる。全部を順番どおり届ける保証が必要。
③ ログ → 要件次第。確実な記録ならTCP、多少欠けてよい速報ならUDP。「欠けたら困るか」で決める。
TCP/IPの基礎とPico WによるWiFi通信
11
1.7 ソケット
下の層への入口「ソケット」— ファイルのように読み書きする
層の中での位置づけ
アプリケーション層
あなたのプログラム
ソケット= 下の層を使うための窓口
作るときにTCPかUDPを選ぶ。プログラムとの境目はここ1か所だけ
トランスポート層TCP / UDP
インターネット層IP(IPアドレス)
リンク層WiFi
ここから下はOS・ライブラリの仕事。プログラムからは見えない
使い方は4ステップ
① 作るTCPかUDPを選んで作るsocket()
② 接続するIPアドレスとポートを指定(TCPのみ)connect()
③ 送る・受け取るバイト列を読み書きsend() / recv()
④ 閉じる後片付けclose()
ファイル操作(open → read/write → close)と同じ発想。C++でもPythonでも共通の流れ
TCP/IPの基礎とPico WによるWiFi通信
12
02
Pico WでWiFi通信
ハードとソフトの全体像をつかんでから、C++ SDK(Software Development Kit)のコードを読む。
2.1 ハードウェア
無線は専用チップCYW43439の仕事 — RP2040は依頼するだけ
Raspberry Pi Pico W 基板
RP2040
マイコン本体。あなたのC++プログラムを実行する
SPIで依頼
CYW43439
無線チップ。WiFi (2.4GHz) の電波送受信を担当。オンボードLEDもここ
電波
WiFiルータ
LANの外の世界へ
対応は2.4GHz帯のみ。5GHz専用のSSID(Service Set Identifier)には接続できない。
TCP/IPの基礎とPico WによるWiFi通信
14
2.2 ソフトウェアスタック
PCではOSがやる仕事を、Picoでは lwIP が肩代わりする
あなたのC++コード送りたいデータを組み立てる
アプリケーション層
pico-sdk(cyw43_arch)初期化・WiFi接続を数行の関数に
つなぎ役
lwIPlightweight IPの略。TCP/IPを実装した軽量ライブラリ。今日の主役
トランスポート層+インターネット層
CYW43439 ドライバ+チップ電波の送受信
リンク層
CMakeで pico_cyw43_arch_lwip_threadsafe_background をリンクすると、この一式がまとめて使える。
TCP/IPの基礎とPico WによるWiFi通信
15
2.3 コード① WiFi接続
WiFi接続は実質3関数 — 初期化・STAモード・接続
#include "pico/cyw43_arch.h"
int main() {
// ① 無線チップを初期化
if (cyw43_arch_init()) return -1;
// ② 子機(ステーション)モードに
cyw43_arch_enable_sta_mode();
// ③ 接続を待つ(最大30秒)
if (cyw43_arch_wifi_connect_timeout_ms(
"MyHomeWiFi", "password",
CYW43_AUTH_WPA2_AES_PSK, 30000)) {
return -1; // 失敗
}
// ここに来ればIPアドレス取得済み
}
接続完了まで待つ関数。戻り値0で成功。IPアドレスはDHCPで自動取得される。
まだLANに参加しただけ。データの送受信はこの後のTCP/UDPで行う。
抜粋。CMakeLists.txtで pico_cyw43_arch_lwip_threadsafe_background のリンクが必要。
TCP/IPの基礎とPico WによるWiFi通信
16
2.4 lwIPの流儀
lwIPは「呼んで待つ」ではなく「起きたら呼ばれる」— コールバック方式
自分から呼ぶ関数
tcp_connect(...) 接続を依頼
tcp_write(...) 送信データを渡す
lwIPから呼ばれる関数(事前に登録)
on_connected(...) 接続が完了したとき
on_recv(...) データが届いたとき
GPIO割り込みハンドラと同じ発想 —「イベントが起きたら、登録した関数が呼ばれる」
TCP/IPの基礎とPico WによるWiFi通信
17
2.5 コード② TCPクライアント
接続を依頼し、完了したら送る — TCPクライアントの骨格
// ① PCB(≒ソケット)を作り、コールバックを登録
struct tcp_pcb *pcb = tcp_new();
tcp_recv(pcb, on_recv); // 受信時に呼ばれる
// ② サーバ(PC)へ接続を依頼
tcp_connect(pcb, &server_ip, 5000, on_connected);
// ③ 接続完了で lwIP から呼ばれる
err_t on_connected(void *arg,
struct tcp_pcb *pcb, err_t err) {
tcp_write(pcb, "hello from pico\n", 16,
TCP_WRITE_FLAG_COPY);
tcp_output(pcb); // 送信を確定
return ERR_OK;
}
PCBがソケットの役。PCB=Protocol Control Block(接続を管理する台帳)。作る→接続→送受信→閉じる、の流れは第1章と同じ。
writeとoutputはセット。writeはバッファに積むだけ。outputで送信を促す。
抜粋。エラー処理・切断処理(tcp_close)は省略。
TCP/IPの基礎とPico WによるWiFi通信
18
2.6 コード③ UDP送信
UDPは「詰めて、投函する」だけ — 接続もコールバックも不要
// ① PCBを作り、宛先(PC)を用意
struct udp_pcb *pcb = udp_new();
ip_addr_t dest;
ipaddr_aton("192.168.1.10", &dest);
// ② データをpbuf(lwIPのバッファ)に詰める
const char *msg = "temp=25.4\n";
struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT,
strlen(msg), PBUF_RAM);
memcpy(p->payload, msg, strlen(msg));
// ③ 投函 — 届いたかの確認はしない
udp_sendto(pcb, p, &dest, 5000);
pbuf_free(p); // 忘れるとメモリ枯渇
TCPよりずっと短い。接続手続きがないぶんコードも単純。定期送信のセンサ向き。
pbuf_freeを忘れない。マイコンはメモリが少ない。解放漏れは送信失敗に直結。
抜粋。1秒ごとに②③を繰り返せば計測ノードになる。
TCP/IPの基礎とPico WによるWiFi通信
19
03
PCと通信する
受け手はPythonの標準ライブラリで10行。組み合わせて通信を成立させる。
3.1 サーバとクライアント
「先に待つ側」がサーバ、「話しかける側」がクライアント
クライアント送りたいときに connect() で話しかける。今日のPicoはこちら
話しかける
サーバ先にポートを開けて待ち受ける(例:5000番)。今日のPC(Python)はこちら
先にサーバが待っていないと、話しかけても「誰も出ない」。起動は必ずサーバが先。
通信は「待つ側」がいないと始まらない。網の相手は、こちらの都合では聞いていない。だから先に「待つ係」を決めておく。線が常設のシリアルには無かった概念。
機械の種類ではなく、プログラムの役割。ソケットを使うプログラム同士の分担で、アプリケーション側の概念。IPやWiFiなど下の層は両者を対等に扱い、この区別を知らない。
役割はいつでも入れ替えられる。同じPCがサーバにもクライアントにもなれる。どちらを待つ側にするかは次のスライドで選ぶ。
TCP/IPの基礎とPico WによるWiFi通信
21
3.2 構成の選択
どちらを「待つ側」にするか — 正解は用途で決まる
前提:PicoとPCは同じルータの下(同じLAN)にいる。待つ側=サーバ、話しかける側=クライアント。
案A:PCが待つ
Pico=クライアント / PC=サーバ
Picoは起きたら送るだけ。データを集める用途に自然
待つ側のIPが必要 → PCのIPはipconfigですぐ分かる
接続を始められるのはPico側だけ。(接続後は双方向に送り合える)
案B:Picoが待つ
Pico=サーバ / PC=クライアント
PCから好きなタイミングで問い合わせ・操作できる
複数の端末から同じPicoへアクセスする用途に自然
PicoのIPはDHCPで変わりうる → 固定IP等の工夫が必要
以降のコード例は案Aで進める。関数の使い方は構成を入れ替えても同じ。
TCP/IPの基礎とPico WによるWiFi通信
22
3.3 コード④ PC側(TCP)
受け手のTCPサーバはPython標準ライブラリで10行
import socket
srv = socket.socket(socket.AF_INET,
socket.SOCK_STREAM) # TCP
srv.bind(("0.0.0.0", 5000)) # 5000番で受付
srv.listen(1)
conn, addr = srv.accept() # Picoの接続を待つ
print("connected:", addr)
while True:
data = conn.recv(1024) # 受信を待つ
if not data:
break # 切断された
print(data.decode())
conn.close()
流れは第1章のソケットのまま。作る → 待つ(bind/listen/accept)→ 読み書き → 閉じる。
"0.0.0.0" = どの口でも受付。このPCが持つすべてのネットワーク口で5000番を開く。
TCP/IPの基礎とPico WによるWiFi通信
23
3.4 コード⑤ PC側(UDP)
UDP受信は接続なし — bindしてrecvfromするだけ
import socket
sock = socket.socket(socket.AF_INET,
socket.SOCK_DGRAM) # UDP
sock.bind(("0.0.0.0", 5000))
while True:
# 届いたデータと送り主が返る
data, addr = sock.recvfrom(1024)
print(addr, data.decode())
listenもacceptもない。接続という概念がないため、開いたらすぐ受信できる。
送り主のアドレスも分かる。recvfromが (データ, 送信元) を返す。複数のPicoの受け口を1つにできる。
TCP/IPの基礎とPico WによるWiFi通信
24
3.5 動作確認
「待つ側が先」— サーバを起動してからPicoを動かす
① PCのIPを調べるipconfig / ip addr。Picoのコードの宛先に書く
② サーバを起動python server.py — 待つ側を先に動かす
③ ビルド&書き込みcmake+make → BOOTSELを押しながら接続、UF2をコピー
④ 受信を確認PCのターミナルに hello from pico が表示されれば成功
$ python server.py
connected: ('192.168.1.23', 54321)
hello from pico
TCP/IPの基礎とPico WによるWiFi通信
25
まとめ
全体像:データは層を下り、網を渡り、層を上る
あなたのC++コードアプリケーション層
"temp=25.4" というデータを作る
lwIPトランスポート+インターネット層
TCP/UDPで包み、宛先(IP・ポート)を付ける
CYW43439リンク層
電波にのせて送り出す
Pythonスクリプトアプリケーション層
recv() にデータが現れる
OSのTCP/IPトランスポート+インターネット層
包みを開け、ポート5000番の部屋へ届ける
WiFiアダプタリンク層
電波から受け取る
送る側は層を下り、受け取る側は層を上る。今日の内容はすべてこの図の中にある。
TCP/IPの基礎とPico WによるWiFi通信
26