Skip to Content

Lecture audio avec ESP32 et PCM5102A

Lecture audio avec ESP32 et PCM5102A

Dans cet article, vous allez apprendre à utiliser un module PCM5102A avec l’ESP32 pour lire de l’audio. Le PCM5102A est un convertisseur numérique-analogique (DAC) haute performance, qui communique via l’interface I2S (Inter-IC Sound).

Il convertit les données audio en un signal analogique de niveau ligne, adapté à l’amplification ou à une utilisation directe avec des écouteurs à haute impédance. Le module dispose d’une sortie stéréo 3,5 mm et fonctionne avec une alimentation standard de 3,3 V à 5 V, ce qui le rend compatible avec les microcontrôleurs courants comme les cartes ESP32 ou Arduino.

Tout au long de ce tutoriel, vous apprendrez à générer des signaux audio, convertir du texte en parole, diffuser la radio internet, lire des fichiers MP3 depuis une carte SD et utiliser l’audio Bluetooth.

Matériel nécessaire

Vous aurez besoin d’un module PCM5102A. Il existe différentes versions de cartes basées sur le PCM5102A, mais elles devraient toutes fonctionner pour ce tutoriel. J’ai utilisé la carte listée ci-dessous, qui possède une prise jack 3,5 mm pour la sortie ligne.

Different types of PCM5102A modules
Différents types de modules PCM5102A

Vous aurez également besoin d’une paire d’enceintes actives, de préférence avec une prise 3,5 mm pour se connecter directement à ce module PCM5102A.

Pour lire des fichiers MP3 depuis une carte SD, il vous faudra en plus une carte SD d’au moins 1 Go et un module lecteur de carte SD.

Enfin, il vous faudra un ESP32, une breadboard et quelques câbles. J’ai utilisé un ESP32 lite mais la plupart des autres cartes ESP32 devraient aussi fonctionner. Prévoyez une carte ESP32-S3 avec PSRAM si vous souhaitez stocker et lire de la musique depuis la mémoire.

Amplificateur PCM5102

Enceinte active

Lecteur de carte Micro SD

Carte Micro SD 8GB

ESP32 lite Lolin32

ESP32 lite

USB data cable

Câble USB de données

Dupont wire set

Jeu de fils Dupont

Half_breadboard56a

Breadboard

Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.

Le protocole audio I2S

Commençons par une brève introduction du protocole I2S utilisé pour transférer les données audio numériquement d’un ESP32 vers un DAC comme le PCM5102A.

I2S, ou Inter-IC Sound, est une norme d’interface de bus série utilisée pour connecter des appareils audio numériques. Il a été introduit par Philips dans les années 1980 pour simplifier la transmission des données audio entre circuits intégrés. Contrairement à des protocoles comme SPI ou I2C, l’I2S est spécialement conçu pour les applications audio, garantissant un timing précis et la synchronisation des flux audio.

Au cœur du protocole, I2S transfère des données audio en modulation par impulsions codées (PCM) de manière synchrone. Le protocole utilise trois signaux principaux : l’horloge série (SCK), la sélection de mot (WS) et les données série (SD). L’horloge série pulse au rythme des bits, dictant quand les bits sont envoyés. Le signal de sélection de mot bascule pour indiquer si les données actuelles correspondent au canal audio gauche ou droit. Enfin, la ligne de données série transporte les bits audio réels, en transmettant d’abord le bit de poids fort (MSB).

I2S Timing Diagram
Diagramme temporel I2S (source)

En général, les données audio sont envoyées sous forme de mots de 16 ou 24 bits, mais le protocole peut supporter d’autres profondeurs de bits selon le matériel.

Le périphérique I2S de l’ESP32 prend en charge la communication full-duplex, permettant une entrée et une sortie audio simultanées. Il peut être configuré en mode maître ou esclave. En mode maître, l’ESP32 génère les signaux d’horloge et de sélection de mot. C’est la configuration la plus courante lors de l’utilisation avec le DAC PCM5102A, qui agit comme un esclave I2S. Dans la section suivante, nous allons examiner de plus près le PCM5102A.

Caractéristiques techniques du module PCM5102

Les modules PCM5102A sont basés sur le Texas Instruments PCM5102A, un convertisseur numérique-analogique stéréo conçu pour la lecture audio haute résolution. Il convertit les flux audio numériques en signaux analogiques de niveau ligne grâce à un chemin de signal entièrement intégré qui minimise le besoin de composants externes.

Le PCM5102A prend en charge la sortie stéréo avec une sortie analogique pleine échelle typique d’environ 2,1 Vrms centrée sur la masse, permettant une connexion directe en niveau ligne sans besoin de condensateurs de blocage DC externes.

Le module utilisé dans ce tutoriel est équipé d’une prise stéréo 3,5 mm pour la sortie audio, ainsi que de composants passifs embarqués pour le filtrage de sortie et le couplage AC. Il y a aussi des ponts de soudure pour la sélection des fonctions – nous y reviendrons plus tard. L’image ci-dessous montre l’avant et l’arrière du module PCM5102 :

Front and back of  PCM5102 Module
Avant et arrière du module PCM5102

Entrée audio numérique et gestion de l’horloge

Les données audio numériques sont acceptées via une interface PCM standard compatible avec les formats I2S et left-justified, prenant en charge les données 16, 24 et 32 bits. Les taux d’échantillonnage de 8 kHz à 384 kHz sont pris en charge, ce qui rend l’appareil adapté aussi bien à l’audio standard qu’à la haute résolution.

Le PCM5102A intègre un PLL haute performance capable de synthétiser l’horloge interne haute vitesse requise par ses moteurs d’interpolation et de conversion à partir de l’horloge de bit (BCK) ou d’une horloge système externe optionnelle (SCK). Cela réduit le besoin d’une source d’horloge maître externe et permet de fonctionner avec une connexion I2S à 3 fils (LRCK, BCK, DIN).

Traitement numérique du signal et filtres

