You are currently viewing Photos sur Carte SD par SPI avec l’ESP32-CAM
Enregistrer des photos sur SD Card par SPI avec l'ESP32-CAM

Gestion de la caméra et finalisation du programme

La bibliothèque de gestion de la caméra a déjà été installée dans l’environnement de développement lors de l’installation de la carte ESP32-CAM pour l’Arduino IDE. Il reste simplement à la déclarer par l’instruction:

#include <esp_camera.h>

La prise de photo avec l’ESP32-CAM passe par 3 étapes:

  1. Configuration de la caméra au démarrage de programme
  2. Capture de l’image
  3. Libération de la mémoire

Configuration de la caméra

La configuration de la caméra pour la photo est similaire à la configuration pour la vidéo précédemment décrite dans le tutoriel Caméra Wi-Fi ESP32-CAM avec Arduino. Nous changeons simplement la résolution pour le format UXGA (1600×1200) qui est le maximum de la caméra OV2640, soit:

ConfigurationCamera_L.frame_size = FRAMESIZE_UXGA;

Le code logiciel obtenu pour la configuration de la caméra est placé dans une nouvelle fonction nommée “camera_demarre”:

esp_err_t camera_demarre()
{
  // 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_sccb_sda = 26;
  ConfigurationCamera_L.pin_sccb_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_UXGA;
  // Lancement de l'initialisation de la caméra
  Retour_L=esp_camera_init(&ConfigurationCamera_L);
  if (Retour_L == ESP_OK)
  {
    Serial.println("La camera est initialiseen");    
  }
  else
  {
    sprintf(tampon_traces_g, "Erreur 0x%x lors de l'initialisation de la cameran", Retour_L);
    Serial.println(tampon_traces_g);    
  }
  return(Retour_L);
}

Capture de l’image

La prise d’une photo par l’ESP32-CAM est permise par l’instruction “esp_camera_fb_get” qui s’utilise sans paramètre. Elle retourne l’adresse de la zone mémoire dans laquelle a été enregistrée la photo: 

pTamponVideo_L = esp_camera_fb_get();

Il est donc nécessaire d’avoir déclaré au préalable un pointeur pour mémoriser l’adresse de cette zone mémoire.

camera_fb_t   *pTamponVideo_L = NULL; 

Libération de la mémoire

La mémoire vive de l’ESP32-CAM est limitée. Une fois la photo copiée dans un fichier jpg sur la carte Micro SD il est important de libérer cette espace par l’instruction “esp_camera_fb_return” avant de prendre la photo suivant:

esp_camera_fb_return(pTamponVideo_L);

Enregistrement des photos en fichiers .jpg.

Pour l’enregistrement des photos en fichiers « .jpg », plusieurs modifications du code logiciel sont à réaliser.

La première est la modification de l’extension du fichier dans la fonction donner_nom_fichier.

void donner_nom_fichier(char* nom_fichier_P, int index_P)
{
  sprintf(nom_fichier_P, "/photo_%05d.jpg", index_P);
}

Ensuite la fonction « photographier_et_enregistrer » est créée pour y ajouter:

  • Les instructions de prises de photos vues ci-dessus.
  • Les instructions d’écriture de fichiers.

Pour l’enregistrement dans un fichier de la photo stockée en mémoire, la méthode write est utilisée. Cette méthode prend en paramètre l’adresse de début et la taille des données de la zone mémoire à copier.

Les instructions …

sprintf(contenu_fichier_l, "Contenu du fichier \'%s\'.", nom_fichier_l+1);
fichier_l.println(contenu_fichier_l);

… sont remplacées par:

fichier_l.write(pTamponVideo_L->buf, pTamponVideo_L->len)

Mesure des performances

Pour mesurer la performance d’écriture des photos par protocole SPI, nous allons simplement:

  1. Démarrer un chronomètre après l’initialisation de la caméra.
  2. Relever sa valeur à l’issue de la prise des 15 photos et de leur écriture sur carte Micro SD.

La bibliothèque Arduino “elapsedMillis” de “Paul Stoffregen” offre le type “elapsedMillis” qui permet de répondre simplement à ce besoin. Il est nécessaire de l’installer dans l’environnement Arduino IDE avant de l’utiliser dans ce programme.

Une fois déclarée la bibliothèque

#include <elapsedMillis.h>

et le chronomètre,

static elapsedMillis chronometre_g;

