• Auteur/autrice de la publication :
  • Post category:Arduino / ESP32
  • Commentaires de la publication :0 commentaire

Enregistrement des données du micro sur la carte Micro SD

Au cours de cette deuxième partie, nous allons utiliser le micro MEMS SPH0645 pour enregistrer notre voix dans un fichier sur la carte Micro SD. L’enregistrement sera réalisé par un appui sur le bouton-poussoir. Nous utiliserons ensuite un logiciel de traitement audio pour en écouter le contenu sur un PC.

Le câblage du microphone SPH0645

Le microphone SPH0645 est un microphone MEMS utilisant l’interface série I2S. L’I2S (Integrated Interchip Sound) est un protocole de communication série synchrone utilisé pour transmettre des données entre deux appareils audio numériques. Le bus I2S utilise généralement trois signaux logiques pour l’échange de données: LRCL, DOUT et BCLK.

Câblage du microphone SPH0645 et du lecteur de cartes Micro SD avec l'ESP32-DevKit
Câblage du microphone SPH0645 et du lecteur de cartes Micro SD avec l’ESP32-DevKit

Pour l’alimentation du micro SPH0645:

  • Fil rouge, la broche 3V3 est à relier au 3V3 de l’ESP32.
  • Fil noir, la broche GND est à brancher à la masse (GND) de l’ESP32.

Pour la sélection du canal d’enregistrement:

  • Fil marron, la broche SEL (Select) à la masse (GND) du micro. Cette configuration demande au micro de transmettre les données audio sur le canal de gauche. Nous utilisons un seul micro pour ce projet et donc réalisons un enregistrement mono.

Pour les échanges d’information entre le micro SPH0645 et l’ESP32 :

  • Fil gris, la broche LRCL (Left/Right  Channel) du micro à la sortie 25 (WS de l’interface I2S) de l’ESP32. Ce signal indique au micro quand commencer à émettre en fonction du canal qui lui a été assigné.
  • Fil violet, la broche DOUT (Data Output) à l’entrée 33 (DIN de l’interface I2S) de l’ESP32. Cette broche transmet à l’ESP32 le signal numérique correspondant au signal sonore capturé par le micro.
  • Fil vert, la broche BCLK (Bit Clock) à la sortie 32 (BCLK de l’interface I2S) de l’ESP32. Ce signal de synchronisation précise au micro la fréquence de transmission à utiliser.

Le code logiciel pour l’enregistrement des données du micro SPH0645

Les drivers permettant de piloter l’interface I2S de l’ESP32 ont déjà été insérés à l’environnement de développement Arduino lors de l’installation de la carte ESP32 pour l’Arduino IDE. Il suffit de les déclarer en début de programme par l’instruction:

#include <driver/i2s.h>

Configuration logicielle de l’interface I2S de l’ESP32 pour le micro SPH0645:

Nous utilisons 2 fonctions Arduino pour configurer l’interface I2S de l’ESP32: la fonction i2s_driver_install  et la fonction i2s_set_pin.

La fonction i2s_driver_install prend en paramètres:

  • i2s_num, la ressource matérielle I2S à associer. 2 valeurs sont possibles, I2S_NUM_0 ou I2S_NUM_1. Nous choisissons I2S_NUM_0.
  • i2s_config, une structure de type i2s_config_t que nous allons détailler plus bas.
  • queue_size, la taille de queue d’évènements. Ce tutoriel n’utilise pas la gestion événementielle.
  • i2s_queue, le pointeur sur la queue d’évènement. Non utilisé dans ce tutoriel.

La fonction i2s_set_pin permet d’associer une ressource matérielle I2S avec les ports d’entrée/sortie à utiliser. Ses paramètres sont:

  • i2s_num, la ressource matérielle I2S à associer. Nous avons précédemment choisi I2S_NUM_0.
  • pin, une structure de type i2s_pin_config_t détaillée ci-dessous.

La structure de type i2s_pin_config_t permet de définir les numéros des broches utilisées par l’interface I2S:

  • int mck_io_num, dans ce montage nous n’utilisons pas de signal MCK (Master Clock). Il n’est pas nécessaire et de toute façon pas présent sur le micro MEMS SPH0645.
  • bck_io_num, il s’agit de notre broche BCLK (Bit Clock).
  • ws_io_num, WS (Word Select) est l’autre dénomination du signal LRCL pour le protocole I2S.
  • data_out_num, l’ESP32-DevKit est uniquement en “écoute” du micro. Cette broche n’est pas utilisée.
  • data_in_num, c’est par cette broche qu’arrivent les données du micro.