Une fois les données audio numériques reçues, le chemin de signal interne du PCM5102A inclut des filtres d’interpolation numérique qui suréchantillonnent le signal d’entrée par un facteur (par exemple, 128× la fréquence d’échantillonnage) avant conversion. Ces étapes d’interpolation réduisent le bruit de quantification et simplifient les exigences du filtre de reconstruction analogique.

Étape de sortie analogique et driver de ligne

L’étage de sortie analogique du module intègre un driver de ligne DirectPath™ à pompe de charge, capable de fournir une sortie niveau ligne sur des charges aussi basses que 1 kΩ par canal. Un circuit intelligent de soft-ramp et de mute analogique aide à éviter les transitoires audibles lors de la mise sous/hors tension ou en cas d’erreur d’horloge.

Notez que vous ne pouvez pas alimenter directement une enceinte passive avec la sortie niveau ligne du PCM5102A. Il vous faut un amplificateur supplémentaire ou une enceinte active !

Alimentation, contrôle et conditions de fonctionnement

Le module PCM5102A est conçu pour fonctionner avec une seule alimentation, comprise entre 3,3 V et 5 V. En interne, le DAC utilise des régulateurs LDO intégrés pour générer les tensions nécessaires au cœur et à l’analogique.

Le PCM5102A passe automatiquement en mode basse consommation lorsque les horloges I2S sont inactives pour réduire la consommation d’énergie. Des broches matérielles et des ponts de soudure permettent de configurer le format audio (par exemple, I2S ou left-justified) et l’état de soft mute, sans qu’une interface de contrôle série (I2C/SPI) ne soit nécessaire pour un fonctionnement de base.

Brochage

L’image suivante montre le brochage de la carte PCM5102A :

Pinout of PCM5102A board
Brochage de la carte PCM5102A

En haut de la carte se trouvent les broches pour la sortie ligne : LROUT = canal gauche, ROUT = canal droit, AGNG = masse) et des broches pour contrôler les fonctions spécifiques de la carte (plus d’infos dans la section suivante).

Les fonctions sont également configurables via des ponts de soudure à l’arrière de la carte (H1L, H2L, H3L, H4L). La prise jack 3,5 mm duplique les broches de sortie ligne.

Sur le côté droit, vous trouverez les broches pour l’interface I2S (SCK, BCK, DIN, LRCK) et les broches d’alimentation (VIN, GND). Les broches I2S passent par un réseau de résistances qui devrait permettre des signaux 5V, par exemple depuis un Arduino UNO. La tension d’alimentation peut être de 3,3V ou 5V, car la carte possède des régulateurs de tension LDO internes.

Cavaliers de fonction

Le module PCM5102A possède cinq cavaliers/ponts de soudure à configurer correctement pour fonctionner avec un ESP32. Ils commutent les fonctions suivantes selon qu’ils sont reliés au niveau haut ou bas. Les réglages recommandés sont indiqués en gras :

  • SCK = Horloge maître : Bas si aucun signal d’horloge maître externe n’est fourni
  • H1L / FLT = Sélection du filtre : Latence normale ( Bas ) / Faible latence (Haut)
  • H2L / DEMP = Contrôle de désaccentuation pour un taux d’échantillonnage de 44,1 kHz : Désactivé ( Bas ) / Activé (Haut)
  • H3L / XSMT = Contrôle du soft mute : Soft mute (Bas) / soft un-mute ( Haut )
  • H4L / FMT = Sélection du format audio I2S : Right justified ( Bas ) / Left justified (Haut)

Le filtre à latence normale est un FIR avec une bonne réponse, retardant le signal d’environ 500 µs (à 44,1 kHz). Le filtre à faible latence est un IIR avec une réponse légèrement moins bonne et retarde le signal d’environ 80 µs. Très peu (voire aucun) de sources audio n’utilisent la préaccentuation, donc le contrôle de désaccentuation doit être désactivé. La broche XSMT permet de couper la sortie via un GPO ou un interrupteur si le pont H3L n’est pas soudé. Pour le format audio I2S, on choisit right justified.

L’image ci-dessous montre le schéma du module PCM5102A avec ces cavaliers de fonction indiqués en jaune :

Schematic of PCM5102A module with function jumpers
Schéma du module PCM5102A avec cavaliers de fonction

Ponts de soudure

Vous trouverez quatre ponts de soudure H1L, H2L, H3L et H4L à l’arrière de la carte. L’image ci-dessous vous montre quels ponts souder au niveau haut (H) ou bas (L) :

Ponts H1L, H2L, H3L et H4L à l’arrière de la carte PCM5102A

Il y a aussi 4 broches (FLT, DEMP, XMST, FMT) en haut de la carte que vous pouvez relier à 3,3V (H) ou GND (L) pour contrôler les fonctions de la carte. C’est utile si vous souhaitez utiliser des interrupteurs ou des GPIO pour piloter les fonctions. La fonction mute (XMST/H3L) peut notamment être intéressante. Mais attention à n’utiliser soit les broches, soit les ponts, mais jamais les deux en même temps !

Pont SCK

Il y a un pont unique à l’avant de la carte à souder également. Il contrôle si un signal d’horloge maître externe (SCK) est fourni ou non. Dans notre cas, nous ne fournissons pas de SCK et devons donc souder le pont SCK pour relier SCK à la masse.

Cavalier SCK à l’arrière de la carte PCM5102A

Spécifications techniques

Le tableau suivant résume les spécifications techniques du PCM5102A :

