Caméra ESP32 S3 - Parcourir les photos sur la carte SD depuis un navigateur Web

Dans les autres tutoriels de carte SD, vous devez débrancher la carte et l'apporter à votre ordinateur pour voir les photos. Ce tutoriel corrige cela. L'ESP32 S3 prend des photos sur la carte microSD et exécute une petite galerie photo. Ouvrez l'adresse dans n'importe quel navigateur Web et toutes les photos sont là, sur votre téléphone ou sur votre ordinateur.

ESP32 S3 Camera - Browse Photos on SD Card from Web Browser

Ce que vous allez construire :

  1. Une caméra ESP32 S3 qui enregistre une photo toutes les 30 secondes sur la carte microSD
  2. Une page Web de galerie photo qui montre chaque photo de la carte
  3. Un clic sur une photo pour l'ouvrir en taille réelle, et pour l'enregistrer
  4. Une caméra que vous n'avez plus jamais besoin d'ouvrir pour récupérer les photos

Matériel nécessaire

1×ESP32 S3 N16R8 OV5640 Camera
1×Alternativement: ESP32 S3 N16R8 OV2640 Camera
1×microSD Card
1×Câble USB Type-A vers Type-C (pour PC USB-A)
1×Câble USB Type-C vers Type-C (pour PC USB-C)
1×Optionnel: Connecteur d'alimentation DC
1×Recommandé: Screw Terminal Expansion Board for ESP32 S3
1×Recommandé: Breakout Expansion Board for ESP32 S3
1×Recommandé: Power Splitter for ESP32 S3

Ou vous pouvez acheter les kits suivants:

1×Kit de Capteurs DIYables (18 capteurs/écrans)
Divulgation : Certains des liens fournis dans cette section sont des liens affiliés Amazon. Nous pouvons recevoir une commission pour tout achat effectué via ces liens, sans coût supplémentaire pour vous. Nous vous remercions de votre soutien.

※ Note:

La carte caméra ESP32 S3 a 40 broches, mais le bornier à vis et la carte d'extension ont 44 broches. Elles fonctionnent quand même ensemble. Placez la carte caméra au centre de la carte à 44 broches, afin que 2 broches restent libres à gauche et 2 broches restent libres à droite.

Le bornier à vis vous permet de connecter des fils avec un tournevis, sans soudure. La carte d'extension vous donne des broches faciles pour une breadboard.

Comment ça marche

Le serveur Web a deux adresses, et elles fonctionnent ensemble :

AdresseCe qu'elle fait
http://<IP>/Lit la carte, et construit une page avec une petite image par photo
http://<IP>/photo?n=00001Envoie un fichier photo depuis la carte

La page de galerie ne contient elle-même aucune photo. Elle écrit seulement de nombreuses lignes <img>, et chacune demande à la carte un fichier différent :

<img src="/photo?n=00001"> <img src="/photo?n=00002"> <img src="/photo?n=00003">

Ainsi, le navigateur Web demande les photos les unes après les autres, et l'ESP32 S3 n'a besoin de lire qu'un seul fichier à la fois. C'est pourquoi une carte avec peu de mémoire peut afficher une carte SD pleine de photos.

※ Note:

Une photo de 180 Ko ne tient pas en une seule fois dans la mémoire de l'ESP32 S3. Le code lit le fichier par morceaux de 1024 octets et envoie chaque morceau à la fois :

uint8_t buffer[1024]; while (file.available()) { size_t read = file.read(buffer, sizeof(buffer)); client.write(buffer, read); }

Lire la liste des fichiers

SD_MMC.open("/") ouvre le dossier racine, et openNextFile() le parcourt :