Afin de pouvoir bien choisir les paramètres de réglage de la configuration de l’interface I2S, une étude de la datasheet du micro SPH0645 disponible sur sa fiche produit permet de déduire les caractéristiques micro suivantes:

  •  24 bits de données utiles sur un échantillon de taille 32 bits:
#define NOMBRE_BITS_PAR_ECHANTILLONS 32
#define NOMBRE_OCTETS_PAR_ECHANTILLONS NOMBRE_BITS_PAR_ECHANTILLONS/8
  • Une fréquence du signal d’horloge entre 1024 et 4096 kHz, soit un taux d’échantillonnage entre 32 et 128 kHz, pour ce projet nous choisissons 44100 Hz:
#define FREQUENCE_ECHANTILLONAGE 44100

Voici en détail le remplissage de la structure de type i2s_config_t utilisée pour ce projet :

  • mode,  le mode de fonctionnement de l’interface I2S sur l’ESP32. Il s’agit d’un champ de bits à positionner en piochant dans les valeurs suivantes: I2S_MODE_MASTER, I2S_MODE_SLAVE, I2S_MODE_TX, I2S_MODE_RX, I2S_MODE_DAC_BUILT_IN, I2S_MODE_ADC_BUILT_IN, I2S_MODE_PDM. Dans ce projet nous recevons des données d’un périphérique micro donc nous sélectionnons I2S_MODE_MASTER et I2S_MODE_RX.
  • sample_rate, le taux d’échantillonnage défini par FREQUENCE_ECHANTILLONAGE.
  • bits_per_sample, le nombre de bits par échantillons défini précédemment par
    NOMBRE_BITS_PAR_ECHANTILLONS.
  • channel_format, les canaux à utiliser en choisissant l’une des valeurs suivantes: I2S_CHANNEL_FMT_RIGHT_LEFT, I2S_CHANNEL_FMT_ALL_RIGHT, I2S_CHANNEL_FMT_ALL_LEFT, I2S_CHANNEL_FMT_ONLY_RIGHT, I2S_CHANNEL_FMT_ONLY_LEFT. Étant donné que la broche SEL a été positionnée à la masse, on s’attend à choisir I2S_CHANNEL_FMT_ONLY_LEFT. Mais non, ça ne fonctionne pas comme cela,  il faut choisir I2S_CHANNEL_FMT_ONLY_RIGHT.
  • communication_format, le format de communication I2S.  Il s’agit d’un champ de bits à positionner en piochant dans les valeurs suivantes: I2S_COMM_FORMAT_I2S, I2S_COMM_FORMAT_I2S_MSB, I2S_COMM_FORMAT_I2S_LSB, I2S_COMM_FORMAT_PCM, I2S_COMM_FORMAT_PCM_SHORT, I2S_COMM_FORMAT_PCM_LONG. J’ai choisi I2S_COMM_FORMAT_STAND_I2S, I2S_COMM_FORMAT_I2S_MSB et I2S_COMM_FORMAT_STAND_PCM_SHORT.
  • intr_alloc_flags, le niveau d’interruption à allouer. Il est conseillé de choisir le plus élevé soit ESP_INTR_FLAG_LEVEL1.
  • dma_buf_count, le nombre de mémoires tampons à utiliser pour récupérer les données audio.
  • dma_buf_len, la dimension des mémoires tampons.
  • use_apll, booléen pour activer l’APLL (Analog Phase Locked Loop). Pas nécessaire.
  • tx_desc_auto_clear, booléen permettant l’activation d’un mécanisme pour éviter le bruit en cas d’indisponibilité des données. Pas nécessaire pour le SPH0645.
  • fixed_mclk, en lien avec la broche MCK (ou MCLK) qui n’est pas utilisée pour ce montage avec le SPH0645. Nous le positionnons à -1 ( pas utilisé).
  • mclk_multiple, également en lien avec la broche MCK (ou MCLK). Positionné à sa valeur par défaut I2S_MCLK_MULTIPLE_DEFAULT.
  • bits_per_chan, le format du canal en nombre de bits.Les valeurs suivantes sont possibles: I2S_BITS_PER_CHAN_DEFAULT, I2S_BITS_PER_CHAN_8BIT, I2S_BITS_PER_CHAN_16BIT, I2S_BITS_PER_CHAN_24BIT, I2S_BITS_PER_CHAN_32BIT. Dans notre cas c’est le même que celui d’un échantillon, donc I2S_BITS_PER_CHAN_DEFAULT.