Paramètre Spécification
Puce DAC Texas Instruments PCM5102A
Architecture interne DAC delta-sigma haute performance avec filtres d’interpolation numériques
Interface audio numérique I2S standard, left-justified, right-justified, formats DSP (PCM)
Taux d’échantillonnage pris en charge 8 kHz à 384 kHz
Profondeur de bits prise en charge 16 bits, 24 bits, 32 bits
Exigences d’horloge d’entrée Bit Clock (BCK), Word Clock (LRCK) ; horloge système externe optionnelle (SCK)
Gestion de l’horloge PLL intégrée avec multiplicateur d’horloge haute performance ; génération d’horloge maître interne
Suréchantillonnage par filtre numérique Plusieurs étages avec un taux de suréchantillonnage élevé (ex. 128 x fs)
Type de sortie analogique Sorties différentielles internes avec filtrage passif embarqué vers une sortie simple
Connecteur de sortie Prise stéréo 3,5 mm
Tension de sortie pleine échelle Environ 2,1 Vrms (centré sur la masse)
Charge de sortie supportée Conçu pour des charges niveau ligne ≥ 1 kΩ par canal
Rapport signal/bruit (SNR) ~112 dB (A-pondéré, typique)
Distorsion harmonique totale + bruit (THD+N) ~-93 dB (typique)
Driver de ligne Driver de ligne à pompe de charge DirectPath™ intégré
Mute/Soft-Ramp Mute analogique intégré lors de la mise sous/hors tension ou perte d’horloge
Tension d’alimentation (AVDD) 3,3 V … 5V
Niveaux logiques Compatible LVCMOS avec les niveaux d’E/S de l’ESP32
Interface de contrôle Configuration par broches matérielles (pas d’I2C/SPI requis pour le fonctionnement de base)
Consommation électrique Mode basse consommation automatique quand les horloges sont inactives

Pour plus d’informations, consultez la datasheet du PCM5102A dont le lien est ci-dessous :

Connecter le PCM5102A à l’ESP32

Dans cette section, nous allons câbler le PCM5102A avec l’ESP32. Commencez par connecter VIN du PCM5102A au 3,3V de l’ESP32 et GND à G. Ensuite, reliez les broches I2S comme indiqué dans le tableau suivant :

PCM5102A ESP32
VIN 3V3
GND G
LRCK 32
BCK 25
DIN 33
SCK G

Si vous avez soudé le pont SCK, vous n’avez en fait pas besoin de relier SCK à la masse, mais cela ne pose pas de problème.

L’image suivante montre le câblage complet du module PCM5102A avec un ESP32 lite. Notez que vous devez connecter la sortie ligne du PCM5102A à un amplificateur ou une enceinte active. N’utilisez pas d’enceinte passive ou d’écouteurs à faible impédance directement sur le PCM5102A.

Connecting PCM5102A to ESP32
Connexion du PCM5102A à l’ESP32

Lecteur de carte SD

Si vous souhaitez lire des fichiers audio, il vous faut connecter un lecteur de carte SD qui stocke les fichiers audio sur une carte SD. Le schéma de câblage ci-dessous montre comment connecter un lecteur de carte SD et le PCM5102A à un ESP32 :

Connexion du PCM5102A et du lecteur de carte SD à l’ESP32

Le lecteur de carte SD communique via SPI et les broches SPI par défaut de l’ESP32 sont CS=5, MOSI=23, CLK=18 et MISO=19. Le tableau ci-dessous résume les connexions à réaliser entre le lecteur de carte SD et l’ESP32 :

Lecteur de carte SD ESP32
3V3 3V
GND G
CS/SS 5
MOSI 23
CLK/SCK 18
MISO 19

Si vous n’êtes pas sûr des broches SPI par défaut de votre ESP32, consultez le Find I2C and SPI default pins tutoriel.

La photo suivante montre mon câblage du PCM5102A avec un lecteur de carte SD, un ESP32 et une petite enceinte active mono pour les tests :

PCM5102A with SD Card Reader, ESP32 and Active Speaker
PCM5102A avec lecteur de carte SD, ESP32 et enceinte active

Installation des bibliothèques

Dans ce tutoriel, nous allons utiliser la bibliothèque arduino-audio-tools de Phil Schatzmann pour envoyer des données audio au PCM5102A. Pour installer la bibliothèque, allez sur le arduino-audio-tools repo, cliquez sur le bouton vert « <> Code » puis sur « Download ZIP » pour télécharger la bibliothèque au format ZIP comme ci-dessous :

Ouvrez ensuite un Sketch, allez dans Sketch -> Include Library -> Add .ZIP Library … pour installer la bibliothèque ZIP téléchargée (arduino-audio-tools-main.zip) :

Pour certains exemples de code, nous aurons besoin de deux autres bibliothèques de Phil Schatzmann : à savoir la bibliothèque arduino-libhelix et la bibliothèque ESP32-A2DP. Vous pouvez les installer de la même manière. Cliquez sur le lien pour accéder au dépôt github, cliquez sur le bouton vert « <> Code » pour télécharger les bibliothèques (arduino-libhelix-main.zip, ESP32-A2DP-main.zip) puis installez-les.

Enfin, si c’est la première fois que vous programmez une carte ESP32 depuis l’IDE Arduino, vous devrez aussi installer le core ESP32. Pour plus de détails, consultez le Install ESP32 core in Arduino IDE tutoriel.

Exemple de code : Son de test

Dans ce premier exemple, nous générons simplement une onde sinusoïdale de test et l’envoyons via l’interface I2S vers le DAC

/*
www.makerguides.com

Libraries:
- ESP32 Core 3.3.6
- [arduino-audio-tools](https://github.com/pschatzmann/arduino-audio-tools) 
  Version: 1.2.2
- [arduino-libhelix](https://github.com/pschatzmann/arduino-libhelix)
*/

#include "AudioTools.h"

// PCM5102A
#define DIN_PIN 33     // serial data
#define LRCK_PIN 32    // word select
#define BCLK_PIN 25    // serial clock
#define VOLUME 1000 // max 32000

AudioInfo info(44100, 2, 16);
SineWaveGenerator<int16_t> sineWave(VOLUME);                
GeneratedSoundStream<int16_t> sound(sineWave);             
I2SStream out; 
StreamCopy copier(out, sound);                             

void setup(void) {  
  auto config = out.defaultConfig(TX_MODE);  
  config.copyFrom(info); 
  config.pin_bck = BCLK_PIN;
  config.pin_ws = LRCK_PIN;
  config.pin_data = DIN_PIN;  

  out.begin(config);
  sineWave.begin(info, N_B4);
}

