Browse Source

Initial commit

master version1.0
scayac 3 months ago
commit
a10c58311d
  1. 2
      .gitignore
  2. 57
      README.md
  3. 17
      platformio.ini
  4. 278
      src/main.cpp

2
.gitignore vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
.pio
.vscode

57
README.md

@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
# Documentation du projet ESP32 MP3 GPIO Web
Ce projet permet de piloter la lecture de fichiers MP3 stockés sur une carte SD via un ESP32, avec une interface web de configuration et des entrées GPIO déclenchant la lecture.
## Fonctionnalités principales
- Lecture de fichiers MP3 depuis une carte SD
- Association de chaque GPIO surveillé à un fichier MP3
- Interface web pour configurer les associations, uploader de nouveaux fichiers MP3, et piloter la lecture (start/stop)
- Sauvegarde de la configuration dans l'EEPROM
## Branchements
##### Module SD
- CS : GPIO5
- SCK : GPIO18
- MOSI : GPIO23
- MISO : GPIO19
- VCC : 5V
##### Module audio PCM5102
- BCK : GPIO27
- LRCK : GPIO26
- DIN : GPIO25
Les soudures suivantes sont à réaliser pour assurer le bon fonctionnement du module.
![紫基盤のPCM5102基盤を試してみた | 原音再生](https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSWczqv2KlCRjKlbMeyDKmtGJXosmtNgfuOvQ&s) <img src="https://i.sstatic.net/yrT4Mqr0l.png" title="" alt="audio - How to make PCM5102 DAC work on Raspberry Pi ZeroW? - Raspberry Pi Stack Exchange" width="305">
Alimentation du module en 3,3V possible.
Possibilité d'utiliser le DAC intégré de l'ESP32 à la place de ce module.
##### GPIO pour les déclenchements
Par défaut : 12, 13, 14, 15, 16, 17, 21, 22
## Utilisation
1. Vérifier que le module utilise une carte SD formatée au préalable en FAT32.
2. Démarrez l'ESP32. Il crée un point d'accès WiFi (SSID et mot de passe définis dans le code). Par défaut SSID : ESP32_MP3 et mot de passe : 12345678.
3. Connectez-vous au WiFi et accédez à l'adresse 192.168.4.1 dans un navigateur.
4. Configurez les associations GPIO/MP3, uploadez de nouveaux fichiers MP3 (l'upload peut prendre du temps), et pilotez la lecture via l'interface web.
5. Les entrées GPIO déclenchent la lecture du MP3 associé lorsqu'elles sont connectées à la masse.
## Dépendances
- ESP32 Arduino Core
- Bibliothèques : SD, SPI, WiFi, EEPROM, WebServer, Audio (selon le matériel)
## Schéma matériel
- ESP32 (modèle Wrover obligatoire ou équivalent avec PSRAM)
- Module PCM5102 (I2S)
- Carte SD
- Entrées sur GPIO (boutons, capteurs, etc.)

17
platformio.ini

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =
ESP32-audioI2S

278
src/main.cpp

@ -0,0 +1,278 @@ @@ -0,0 +1,278 @@
void handleStart();
void handleStop();
void handleFileUpload();
#include <FS.h>
#include <SD.h>
#include <SPI.h>
#include <WiFi.h>
#include <EEPROM.h>
#include <WebServer.h>
#include <Audio.h>
Audio audio;
#define SSID "ESP32_MP3"
#define PASSWORD "12345678"
#define SD_CS 5
#define EEPROM_SIZE 512
#define MAX_MP3_FILES 32
#define MAX_FILENAME_LEN 64
#define NUM_GPIO 8
#define LED_BUILTIN 2
// GPIOs à surveiller
uint8_t gpio_pins[NUM_GPIO] = {12, 13, 14, 15, 16, 17, 21, 22};
String mp3_files[MAX_MP3_FILES];
int mp3_file_count = 0;
int gpio_to_mp3[NUM_GPIO];
File uploadFile;
String uploadFileName;
bool uploadFileExists = false;
volatile bool gpio_triggered[NUM_GPIO] = {false};
WebServer server(80);
// Scanne la carte SD et remplit la liste des fichiers MP3 disponibles
void scan_mp3_files() {
mp3_file_count = 0;
File root = SD.open("/");
while (true) {
File entry = root.openNextFile();
if (!entry) break;
if (!entry.isDirectory()) {
String fname = entry.name();
if (fname.endsWith(".mp3")) {
if (mp3_file_count < MAX_MP3_FILES) {
mp3_files[mp3_file_count++] = fname;
}
}
}
entry.close();
}
}
// Sauvegarde l'association GPIO <-> index MP3 dans l'EEPROM
void save_gpio_mp3_map() {
for (int i = 0; i < NUM_GPIO; i++) {
EEPROM.write(i, gpio_to_mp3[i]);
}
EEPROM.commit();
}
// Charge l'association GPIO <-> index MP3 depuis l'EEPROM
void load_gpio_mp3_map() {
for (int i = 0; i < NUM_GPIO; i++) {
gpio_to_mp3[i] = EEPROM.read(i);
Serial.println("GPIO " + String(gpio_pins[i]) + " -> MP3 index " + String(gpio_to_mp3[i]));
if (gpio_to_mp3[i] >= mp3_file_count) gpio_to_mp3[i] = 0;
}
}
// Génère et envoie la page web principale (configuration, upload, actions)
void handleRoot() {
String html = "<html><title>ESP32 MP3 trigger</title><meta charset='utf-8'><body><h2>ESP32 MP3 trigger</h2>";
// Affichage d'un message si présent dans l'URL
if (server.hasArg("msg")) {
html += "<div style='color:blue;font-weight:bold'>" + server.arg("msg") + "</div>";
}
html += "<form method='POST' action='/set'>";
html += "<table border='1'><tr><th>GPIO</th><th>Fichier MP3</th><th>Action</th></tr>";
for (int i = 0; i < NUM_GPIO; i++) {
html += "<tr><td>" + String(gpio_pins[i]) + "</td><td><select name='mp3_" + String(i) + "'>";
for (int j = 0; j < mp3_file_count; j++) {
html += "<option value='" + String(j) + "'";
if (gpio_to_mp3[i] == j) html += " selected";
html += ">" + mp3_files[j] + "</option>";
}
html += "</select></td>";
// Bouton Start pour chaque ligne
html += "<td><form method='POST' action='/start' style='display:inline'><input type='hidden' name='gpio' value='" + String(i) + "'><input type='submit' value='Start'></form></td>";
html += "</tr>";
}
html += "</table><input type='submit' value='Enregistrer'></form>";
// Bouton Stop global
html += "<form method='POST' action='/stop' style='margin-top:10px;display:inline'><input type='submit' value='Stop'></form>";
// Ajout du formulaire d'upload
html += "<h3>Uploader un fichier MP3 sur la carte SD</h3>";
html += "<form method='POST' action='/upload' enctype='multipart/form-data'>";
html += "<input type='file' name='file' accept='.mp3'>";
html += "<input type='submit' value='Uploader'>";
html += "</form>";
html += "</body></html>";
server.send(200, "text/html", html);
}
// Démarre la lecture d'un fichier MP3 depuis la carte SD
void play_mp3(const String &filename) {
audio.stopSong();
String path = "/" + filename;
audio.connecttoFS(SD, path.c_str());
}
// Handler HTTP POST /start : force la lecture du MP3 associé à un GPIO
void handleStart() {
if (!server.hasArg("gpio")) {
server.send(400, "text/plain", "GPIO non spécifié");
return;
}
int idx = server.arg("gpio").toInt();
if (idx < 0 || idx >= NUM_GPIO) {
server.send(400, "text/plain", "Index GPIO invalide");
return;
}
audio.stopSong();
play_mp3(mp3_files[gpio_to_mp3[idx]]);
server.sendHeader("Location", "/", true);
server.send(303, "text/plain", "Lecture forcée");
}
// Handler HTTP POST /stop : arrête la lecture audio en cours
void handleStop() {
audio.stopSong();
server.sendHeader("Location", "/", true);
server.send(303, "text/plain", "Lecture stoppée");
}
// Handler d'événement d'upload multipart : gère l'enregistrement du fichier MP3 uploadé
void handleFileUpload() {
HTTPUpload& upload = server.upload();
String msg;
if (upload.status == UPLOAD_FILE_START) {
uploadFileName = "/" + upload.filename;
if (!upload.filename.endsWith(".mp3")) {
msg = "Extension refusée : " + upload.filename;
Serial.println(msg);
uploadFileExists = true;
server.sendHeader("Location", "/?msg=" + msg, true);
server.send(303);
return;
}
if (SD.exists(uploadFileName)) {
msg = "Fichier déjà existant : " + uploadFileName;
Serial.println(msg);
uploadFileExists = true;
server.sendHeader("Location", "/?msg=" + msg, true);
server.send(303);
return;
}
uploadFile = SD.open(uploadFileName, FILE_WRITE);
uploadFileExists = false;
if (!uploadFile) {
msg = "Impossible d'ouvrir le fichier sur la SD";
Serial.println(msg);
uploadFileExists = true;
server.sendHeader("Location", "/?msg=" + msg, true);
server.send(303);
return;
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
if (!uploadFileExists && uploadFile) {
uploadFile.write(upload.buf, upload.currentSize);
}
} else if (upload.status == UPLOAD_FILE_END) {
if (!uploadFileExists && uploadFile) {
uploadFile.close();
scan_mp3_files();
msg = "Upload terminé : " + uploadFileName;
Serial.println(msg);
server.sendHeader("Location", "/?msg=" + msg, true);
server.send(303);
}
uploadFileExists = false;
} else if (upload.status == UPLOAD_FILE_ABORTED) {
if (uploadFile) uploadFile.close();
msg = "Upload annulé";
Serial.println(msg);
uploadFileExists = false;
server.sendHeader("Location", "/?msg=" + msg, true);
server.send(303);
}
}
// Handler HTTP POST /set : enregistre la configuration GPIO/MP3 depuis le formulaire web
void handleSet() {
for (int i = 0; i < NUM_GPIO; i++) {
if (server.hasArg("mp3_" + String(i))) {
gpio_to_mp3[i] = server.arg("mp3_" + String(i)).toInt();
}
}
save_gpio_mp3_map();
server.sendHeader("Location", "/", true);
server.send(302, "text/plain", "Updated");
}
// Routine d'interruption pour détecter les changements d'état sur les GPIO surveillés
void IRAM_ATTR gpio_isr() {
for (int i = 0; i < NUM_GPIO; i++) {
if (digitalRead(gpio_pins[i]) == LOW) {
gpio_triggered[i] = true;
}
}
}
// Initialisation du système, du WiFi, de la carte SD, des GPIO, du serveur web
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW); // éteint la LED intégrée
// Mode I2S PCM5102 : BCK=27, LRCK=26, DIN=25 (adapter si besoin)
audio.setPinout(27, 26, 25, -1, false);
audio.setVolume(21); // Volume max
Serial.begin(115200);
EEPROM.begin(EEPROM_SIZE);
WiFi.softAP(SSID, PASSWORD);
Serial.println("AP démarré. Connectez-vous à: " + String(SSID));
if (!SD.begin(SD_CS)) {
Serial.println("SD Card Mount Failed");
return;
}
scan_mp3_files();
load_gpio_mp3_map();
for (int i = 0; i < NUM_GPIO; i++) {
pinMode(gpio_pins[i], INPUT_PULLUP);
attachInterrupt(gpio_pins[i], gpio_isr, FALLING);
gpio_triggered[i] = false;
}
server.on("/", handleRoot);
server.on("/set", HTTP_POST, handleSet);
server.on("/start", HTTP_POST, handleStart);
server.on("/stop", HTTP_POST, handleStop);
server.on("/upload", HTTP_POST, [](){ server.send(200); }, handleFileUpload);
server.begin();
Serial.println("Serveur web démarré sur 192.168.4.1");
}
// Boucle principale : gestion web, lecture audio, détection et traitement des entrées GPIO
void loop() {
server.handleClient();
audio.loop();
static bool led_on = false;
for (int i = 0; i < NUM_GPIO; i++) {
if (gpio_triggered[i]) {
gpio_triggered[i] = false;
// Vérifier que la broche est toujours LOW (anti-rebond)
if (digitalRead(gpio_pins[i]) == LOW) {
Serial.println("GPIO " + String(gpio_pins[i]) + " déclenchée");
if (!audio.isRunning()) { // Ne lance la lecture que si aucune lecture n'est en cours
Serial.println("Lecture de " + mp3_files[gpio_to_mp3[i]]);
digitalWrite(LED_BUILTIN, HIGH); // allume la LED intégrée
led_on = true;
play_mp3(mp3_files[gpio_to_mp3[i]]);
delay(500); // anti-rebond
}
}
}
}
// Éteindre la LED quand la musique est terminée
if (led_on && !audio.isRunning()) {
digitalWrite(LED_BUILTIN, LOW);
led_on = false;
}
}
Loading…
Cancel
Save