#include /* * ESP32 Lecteur Audio WAV depuis carte SD avec gestion de veille * * Ce programme lit un fichier WAV stocké sur une carte microSD et le diffuse * via un module amplificateur I2S MAX98357. Il gère deux modes de fonctionnement : * - Premier démarrage (bootCount = 1) : lit "coucou.wav" puis se met en veille 5 minutes. * - Démarrages suivants (bootCount >= 2) : lit "son01.wav" puis se met en veille 24 heures et répète. * * La durée de veille est ajustée pour que le cycle total (lecture + veille) soit exactement * la durée programmée, en tenant compte de la longueur du fichier audio. * * Brochage matériel : * - Lecteur SD : CS=21, MOSI=GPIO10, MISO=GPIO9, SCK=GPIO8 * - I2S : DOUT=GPIO4, BCLK=GPIO5, LRC=GPIO6 * - Bouton (non utilisé) : GPIO3 * - Potentiomètre (non utilisé) : A2 (GPIO? sur certaines cartes) * - LED intégrée (non utilisée) * * Bibliothèques requises : * - ESP32-audioI2S (https://github.com/schreibfaul1/ESP32-audioI2S) * - SD de ESP32 (incluse dans le core) esp32 by espressif preferences URL : https://espressif.github.io/arduino-esp32/package_esp32_index.json */ #include "Arduino.h" #include "Audio.h" #include "SD.h" #include "FS.h" // --- Définitions des broches --- // Carte SD #define SD_CS 21 #define SPI_MOSI D10 // GPIO10 #define SPI_MISO D9 // GPIO9 #define SPI_SCK D8 // GPIO8 // I2S vers MAX98357 #define I2S_DOUT D4 // GPIO4 (Data Out) #define I2S_BCLK D5 // GPIO5 (Bit Clock) #define I2S_LRC D6 // GPIO6 (Left/Right Clock) // Autres broches (non utilisées dans ce programme, mais conservées pour référence) #define sensorPin A2 // Potentiomètre (optionnel) #define ledPin D1 // LED #define bouton D3 // Bouton poussoir // --- Objets --- Audio audio; // Objet principal pour la gestion audio // --- Variables globales --- int compteur = 0; // Compteur pour n'ouvrir le fichier qu'une seule fois par cycle // Paramètres de veille (en secondes) const unsigned long uS_TO_S_FACTOR = 1000000; // Conversion secondes -> microsecondes const int TIME_TO_SLEEP = 300; // 5 minutes (premier cycle) const int TIME_TO_SLEEP2 = 86400; // 24 heures (cycles suivants) float tempsMorceau = 0; // Durée du morceau actuel (en secondes) const float corr = 1.01; // Facteur de correction de l'horloge interne (compense la dérive) // Variable stockée en RTC (conserve sa valeur pendant la veille) RTC_DATA_ATTR int bootCount = 0; // Compteur de démarrages // --- Fonction d'affichage de la cause du réveil --- void print_wakeup_reason() { esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause(); switch (wakeup_reason) { case ESP_SLEEP_WAKEUP_EXT0: Serial.println("Réveil par signal externe (RTC_IO)"); break; case ESP_SLEEP_WAKEUP_EXT1: Serial.println("Réveil par signal externe (RTC_CNTL)"); break; case ESP_SLEEP_WAKEUP_TIMER: Serial.println("Réveil par minuterie"); break; case ESP_SLEEP_WAKEUP_TOUCHPAD: Serial.println("Réveil par toucher"); break; case ESP_SLEEP_WAKEUP_ULP: Serial.println("Réveil par programme ULP"); break; default: Serial.printf("Réveil non causé par deep sleep : %d\n", wakeup_reason); break; } } // --- Fonction d'analyse du fichier WAV pour en extraire la durée --- float lireDureeWAV(const char* nomFichier) { File fichier = SD.open(nomFichier); if (!fichier) { Serial.println("Erreur : impossible d'ouvrir le fichier WAV"); return 0; } // Lire l'en-tête WAV (44 premiers octets) unsigned char wavHeader[44]; fichier.read(wavHeader, 44); // Extraire les informations nécessaires uint32_t fileSize = *(uint32_t*)&wavHeader[4] + 8; // Taille réelle du fichier (octets) uint32_t sampleRate = *(uint32_t*)&wavHeader[24]; // Fréquence d'échantillonnage (Hz) uint16_t numChannels = *(uint16_t*)&wavHeader[22]; // Nombre de canaux (1=mono,2=stéréo) uint16_t bitsPerSample = *(uint16_t*)&wavHeader[34]; // Profondeur (8,16,24,32 bits) // Taille des données audio = fichier - en-tête (44 octets) uint32_t dataSize = fileSize - 44; // Durée = taille données / (sampleRate * nbCanaux * nbOctetsParEchantillon) float duree = (float)dataSize / (sampleRate * numChannels * (bitsPerSample / 8)); fichier.close(); // Affichage des infos (pour debug) Serial.println("==== Infos fichier WAV ===="); Serial.print("Nom : "); Serial.println(nomFichier); Serial.print("Taille fichier : "); Serial.print(fileSize); Serial.println(" octets"); Serial.print("Fréquence échantillonnage : "); Serial.print(sampleRate); Serial.println(" Hz"); Serial.print("Canaux : "); Serial.println(numChannels); Serial.print("Profondeur : "); Serial.print(bitsPerSample); Serial.println(" bits"); Serial.print("Durée estimée : "); Serial.print(duree); Serial.println(" sec"); Serial.println("==========================="); return duree; } // --- Fonction de mise en veille profonde --- void modeVeille(int dureeVeilleSec) { // Ajuste la durée de veille pour compenser le temps de lecture du morceau // Si le morceau est plus court que la durée de veille, on soustrait sa durée // pour que le cycle total (lecture + veille) soit exactement dureeVeilleSec. if (tempsMorceau < dureeVeilleSec) { esp_sleep_enable_timer_wakeup((dureeVeilleSec - tempsMorceau) * corr * uS_TO_S_FACTOR); Serial.println("Veille programmée pour " + String(dureeVeilleSec - tempsMorceau) + " secondes"); } else { // Cas où le morceau est plus long que la veille (peu probable, mais sécurité) esp_sleep_enable_timer_wakeup(dureeVeilleSec * corr * uS_TO_S_FACTOR); Serial.println("Veille programmée pour " + String(dureeVeilleSec) + " secondes"); } Serial.println("Passage en deep sleep..."); Serial.flush(); esp_deep_sleep_start(); // Le programme s'arrête ici jusqu'au réveil } // --- Initialisation --- void setup() { // Configuration des broches pinMode(SD_CS, OUTPUT); digitalWrite(SD_CS, HIGH); pinMode(bouton, INPUT_PULLUP); // Bouton non utilisé mais défini pinMode(ledPin, OUTPUT); // LED non utilisée digitalWrite(bouton, HIGH); // Initialisation du bus SPI pour la carte SD SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI); // Port série pour les messages de debug Serial.begin(115200); delay(1000); // Laisse le temps à l'utilisateur d'ouvrir le moniteur série // Incrémente le compteur de démarrages et affiche ++bootCount; Serial.println("Démarrage n° " + String(bootCount)); // Affiche la cause du dernier réveil print_wakeup_reason(); // Initialisation de la carte SD if (!SD.begin(SD_CS)) { Serial.println("Erreur : carte SD non détectée !"); while (true) ; // Bloque si pas de carte } // Configuration de l'interface I2S audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT); // NOTE : Le fichier audio sera ouvert dans loop() en fonction de bootCount // pour éviter d'en ouvrir un par défaut ici. } // --- Boucle principale --- void loop() { // ------------------------------------------------------------ // Premier démarrage : jouer "coucou.wav" puis veille 5 minutes // ------------------------------------------------------------ if (bootCount == 1) { // Ouvre le fichier une seule fois if (compteur == 0) { audio.setVolume(20); // Volume max pour le message d'accueil audio.connecttoFS(SD, "/coucou.wav"); Serial.println("Fichier coucou ouvert"); // Calcule la durée du morceau tempsMorceau = lireDureeWAV("/coucou.wav"); compteur++; } audio.loop(); // Nécessaire pour la lecture audio (remplit le buffer I2S) // Attend la fin du morceau avant de passer en veille if (millis() > (tempsMorceau * 1000 * corr)) { compteur = 0; // Remet à zéro pour le prochain cycle modeVeille(TIME_TO_SLEEP); // Veille 5 minutes } } // ------------------------------------------------------------ // Démarrages suivants : jouer "son01.wav" puis veille 24 heures // ------------------------------------------------------------ else if (bootCount >= 2) { // Ouvre le fichier une seule fois if (compteur == 0) { audio.setVolume(11); // Volume modéré audio.connecttoFS(SD, "/son01.wav"); Serial.println("Fichier son01 ouvert"); // Calcule la durée du morceau tempsMorceau = lireDureeWAV("/son01.wav"); compteur++; } audio.loop(); // Attend la fin du morceau avant la veille if (millis() > (tempsMorceau * 1000 * corr)) { compteur = 0; modeVeille(TIME_TO_SLEEP2); // Veille 24 heures } } }