void loop() {
  copier.copy();
}

Imports

Le code commence par inclure la bibliothèque AudioTools.h, qui fournit des classes et fonctions pour générer et diffuser facilement des signaux audio sur l’ESP32.

#include "AudioTools.h";

Constantes

Ensuite, nous définissons les broches connectées au module DAC PCM5102A. Ces broches correspondent aux signaux de l’interface I2S : DIN_PIN pour les données série, LRCK_PIN pour la sélection de mot (left-right clock), et BCLK_PIN pour l’horloge série. Nous définissons aussi une constante VOLUME pour régler l’amplitude de l’onde sinusoïdale générée, avec une amplitude maximale de 32000.

#define DIN_PIN 33     // serial data
#define LRCK_PIN 32    // word select
#define BCLK_PIN 25    // serial clock
#define VOLUME 1000    // max 32000

Configuration audio et objets

L’objet AudioInfo info définit le format audio : un taux d’échantillonnage de 44 100 Hz, 2 canaux (stéréo) et 16 bits par échantillon. Cela correspond à la qualité audio standard CD.

L’objet SineWaveGenerator sineWave sert à générer un signal audio sinusoïdal avec le volume spécifié. Il produit des échantillons entiers signés 16 bits.

L’objet GeneratedSoundStream sound encapsule le générateur d’onde sinusoïdale dans une interface de flux, ce qui facilite l’envoi des données audio vers une sortie.

L’objet I2SStream out représente l’interface de sortie I2S sur l’ESP32, qui enverra les données audio au DAC PCM5102A.

Enfin, l’objet StreamCopy copier est responsable de la copie continue des données audio du flux généré vers le flux de sortie I2S.

AudioInfo info(44100, 2, 16);
SineWaveGenerator<int16_t> sineWave(VOLUME);                
GeneratedSoundStream<int16_t> sound(sineWave);             
I2SStream out; 
StreamCopy copier(out, sound);

Fonction Setup

Dans la fonction setup(), nous configurons le flux de sortie I2S. On commence par obtenir une configuration par défaut pour le mode transmission avec out.defaultConfig(TX_MODE). Ensuite, on copie les paramètres du format audio de l’objet info dans cette configuration.

Ensuite, on assigne les broches I2S à la configuration : BCLK_PIN pour l’horloge de bit, LRCK_PIN pour la sélection de mot, et DIN_PIN pour la ligne de données série.

Après avoir configuré les broches, on initialise le flux de sortie I2S avec out.begin(config). Enfin, on démarre le générateur d’onde sinusoïdale avec le format audio et une fréquence de note musicale prédéfinie N_B4 (qui correspond à la note B4).

void setup(void) {  
  auto config = out.defaultConfig(TX_MODE);  
  config.copyFrom(info); 
  config.pin_bck = BCLK_PIN;
  config.pin_ws = LRCK_PIN;
  config.pin_data = DIN_PIN;  

  out.begin(config);
  sineWave.begin(info, N_B4);
}

Fonction Loop

La fonction loop() copie en continu les données audio du flux généré vers le flux de sortie I2S avec copier.copy(). Cela permet de jouer l’audio indéfiniment sans interruption.

void loop() {
  copier.copy();
}

Exemple de code : Bluetooth

Ce code montre comment lire de l’audio via Bluetooth sur un ESP32 en utilisant le DAC PCM5102A. Il utilise plusieurs bibliothèques pour gérer le streaming audio et la fonctionnalité Bluetooth A2DP (Advanced Audio Distribution Profile). L’ESP32 reçoit les données audio via Bluetooth et les transmet via l’interface I2S au convertisseur numérique-analogique PCM5102A.

/*
www.makerguides.com

Libraries:
- ESP32 Core 3.3.6
- [arduino-audio-tools](https://github.com/pschatzmann/arduino-audio-tools) 
  Version: 1.2.2
- [arduino-libhelix](https://github.com/pschatzmann/arduino-libhelix)
  Version: 0.9.2
- [ESP32-A2DP](https://github.com/pschatzmann/ESP32-A2DP)
  Version: 1.8.8
*/

#include "AudioTools.h"
#include "BluetoothA2DPSink.h"

#define DIN_PIN 33   // serial data
#define LRCK_PIN 32  // word select
#define BCLK_PIN 25  // serial clock

I2SStream i2s;
BluetoothA2DPSink a2dp_sink(i2s);

void setup() {
  auto config = i2s.defaultConfig();
  config.pin_bck = BCLK_PIN;
  config.pin_ws = LRCK_PIN;
  config.pin_data = DIN_PIN;  
  i2s.begin(config);

  a2dp_sink.start("MyMusic");
}

void loop() { }

Imports

Le code commence par inclure les bibliothèques nécessaires. AudioTools.h fournit les capacités de streaming audio, tandis que BluetoothA2DPSink.h gère la fonctionnalité Bluetooth A2DP sink, permettant à l’ESP32 de recevoir des flux audio depuis des sources Bluetooth comme des smartphones.

#include "AudioTools.h"
#include "BluetoothA2DPSink.h"

Constantes

Ensuite, trois constantes définissent les broches GPIO utilisées pour l’interface I2S, qui est le protocole de communication entre l’ESP32 et le DAC PCM5102A. Ces broches correspondent à la ligne de données série (DIN_PIN), à la sélection de mot ou horloge gauche-droite (LRCK_PIN), et à la ligne d’horloge de bit (BCLK_PIN).

#define DIN_PIN 33   // serial data
#define LRCK_PIN 32  // word select
#define BCLK_PIN 25  // serial clock

Objets

Un objet I2SStream nommé i2s est créé pour gérer le flux de données audio I2S. Ensuite, un objet BluetoothA2DPSink appelé a2dp_sink est instancié, en passant le flux i2s en paramètre. Cela relie directement l’entrée audio Bluetooth à la sortie I2S, permettant une lecture audio fluide.

