ワイヤレスタイムラプスカメラをXIAO ESP32-S3で作った(SDカード無し)
目的
家庭菜園用に成長を記録するカメラをXIAO ESP32-S3 Senseで作りました。
概要
ESP32-S3で撮影したデータをftpを使ってラズベリーパイに送信します。
ラズベリーパイはシェルスクリプトを使い、データ受信後ファイル名を日付時刻付きに変更します。
カメラ画像はPSRAMに記録後,ftpでラズベリーパイに送信しているのでSDカードは使っていません。
ご注意
この製作例はセキュリティがほぼ無い状態です。
フォルダ名やディレクトリ名の変更はラズパイ側とESP32側いずれも変更必要です。
部品表
| 番号 | 品名 | |
|---|---|---|
| 1 | ESP32-S3 Sense | カメラ付きのESP32です |
| 2 | ラズベリーパイ | ネットワーク接続できればなんでも大丈夫 |
| 3 | モバイルバッテリー | 低消費電流停止しなければより良いです |
| 4 | Wi-Fiルーター | 2.4GのWiFiが使えれば良いです |
プログラムの導入と設定(ラズベリーパイ側)
ネットワーク接続できているラズベリーパイにvsftpdとinotifywait をインストールします。
インストールコマンド
sudo apt install vsfptd sudo apt install inotify-tools
/etc/vsftpd.confのバックアップをとりvsftpd.confに以下の記述を追加します。
vsftpd.conf-orgとvsftpd.confのdiff
/etc $ diff vsftpd.conf-org vsftpd.conf
25a26,28
>
> allow_writeable_chroot=YES
>
31c34
< #write_enable=YES
---
> write_enable=YES
35c38
< #local_umask=022
---
> local_umask=022
114c117
< #chroot_local_user=YES
---
> chroot_local_user=YES
test-camというユーザ名を作成します。
以下のシェルスクリプトを/home/test-cam直下に生成します。
実行できるよう chmod 755 ./check.shとします
check.sh
#!/bin/bash
WATCH_DIR="/home/test-cam" #ディレクトリの変更はESP32側のプログラムも変更必要です
FILENAME="directfb.jpg"
inotifywait -m -e close_write --format "%f" "$WATCH_DIR" |while read FILE
do
if [ "$FILE" = "$FILENAME" ]; then
TIMESTAMP=$(date +"%Y-%m-%d-%H-%M")
sleep 3
NEW_NAME="cam$TIMESTAMP.jpg"
mv "$WATCH_DIR/$FILENAME" "$WATCH_DIR/$NEW_NAME"
echo "Renamed $FILENAME to $NEW_NAME" #echoの行は自動起動する時はコメントにします
fi
done
プログラムの導入と設定(ESP32-S3側)
ボードマネージャーでesp32 by Espressifをインストールします
ライブラリマネージャーでesp32_ftpclientをインストールします
ボードのタイプは"XIAO_ESP32S3"を指定します
指定が終わったら"ツール"タブのメニューからPSRAMを選択して"OPI PSRAM"に変更します。
書き込むプログラムは2つあります、同じフォルダ内に入れて使います。
camera_pins.h
#if defined(CAMERA_MODEL_WROVER_KIT)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 21
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 19
#define Y4_GPIO_NUM 18
#define Y3_GPIO_NUM 5
#define Y2_GPIO_NUM 4
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
#elif defined(CAMERA_MODEL_ESP_EYE)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 4
#define SIOD_GPIO_NUM 18
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 36
#define Y8_GPIO_NUM 37
#define Y7_GPIO_NUM 38
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 35
#define Y4_GPIO_NUM 14
#define Y3_GPIO_NUM 13
#define Y2_GPIO_NUM 34
#define VSYNC_GPIO_NUM 5
#define HREF_GPIO_NUM 27
#define PCLK_GPIO_NUM 25
#define LED_GPIO_NUM 22
#elif defined(CAMERA_MODEL_M5STACK_PSRAM)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 25
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 32
#define VSYNC_GPIO_NUM 22
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
#elif defined(CAMERA_MODEL_M5STACK_V2_PSRAM)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 22
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 32
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
#elif defined(CAMERA_MODEL_M5STACK_WIDE)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 22
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 32
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
#define LED_GPIO_NUM 2
#elif defined(CAMERA_MODEL_M5STACK_ESP32CAM)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 25
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 17
#define VSYNC_GPIO_NUM 22
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
#elif defined(CAMERA_MODEL_M5STACK_UNITCAM)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 25
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 32
#define VSYNC_GPIO_NUM 22
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
#elif defined(CAMERA_MODEL_AI_THINKER)
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
#define LED_GPIO_NUM 33
#elif defined(CAMERA_MODEL_TTGO_T_JOURNAL)
#define PWDN_GPIO_NUM 0
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 25
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 17
#define VSYNC_GPIO_NUM 22
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
#elif defined(CAMERA_MODEL_XIAO_ESP32S3)
#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
#define LED_GPIO_NUM 21
#elif defined(CAMERA_MODEL_ESP32_CAM_BOARD)
// The 18 pin header on the board has Y5 and Y3 swapped
#define USE_BOARD_HEADER 0
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM 33
#define XCLK_GPIO_NUM 4
#define SIOD_GPIO_NUM 18
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 36
#define Y8_GPIO_NUM 19
#define Y7_GPIO_NUM 21
#define Y6_GPIO_NUM 39
#if USE_BOARD_HEADER
#define Y5_GPIO_NUM 13
#else
#define Y5_GPIO_NUM 35
#endif
#define Y4_GPIO_NUM 14
#if USE_BOARD_HEADER
#define Y3_GPIO_NUM 35
#else
#define Y3_GPIO_NUM 13
#endif
#define Y2_GPIO_NUM 34
#define VSYNC_GPIO_NUM 5
#define HREF_GPIO_NUM 27
#define PCLK_GPIO_NUM 25
#elif defined(CAMERA_MODEL_ESP32S3_CAM_LCD)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 40
#define SIOD_GPIO_NUM 17
#define SIOC_GPIO_NUM 18
#define Y9_GPIO_NUM 39
#define Y8_GPIO_NUM 41
#define Y7_GPIO_NUM 42
#define Y6_GPIO_NUM 12
#define Y5_GPIO_NUM 3
#define Y4_GPIO_NUM 14
#define Y3_GPIO_NUM 47
#define Y2_GPIO_NUM 13
#define VSYNC_GPIO_NUM 21
#define HREF_GPIO_NUM 38
#define PCLK_GPIO_NUM 11
#elif defined(CAMERA_MODEL_ESP32S2_CAM_BOARD)
// The 18 pin header on the board has Y5 and Y3 swapped
#define USE_BOARD_HEADER 0
#define PWDN_GPIO_NUM 1
#define RESET_GPIO_NUM 2
#define XCLK_GPIO_NUM 42
#define SIOD_GPIO_NUM 41
#define SIOC_GPIO_NUM 18
#define Y9_GPIO_NUM 16
#define Y8_GPIO_NUM 39
#define Y7_GPIO_NUM 40
#define Y6_GPIO_NUM 15
#if USE_BOARD_HEADER
#define Y5_GPIO_NUM 12
#else
#define Y5_GPIO_NUM 13
#endif
#define Y4_GPIO_NUM 5
#if USE_BOARD_HEADER
#define Y3_GPIO_NUM 13
#else
#define Y3_GPIO_NUM 12
#endif
#define Y2_GPIO_NUM 14
#define VSYNC_GPIO_NUM 38
#define HREF_GPIO_NUM 4
#define PCLK_GPIO_NUM 3
#elif defined(CAMERA_MODEL_ESP32S3_EYE)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 15
#define SIOD_GPIO_NUM 4
#define SIOC_GPIO_NUM 5
#define Y2_GPIO_NUM 11
#define Y3_GPIO_NUM 9
#define Y4_GPIO_NUM 8
#define Y5_GPIO_NUM 10
#define Y6_GPIO_NUM 12
#define Y7_GPIO_NUM 18
#define Y8_GPIO_NUM 17
#define Y9_GPIO_NUM 16
#define VSYNC_GPIO_NUM 6
#define HREF_GPIO_NUM 7
#define PCLK_GPIO_NUM 13
#else
#error "Camera model not selected"
#endif
ESP32S3_take_camera.ino
#include "esp_camera.h"
#include "SPI.h"
#define CAMERA_MODEL_XIAO_ESP32S3 // Has PSRAM
#include "camera_pins.h"
//ftp-client
#include "Arduino.h"
#include <WiFi.h>
#include <ESP32_FTPClient.h>
//ftp-client
#define WIFI_SSID ""
#define WIFI_PASS ""
char ftp_server[] = ""; //サーバー名を記入します
char ftp_user[] = "test-cam";//このユーザのホームディレクトリに画像ファイルを作ります
char ftp_pass[] = "";
ESP32_FTPClient ftp (ftp_server,ftp_user,ftp_pass, 5000, 2);
//ftp-client
bool camera_sign = false; // Check camera status
void setup() {
/* Serial.begin(9600); //モバイルバッテリー使用時はコメントにしないと起動しないです
while(!Serial){ // When the serial monitor is turned on, the program starts to execute
delay(10);
} */
Serial.println("Photos will begin in 30sec, please be ready0.");
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_UXGA;
config.pixel_format = PIXFORMAT_JPEG; // for streaming
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
config.fb_location = CAMERA_FB_IN_PSRAM;
config.jpeg_quality = 12;
config.fb_count = 1;
// if PSRAM IC present, init with UXGA resolution and higher JPEG quality
// for larger pre-allocated frame buffer.
if(config.pixel_format == PIXFORMAT_JPEG){
if(psramFound()){
config.jpeg_quality = 10;
config.fb_count = 2;
config.grab_mode = CAMERA_GRAB_LATEST;
} else {
// Limit the frame size when PSRAM is not available
config.frame_size = FRAMESIZE_SVGA;
config.fb_location = CAMERA_FB_IN_DRAM;
}
} else {
// Best option for face detection/recognition
config.frame_size = FRAMESIZE_240X240;
#if CONFIG_IDF_TARGET_ESP32S3
config.fb_count = 2;
#endif
}
// camera init
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
//Serial.printf("Camera init failed with error 0x%x", err);
return;
}
camera_sign = true; // Camera initialization check passes
//Serial.println("Photos will begin in 30sec, please be ready1.");
//画像劣化対策
camera_fb_t *fb = esp_camera_fb_get();
delay(10);
esp_camera_fb_return(fb);
//画像劣化対策
}
void loop() {
if(camera_sign){
// Get the current time
// unsigned long now = millis();
//Serial.println("Photos will begin in 30sec, please be ready2.");
camera_fb_t *fb = esp_camera_fb_get();
delay(10);
// camera_fb_t *fb = esp_camera_fb_get();
if (!fb) {
//Serial.println("Failed to get camera frame buffer");
return;
}
//ftp-client start
delay(100);
WiFi.begin( WIFI_SSID, WIFI_PASS );
WiFi.setTxPower(WIFI_POWER_15dBm); //送信出力を変更しています
//Serial.println("Connecting Wifi...");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
// Serial.print(".");
}
//Serial.println("");
//Serial.print("IP address: ");
//Serial.println(WiFi.localIP());
ftp.OpenConnection();
ftp.InitFile("Type I");
ftp.NewFile("directfb.jpg");
ftp.WriteData( fb->buf,fb->len);
ftp.CloseFile();
esp_camera_fb_return(fb);
//ftp-client end
delay(100000);//タイムラプスの間隔指定です
}
}
雑記
ESP32-S3は電波の強度をデフォルトのまま使うと接続エラー出ます 電波の強さを変更しておく必要あります。
モバイルバッテリーで起動するにはSerial.begin(9600);を停止しておきます。
モバイルバッテリーが低消費電流で停止するタイプだったのでdelay()で間隔指定しています。
カメラは初回の画像が緑かぶりしていたのでsetup()の中で読み込んだのちloop()で使う画像を読み込んでいます。
これらの対策情報はネットから拾っておりますが、具体的な参照先はよく覚えていないので、参照先記載しておりません。
ArduinoIDEのシリアルモニタをつかって動作確認する時はSerial.bigin()を使えるようにしてください。
シェルスクリプトは/etc/rc.localに起動時動作するよう記述しておけば手間要らずです。
SDカードを使っている時と比べて発熱がかなりありませんでした。
inotifywaitはファイルの生成や変更などを監視するコマンドでchatgptに教えてもらいました。
使い方
ラズベリーパイのコマンドラインでcheck.shを起動します。(シェルスクリプトの終了はCtrl-cで停止します)
ESP32-S3にモバイルバッテリーを接続します。
ラズベリーパイの画像を保存するフォルダをlsコマンドで見るとcam2025-07-14-10-33.jpgといったファイルができていれば成功です。
追記(2025−7−15)
これはFTPでデータを送信しています。MQTTでも同様の事できそうなのでチャレンジする予定です
ESP-NOWでは250バイトの制限あるので無理でした
追記(2025−7−30)
MQTTでの送信はpubsubclientを使った場合client.setBufferSize();でバッファーサイズを大きくする必要あり、chatgtに聞いたら送るデータ長が大きくなった場合の弊害も考慮する必要ありますよと指摘されたので保留としました。
その代わりM5StackをMQTTサブスクライバーにしてラズパイからパブリッシュされたJPEG画像を表示するタイムラプスモニターを製作中です。
追記(2025−8−2)
M5Stackを使って画角確認用モニターを製作しました。
M5Stackをタイムラプスモニターにしました併せてご覧ください。
投稿者の人気記事




-
Makato-kan
さんが
2025/07/15
に
編集
をしました。
(メッセージ: 初版)
-
Makato-kan
さんが
2025/07/15
に
編集
をしました。
(メッセージ: 追記の追記)
-
Makato-kan
さんが
2025/07/15
に
編集
をしました。
(メッセージ: タイトル修正)
-
Makato-kan
さんが
2025/07/16
に
編集
をしました。
(メッセージ: 記述追加)
-
Makato-kan
さんが
2025/07/18
に
編集
をしました。
(メッセージ: タイトル修正)
-
Makato-kan
さんが
2025/07/21
に
編集
をしました。
-
Makato-kan
さんが
2025/07/30
に
編集
をしました。
(メッセージ: 追記しました。)
-
Makato-kan
さんが
2025/08/02
に
編集
をしました。
(メッセージ: 追記)
-
Makato-kan
さんが
2025/10/13
に
編集
をしました。
(メッセージ: タグの追加した)
ログインしてコメントを投稿する