Lecture des données audio reçues du micro SPH0645 sur l’interface I2S :

L’interface I2S stocke automatiquement les données reçues du micro dans les buffers de réception dont nous avons défini la taille et le nombre précédement.
L’extraction des données de ces buffers s’effectue par l’utilisation de la fonction i2s_read qui prend en paramètres:

  • i2s_num, la ressource matérielle I2S associée précédemment (I2S_NUM_0).
  • dest, l’adresse de la zone mémoire dans laquelle nous souhaitons copier les données reçues du micro MEMS.
  • size, la taille de la zone mémoire dest. (La quantité maximale de données à copier).
  • bytes_read, la taille de données effectivement copiées suite à l’appel de la fonction i2s_read.
  • ticks_to_wait, le délai d’attente du remplissage du buffer de réception avant de copier les données. Pour de meilleures performances, nous le positionnons à portMAX_DELAY ce qui a pour effet de désactiver cette interruption sur délai d’attente.

Activation/désactivation de la capture des données sur le port I2S :

Les fonctions i2s_start et i2s_stop (qui prennent en paramètre la ressource matérielle I2S associée précédemment) permettent respectivement de démarrer et de stopper la capture de données sur le port IS, c’est-à-dire l’enregistrement dans les buffers de réceptions.

Le code source  pour l’enregistrement des données du micro SPH0645:

En dehors de la gestion I2S du micro SPH0645, le code logiciel ne comporte pas de difficulté particulière:

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

// Brochage lecteur de cartes:
#define PORT_CARD_CS    5

// Brochage du micro:
#define PORT_MIC_DIN    33  // Entree pour des données
#define PORT_MIC_LRCL   25  // Selection du canal pour l'émission
#define PORT_MIC_BCLK   32  // Signal d'horloge

// Configuration I2S pour le micro
#define PORT_I2S I2S_NUM_0

// Brochage des boutons:
#define PORT_BOUTON_ENREGISTRER 17

// Format audio d'enregistrement
#define FREQUENCE_ECHANTILLONAGE 44100
#define NOMBRE_CANAUX 1
#define NOMBRE_BITS_PAR_ECHANTILLONS 32
#define NOMBRE_OCTETS_PAR_ECHANTILLONS NOMBRE_BITS_PAR_ECHANTILLONS/8

// Performances d'enregistrement
#define I2S_TAILLE_BUFFER_ENREGISTREMENT 640 // Doit être un multiple de NOMBRE_OCTETS_PAR_ECHANTILLONS
#define I2S_NOMBRE_BUFFER 16
#define TAILLE_EXTRACTION_BUFFER 640
#define FACTEUR_AMPLIFICATION 4

// Autres constantes
#define NOM_RACINE_ENREGISTREMENT "/Enr_"

/* Déclaration des fonctions */
void SD_demarre(int port_CS_p);
File SD_cree_fichier_dat_pour_ecriture(char *nom_fichier_p);
void donner_nom_fichier(char* nom_fichier_P, int index_P);
int donner_prochain_index_enregistrement(void);
void micro_configure(int nombre_buffer_p, int taille_buffer_p, int port_DIN_p, int port_WS_p, int port_BCK_p);
void enregistrement_voix(File fichier_enregistrement_p, int bouton_maintien_p);
size_t I2S_micro_lire(byte *donnees_micro_p, size_t taille_demande_p);

/* Déclaration globales */
int prochain_index_enregistrement_g;
bool bouton_relache_g;
char tampon_traces_g[200];

/* Fonction de démarrage, s'exécute une seule fois: */
void setup()
{
  // Pour le debug
  Serial.begin(115200);
  Serial.println("-----\n");

  // Intialisation des variables globales
  bouton_relache_g = true;

  // Initialisation des ports des boutons
  pinMode(PORT_BOUTON_ENREGISTRER, INPUT_PULLUP);
 
  // Initialisation de la carte SD
  SD_demarre(PORT_CARD_CS);

  // Initialisation du micro
  micro_configure(I2S_NOMBRE_BUFFER, I2S_TAILLE_BUFFER_ENREGISTREMENT, PORT_MIC_DIN, PORT_MIC_LRCL, PORT_MIC_BCLK);
  i2s_stop(PORT_I2S); // Micro coupé en attendant d'en avoir besoin

  // Parcourt les fichiers d'enregistrement déjà présents
  prochain_index_enregistrement_g = donner_prochain_index_enregistrement();
}