I2SStream i2s;
BluetoothA2DPSink a2dp_sink(i2s);

Fonction Setup

Dans la fonction setup(), l’interface I2S est configurée. La configuration I2S par défaut est obtenue via i2s.defaultConfig(), et les broches correspondantes pour l’horloge de bit, la sélection de mot et les données sont assignées selon les constantes définies plus haut. L’interface I2S est ensuite initialisée avec cette configuration à l’aide de i2s.begin(config).

Après avoir configuré l’interface I2S, le Bluetooth A2DP sink est démarré avec le nom d’appareil "MyMusic". Cela rend l’ESP32 détectable comme récepteur audio Bluetooth sous ce nom, permettant à d’autres appareils de s’y connecter et de diffuser de l’audio.

void setup() {
  auto config = i2s.defaultConfig();
  config.pin_bck = BCLK_PIN;
  config.pin_ws = LRCK_PIN;
  config.pin_data = DIN_PIN;  
  i2s.begin(config);

  a2dp_sink.start("MyMusic");
}

Ouvrez votre téléphone et recherchez les appareils Bluetooth pour trouver l’appareil "MyMusic" puis commencez à jouer de la musique.

Fonction Loop

La fonction loop() est vide car le Bluetooth A2DP sink et le flux I2S gèrent le traitement et la lecture audio de façon asynchrone. Une fois la configuration terminée, l’ESP32 écoute en continu les flux audio Bluetooth et les transmet via l’interface I2S sans nécessiter de code supplémentaire dans la boucle principale.

void loop() { }

Exemple de code : Radio Internet

Ce code montre comment réaliser un lecteur radio internet avec un microcontrôleur ESP32 et un module DAC PCM5102A. Il se connecte à un réseau WiFi, diffuse de l’audio MP3 depuis une station de radio en ligne, décode l’audio et le transmet via l’interface I2S au DAC. Le code gère aussi les métadonnées du flux, comme les titres des morceaux.

/*
www.makerguides.com

Libraries:
- ESP32 Core 3.3.6
- [arduino-audio-tools](https://github.com/pschatzmann/arduino-audio-tools) 
  Version: 1.2.2
- [arduino-libhelix](https://github.com/pschatzmann/arduino-libhelix)
  Version: 0.9.2
*/

#include <Arduino.h>
#include <WiFi.h>
#include <Wire.h>
#include "AudioTools.h"
#include "AudioTools/AudioCodecs/CodecMP3Helix.h"
#include "AudioTools/Communication/HTTP/ICYStream.h"

// PCM5102A
#define DIN_PIN 33   // serial data
#define LRCK_PIN 32  // word select
#define BCLK_PIN 25  // serial clock
#define VOLUME 0.3   // Volume

const char* ssid = "ssid";
const char* password = "pwd";
const char* url = "https://jazz.stream.laut.fm/jazz";

ICYStream icystream;
I2SStream i2s;
VolumeStream volume(i2s);
EncodedAudioStream mp3decode(&volume, new MP3DecoderHelix());
StreamCopy copier(mp3decode, icystream);

void callbackMetadata(MetaDataType type, const char* str, int len) {
  Serial.printf("%s: %s\n", toStr(type), str);
}

void setup() {
  Serial.begin(115200);
  AudioLogger::instance().begin(Serial, AudioLogger::Warning);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  auto config = i2s.defaultConfig(TX_MODE);
  config.pin_bck = BCLK_PIN;
  config.pin_ws = LRCK_PIN;
  config.pin_data = DIN_PIN;

  i2s.begin(config);
  volume.begin(config);
  volume.setVolume(VOLUME);
  mp3decode.begin();
  icystream.begin(url);
  icystream.setMetadataCallback(callbackMetadata);
}

void loop() {
  copier.copy();
}

Imports

Le code commence par inclure plusieurs bibliothèques nécessaires à la connexion WiFi, à la sortie audio I2S et au décodage MP3. Les bibliothèques Arduino.h et WiFi.h fournissent les fonctions de base Arduino et WiFi. La bibliothèque Wire.h est incluse mais pas utilisée explicitement ici. La bibliothèque AudioTools et ses composants associés gèrent le streaming audio, le décodage et la lecture.

#include <Arduino.h>
#include <WiFi.h>
#include <Wire.h>
#include "AudioTools.h"
#include "AudioTools/AudioCodecs/CodecMP3Helix.h"
#include "AudioTools/Communication/HTTP/ICYStream.h"

Constantes et définition des broches

Ensuite, le code définit les broches utilisées pour l’interface I2S afin de communiquer avec le DAC PCM5102A. Ces broches correspondent aux signaux de données série, de sélection de mot (left-right clock) et d’horloge de bit. Un niveau de volume est également défini comme constante à virgule flottante.

#define DIN_PIN 33   // serial data
#define LRCK_PIN 32  // word select
#define BCLK_PIN 25  // serial clock
#define VOLUME 0.3   // Volume

Les identifiants WiFi et l’URL du flux radio internet sont stockés sous forme de pointeurs de caractères constants.

const char* ssid = "ssid";
const char* password = "pwd";
const char* url = "https://jazz.stream.laut.fm/jazz";

Objets de flux audio

Plusieurs objets sont créés pour gérer la chaîne de streaming audio. L’objet ICYStream gère le streaming HTTP des données MP3 depuis l’URL de la radio internet, tandis que l’objet I2SStream gère la communication I2S avec le DAC. L’objet VolumeStream permet d’ajuster le volume de lecture et est relié au flux I2S. Enfin, l’objet EncodedAudioStream décode les données MP3 avec le décodeur Helix MP3 et envoie l’audio décodé au flux de volume. L’objet StreamCopy copie les données audio décodées du décodeur MP3 vers le flux ICY.

ICYStream icystream;
I2SStream i2s;
VolumeStream volume(i2s);
EncodedAudioStream mp3decode(&volume, new MP3DecoderHelix());
StreamCopy copier(mp3decode, icystream);

