「Seeed Studio XIAO ESP32S3 Series」を買ってきたので、とりあえずカメラで撮った画像をサーバに転送するプログラムを作っていきます。
今回もプログラマはGeminiです。
XIAO ESP32S3 Series のスペックまとめ
XIAO ESP32S3シリーズには、基本モデルの「XIAO ESP32S3」と、カメラとマイクがセットになった「XIAO ESP32S3 Sense」の2種類があります。今回は、カメラ付きの「Sense」モデルの機能使っていきます。
| 項目 | スペック詳細 | 補足 |
|---|---|---|
| MCU (マイコン) | ESP32-S3R8 (Xtensa 32-bit LX7 デュアルコア, 最大240MHz) | 高速な処理とAI演算(ベクトル命令)に対応 |
| ワイヤレス通信 | 2.4GHz Wi-Fi (802.11 b/g/n) Bluetooth 5.0 (BLE)/ Bluetooth Mesh |
ネットワーク接続やスマホ連携が容易 |
| メモリ | 8MB PSRAM / 8MB Flashメモリ | 画像処理やAIモデルの保存に十分な容量 |
| ** SD カードスロット** | 32GB FAT 対応 | |
| インターフェース | 1x UART 1x IIC 1x IIS 1x SPI 11x GPIOs (PWM) 9x ADC 1x User LED 1x Charge LED 1x B2B Connector (with 2 additional GPIOs) 1x Reset button 1x Boot button |
各種センサーやディスプレイの接続が可能 |
| USBポート | Type-C | 電源供給とプログラム書き込み用 |
| 内蔵センサ | 1x OV3660 カメラセンサ 1x デジタルマイク |
|
| サイズ | 21 x 17.5 mm | 非常に小さく、組み込み用途に最適 |
開発環境の構築手順 (Arduino IDE)
Arduino IDE を使ってプログラミングする準備をします。
1. Arduino IDE のインストール
- PC(Windows/Mac/Linux)に、Arduino公式サイトから最新の「Arduino IDE (v2.x系)」をダウンロードしてインストールします。
2. ESP32ボードパッケージの追加
-
Arduino IDEを開き、メニューの
ファイル>基本設定(Macの場合はArduino IDE>Settings) を開きます。


-
「追加のボードマネージャーのURL」の欄に以下のURLをコピー&ペーストし、「OK」を押します。
https://espressif.github.io/arduino-esp32/package_esp32_index.json

3. ボードマネージャーからESP32をインストール
- 左側のメニューアイコンから「ボードマネージャー」(基板のアイコン)をクリックします。
- 検索窓に
esp32と入力し、「Espressif Systems」が提供しているパッケージをインストールします。
4. ボードの選択
-
XIAO ESP32S3をUSB Type-CケーブルでPCに接続します。
-
ボード検索窓で
XIAO_ESP32S3と入力し、リストから「XIAO_ESP32S3」を選択します。(Senseモデルの場合もこれを選びます)。
設定タブのツールメニューから「PSRAM: "OPI PSRAM"」が有効になっていることを確認してください(カメラ機能を使うために必須です)。



画像データを送信するサンプルコード
ここでは、XIAO ESP32S3 Senseのカメラで撮影した画像を、Wi-Fi経由で指定したサーバーにHTTP POSTで送信するサンプルコードを紹介します。
#include <WiFi.h>
#include <HTTPClient.h>
#include "esp_camera.h"
// ==========================================
// ご自身の環境に合わせて変更してください
// ==========================================
const char* ssid = "YOUR_WIFI_SSID"; // Wi-FiのSSID
const char* password = "YOUR_WIFI_PASSWORD"; // Wi-Fiのパスワード
const char* serverUrl = "http://your-server-address.com/upload"; // 画像の送信先URL
// XIAO ESP32S3 Sense のカメラピン定義
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 10
#define SIOD_GPIO_NUM 40
#define SIOC_GPIO_NUM 39
#define Y9_GPIO_NUM 48
#define Y8_GPIO_NUM 11
#define Y7_GPIO_NUM 12
#define Y6_GPIO_NUM 14
#define Y5_GPIO_NUM 16
#define Y4_GPIO_NUM 18
#define Y3_GPIO_NUM 17
#define Y2_GPIO_NUM 15
#define VSYNC_GPIO_NUM 38
#define HREF_GPIO_NUM 47
#define PCLK_GPIO_NUM 13
void setup() {
Serial.begin(115200);
delay(1000);
// 1. Wi-Fiに接続
Serial.print("Connecting to Wi-Fi...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConnected to Wi-Fi!");
// 2. カメラの初期設定
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.frame_size = FRAMESIZE_VGA; // 画像サイズ (VGA: 640x480)
config.pixel_format = PIXFORMAT_JPEG; // フォーマット (JPEG)
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
config.fb_location = CAMERA_FB_IN_PSRAM; // PSRAMを使用
config.jpeg_quality = 12; // 画質 (数値が小さいほど高画質 0-63)
config.fb_count = 1;
// カメラの初期化を実行
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x\n", err);
return;
}
Serial.println("Camera initialized successfully!");
}
void loop() {
// 1. 画像をキャプチャ
camera_fb_t * fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Camera capture failed");
delay(1000);
return;
}
Serial.printf("Picture taken! Size: %d bytes\n", fb->len);
// 2. サーバーへ画像を送信 (Wi-Fi接続時のみ)
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(serverUrl);
// 送信するデータがJPEG画像であることをサーバーに伝える
http.addHeader("Content-Type", "image/jpeg");
Serial.println("Sending image to server...");
// HTTP POSTリクエストを実行し、画像を送信
int httpResponseCode = http.POST(fb->buf, fb->len);
if (httpResponseCode > 0) {
Serial.printf("HTTP Response code: %d\n", httpResponseCode);
} else {
Serial.printf("Error code: %d\n", httpResponseCode);
}
http.end(); // リソースを解放
} else {
Serial.println("Wi-Fi Disconnected");
}
// 3. カメラのメモリを解放 (重要)
esp_camera_fb_return(fb);
// 10秒待機してから再度撮影&送信
delay(10000);
}
サンプルコードの動かし方
-
コード内の
YOUR_WIFI_SSID、YOUR_WIFI_PASSWORD、http://your-server-address.com/uploadの部分を実際の環境に合わせて書き換えてください。 -
書き込みが完了したら、右上の「シリアルモニタ」(虫眼鏡のアイコン)を開き、通信速度を
115200 baudに設定すると、Wi-Fiへの接続状況や送信ステータスが確認できます。