/* Fonction principale du programme, s'exécute en boucle: */
void loop()
{
  char nom_fichier_l[13];
  File fichier_enregistrement_l;

  if(digitalRead(PORT_BOUTON_ENREGISTRER) == LOW)
  {
    if(bouton_relache_g)
    {
      donner_nom_fichier(nom_fichier_l, prochain_index_enregistrement_g);
      fichier_enregistrement_l = SD_cree_fichier_dat_pour_ecriture(nom_fichier_l);
      if (fichier_enregistrement_l)
      {
        enregistrement_voix(fichier_enregistrement_l, PORT_BOUTON_ENREGISTRER);
        fichier_enregistrement_l.close();
        sprintf(tampon_traces_g, "Fichier son \'%s\' créé.", nom_fichier_l+1);
        Serial.println(tampon_traces_g);
      }
      prochain_index_enregistrement_g = donner_prochain_index_enregistrement();
      bouton_relache_g = false;
      delay(300);  // Pour éviter les rebonds parasites
    }
  }
  else
  {
    bouton_relache_g = true;
  }
}

void SD_demarre(int port_CS_p)
{ 
  // Initialisation des ports du lecteur carte SD
  pinMode(port_CS_p, OUTPUT);
  // Carte SD insérée ?
  // Initialisation de la carte SD
  if (!SD.begin(port_CS_p))
  {
    Serial.println("Carte SD absente, mal formatée ou lecteur mal câblé.");
  }
}

File SD_cree_fichier_dat_pour_ecriture(char *nom_fichier_p)
{
  File fichier_l;
  
  SD.remove(nom_fichier_p);
  fichier_l = SD.open(nom_fichier_p, FILE_WRITE);
  if(!fichier_l)
  {
    sprintf(tampon_traces_g, "Erreur de creation du fichie \'%s\'.", nom_fichier_p+1);
    Serial.println(tampon_traces_g);
  }
  return(fichier_l);
}


int donner_prochain_index_enregistrement(void)
{
  char nom_fichier_l[13];
  int index_message_l;

  index_message_l = 0;
  do
  {
    index_message_l++;
    donner_nom_fichier(nom_fichier_l, index_message_l);
  }
  while(SD.exists(nom_fichier_l));
  return(index_message_l);
}

void donner_nom_fichier(char* nom_fichier_P, int index_P)
{
  sprintf(nom_fichier_P, "%s%03d%s", NOM_RACINE_ENREGISTREMENT, index_P, ".dat");
}

void micro_configure(int nombre_buffer_p, int taille_buffer_p, int port_DIN_p, int port_WS_p, int port_BCK_p)
{
  esp_err_t retour_l;
  
  const i2s_config_t configuration_i2s =
  {
    .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = FREQUENCE_ECHANTILLONAGE,
    .bits_per_sample = (i2s_bits_per_sample_t)NOMBRE_BITS_PAR_ECHANTILLONS,
    .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
    .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S | I2S_COMM_FORMAT_STAND_PCM_SHORT),
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,     // Interrupt level 1
    .dma_buf_count = nombre_buffer_p,
    .dma_buf_len = taille_buffer_p,
    .use_apll = false,
    .tx_desc_auto_clear = true,
    .fixed_mclk = -1,
    .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
    .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT
  };
 
  retour_l = i2s_driver_install(PORT_I2S, &configuration_i2s, 0, NULL);
  switch(retour_l)
  {
    case ESP_OK:
      break;
    case ESP_ERR_INVALID_ARG:
      Serial.println(F("Parametrage incorrect pour la fonction 'micro_configure/i2s_driver_install'"));     
      break;
  }
  const i2s_pin_config_t brochage_i2s =
  {
    .mck_io_num = I2S_PIN_NO_CHANGE,
    .bck_io_num = port_BCK_p,
    .ws_io_num = port_WS_p,
    .data_out_num = I2S_PIN_NO_CHANGE,
    .data_in_num = port_DIN_p
  };
  retour_l = i2s_set_pin(PORT_I2S, &brochage_i2s);
  switch(retour_l)
  {
    case ESP_OK:
      break;
    case ESP_FAIL:
      Serial.println(F("Erreur de communication lors de l'appel de 'micro_configure/i2s_set_pin'"));     
      break;
    case ESP_ERR_INVALID_ARG:
      Serial.println(F("Parametrage incorrect pour la fonction 'micro_configure/i2s_set_pin'"));     
      break;
  }
}

