Caméra Wi-Fi ESP32-CAM avec Arduino

Caméra Wi-Fi ESP32-CAM avec Arduino

Configuration de la caméra OV2640

La caméra OV2640 se pilote en utilisant la bibliothèque « esp camera » déjà incluse dans les librairies par défaut de l’ESP32. Pour l’utiliser, il suffit simplement de la déclarer au début de notre programme par l’instruction suivante:

#include <esp_camera.h>

Nous allons regrouper l’initialisation et la configuration de la caméra OV2640 dans une fonction baptisée « InitialiserCamera » :

esp_err_t InitialiserCamera()
{
    // Variables locales de la fonction
    esp_err_t       Retour_L;

    return(Retour_L);
}

L’instruction proposée par la bibliothèque pour l’initialisation de la caméra est la fonction « esp_camera_init » qui prend en paramètre une structure de type « camera_config_t ». Nous commençons par déclarer cette structure avant de la renseigner:

camera_config_t ConfigurationCamera_L;

L’OV2640 étant un périphérique externe au microcontrôleur ESP32, il est nécessaire de préciser le brochage de cette caméra dans la structure. La fiche technique de l’ESP32-CAM du fabricant AI-Thinker nous fournit ces informations page 3. Nous en déduisons les valeurs suivantes:

ConfigurationCamera_L.pin_d0 = 5;
ConfigurationCamera_L.pin_d1 = 18;
ConfigurationCamera_L.pin_d2 = 19;
ConfigurationCamera_L.pin_d3 = 21;
ConfigurationCamera_L.pin_d4 = 36;
ConfigurationCamera_L.pin_d5 = 39;
ConfigurationCamera_L.pin_d6 = 34;
ConfigurationCamera_L.pin_d7 = 35;
ConfigurationCamera_L.pin_xclk = 0;
ConfigurationCamera_L.pin_pclk = 22;
ConfigurationCamera_L.pin_vsync = 25;
ConfigurationCamera_L.pin_href = 23;
ConfigurationCamera_L.pin_sscb_sda = 26;
ConfigurationCamera_L.pin_sscb_scl = 27;
ConfigurationCamera_L.pin_pwdn = 32;

Il est également à préciser que l’entrée « ResetB » (broche C6) de l’OV2640 n’est pas câblée à l’ESP32. On indique à la bibliothèque que cette entrée ne sera pas utilisée par la valeur « -1 » :

ConfigurationCamera_L.pin_reset = -1;

Utilisée en mode esclave, la caméra OV2640 nécessite en entrée un signal d’horloge.  Pour fournir ce signal, nous allons utiliser le générateur de fréquence 0 (PWM0) du microcontrôleur ESP32 à une fréquence de 20 MHz:

ConfigurationCamera_L.ledc_channel = LEDC_CHANNEL_0;
ConfigurationCamera_L.ledc_timer = LEDC_TIMER_0;
ConfigurationCamera_L.xclk_freq_hz = 20000000;

Le format de compression jpeg est choisi avec l’utilisation de 2 buffers:

ConfigurationCamera_L.pixel_format = PIXFORMAT_JPEG;
ConfigurationCamera_L.fb_count = 2;

Sur une échelle de 63 (qualité bas) à 0 (meilleur qualité), nous choisissons un niveau de 10:

ConfigurationCamera_L.jpeg_quality = 10;

La caméra est capable d’acquérir des images au format UXGA (1632×1232) cependant afin d’avoir une vidéo fluide sur notre navigateur internet nous abaissons cette résolution au format SVGA (800×600):

ConfigurationCamera_L.frame_size = FRAMESIZE_SVGA;

Maintenant que la structure « ConfigurationCamera_L » est remplie, il reste à lancer l’initialisation de la caméra pour finaliser la fonction « InitialiserCamera » :

Retour_L=esp_camera_init(&ConfigurationCamera_L);
if (Retour_L == ESP_OK)
{
    Serial.printf("La camera est initialisee\n");
}
else
{
    Serial.printf("Erreur 0x%x lors de l'initialisation de la camera\n", Retour_L);
}

Lors de l’appel de cette fonction d’initialisation de la caméra dans la fonction « setup » nous choisissons de transmettre le code d’erreur par e-mail si l’initialisation échoue.