Fonction de rappel pour les métadonnées

La fonction callbackMetadata est définie pour gérer les métadonnées reçues du flux radio internet, comme le titre ou l’artiste en cours. Elle affiche le type de métadonnée et son contenu sur le moniteur série pour le débogage ou l’affichage.

void callbackMetadata(MetaDataType type, const char* str, int len) {
  Serial.printf("%s: %s\n", toStr(type), str);
}

Fonction Setup

Dans la fonction setup(), la communication série est initialisée à 115200 bauds pour l’affichage des messages de débogage. Le logger audio est aussi démarré pour capturer les avertissements et erreurs. L’ESP32 tente ensuite de se connecter au réseau WiFi spécifié, en réessayant toutes les 500 ms jusqu’à succès.

Une fois la connexion WiFi établie, l’interface I2S est configurée en mode transmission avec les broches spécifiées pour l’horloge de bit, la sélection de mot et les données. Le flux I2S et le flux de volume sont initialisés avec cette configuration, et le volume est réglé au niveau prédéfini.

Le décodeur MP3 est démarré, et le flux ICY est initialisé avec l’URL de la radio internet. La fonction de rappel pour les métadonnées est enregistrée pour recevoir et traiter les métadonnées du flux.

void setup() {
  Serial.begin(115200);
  AudioLogger::instance().begin(Serial, AudioLogger::Warning);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  auto config = i2s.defaultConfig(TX_MODE);
  config.pin_bck = BCLK_PIN;
  config.pin_ws = LRCK_PIN;
  config.pin_data = DIN_PIN;

  i2s.begin(config);
  volume.begin(config);
  volume.setVolume(VOLUME);
  mp3decode.begin();
  icystream.begin(url);
  icystream.setMetadataCallback(callbackMetadata);
}

Fonction Loop

La fonction loop() copie en continu les données audio du flux radio internet à travers le décodeur MP3 et le contrôle de volume vers la sortie I2S. Cela permet de maintenir la lecture audio en continu.

void loop() {
  copier.copy();
}

Exemple de code : Lecture MP3 depuis carte SD

Le code suivant montre comment lire des fichiers audio MP3 stockés sur une carte SD avec un microcontrôleur ESP32 et le module DAC PCM5102A. Il utilise les bibliothèques arduino-audio-tools et arduino-libhelix pour gérer le décodage audio et la lecture via l’interface I2S.

/*
www.makerguides.com

Libraries:
- ESP32 Core 3.3.6
- [arduino-audio-tools](https://github.com/pschatzmann/arduino-audio-tools) 
  Version: 1.2.2
- [arduino-libhelix](https://github.com/pschatzmann/arduino-libhelix)
  Version: 0.9.2
*/

#include "AudioTools.h"
#include "AudioTools/Disk/AudioSourceSD.h"
#include "AudioTools/AudioCodecs/CodecMP3Helix.h"

// PCM5102A
#define DIN_PIN 33   // serial data
#define LRCK_PIN 32  // word select
#define BCLK_PIN 25  // serial clock
#define VOLUME 0.5   // Volume

#define PATH "/"
#define EXT "mp3"

AudioSourceSD source(PATH, EXT);
I2SStream i2s;
MP3DecoderHelix decoder;
AudioPlayer player(source, i2s, decoder);

void printMetaData(MetaDataType type, const char* str, int len){
  Serial.printf("%s: %s\n", toStr(type), str);
}

void setup() {
  Serial.begin(115200);
  AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);

  auto config = i2s.defaultConfig(TX_MODE);
  config.pin_bck = BCLK_PIN;
  config.pin_ws = LRCK_PIN;
  config.pin_data = DIN_PIN;  
  i2s.begin(config);

  player.setMetadataCallback(printMetaData);
  player.setVolume(VOLUME);
  player.begin();
}

void loop() {
  player.copy();
}

Imports

Au début, le code inclut plusieurs fichiers d’en-tête des bibliothèques audio. Ils fournissent les classes et fonctions nécessaires pour lire les fichiers audio depuis la carte SD, décoder les flux MP3 et sortir l’audio via I2S.

#include "AudioTools.h"
#include "AudioTools/Disk/AudioSourceSD.h"
#include "AudioTools/AudioCodecs/CodecMP3Helix.h"

Constantes et définition des broches

Le code définit les broches utilisées pour connecter l’ESP32 au DAC PCM5102A. Ces broches correspondent aux signaux I2S : données série (DIN), sélection de mot (LRCK) et horloge série (BCLK). Un niveau de volume est également défini comme valeur flottante entre 0,0 et 1,0.

#define DIN_PIN 33   // serial data
#define LRCK_PIN 32  // word select
#define BCLK_PIN 25  // serial clock
#define VOLUME 0.5   // Volume

Les constantes PATH et EXT spécifient le répertoire racine et l’extension de fichier pour les fichiers audio à lire, ici des fichiers MP3 situés à la racine de la carte SD.

#define PATH "/"
#define EXT "mp3"

Objets audio

Plusieurs objets sont instanciés pour gérer la lecture audio. AudioSourceSD gère la lecture des fichiers audio sur la carte SD correspondant au chemin et à l’extension spécifiés. I2SStream gère le flux de sortie audio I2S. MP3DecoderHelix est le décodeur MP3 basé sur la bibliothèque Helix. Enfin, AudioPlayer relie ces composants pour coordonner la lecture.

AudioSourceSD source(PATH, EXT);
I2SStream i2s;
MP3DecoderHelix decoder;
AudioPlayer player(source, i2s, decoder);

Fonction de rappel pour les métadonnées

La fonction printMetaData() est définie pour recevoir les informations de métadonnées telles que l’artiste, le titre ou l’album des fichiers MP3 lors de la lecture. Elle affiche ces informations sur le moniteur série avec Serial.printf. La fonction prend en paramètres le type de métadonnée, une chaîne contenant la métadonnée et sa longueur.