on démarre celui-ci par une mise à zéro.

chronometre_g = 0;

Son relevé s’effectue par une simple lecture de la variable déclarée:

sprintf(tampon_traces_g, "---\n%d photos prises en %lu ms.", NOMBRE_PHOTOS, (unsigned long )chronometre_g);
Serial.println(tampon_traces_g);

Code complet

Nous arrivons à la fin du tutoriel. Voici le code complet:

/* Déclaration des librairies utilisées */
#include <SD.h>
#include <esp_camera.h>
#include <elapsedMillis.h>

/* Définition des constantes globaless */
// Brochage lecteur de cartes:
#define PORT_CARD_SCLK    14
#define PORT_CARD_MISO    2
#define PORT_CARD_MOSI    15
#define PORT_CARD_CS      13
// Autres constantes
#define NOMBRE_PHOTOS 15

/* Déclaration des fonctions */
bool SD_demarre(int port_CLK_p, int port_MISO_p, int port_MOSI_p, int port_CS_p);
void donner_nom_fichier(char* nom_fichier_P, int index_P);
int donner_prochain_index_fichier(void);
esp_err_t camera_demarre(void);
bool photographier_et_enregistrer(int index_fichier_P);

/* Déclaration globales */
SPIClass hspi_G(HSPI);
char tampon_traces_g[120];
static elapsedMillis chronometre_g;

// Fonction de démarrage, s'exécute une seule fois:
void setup()
{
  int   ii_l;
  int   prochain_index_fichier_l;
  bool  resultat_l = true;
  File  fichier_l;

  // Pour le debug
  Serial.begin(115200);
  Serial.println("-----\n");
  
  // Initialisation de la carte SD
  while(!SD_demarre(PORT_CARD_SCLK, PORT_CARD_MISO, PORT_CARD_MOSI, PORT_CARD_CS))
  {
    // On bloque la poursuite du programme tant qu'une carte SD correcte n'a pas été insérée
    delay(1000);
  }

  // Initialisation de la caméra
  camera_demarre();
  // On laisse la caméra chauffer 1 seconde
  delay(1000);

  // Parcourt les fichiers d'enregistrement déjà présents
  prochain_index_fichier_l = donner_prochain_index_fichier();

  // Demarrage du chrono
  chronometre_g = 0;

  // Lancement de la série de prise de photos et d'enregistrement sur carte SD
  for(ii_l=0; (ii_l<NOMBRE_PHOTOS)&&resultat_l; ii_l++)
  {
    resultat_l = photographier_et_enregistrer(prochain_index_fichier_l+ii_l);
  }

  // Fermeture de la carte SD
  SD.end();

  // PErformances
  sprintf(tampon_traces_g, "---\n%d photos prises en %lu ms.", NOMBRE_PHOTOS, (unsigned long )chronometre_g);
  Serial.println(tampon_traces_g);
}

// Fonction principale du programme, s'exécute en boucle:
void loop()
{
}

bool SD_demarre(int port_CLK_p, int port_MISO_p, int port_MOSI_p, int port_CS_p)
{
  bool      Retour_L;
  uint8_t   type_carte_L;
  size_t    nombre_secteurs_l, taille_secteur_l;
  
  // Valeur par defaut
  Retour_L = false;

  // Réassigne les ports SPI du bus HSPI
  hspi_G.begin(port_CLK_p, PORT_CARD_MISO, port_MOSI_p, port_CS_p);
  if (!SD.begin(port_CS_p, hspi_G, 80000000))
  {
    Serial.println("Carte SD absente, mal formatée ou lecteur mal câblé.");
  }
  else
  {
    // Identification du type de carte
    type_carte_L = SD.cardType();
    if(type_carte_L == CARD_NONE)
    {
      Serial.println("Carte SD illisible");
    }
    else
    {
      Serial.print("Type de carte SD: ");
      switch(type_carte_L)
      {
        case CARD_MMC:
          Serial.println("MMC");
          break;
        case CARD_SD:
          Serial.println("SDSC");
          break;
        case CARD_SDHC:
          Serial.println("SDHC");
          break;
        default:
          Serial.println("Inconnu");
          break;
      }
      // Caractéristiques de la carte
      nombre_secteurs_l = SD.numSectors();
      taille_secteur_l = SD.sectorSize();
      sprintf(tampon_traces_g, "Découpée en %u secteurs de %u octets, soit une capacité de %.2f Go ou de %.2f Gio", nombre_secteurs_l, taille_secteur_l, ((float)nombre_secteurs_l * (float)taille_secteur_l)/(1000.0*1000.0*1000.0), ((float)nombre_secteurs_l * (float)taille_secteur_l)/(1024.0*1024.0*1024.0));
      Serial.println(tampon_traces_g);
      sprintf(tampon_traces_g, "Il reste  %.2f Gio de disponibles.", ((float)SD.totalBytes()-(float)SD.usedBytes())/(1024.0*1024.0*1024.0));
      Serial.println(tampon_traces_g);
      Retour_L = true;
    }
  }
  return(Retour_L);
}