Retour_L = InitialiserCamera();
if(Retour_L == ESP_OK)
{
  sprintf(Buffer_L,"<p><strong>La caméra a démarré avec succès !</strong></p><p>Cliquez sur le lien \"http://%u.%u.%u.%u\" pour vous connecter.</p>", AdresseIpLocale_G[0], AdresseIpLocale_G[1], AdresseIpLocale_G[2], AdresseIpLocale_G[3]);
}
else
{
  sprintf(Buffer_L,"<p><strong>Erreur d'initialisation de la caméra !</strong></p><p>L'erreur 0x%x a été rencontrée.</p>", Retour_L);
}
EnvoyerEmail("Démarrage de la caméra Wi-Fi", Buffer_L);

Après ces différents ajouts de ligne de code, nous arrivons au programme suivant:

// Déclaration des bibliothèques utilisées
#include <WiFi.h>
#include <ESP32_MailClient.h>
#include <esp_camera.h>

// Définition des constantes globales
#define PORT_LED_FLASH      4   // Numéro de port auquel est branchée la LED servant de flash.
 
// Déclaration globales
IPAddress AdresseIpLocale_G; // Permet de mémoriser l'adresse IP de la carte ESP32-CAM

// Fonction de démarrage, s'exécute une seule fois:
void setup()
{
    // Constantes de la fonction
    const char* SSID_L = "LiveBox_1324"; // Nom du réseau Wi-Fi
    const char* MOT_DE_PASSE_L = "12345?ABCDE"; // Mot De Passe du réseau

    // Variables de la fonction
    wl_status_t StatutConnexion_L; // Pour mémoriser l'état de la connexion
    esp_err_t   Retour_L;

    // Variables de la fonction
    char Buffer_L[200];

    pinMode(PORT_LED_FLASH, OUTPUT); // Initialisation en "sortie" de la broche d'E/S connectée au flash
    WiFi.begin(SSID_L, MOT_DE_PASSE_L); // Tentative de connexion au point d'accès Wi-Fi
    StatutConnexion_L = WiFi.status(); // Lecture de l'état de la connexion et mémorisation dans la variable "StatutConnexion_L"
    while ((StatutConnexion_L != WL_NO_SSID_AVAIL)&&(StatutConnexion_L != WL_CONNECTED)&&(StatutConnexion_L != WL_CONNECT_FAILED))
    {
        digitalWrite(PORT_LED_FLASH, HIGH);
        delay(100);
        digitalWrite(PORT_LED_FLASH, LOW);
        delay(500);
        StatutConnexion_L = WiFi.status(); // Lecture de l'état de la connexion et mémorisation dans la variable "StatutConnexion_L"
    }

    Serial.begin(115200); // Ouverture du port série à 115200 bauds

    // Affichage du résultat de la tentative de connexion
    if (StatutConnexion_L == WL_CONNECTED)
    {
        Serial.println("Connection OK");
        AdresseIpLocale_G = WiFi.localIP(); // Mémorisation de l'adresse actuelle
        Retour_L = InitialiserCamera();
        if(Retour_L == ESP_OK)
        {
          sprintf(Buffer_L,"<p><strong>La caméra a démarré avec succès !</strong></p><p>Cliquez sur le lien \"http://%u.%u.%u.%u\" pour vous connecter.</p>", AdresseIpLocale_G[0], AdresseIpLocale_G[1], AdresseIpLocale_G[2], AdresseIpLocale_G[3]);
        }
        else
        {
          sprintf(Buffer_L,"<p><strong>Erreur d'initialisation de la caméra !</strong></p><p>L'erreur 0x%x a été rencontrée.</p>", Retour_L);
        }
        EnvoyerEmail("Démarrage de la caméra Wi-Fi", Buffer_L);
    }
    else if (StatutConnexion_L == WL_NO_SSID_AVAIL)
    {
        Serial.println("SSID introuvable");
    }
    else if (StatutConnexion_L == WL_CONNECT_FAILED)
    {
        Serial.println("Mot de passe KO");
    }
    else
    {
        Serial.println("Autre erreur");
    }
}