void printMetaData(MetaDataType type, const char* str, int len){
  Serial.printf("%s: %s\n", toStr(type), str);
}

Fonction Setup

Dans la fonction setup(), la communication série est initialisée à 115200 bauds pour permettre l’affichage des messages de débogage. Le niveau de log audio est réglé sur Warning pour réduire la verbosité.

Ensuite, l’interface I2S est configurée en mode transmission. La configuration I2S par défaut est obtenue et modifiée pour assigner les broches définies précédemment pour l’horloge de bit, la sélection de mot et les données. Le flux I2S est ensuite démarré avec cette configuration.

Le lecteur audio est configuré pour utiliser le callback printMetaData pour afficher les métadonnées lors de la lecture. Le volume est réglé au niveau défini, et le lecteur est démarré avec player.begin().

void setup() {
  Serial.begin(115200);
  AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);

  auto config = i2s.defaultConfig(TX_MODE);
  config.pin_bck = BCLK_PIN;
  config.pin_ws = LRCK_PIN;
  config.pin_data = DIN_PIN;  
  i2s.begin(config);

  player.setMetadataCallback(printMetaData);
  player.setVolume(VOLUME);
  player.begin();
}

Fonction Loop

La fonction loop() appelle en continu player.copy(), qui traite les données audio en lisant depuis la carte SD, en décodant le flux MP3 et en envoyant les échantillons audio à la sortie I2S. Cela permet une lecture audio fluide.

void loop() {
  player.copy();
}

Exemple de code : Text to Speech

Cet exemple de code montre comment mettre en place un système Text-to-Speech (TTS) sur un ESP32 en utilisant l’API TTS d’OpenAI et en sortant l’audio via un DAC PCM5102A. Les données audio sont diffusées au format MP3, décodées et lues via l’interface I2S. Le programme se connecte au WiFi, envoie du texte à l’API OpenAI, reçoit la synthèse vocale et la lit en temps réel.

/*
www.makerguides.com

Libraries:
- ESP32 Core 3.3.6
- [arduino-audio-tools](https://github.com/pschatzmann/arduino-audio-tools) 
  Version: 1.2.2
- [arduino-libhelix](https://github.com/pschatzmann/arduino-libhelix)
  Version: 0.9.2
*/

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "AudioTools.h"
#include "AudioTools/AudioCodecs/CodecMP3Helix.h"

// PCM5102A
#define DIN_PIN 33   // serial data
#define LRCK_PIN 32  // word select
#define BCLK_PIN 25  // serial clock

// Text to Speech
#define TTS_MODEL "gpt-4o-mini-tts"
#define TTS_VOICE "marin"
#define TTS_VOLUME 0.6

// WiFi credentials
const char* ssid     = "ssid";
const char* password = "pwd";

// OpenAI configuration
const char* openaiHost = "api.openai.com";
const int   openaiPort = 443;
const char* openaiApiKey = "apikey";


WiFiClientSecure client;
I2SStream i2s;
VolumeStream volume(i2s);
EncodedAudioStream mp3decode(&volume, new MP3DecoderHelix());
StreamCopy copier(mp3decode, client);

void text2speech(const char* text) {
  client.setInsecure(); 

  if (!client.connect("api.openai.com", 443)) {
    Serial.println("Connection failed");
    return;
  }

  String body = String("{") +
    "\"model\":\"" + TTS_MODEL + "\"," +
    "\"voice\":\"" + TTS_VOICE +  "\"," +
    "\"format\":\"mp3\"," +
    "\"input\":\"" + text + "\"" +
  "}";

  client.println("POST /v1/audio/speech HTTP/1.1");
  client.println("Host: api.openai.com");
  client.println("Authorization: Bearer " + String(openaiApiKey));
  client.println("Content-Type: application/json");
  client.print("Content-Length: ");
  client.println(body.length());
  client.println();
  client.print(body);

  // ---- Skip HTTP headers ----
  while (client.connected()) {
    String line = client.readStringUntil('\n');
    if (line == "\r") break;
  }
}

void setup() {
  Serial.begin(115200);
  AudioLogger::instance().begin(Serial, AudioLogger::Warning);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  auto config = i2s.defaultConfig(TX_MODE);
  config.pin_bck = BCLK_PIN;
  config.pin_ws = LRCK_PIN;
  config.pin_data = DIN_PIN;
  i2s.begin(config);

  mp3decode.begin();
  volume.begin(config);
  volume.setVolume(TTS_VOLUME);

  text2speech("Hello, how are you doing today?");
}

void loop() {
  copier.copy();
}

Imports

Le code commence par inclure les bibliothèques nécessaires. Arduino.h fournit les fonctions de base Arduino. WiFi.h et WiFiClientSecure.h gèrent la connexion WiFi et la communication HTTPS sécurisée. La bibliothèque AudioTools et son codec MP3 CodecMP3Helix sont utilisés pour le streaming et le décodage audio.

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "AudioTools.h"
#include "AudioTools/AudioCodecs/CodecMP3Helix.h"

Constantes et définition des broches

Les broches pour le DAC PCM5102A sont définies pour configurer l’interface I2S. Cela inclut la broche de données série (DIN_PIN), la broche de sélection de mot (LRCK_PIN) et la broche d’horloge série (BCLK_PIN). Des constantes pour le modèle TTS, la voix et le niveau de volume sont également définies. Les identifiants WiFi et les détails de l’API OpenAI sont aussi déclarés comme pointeurs de caractères constants.

// PCM5102A
#define DIN_PIN 33   // serial data
#define LRCK_PIN 32  // word select
#define BCLK_PIN 25  // serial clock

// Text to Speech
#define TTS_MODEL "gpt-4o-mini-tts"
#define TTS_VOICE "marin"
#define TTS_VOLUME 0.6

// WiFi credentials
const char* ssid     = "ssid";
const char* password = "pwd";

// OpenAI configuration
const char* openaiHost = "api.openai.com";
const int   openaiPort = 443;
const char* openaiApiKey = "apikey";