void enregistrement_voix(File fichier_enregistrement_p, int bouton_maintien_p)
{
  byte donnees_recues_l[TAILLE_EXTRACTION_BUFFER];
  size_t nombre_octets_recus_l;

  i2s_start(PORT_I2S);  // Ouvre le micro
  do
  {
    nombre_octets_recus_l = I2S_micro_lire(donnees_recues_l, TAILLE_EXTRACTION_BUFFER);
    if (nombre_octets_recus_l > 0)
    {
      if(!fichier_enregistrement_p.write(donnees_recues_l, nombre_octets_recus_l))
      {
        Serial.println(F("Erreur d'ecriture des donnees audio."));
      }      
    }
  }
  while(digitalRead(bouton_maintien_p)==LOW);
  
  i2s_stop(PORT_I2S); // Coupe le micro
}

size_t I2S_micro_lire(byte *donnees_micro_p, size_t taille_demande_p)
{
  esp_err_t retour_l;
  size_t    nombre_octets_lus_l;

  /* Valeurs par defaut */
  nombre_octets_lus_l = 0;

  retour_l = i2s_read(PORT_I2S, donnees_micro_p, taille_demande_p, &nombre_octets_lus_l, portMAX_DELAY);
  switch(retour_l)
  {
    case ESP_OK:
      break;
    case ESP_ERR_INVALID_ARG:
      Serial.println(F("Parametrage incorrect pour la fonction  'I2S_micro_lire/i2s_read'"));     
      break;
    case ESP_ERR_NO_MEM:
      Serial.println(F("Plus assez de mémoire disponible pour exécuter 'I2S_micro_lire/i2s_read'"));     
      break;
 }
  return(nombre_octets_lus_l);
}

Test du programme d’enregistrement du micro sur carte Micro SD

Une fois le programme compilé, transféré et l’ESP32-DevKitC redémarré (bref appui sur le bouton reset identifié “EN”), vous pouvez enregistrer vos plus beaux messages sur la carte Micro SD. N’oubliez pas de garder le doigt appuyé sur le bouton-poussoir pendant l’enregistrement.

Test d’enregistrement audio sur carte Micro SD avec le microphone SPH0645

En banchant ensuite la carte Micro SD sur un PC, vous retrouvez les fichiers correspondant à vos enregistrements. Pour exemple, voici un partage de ceux créés dans la vidéo: Enr_001.dat, Enr_002.dat et Enr_003.dat.

À cette étape du projet, les fichiers contiennent des données audio brutes qui ont besoin d’un logiciel de traitement audio pour être les écoutées. Je vous invite à installer le logiciel Open source Audacity  sur le site officiel ou ici avant de poursuivre.

Une fois Audacity installé et lancé sur votre PC, vous pouvez charger les fichiers audio générés par Arduino.  Pour cela, allez dans le menu « Fichier », « Importer » puis cliquez sur « Données brutes (Raw)… »:

Menu d'Audacity pour importer des données audio brutes
Menu d’Audacity pour importer des données audio brutes

Vous choisissez ensuite le fichier de la carte Micro SD qui vous intéresse:

Sélection d'un fichier de données audio brutes
Sélection d’un fichier de données audio brutes

Il est important de bien préciser à Audacity, le format utilisé pour générer les données audio:

  • Encodage (1): Signed 32-bit PCM
  • Ordre des bits (2): Mode “petit-boutiste”
  • Canaux (3): 1 canal
  • Taux d’échantillonnage (4): 44100 Hz

avant de cliquer sur le bouton Importer (5) :

Format des données à renseigner pour ce tutoriel
Format des données à renseigner pour ce tutoriel

Vous obtenez ensuite quelque chose comme l’écran ci-dessous. Il ne reste alors plus qu’à appuyer sur le triangle vert pour écouter l’enregistrement.

Résultat de l’importation du premier fichier de données audio
Résultat de l’importation du premier fichier de données audio

Je ne sais pas ce que vous en pensez, pour ma part je trouve l’enregistrement de bonne qualité pour de l’embarqué.

Et si, plutôt que d’utiliser Audacity, nous générions directement des fichiers audio qui pouvant être lus sur un PC ? un smartphone ? ou un lecteur électronique audio ?
… C’est l’objet de la page suivante.

S’abonner
Notification pour
guest
0 Commentaires
Commentaires en ligne
Afficher tous les commentaires