void donner_nom_fichier(char* nom_fichier_P, int index_P)
{
  sprintf(nom_fichier_P, "/photo_%05d.jpg", index_P);
}

int donner_prochain_index_fichier(void)
{
  char nom_fichier_l[30];
  int index_fichier_l;
  index_fichier_l = 0;
  do
  {
    index_fichier_l++;
    donner_nom_fichier(nom_fichier_l, index_fichier_l);
  }
  while(SD.exists(nom_fichier_l));
  return(index_fichier_l);
}

esp_err_t camera_demarre()
{
  // 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_sccb_sda = 26;
  ConfigurationCamera_L.pin_sccb_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_UXGA;
  // Lancement de l'initialisation de la caméra
  Retour_L=esp_camera_init(&ConfigurationCamera_L);
  if (Retour_L == ESP_OK)
  {
    Serial.println("La caméra est initialisée\n");    
  }
  else
  {
    sprintf(tampon_traces_g, "Erreur 0x%x lors de l'initialisation de la cameran", Retour_L);
    Serial.println(tampon_traces_g);    
  }
  return(Retour_L);
}

bool photographier_et_enregistrer(int index_fichier_P)
{
  bool  Retour_L;
  char  nom_fichier_l[30];
  File  fichier_l;
  camera_fb_t   *pTamponVideo_L = NULL;
   
   // Valeur par defaut
  Retour_L = false;
  // Récupèration d'une image depuis la caméra    
  pTamponVideo_L = esp_camera_fb_get();
  // Ecriture de l'image dans un fichier jpg
  donner_nom_fichier(nom_fichier_l, index_fichier_P);
  fichier_l = SD.open(nom_fichier_l, FILE_WRITE);
  if(!fichier_l)
  {
    sprintf(tampon_traces_g, "Erreur de creation du fichier '%s'.", nom_fichier_l);
    Serial.println(tampon_traces_g);
  }
  else
  {
    if(!fichier_l.write(pTamponVideo_L->buf, pTamponVideo_L->len))
    {
      sprintf(tampon_traces_g, "Erreur d'enregistrement de l'image dans le fichier '%s'.", nom_fichier_l);
      Serial.println(tampon_traces_g);
    }
    else
    {
      sprintf(tampon_traces_g, "Photo '%s' enregistrée.", nom_fichier_l);
      Serial.println(tampon_traces_g);
      fichier_l.close();  
      Retour_L = true;
    }      
  }
  // Réamorcage du tampon video, indispensable pour sa réutilisation
  if (pTamponVideo_L)
  {
      esp_camera_fb_return(pTamponVideo_L);
      pTamponVideo_L = NULL;
  }
  return(Retour_L);
}

et les traces affichées sur le Moniteur série lors de son exécution :

Traces de la prise de photos affichées sur le moniteur série
Traces de la prise de photos affichées sur le moniteur série

Un exemple de photo présente sur la carte Micro SD :

Photo enregistrée avec l'ESP32-CAM pour ce tutoriell
Photo enregistrée avec l’ESP32-CAM pour ce tutoriel

La résolution de la caméra a été paramétrée à son maximum, aussi il est important d’avoir un bon éclairage pour avoir des photos nettes. Ce tutoriel ayant été réalisé au mois de novembre j’ai ajouté une lampe directionnelle.

Nous sommes à la fin du tutoriel. J’espère que cet article vous a donné des idées pour vos prochains projets !

S’abonner
Notification pour
guest
2 Commentaires
Le plus ancien
Le plus récent Le plus populaire
Commentaires en ligne
Afficher tous les commentaires
Romain
Romain
2 mois il y a

Super article, cela va beaucoup m’aider dans mon projet. Merci beaucoup.