Vous pouvez essayer d’autres modèles TTS, comme « tts-1 » et d’autres voix telles que « alloy », « ash », « coral », « echo », « fable », « onyx », « nova », « sage », « shimmer », « marin », « cedar ». Pour plus de détails, consultez platform.openai.com/docs/guides/text-to-speech.

Les identifiants WiFi et les détails de l’API OpenAI sont aussi stockés sous forme de chaînes constantes. Vous devrez remplacer « ssid » et « pwd » par les identifiants de votre WiFi.

// WiFi credentials
const char* ssid     = "ssid";
const char* password = "pwd";

Vous devrez également obtenir une « apikey » auprès d’OpenAI. Rendez-vous sur https://platform.openai.com et inscrivez-vous avec une adresse email ou un compte Google ou Microsoft existant.

// OpenAI configuration
const char* openaiHost = "api.openai.com";
const int   openaiPort = 443;
const char* openaiApiKey = "apikey";

Après avoir vérifié votre email et terminé la configuration initiale, connectez-vous au tableau de bord OpenAI, platform.openai.com/api-keys et trouvez ou créez votre clé API (=SECRET KEY) comme ci-dessous :

OpenAI API keys
Clés API OpenAI

La clé API est une longue chaîne unique commençant par « sk-proj- » qui est nécessaire pour authentifier vos requêtes API (voir ci-dessous).

sk-proj-xcA.......................OtDu0U

C’est tout ce qu’il vous faut pour obtenir une clé API, mais je vous recommande de définir une limite d’utilisation pour votre compte. Pour plus de détails, consultez le Vision Chatbot with DFRobot ESP32-S3 AI Camera and OpenAI tutoriel.

Objets audio et réseau

Plusieurs objets sont instanciés pour gérer le streaming audio et la communication réseau. WiFiClientSecure gère la connexion HTTPS au serveur OpenAI. I2SStream gère la sortie audio I2S. L’objet VolumeStream encapsule le flux I2S pour contrôler le volume audio. EncodedAudioStream est utilisé pour décoder le flux audio MP3 avec le décodeur Helix MP3. Enfin, StreamCopy copie le flux audio décodé vers le client réseau.

WiFiClientSecure client;
I2SStream i2s;
VolumeStream volume(i2s);
EncodedAudioStream mp3decode(&volume, new MP3DecoderHelix());
StreamCopy copier(mp3decode, client);

Fonction Text-to-Speech

La fonction text2speech() prend une chaîne de texte en entrée et l’envoie à l’API TTS d’OpenAI pour générer l’audio. Elle configure d’abord le client pour accepter les connexions SSL non sécurisées (pratique pour le développement). Ensuite, elle tente de se connecter au serveur OpenAI sur le port 443. Si la connexion échoue, elle affiche un message d’erreur et retourne.

La fonction construit un corps JSON contenant le modèle TTS, la voix, le format de sortie (MP3) et le texte d’entrée. Elle envoie une requête HTTP POST avec les bons en-têtes, y compris le token d’autorisation avec la clé API. Après l’envoi du corps de la requête, elle lit et saute les en-têtes de la réponse HTTP pour préparer le streaming des données audio.

void text2speech(const char* text) {
  client.setInsecure(); 

  if (!client.connect("api.openai.com", 443)) {
    Serial.println("Connection failed");
    return;
  }

  String body = String("{") +
    "\"model\":\"" + TTS_MODEL + "\"," +
    "\"voice\":\"" + TTS_VOICE +  "\"," +
    "\"format\":\"mp3\"," +
    "\"input\":\"" + text + "\"" +
  "}";

  client.println("POST /v1/audio/speech HTTP/1.1");
  client.println("Host: api.openai.com");
  client.println("Authorization: Bearer " + String(openaiApiKey));
  client.println("Content-Type: application/json");
  client.print("Content-Length: ");
  client.println(body.length());
  client.println();
  client.print(body);

  // ---- Skip HTTP headers ----
  while (client.connected()) {
    String line = client.readStringUntil('\n');
    if (line == "\r") break;
  }
}

Fonction Setup

Dans la fonction setup(), la communication série est initialisée pour l’affichage des messages de débogage. Le logger audio est configuré pour signaler les avertissements. L’ESP32 se connecte au réseau WiFi spécifié, en attendant que la connexion soit établie.

Ensuite, l’interface I2S est configurée avec les broches définies pour l’horloge de bit, la sélection de mot et la sortie de données. Le décodeur MP3 et le contrôle de volume sont initialisés avec cette configuration, et le volume est réglé au niveau défini.

Enfin, la fonction text2speech() est appelée avec un texte d’accueil exemple, ce qui déclenche le processus TTS et commence à diffuser l’audio depuis l’API OpenAI.

void setup() {
  Serial.begin(115200);
  AudioLogger::instance().begin(Serial, AudioLogger::Warning);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  auto config = i2s.defaultConfig(TX_MODE);
  config.pin_bck = BCLK_PIN;
  config.pin_ws = LRCK_PIN;
  config.pin_data = DIN_PIN;
  i2s.begin(config);

  mp3decode.begin();
  volume.begin(config);
  volume.setVolume(TTS_VOLUME);

  text2speech("Hello, how are you doing today?");
}

Fonction Loop

La fonction loop() copie en continu le flux audio MP3 décodé du serveur OpenAI vers la sortie I2S. Cela permet de maintenir la lecture audio tant que la connexion reste ouverte.

void loop() {
  copier.copy();
}

Conclusions

Dans ce projet, vous avez appris à lire de l’audio avec l’ESP32 et le DAC PCM5102A. Nous avons exploré les détails techniques du module PCM5102A et comment le câbler à l’ESP32. Vous avez aussi appris à convertir du texte en parole, diffuser la radio internet, lire des fichiers MP3 depuis une carte SD et lire de l’audio via Bluetooth.

Si vous avez des questions, n’hésitez pas à les poser dans les commentaires.

Bon bidouillage ; )