File root = SD_MMC.open("/"); File entry = root.openNextFile(); while (entry) { String name = String(entry.name()); // ... use the name ... entry.close(); entry = root.openNextFile(); }

※ Note:

Appelez toujours entry.close() à l'intérieur de la boucle. L'ESP32 S3 ne peut garder que quelques fichiers ouverts en même temps, et la liste s'arrête au milieu si vous l'oubliez.

Schéma de câblage

Il n'y a aucun câblage dans ce projet. Vous devez seulement insérer la carte microSD dans son logement.

ESP32 S3 Photo Gallery Wiring Diagram

Cette image a été créée avec Fritzing. Cliquez pour agrandir l'image.

  1. Débranchez le câble USB.
  2. Assurez-vous que le module caméra est connecté avec son câble plat.
  3. Poussez la carte microSD dans son logement jusqu'à entendre un petit clic.
  4. Rebranchez le câble USB.

Notes de sécurité

Ne retirez jamais la carte pendant que la carte écrit une photo. La photo, et parfois la carte entière, devient corrompue. Débranchez toujours le câble USB en premier, attendez deux secondes, puis retirez la carte.

Réglages de l'Arduino IDE pour la caméra ESP32 S3

La caméra ne fonctionne pas avec les réglages par défaut de l'Arduino IDE. Vous devez changer certains éléments dans le menu Tools avant de téléverser le code.

Tools Menu ItemSelect This
BoardESP32S3 Dev Module
Flash Size16MB (128Mb)
PSRAMOPI PSRAM
Partition SchemeHuge APP (3MB No OTA/1MB SPIFFS)
CPU Frequency240MHz (WiFi)
USB ModeHardware CDC and JTAG
Upload ModeUART0 / Hardware CDC
Upload Speed921600
USB CDC On BootEnabled

※ Note:

PSRAM doit être réglé sur OPI PSRAM, et Partition Scheme doit être réglé sur Huge APP. Changez Flash Size en premier, car la liste des options de Partition Scheme change ensuite.

Installer la bibliothèque DIYables ESP32 WebServer

  1. Ouvrez l'Arduino IDE.
  2. Cliquez sur l'icône Libraries dans la barre latérale gauche.
  3. Tapez DIYables ESP32 WebServer dans la zone de recherche.
  4. Trouvez la bibliothèque de DIYables.io et cliquez sur INSTALL.
  • Search for DIYables ESP32 WebServer created by DIYables.io and click the Install button.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32S3 Dev Module
Library Manager
Type:
All
Topic:
All
DIYables ESP32 WebServer by DIYables.io
DIYables ESP32 WebServer library designed for ESP32 boards. It includes multi-page web server capabilities and WebSocket support for real-time communication, perfect for IoT projects and DIYables ESP32 boards. More info
1.0.1
INSTALL
Newbiely.ino
···
1 void setup() {
Output
Serial Monitor
Ln 1, Col 1
ESP32S3 Dev Module on COM15
1

SD_MMC et esp_camera sont déjà inclus dans le package de la carte ESP32.

Comment programmer la galerie photo

  • Écrivez une fonction pour chaque adresse, et ajoutez-les dans setup().
server.addRoute("/", handleGallery); server.addRoute("/photo", handlePhoto); server.begin();
  • Lisez le numéro dans l'adresse \/photo?n=00001. La bibliothèque a déjà découpé l'adresse en clés et valeurs pour vous.
String number = ""; for (int i = 0; i < params.count; i++) if (String(params.params[i].key) == "n") number = String(params.params[i].value); String fileName = "/photo_" + number + ".jpg";

※ Note:

Nous envoyons uniquement les cinq chiffres, pas le nom de fichier complet. La bibliothèque garde une valeur plus courte que 16 lettres, et 00001 est court et sûr.

  • Extrayez les cinq chiffres d'un nom de fichier, pour construire le lien sur la page de galerie.
String name = String(entry.name()); // photo_00001.jpg String number = name.substring(6, 11); // 00001
  • Envoyez le fichier au navigateur avec la bonne longueur, puis les octets.
client.print("HTTP/1.1 200 OK\r\n"); client.print("Content-Type: image/jpeg\r\n"); client.printf("Content-Length: %u\r\n", (unsigned)file.size()); client.print("Connection: close\r\n\r\n");
  • Continuez à prendre des photos dans loop(), à côté du serveur Web.
void loop() { server.handleClient(); if (millis() - lastPhotoTime >= PHOTO_INTERVAL) { lastPhotoTime = millis(); takeAndSavePhoto(); } }

※ Note:

Le serveur Web répond à un seul navigateur Web à la fois. Pendant qu'il envoie une grande photo, la prochaine photo de la caméra attend un instant. C'est normal pour une petite carte.

Code ESP32 S3

Le code enregistre une photo toutes les 30 secondes et les montre toutes dans la galerie. Pour changer le temps, changez la ligne #define PHOTO_INTERVAL 30000.

/* * Ce code ESP32 S3 a été développé par newbiely.fr * Ce code ESP32 S3 est mis à disposition du public sans aucune restriction. * Pour des instructions complètes et des schémas de câblage, veuillez visiter: * https://newbiely.fr/tutorials/esp32-s3/esp32-s3-camera-browse-photos-on-sd-card-from-web-browser */ #include <WiFi.h> #include <DIYables_ESP32_WebServer.h> // DIYables ESP32 WebServer library #include "esp_camera.h" #include "FS.h" #include "SD_MMC.h" const char *WIFI_SSID = "YOUR_WIFI_SSID"; // CHANGE IT const char *WIFI_PASSWORD = "YOUR_WIFI_PASSWORD"; // CHANGE IT #define PHOTO_INTERVAL 30000 // take one photo every 30 seconds (30000 milliseconds) // Camera pins of the ESP32-S3-N16R8 camera board. // The same pins work for the OV2640, OV3660 and OV5640 versions. #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 Y9_GPIO_NUM 16 #define Y8_GPIO_NUM 17 #define Y7_GPIO_NUM 18 #define Y6_GPIO_NUM 12 #define Y5_GPIO_NUM 10 #define Y4_GPIO_NUM 8 #define Y3_GPIO_NUM 9 #define Y2_GPIO_NUM 11 #define VSYNC_GPIO_NUM 6 #define HREF_GPIO_NUM 7 #define PCLK_GPIO_NUM 13 // Pins of the microSD card slot on the board #define SD_CLK_GPIO_NUM 39 #define SD_CMD_GPIO_NUM 38 #define SD_D0_GPIO_NUM 40 DIYables_ESP32_WebServer server(80); int photoNumber = 1; unsigned long lastPhotoTime = 0; // Returns the name of the camera sensor that is installed on the board const char *getSensorName(int pid) { switch (pid) { case OV2640_PID: return "OV2640 (2 MP, max UXGA 1600x1200)"; case OV3660_PID: return "OV3660 (3 MP, max QXGA 2048x1536)"; case OV5640_PID: return "OV5640 (5 MP, max QSXGA 2592x1944)"; default: return "Unknown sensor"; } } bool initCamera() { 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_sccb_sda = SIOD_GPIO_NUM; config.pin_sccb_scl = SIOC_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; config.frame_size = FRAMESIZE_SVGA; // 800x600 config.jpeg_quality = 12; // 0 to 63, a lower number means a better image config.fb_count = 1; config.fb_location = CAMERA_FB_IN_DRAM; config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; if (psramFound()) { config.fb_location = CAMERA_FB_IN_PSRAM; config.jpeg_quality = 10; config.fb_count = 2; config.grab_mode = CAMERA_GRAB_LATEST; } else { config.frame_size = FRAMESIZE_QVGA; Serial.println("WARNING: PSRAM not found. Please set PSRAM to OPI PSRAM in the Tools menu."); } esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x\n", err); return false; } sensor_t *s = esp_camera_sensor_get(); Serial.printf("Camera sensor: %s\n", getSensorName(s->id.PID)); // Each sensor needs slightly different settings switch (s->id.PID) { case OV2640_PID: s->set_vflip(s, 0); s->set_hmirror(s, 0); break; case OV3660_PID: s->set_vflip(s, 1); // the OV3660 image is upside down by default s->set_brightness(s, 1); // make the image a little brighter s->set_saturation(s, -2); // make the colors a little softer break; case OV5640_PID: s->set_vflip(s, 0); s->set_hmirror(s, 0); break; default: break; } return true; } bool initSDCard() { SD_MMC.setPins(SD_CLK_GPIO_NUM, SD_CMD_GPIO_NUM, SD_D0_GPIO_NUM); // true means 1-bit mode. The camera uses the other pins, so we cannot use 4-bit mode. if (!SD_MMC.begin("/sdcard", true)) { Serial.println("SD card not found. Please check the card and push it in again."); return false; } if (SD_MMC.cardType() == CARD_NONE) { Serial.println("No SD card inside the slot"); return false; } Serial.printf("SD card size: %llu MB\n", SD_MMC.cardSize() / (1024 * 1024)); return true; } // Makes a file name like /photo_00001.jpg String makeFileName(int number) { char name[32]; sprintf(name, "/photo_%05d.jpg", number); return String(name); } // Looks for the first free file name, so old photos are never lost void findFirstFreeNumber() { while (SD_MMC.exists(makeFileName(photoNumber))) photoNumber++; Serial.printf("The next photo will be photo_%05d.jpg\n", photoNumber); } void takeAndSavePhoto() { camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { Serial.println("Photo capture failed"); return; } String fileName = makeFileName(photoNumber); File file = SD_MMC.open(fileName.c_str(), FILE_WRITE); if (file) { file.write(fb->buf, fb->len); file.close(); Serial.printf("Saved %s (%u bytes)\n", fileName.c_str(), fb->len); photoNumber++; } else { Serial.printf("Cannot write the file %s\n", fileName.c_str()); } esp_camera_fb_return(fb); } // Shows the list of photos that are on the card void handleGallery(WiFiClient &client, const String &method, const String &request, const QueryParams &params, const String &jsonData) { client.print("HTTP/1.1 200 OK\r\n"); client.print("Content-Type: text/html\r\n"); client.print("Connection: close\r\n\r\n"); client.print(F("<!DOCTYPE html><html><head><meta charset=\"utf-8\">")); client.print(F("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">")); client.print(F("<title>ESP32 S3 Photo Gallery</title><style>")); client.print(F("body{background:#111;color:#eee;font-family:sans-serif;text-align:center;margin:0;padding:16px}")); client.print(F("a{color:#4da3ff;text-decoration:none}")); client.print(F(".card{display:inline-block;margin:8px;padding:8px;background:#222;border-radius:8px}")); client.print(F("img{width:240px;border-radius:6px;display:block}")); client.print(F(".note{color:#aaa;font-size:14px;margin-top:-8px}")); client.print(F("</style></head><body><h2>ESP32 S3 Photo Gallery</h2>")); client.print(F("<p class=\"note\">Images saved to Micro SD card</p>")); File root = SD_MMC.open("/"); int count = 0; if (root) { File entry = root.openNextFile(); while (entry) { String name = String(entry.name()); // We only show our own photos if (name.startsWith("photo_") && name.endsWith(".jpg")) { String number = name.substring(6, 11); // the 5 digits inside photo_00001.jpg client.print(F("<div class=\"card\"><a href=\"/photo?n=")); client.print(number); client.print(F("\" target=\"_blank\"><img src=\"/photo?n=")); client.print(number); client.print(F("\"></a><br>")); client.print(name); client.print(F(" - ")); client.print(entry.size() / 1024); client.print(F(" KB</div>")); count++; } entry.close(); entry = root.openNextFile(); } root.close(); } if (count == 0) client.print(F("<p>No photo on the card yet. Please wait.</p>")); client.print(F("<p>")); client.print(count); client.print(F(" photos. <a href=\"/\">Reload</a></p></body></html>")); Serial.printf("The gallery was opened. %d photos on the card.\n", count); } // Sends one photo file from the card to the web browser void handlePhoto(WiFiClient &client, const String &method, const String &request, const QueryParams &params, const String &jsonData) { String number = ""; for (int i = 0; i < params.count; i++) if (String(params.params[i].key) == "n") number = String(params.params[i].value); String fileName = "/photo_" + number + ".jpg"; File file = SD_MMC.open(fileName.c_str(), FILE_READ); if (!file) { Serial.printf("The file %s was not found\n", fileName.c_str()); server.sendResponse(client, "Photo not found", "text/plain"); return; } client.print("HTTP/1.1 200 OK\r\n"); client.print("Content-Type: image/jpeg\r\n"); client.printf("Content-Length: %u\r\n", (unsigned)file.size()); client.print("Connection: close\r\n\r\n"); // Send the file in small pieces, because it does not fit in the memory at once uint8_t buffer[1024]; while (file.available()) { size_t read = file.read(buffer, sizeof(buffer)); client.write(buffer, read); } file.close(); Serial.printf("Sent %s\n", fileName.c_str()); } void setup() { Serial.begin(115200); delay(1000); if (!initCamera()) { Serial.println("Stopped"); while (true) delay(1000); } if (!initSDCard()) { Serial.println("Stopped"); while (true) delay(1000); } findFirstFreeNumber(); WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to WiFi"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(); server.addRoute("/", handleGallery); server.addRoute("/photo", handlePhoto); server.begin(); Serial.print("Gallery is ready. Open this address in your web browser: http://"); Serial.println(WiFi.localIP()); takeAndSavePhoto(); lastPhotoTime = millis(); } void loop() { server.handleClient(); if (millis() - lastPhotoTime >= PHOTO_INTERVAL) { lastPhotoTime = millis(); takeAndSavePhoto(); } }

Étapes rapides

  • Nouveau sur l'ESP32 S3 ? Terminez d'abord notre guide ESP32 S3 - Premiers pas.
  • Formatez votre carte microSD en FAT32 sur votre ordinateur.
  • Débranchez le câble USB, puis insérez la carte dans son logement.
  • Installez la bibliothèque DIYables ESP32 WebServer.
  • Copiez le code ci-dessus et collez-le dans l'Arduino IDE.
  • Changez WIFI_SSID et WIFI_PASSWORD pour votre propre WiFi.
  • Changez les réglages dans le menu Tools. Voir le tableau ci-dessus.
  • Compilez et téléversez le code vers la carte ESP32 S3 en cliquant sur le bouton Upload dans l'Arduino IDE.
Arduino IDE Upload Code
  • Ouvrez le moniteur série dans l'Arduino IDE.
How to open serial monitor on Arduino IDE
  • Appuyez une fois sur le bouton RESET, et notez l'adresse IP.
  • Attendez deux minutes, pour que la carte enregistre quelques photos.
  • Tapez l'adresse dans votre navigateur Web. La galerie apparaît.
ESP32 S3 Photo Gallery in Web Browser

Moniteur série

Les relevés suivants ont été capturés en 2026 :

∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32S3 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32S3 Dev Module' on 'COM15')
New Line
9600 baud
[2026-09-28 12:00:01] Camera sensor: OV5640 (5 MP, max QSXGA 2592x1944) [2026-09-28 12:00:01] SD card size: 15193 MB [2026-09-28 12:00:01] The next photo will be photo_00001.jpg [2026-09-28 12:00:04] Gallery is ready. Open this address in your web browser: http://192.168.0.2 [2026-09-28 12:00:05] Saved /photo_00001.jpg (51353 bytes) [2026-09-28 12:00:35] Saved /photo_00002.jpg (51259 bytes) [2026-09-28 12:01:05] Saved /photo_00003.jpg (52104 bytes) [2026-09-28 12:01:20] The gallery was opened. 3 photos on the card. [2026-09-28 12:01:21] Sent /photo_00001.jpg [2026-09-28 12:01:21] Sent /photo_00002.jpg [2026-09-28 12:01:22] Sent /photo_00003.jpg
Ln 11, Col 1
ESP32S3 Dev Module on COM15
2

Dépannage

La page de galerie s'ouvre mais indique "No photo on the card yet".

Attendez 30 secondes et rechargez. La galerie n'affiche que les fichiers dont le nom commence par photo_ et se termine par .jpg. Les photos prises par les autres tutoriels, comme motion_00001.jpg, ne sont pas affichées. Changez la ligne name.startsWith("photo_") si vous voulez les voir.

Les petites images restent grises et n'apparaissent jamais.

La carte affiche des photos en taille réelle comme petites images, donc une page avec 50 photos est très lente. Patientez, ou baissez config.frame_size à FRAMESIZE_VGA pour que les fichiers soient plus petits.

La page se charge, mais il manque des photos.

L'ESP32 S3 ne peut garder que quelques fichiers ouverts à la fois. Assurez-vous que entry.close() est à l'intérieur de la boucle.

La page est très lente avec de nombreuses photos.

C'est normal. La carte envoie une photo à la fois. Pour une carte avec des centaines de photos, il est plus rapide de retirer la carte et de la lire sur un ordinateur.

Le moniteur série affiche SD card not found.

Formatez la carte en FAT32, utilisez une carte de 32 Go ou moins, et réinsérez la carte.

La page ne s'ouvre pas du tout.

Votre ordinateur et votre ESP32 S3 doivent être sur le même réseau WiFi. Tapez http://, pas https://.

Le moniteur série n'affiche rien après les messages de démarrage.

Si votre câble USB est dans le port nommé UART, réglez USB CDC On Boot sur Disabled. S'il est dans le port nommé USB, réglez-le sur Enabled.

Applications

Une caméra qui montre ses propres photos via WiFi vous évite un déplacement jusqu'à la carte à chaque fois. Voici des projets pratiques que vous pouvez construire avec ce code :

  1. Caméra de time-lapse consultable depuis votre bureau : Voyez à quoi ressemblent les images avant de la laisser tourner pendant une semaine.
  2. Caméra de chantier : Montrez les photos du jour à un client sur un téléphone, directement sur le site.
  3. Surveillance de plantes et de serre : Regardez la croissance de vos plantes sans ouvrir la serre.
  4. Registre de magasin et de bureau : Un responsable peut regarder les photos depuis une autre pièce.
  5. Caméra de classe : Chaque élève peut ouvrir la galerie sur son propre téléphone.
  6. Journal de machine et d'imprimante 3D : Vérifiez les dernières photos quand une impression a échoué, sans câble.
  7. Surveillance d'aquarium : Regardez vos poissons depuis le canapé.
  8. Caméra météo et de ciel : Montrez le ciel de toute la journée sous forme d'une page de petites images.
  9. Caméra de porte avec enregistrement : Regardez qui est venu, et enregistrez seulement les photos que vous voulez garder.
  10. Caméra de résidence de vacances : Vérifiez les photos via le WiFi local à votre arrivée.

Vidéo

Regardez la vidéo pas à pas pour ce projet ESP32 S3 ci-dessous.

Défis

  1. Débutant : Changez la galerie pour montrer la photo la plus récente en premier.
  2. Intermédiaire : Ajoutez une adresse /delete?n=00001 qui supprime une photo de la carte avec SD_MMC.remove().
  3. Avancé : Affichez seulement 20 photos par page, et ajoutez des liens Next et Back, afin qu'une carte avec mille photos s'ouvre encore rapidement.

Commentaires

※ NOS MESSAGES

  • N'hésitez pas à partager le lien de ce tutoriel. Cependant, veuillez ne pas utiliser notre contenu sur d'autres sites web. Nous avons investi beaucoup d'efforts et de temps pour créer ce contenu, veuillez respecter notre travail !