// Fonction principale du programme, s'exécute en boucle:
void loop()
{
    // Variables de la fonction
    char Buffer_L[200];

    if(WiFi.status()==WL_CONNECTED)
    {  
        // Internet est disponible
        if (WiFi.localIP()!=AdresseIpLocale_G)
        {
            // L'ESP32-CAM vient d'obtenir une nouvelle adresse IP
            AdresseIpLocale_G = WiFi.localIP(); // Mémorisation de l'adresse actuelle
            sprintf(Buffer_L,"<p><strong>La caméra a changé d'adresse IP</strong></p><p>Cliquez sur le lien \"http://%u.%u.%u.%u\" pour vous connecter.</p>", AdresseIpLocale_G[0], AdresseIpLocale_G[1], AdresseIpLocale_G[2], AdresseIpLocale_G[3]);
            EnvoyerEmail("Changement d'adresse IP de la caméra Wi-Fi", Buffer_L);
        }
    }
    else
    {
        // Pas de connexion internet
        digitalWrite(PORT_LED_FLASH, HIGH);
        delay(100);
        digitalWrite(PORT_LED_FLASH, LOW);
        delay(500);
    }
}

void EnvoyerEmail(const char *pObjet_P, const char *pMessage_P)
{
    const char* ADRESSE_EMAIL_EMISSION_L = "votremaildetest@gmail.com";
    const char* MOT_DE_PASSE_EMAIL_EMISSION_L = "12345MoteDePasseMail";
    const char* ADRESSE_SERVEUR_SMTP_L = "smtp.gmail.com";
    int PORT_SERVEUR_SMTP_L = 465; // Port SSL utilisé
    const char* ADRESSE_EMAIL_DESTINATAIRE_L = "votremail@gmail.com";

    // Variables locales de la fonction
    SMTPData DonneesEmail_L;  // Données de l'e-mail à envoyer
    
    // Les caractéristique de l'e-mail sont renseignées
    DonneesEmail_L.setLogin(ADRESSE_SERVEUR_SMTP_L, PORT_SERVEUR_SMTP_L, ADRESSE_EMAIL_EMISSION_L, MOT_DE_PASSE_EMAIL_EMISSION_L); // Identifiants de connexion au serveur SMTP
    DonneesEmail_L.setSender("Camera Wi-Fi", ADRESSE_EMAIL_EMISSION_L); // Nom et adresse email de l'emetteur
    DonneesEmail_L.setPriority("Normal"); // Niveau d'importance du mail
    DonneesEmail_L.setSubject(pObjet_P);  // Objet du mail
    DonneesEmail_L.setMessage(pMessage_P, true); // Contenu du message au format HTML
    DonneesEmail_L.addRecipient(ADRESSE_EMAIL_DESTINATAIRE_L);  // Adresse du destinataire
    
    //Emission de l'e-mail
    if (!MailClient.sendMail(DonneesEmail_L))
    {
      Serial.println("Erreur lors de l'envoi d'e-mail:" + MailClient.smtpErrorReason());
    }
    DonneesEmail_L.empty(); // Nettoyage des données d'envoi d'e-mail
}

esp_err_t InitialiserCamera()
{
    // Variables locales de la fonction
    esp_err_t       Retour_L;
    camera_config_t ConfigurationCamera_L;

    // Cablage de la caméra sur l'ESP32-CAM du fabricant AI-Thinker
    ConfigurationCamera_L.pin_d0 = 5;
    ConfigurationCamera_L.pin_d1 = 18;
    ConfigurationCamera_L.pin_d2 = 19;
    ConfigurationCamera_L.pin_d3 = 21;
    ConfigurationCamera_L.pin_d4 = 36;
    ConfigurationCamera_L.pin_d5 = 39;
    ConfigurationCamera_L.pin_d6 = 34;
    ConfigurationCamera_L.pin_d7 = 35;
    ConfigurationCamera_L.pin_xclk = 0;
    ConfigurationCamera_L.pin_pclk = 22;
    ConfigurationCamera_L.pin_vsync = 25;
    ConfigurationCamera_L.pin_href = 23;
    ConfigurationCamera_L.pin_sscb_sda = 26;
    ConfigurationCamera_L.pin_sscb_scl = 27;
    ConfigurationCamera_L.pin_pwdn = 32;
    ConfigurationCamera_L.pin_reset = -1;

    // La génération du signal d'horloge
    ConfigurationCamera_L.ledc_channel = LEDC_CHANNEL_0;
    ConfigurationCamera_L.ledc_timer = LEDC_TIMER_0;
    ConfigurationCamera_L.xclk_freq_hz = 20000000;

    // Compression jpeg
    ConfigurationCamera_L.pixel_format = PIXFORMAT_JPEG;
    ConfigurationCamera_L.fb_count = 2;
    ConfigurationCamera_L.jpeg_quality = 10;

    // Résolution de l'image
    ConfigurationCamera_L.frame_size = FRAMESIZE_SVGA;

    // Lancement de l'initialisation de la caméra
    Retour_L=esp_camera_init(&ConfigurationCamera_L);
    if (Retour_L == ESP_OK)
    {
        Serial.printf("La camera est initialisee\n");
    }
    else
    {
        Serial.printf("Erreur 0x%x lors de l'initialisation de la camera\n", Retour_L);
    }
    return(Retour_L);
}