Server側の構築
1. 準備:PythonとFlaskのインストール
Pythonのインストール手順については省略します。
Pythonをインストールしたら、ターミナルを開き以下のコマンドを入力してFlaskをインストールします。
pip install Flask
2. Pythonサーバーのコード (server.py)
お好きな場所にフォルダを作り、その中に server.py という名前で以下のコードを保存してください。
from flask import Flask, request
import os
import datetime
app = Flask(__name__)
# 画像の保存先フォルダ名
UPLOAD_FOLDER = 'uploads'
# フォルダが存在しない場合は自動で作成
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
@app.route('/upload', methods=['POST'])
def upload_image():
# ESP32から送られてきた生のバイナリデータ(画像)を取得
img_data = request.data
if not img_data:
return "No data received", 400
# ファイル名を現在時刻から生成(例: 20260228_143000.jpg)
filename = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + ".jpg"
filepath = os.path.join(UPLOAD_FOLDER, filename)
# データをファイルとして保存
with open(filepath, 'wb') as f:
f.write(img_data)
print(f"画像を保存しました: {filepath} (サイズ: {len(img_data)} bytes)")
return "Image uploaded successfully", 200
if __name__ == '__main__':
# host='0.0.0.0' にすることで、同一Wi-Fiネットワーク内のESP32からアクセス可能になります
# port=3000 は待ち受けるポート番号です
app.run(host='0.0.0.0', port=3000)
サーバーを起動
- コマンドプロンプト(またはターミナル)を開き、
server.pyを保存したフォルダに移動(cdコマンドを使用)します。 - 以下のコマンドを実行してサーバーを起動します。
python server.py
起動すると、Running on http://192.168.X.X:3000 のようなメッセージが表示されます。これでサーバー側は画像を受け入れる準備が完了です。
* Serving Flask app 'server'
* Debug mode: off
Address already in use
Port 5000 is in use by another program. Either identify and stop that program, or start the server with a different port.
On macOS, try searching for and disabling 'AirPlay Receiver' in System Settings.
nomura@M4MBA upload-web % !vi
vi server.py
nomura@M4MBA upload-web % vi server.py
nomura@M4MBA upload-web % python server.py
* Serving Flask app 'server'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:3000
* Running on http://192.168.1.18:3000
Press CTRL+C to quit
ESP32側のコード(Arduino IDE)の書き換え
最後に、前回作成したArduinoコードの送信先URL(serverUrl)を、サーバーを動かしているPCのIPアドレスに書き換えます。
PCとESP32は同じWi-Fiルーターに接続されている必要があります。
PCのローカルIPアドレス(192.168.X.X など)を確認し、Arduinoコードの以下の部分を修正してください。
// 変更前
const char* serverUrl = "http://your-server-address.com/upload";
// 変更後(例:PCのIPアドレスが 192.168.1.15 の場合)
const char* serverUrl = "http://192.168.1.15:3000/upload";
書き換え後、ESP32にプログラムを書き込みます。
シリアルモニタで HTTP Response code: 200 と表示され、PCの uploads フォルダ内に写真がどんどん保存されていけば大成功です!
サーバ側のターミナルには以下のような画像を保存した旨のメッセージが繰り返し表示されます。
画像を保存しました: uploads/20260301_132226.jpg (サイズ: 13957 bytes)
192.168.1.4 - - [01/Mar/2026 13:22:26] "POST /upload HTTP/1.1" 200 -
画像を保存しました: uploads/20260301_132236.jpg (サイズ: 13983 bytes)
192.168.1.4 - - [01/Mar/2026 13:22:36] "POST /upload HTTP/1.1" 200 -
画像を保存しました: uploads/20260301_132246.jpg (サイズ: 13146 bytes)
192.168.1.4 - - [01/Mar/2026 13:22:46] "POST /upload HTTP/1.1" 200 -
画像を保存しました: uploads/20260301_132257.jpg (サイズ: 14068 bytes)
192.168.1.4 - - [01/Mar/2026 13:22:57] "POST /upload HTTP/1.1" 200 -