Afin de valider cette nouvelle étape du programme Arduino de caméra Wi-Fi , vous pouvez le compiler puis le transférer dans l’ESP32-CAM.

Lorsque vous visualisez les traces de déverminage, un message « La camera est initialisee » vous confirme que l’initialisation de la caméra OV260 s’est bien déroulée.

Traces du compte-rendu de l'initialisation de la caméra

Maintenant que cette étape est validée, passons à la création du serveur de flux vidéo pour terminer le programme (page suivante).

Cet article a 6 commentaires

  1. bariod

    Bonjour et félicitations pour ce tuto.
    Pourrait-il être utilisé pour détecter la sortie de pelouse (plus de vert) par un robot tondeuse ?
    Merci d’avance si vous me répondez.

    1. Nico

      Bonjour,
      Merci pour ce retour

      Effectivement, en disposant la caméra verticalement à quelques centimètres du sol, l’image sera plus ou moins remplie de vert en fonction de la hauteur de pousse de l’herbe.
      Une solution de programme de « détection de sortie de tondeuse » réalisé avec l’ESP32-CAM est d’effectuer les actions suivantes :
      • Prendre 1 photo par jour
      • Analyser la quantité de pixels verts de l’image à partir d’un algorithme
      • Déclencher le robot tondeuse au-delà d’un certain niveau de « verdure » de la photo
      Et voilà 😉

  2. Stéphane

    Bonjour Nico,
    Waouh, superbe travail!!
    Ca va grandement m’aider dans mon projet, terrarium connecté avec capteurs température, hygrométrie, lumière, niveau eau réservoir et camera, le tout sur wifi pour site internet et smartphone…
    J’ai fait de la programmation il y a trés trés longtemps et j’ai aucune connaissance en arduino, donc va y avoir du boulot…..
    Et ton travail va me fait gagner du temps à un moment donné!!!
    MERCI

  3. Weado

    Génial ce tuto en plus avec toutes les explications associées. Au top !
    Je vais tenter d’adapter tout cela pour faire un timelaspe en mode low energy, même si je sais qu’il va falloir du boulot et surtout la question du stockage des images/gif.
    Si certain on des idées je suis preneur.

  4. Bruno

    Bonjour
    Merci pour votre tuto.
    N’étant pas du tout compétent niveau serveur, je me demandais si le flux de la caméra est disponible en local seulement ou bien en « extérieur » également ?
    Pourrais-je voir le flux de ma caméra à la maison sur mon ordinateur au travail par exemple ?

    Merci
    Bruno

    1. Nico

      Bonsoir Bruno,

      Par défaut le serveur de flux vidéo n’est disponible qu’en local. Cependant il est possible de le rendre accessible depuis l’extérieur (internet) par les 2 opérations suivantes:
      – Utilisation d’une IP fixe par l’ESP32-CAM
      – Configuration du point d’accès Wi-Fi pour qu’il effectue une redirection de port (port forwarding) entre l’adresse publique du point d’accès et l’adresse privée de l’ESP32-CAM.

      Pour l’utilisation d’une adresse IP fixe par l’ESP32-CAM il suffit d’ajouter les lignes de code suivantes en les adaptant aux caractéristiques de votre réseau local avant l’appel à « WiFi.begin(SSID_L, MOT_DE_PASSE_L); » :
      IPAddress AdresseIP_L(192, 168, 0, 130); // Définition de l’adresse IP de la caméra
      IPAddress Passerelle_L(192, 168, 0, 1); // Passerelle par défaut de votre sous-réseau
      IPAddress SousReseau_L(255, 255, 0, 0); // Masque de votre sous-réseau
      WiFi.config(AdresseIP_L, Passerelle_L, SousReseau_L) // Configuration de l’adresse IP statique

Laisser un commentaire