Boite à histoires / musiques

Bonjour,

J'ai pour projet de faire une boite à histoires type Lunii.
Mon projet comprendra :

  • une batterie
  • un écran tactile
  • un ou deux HPs (à voir)
  • une carte SD
  • un bouton ON/OFF physique
  • un bouton volume

Fonctions :

  • lire des fichiers MP3 soit simplement comme une musique, soit avec des choix interactifs (par exemple choisir un personnage, un lieu etc) [ cette seconde partie est optionnelle pour l'instant]
  • Afficher un menu stop/play
  • Afficher un menu de sélection (probablement pour commencer, sous la forme de deux boutons précédent/suivant + un bouton "Home") sur l'écran tactile
  • Avoir un menu paramètre pour désactiver si besoin certaines fonctionnalités si présentes sur la carte (comme le wifi/BT)
  • pouvoir éteindre l'appareil
  • pouvoir régler le volume.

La taille : ça sera un peu plus grand/large que la boite à histoire de lunii j'imagine l'écran devant faire dans les 7 pouces idéalement.
Concernant le boitier, j'ai un ami qui dispose d'un laser de découpe donc ça sera sur du bois.

Je ne connais pas bien le matériel arduino.

Ce que je recherche sur ce forum ce sont des conseils de matériels à choisir pour réaliser ce projet et surtout qui fonctionnent. Je pense qu'en tant que connaisseurs ou habitués, ça sera plus rapide de vous demander.

Merci d'avance !

et donc pourquoi postez vous dans la catégorie Projets finis ??

➜ déplacé dans le forum principal

merci de lire et appliquer les recommandations listées dans "Les bonnes pratiques du Forum Francophone”

J'avais plusieurs onglets ouverts, il a du prendre la session du dernier.

Attention au public visé. Je pense qu'il y a une recherche à faire avant d'aller plus loin.
Les écrans que l'on trouve sur les sites de ventes de produits DIY proposent généralement des écrans sans glace de protection donc c'est assez fragile. Et trouver une vitre gorilla glass en 7" ne doit pas être si simple sans compter qu'il faut s'assurer que l'écran tactile fonctionne une fois derrière la glace.

et 7" c'est grand pour un Arduino "de base". il faudra prendre un arduino autre que le UNO ou Nano et similaire (un truc 32 bits)

pour l'audio, on voit souvent l'usage du DF Player. la version "pro" a l'avantage de ne pas nécessiter de carte SD (sortie audio mono)

j'ai quelqu'un qui peut m'appliquer une protection qui fonctionnera sur l'écran , déjà testé avec un écran similaire.

ça peut être éventuellement du 4 pouces mais c'est la taille minimum.
DF Player oui j'avais déjà noté.

J'ai besoin d'un stockage interne, je ne veux pas que ce soit branché sur des HP via une sortie.

pour autre chose que l'audio ?
la carte SD du DF Player ne sert qu'au lecteur MP3, elle n'est pas accessible à ma connaissance pour lire ou écrire autre chose

Salut et bienvenue, @jeromemoc !
Super projet ! Mais je m'inquiète un peu de l'espace mémoire. Avec toutes les bibliothèques nécessaires - écran tactile, DFplayer, carte SD, et d'autres qui arriveront certainement avec l'avancement du projet, tu dépasseras vite la capacité de mémoire du carte Arduino, même une méga ! Personnellement, je serai passé sur un ESP32. Avec en plus la possibilité de connecté tout ça... :wink:
Il est apparemment possible d'afficher des images au format .BMP sur un écran avec un Arduino :

Bon courage en tout cas !

Bonne bidouille

Amitiés
Pandaroux007

Il faut qu'il y ait des mp3 et des images (des albums par exemple), éventuellement des xml / json si je fais des histoires interactives.

Bonjour,

Justement, je regardais aussi de ce côté là.

J'ai acquis récemment une carte ESP32-WROVER KIT V4.1 :
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-wrover-kit.html
Elle est très puissante, possède un écran intégré (non tactile, couleur), et également un support de carte SD :


Si ça peut t'aider :wink:
Comme çe n'est pas tactile, pas besoin de protection(s). Il suffit de 2 ou 3 gros bouton colorés style borne d'arcade, et le tour et joué !


image

Bonne journée

Amitiés
Pandaroux007

intéressant, je voulais éviter au maximum les boutons pour faciliter l'intégration dans le boitier.

Est ce que ce modèle serait sympa ? ESP32-S3-LCD-EV-Board - - — esp-dev-kits latest documentation

On dirait qu'il y a quasi tout sur la carte, il manquerait juste la partie bouton et la batterie ?

Pensez vous qu'il soit possible d'ajouter une batterie et des HP sur ce modèle ?
https://www.aliexpress.us/item/1005004952726089.html?gatewayAdapt=4itemAdapt

(à défaut des boutons on/off + volume que je peux programmer via l'interface)

Une autre possibilité : le Lilygo T-HMI

Ecran tactile, lecteur SD, ESP32.

Autre chose : les ESP32 sont programmables en (micro) Python. Ca permet peut-être de faire un code plus simple.

c'est pas mal mais écran résistif c'est bête d'avoir mis ça.
ça n'existe pas une carte comme ça, sans l'écran et je le rajoute moi même ?

Je suis passé sur une ESP32 S3 avec un module audio et un petit écran non tactile pour tester.
Si c'est concluant je prendrai bien un écran type e-ink comme les liseuses. Ca consomme quasi rien et ça fait son petit effet.

Bon j'ai pris un ESP32-S3 avec un écran ISP.
Mon code fonctionne mais j'ai besoin de votre aide pour le code.
Je sais que c'est un ESP32 mais le code est kif kif et j'aurai peut être une réponse ici.

Globalement j'ai une interface et ça lit les mp3 mais j'ai un problème de son qui craque quelque soit le type de haut parleur que j'utilise.
Peut être avez vous un conseil pour optimiser le code. Je sais qu'il y a du code inutilisé que je vais supprimer mais à part ça, est-ce possible d'améliorer pour libérer un peu de puissance?

#include "Arduino.h"
#include "Audio.h"
#include "FS.h"
#include "SPI.h"
#include "SD.h"
#include <Wire.h>
#include <vector>
#include <lvgl.h>
#include <ui.h>
#include <Arduino_GFX_Library.h>

#define TFT_BL 2

#define SD_SCK 12 // CLK
#define SD_MISO 13 // D0
#define SD_MOSI 11 // CMD
#define SD_CS 10 // CLK

#define I2S_DOUT 17
#define I2S_BCLK 0
#define I2S_LRC 18

#define GFX_BL DF_GFX_BL // default backlight pin, you may replace DF_GFX_BL to actual backlight pin

#if defined(DISPLAY_DEV_KIT)
Arduino_GFX *gfx = create_default_Arduino_GFX();
#else /* !defined(DISPLAY_DEV_KIT) */

Arduino_ESP32RGBPanel *bus = new Arduino_ESP32RGBPanel(
    GFX_NOT_DEFINED /* CS */, GFX_NOT_DEFINED /* SCK */, GFX_NOT_DEFINED /* SDA */,
    40 /* DE */, 41 /* VSYNC */, 39 /* HSYNC */, 42 /* PCLK */,
    45 /* R0 */, 48 /* R1 */, 47 /* R2 */, 21 /* R3 */, 14 /* R4 */,
    5 /* G0 */, 6 /* G1 */, 7 /* G2 */, 15 /* G3 */, 16 /* G4 */, 4 /* G5 */,
    8 /* B0 */, 3 /* B1 */, 46 /* B2 */, 9 /* B3 */, 1 /* B4 */
);
// option 1:
// ST7262 IPS LCD 800x480
Arduino_RPi_DPI_RGBPanel *gfx = new Arduino_RPi_DPI_RGBPanel(
    bus,
    800 /* width */, 0 /* hsync_polarity */, 8 /* hsync_front_porch */, 4 /* hsync_pulse_width */, 8 /* hsync_back_porch */,
    480 /* height */, 0 /* vsync_polarity */, 8 /* vsync_front_porch */, 4 /* vsync_pulse_width */, 8 /* vsync_back_porch */,
    1 /* pclk_active_neg */, 12000000 /* prefer_speed */, true /* auto_flush */);
#endif /* !defined(DISPLAY_DEV_KIT) */
/*******************************************************************************
 * End of Arduino_GFX setting
 ******************************************************************************/

/*******************************************************************************
 * Please config the touch panel in touch.h
 ******************************************************************************/
#include "touch.h"

Audio audio;
File musical;
std::vector<String> mp3Files;
int currentSongIndex = -1;
bool isPlaying = false;
bool playbackFinished = false; // Flag to detect playback completion

struct Music_info
{
    String name;
    int length;
    int runtime;
    int volume;
    int status;
    int mute_volume;
} music_info = {"", 0, 0, 0, 0, 0};

// Structure for storing file information
struct FileInfo
{
    String parentFolder;
    String currentFolder;
    String shortFileName;
    String fullPath;
};

std::vector<FileInfo> fileInfoList; // Store file information

// Structure for storing folder information
struct FolderInfo
{
    String parentFolder;
    String currentFolder;
    std::vector<FileInfo> files;
};

std::vector<FolderInfo> folderInfoList; // Store folder information

/* Change to your screen resolution */
static uint32_t screenWidth;
static uint32_t screenHeight;
static lv_disp_draw_buf_t draw_buf;
static lv_color_t *disp_draw_buf;
static lv_disp_drv_t disp_drv;

// Add a variable to keep track of the current folder
String currentFolder = "/m"; // Start with the root folder

void setup()
{
    // Hardware initialization
    pin_init();
    sd_init();

    // Init Display
    gfx->begin();

    gfx->fillScreen(RED);
    delay(500);
    gfx->fillScreen(GREEN);
    delay(500);
    gfx->fillScreen(BLUE);
    delay(500);
    gfx->fillScreen(BLACK);
    delay(500);
    lv_init();
    delay(10);
    touch_init();
    screenWidth = gfx->width();
    screenHeight = gfx->height();
#ifdef ESP32
    disp_draw_buf = (lv_color_t *)heap_caps_malloc(sizeof(lv_color_t) * screenWidth * screenHeight / 4, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
#else
    disp_draw_buf = (lv_color_t *)malloc(sizeof(lv_color_t) * screenWidth * screenHeight / 4);
#endif
    if (!disp_draw_buf)
    {
        Serial.println("LVGL disp_draw_buf allocate failed!");
    }
    else
    {
        lv_disp_draw_buf_init(&draw_buf, disp_draw_buf, NULL, screenWidth * screenHeight / 4);

        /* Initialize the display */
        lv_disp_drv_init(&disp_drv);
        /* Change the following line to your display resolution */
        disp_drv.hor_res = screenWidth;
        disp_drv.ver_res = screenHeight;
        disp_drv.flush_cb = my_disp_flush;
        disp_drv.draw_buf = &draw_buf;
        lv_disp_drv_register(&disp_drv);

        /* Initialize the (dummy) input device driver */
        static lv_indev_drv_t indev_drv;
        lv_indev_drv_init(&indev_drv);
        indev_drv.type = LV_INDEV_TYPE_POINTER;
        indev_drv.read_cb = my_touchpad_read;
        lv_indev_drv_register(&indev_drv);

        // Loading interface
        ui_init();

        lv_obj_add_event_cb(ui_btPrevious, playPreviousSong, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_btPlay, playNextSong, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_btPause, tooglePlayPause, LV_EVENT_PRESSED, NULL);        
        lv_obj_add_event_cb(ui_btNext, playNextSong, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_btStop, stopSong, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_btHome, ui_event_btHome, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_btUp, goToPreviousFolder, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_btDown, goToNextFolder, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_btHistory, ui_event_btHistory, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_btMusical, setMusicalMode, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_sliderVolume, changeVolume, LV_EVENT_VALUE_CHANGED, NULL);

        // Setup MP3
        audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
        audio.setVolume(7); // Adjust the initial volume as needed

    // Set the root directory to "/m"
    //setRootDirectory(currentFolder); // Set the root directory to the current folder
    //printFolderInfoList();
    // Add more debugging statements
    //Serial.print("After setRootDirectory: ");
    //Serial.println(fileInfoList.size());

    // Play the first MP3 file if available
    //playFirstMP3IfAvailable();

    // Add a delay to ensure serial output is visible before the loop starts
    //delay(1000); // You can adjust the delay time as needed
    }
}

void loop()
{
    // Your other main loop code can go here
    audio.loop();

    lv_event_t lv_event;
    checkAndPlayNextSong(&lv_event);

    // Check if playback is finished
    if (playbackFinished && isPlaying)
    {
        isPlaying = false;
        playbackFinished = false; // Reset the flag
    }

    lv_timer_handler(); /* let the GUI do its work */
    delay(2);
}

// Functions
/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
    uint32_t w = (area->x2 - area->x1 + 1);
    uint32_t h = (area->y2 - area->y1 + 1);

#if (LV_COLOR_16_SWAP != 0)
    gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
#else
    gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
#endif

    lv_disp_flush_ready(disp);
}

void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data)
{
    if (touch_has_signal())
    {
        if (touch_touched())
        {
            data->state = LV_INDEV_STATE_PR;

            /*Set the coordinates*/
            data->point.x = touch_last_x;
            data->point.y = touch_last_y;
        }
        else if (touch_released())
        {
            data->state = LV_INDEV_STATE_REL;
        }
    }
    else
    {
        data->state = LV_INDEV_STATE_REL;
    }
}

// Function to play an MP3 file
void setMusicalMode(lv_event_t *e)
{
    currentFolder = "/m"; // Set the current folder to /m
    setRootDirectory(currentFolder); // Set the root directory to /m
    playFirstMP3IfAvailable(); // Play the first MP3 file in the new folder if available
}

void playFirstMP3IfAvailable()
{
    if (!fileInfoList.empty())
    {
        currentSongIndex = 0; // Start with the first MP3 file
        const String &firstMP3 = fileInfoList[currentSongIndex].fullPath;
        currentFolder = fileInfoList[currentSongIndex].currentFolder; // Update currentFolder
        playMP3(firstMP3);
    }
}

void playMP3(const String &mp3FilePath)
{
    const char *mp3Path = mp3FilePath.c_str();
    audio.connecttoSD(mp3Path);
    updateTitleLabel();
    isPlaying = true;
    playbackFinished = false; // Reset the flag
}

void checkAndPlayNextSong(lv_event_t *event)
{
    if (isPlaying && !audio.isRunning())
    {
        // Current MP3 file has finished playing, play the next one if available
        currentSongIndex++;
        if (currentSongIndex < fileInfoList.size())
        {
            const String &nextMP3 = fileInfoList[currentSongIndex].fullPath;
            playMP3(nextMP3);
        }
        else
        {
            // All MP3 files in the folder have been played
            isPlaying = false;
        }
    }
}

void playNextSong(lv_event_t *event)
{
    if (currentSongIndex >= 0 && currentSongIndex < fileInfoList.size() - 1)
    {
        currentSongIndex++;
        const String &nextMP3 = fileInfoList[currentSongIndex].fullPath;
        playMP3(nextMP3);
    }
    else if (!fileInfoList.empty())
    {
        // At the last song, play the first song of the folder
        currentSongIndex = 0;
        const String &firstMP3 = fileInfoList[currentSongIndex].fullPath;
        playMP3(firstMP3);
    }
}

void playPreviousSong(lv_event_t *event)
{
    if (currentSongIndex > 0)
    {
        currentSongIndex--;
        const String &prevMP3 = fileInfoList[currentSongIndex].fullPath;
        playMP3(prevMP3);
    }
    else
    {
        // Already at the first song, play it again or handle as needed
        const String &firstMP3 = fileInfoList[0].fullPath;
        playMP3(firstMP3);
    }
}

void replayCurrentSong()
{
    if (currentSongIndex >= 0 && currentSongIndex < fileInfoList.size())
    {
        const String &currentMP3 = fileInfoList[currentSongIndex].fullPath;
        playMP3(currentMP3);
        isPlaying = true;
    }
    else
    {
        // No song is currently playing, so start playing the first one
        playFirstMP3IfAvailable();
    }
}

void stopSong(lv_event_t *event) {
  audio.stopSong();
  isPlaying = false;
}

void tooglePlayPause(lv_event_t *event) {
    audio.pauseResume();

    if (isPlaying) {
      isPlaying = false;
    } else {
      isPlaying = true;
    } 
}

void updateTitleLabel() {
    if (currentSongIndex >= 0 && currentSongIndex < fileInfoList.size()) {
        String album = currentFolder; // Use the current folder as the album name
        int lastSlashIndex = album.lastIndexOf('/');
        if (lastSlashIndex != -1) {
            album = album.substring(lastSlashIndex + 1);
        }
        String title = fileInfoList[currentSongIndex].shortFileName;
        lv_label_set_text(ui_labelAlbum, album.c_str());
        lv_label_set_text(ui_labelTitle, title.c_str());
    } else {
        lv_label_set_text(ui_labelAlbum, "");
        lv_label_set_text(ui_labelTitle, "");
    }
}

void changeVolume(lv_event_t *event) {
    lv_event_code_t code = lv_event_get_code(event);
    
    if (code == LV_EVENT_VALUE_CHANGED) {
        int volume = lv_slider_get_value(ui_sliderVolume);
        audio.setVolume(volume);

        char volumeText[8];
        snprintf(volumeText, sizeof(volumeText), "%d", volume);
        lv_label_set_text(ui_labelVolume, volumeText);
    }
}

void goToPreviousFolder(lv_event_t *event)
{
    if (folderInfoList.empty()) {
        return;
    }

    int currentFolderIndex = -1;
    for (int i = 0; i < folderInfoList.size(); i++) {
        if (folderInfoList[i].currentFolder == currentFolder) {
            currentFolderIndex = i;
            break;
        }
    }

    if (currentFolderIndex <= 0) {
        currentFolderIndex = folderInfoList.size() - 1;
    } else {
        currentFolderIndex--;
    }

    currentFolder = folderInfoList[currentFolderIndex].currentFolder;
    fileInfoList = folderInfoList[currentFolderIndex].files;
    currentSongIndex = -1;


    playFirstMP3IfAvailable();
}

void goToNextFolder(lv_event_t *event)
{
    if (folderInfoList.empty()) {
        return;
    }

    int currentFolderIndex = -1;
    for (int i = 0; i < folderInfoList.size(); i++) {
        if (folderInfoList[i].currentFolder == currentFolder) {
            currentFolderIndex = i;
            break;
        }
    }

    if (currentFolderIndex == -1) {
        return;
    }

    if (currentFolderIndex == folderInfoList.size() - 1) {
        currentFolderIndex = 0;
        currentFolder = folderInfoList[currentFolderIndex].currentFolder;
        fileInfoList = folderInfoList[currentFolderIndex].files;
    } else {
        currentFolderIndex++;
        currentFolder = folderInfoList[currentFolderIndex].currentFolder;
        fileInfoList = folderInfoList[currentFolderIndex].files;
    }

    updateTitleLabel();
    currentSongIndex = -1;

    playFirstMP3IfAvailable();

    int currentIndex = currentFolderIndex;
}

void printFolderInfoList() {
    Serial.println("Folder Information List:");

    for (int i = 0; i < folderInfoList.size(); i++) {
        Serial.println("Folder Index: " + String(i));
        Serial.println("Parent Folder: " + folderInfoList[i].parentFolder);
        Serial.println("Current Folder: " + folderInfoList[i].currentFolder);
    }
}

void setRootDirectory(const String &rootDir)
{
    folderInfoList.clear();
    fileInfoList.clear();

    File root = SD.open(rootDir);
    if (!root)
    {
        Serial.println("Failed to open root directory.");
        return;
    }

    collectFolderInfo(root, "", rootDir);
    fileInfoList = folderInfoList[0].files; // Set fileInfoList based on the root folder

    root.close();
}

void collectFolderInfo(fs::File dir, String parentFolder, String currentFolder)
{
    FolderInfo folderInfo;
    folderInfo.parentFolder = parentFolder;
    folderInfo.currentFolder = currentFolder;

    while (File entry = dir.openNextFile())
    {
        String entryName = entry.name();

        if (entry.isDirectory())
        {
            String subfolder = currentFolder + "/" + entryName;
            Serial.print("Entering folder: ");
            Serial.println(subfolder);
            collectFolderInfo(entry, currentFolder, subfolder);
        }
        else
        {
            String fileExtension = entryName.substring(entryName.lastIndexOf('.') + 1);
            // Check if the file has an MP3 extension (you can add other audio file extensions if needed)
            if (fileExtension.equalsIgnoreCase("mp3"))
            {
                FileInfo fileInfo;
                fileInfo.parentFolder = parentFolder;
                fileInfo.currentFolder = currentFolder;
                fileInfo.shortFileName = entryName;
                fileInfo.fullPath = currentFolder + "/" + entryName;
                fileInfoList.push_back(fileInfo);
            }
        }

        entry.close();
    }

    if (currentFolder != "/m" && currentFolder != "/h") {
        folderInfo.files = fileInfoList;
        folderInfoList.push_back(folderInfo);
    }
    fileInfoList.clear();

    Serial.print("File count in ");
    Serial.print(currentFolder);
    Serial.print(": ");
    Serial.println(folderInfo.files.size());
}

void pin_init()
{
    pinMode(TFT_BL, OUTPUT);
    digitalWrite(TFT_BL, HIGH);
}

void sd_init()
{
    pinMode(SD_CS, OUTPUT);
    digitalWrite(SD_CS, HIGH);
    SPI.begin(SD_SCK, SD_MISO, SD_MOSI);
    SPI.setFrequency(1000000);
    if (!SD.begin(SD_CS, SPI))
    {
        Serial.println("Card Mount Failed");
        while (1)
            ;
    }
    else
    {
        Serial.println("SD OK");
    }
}

Bonsoir,
Ça fait longtemps que je voulais me faire une petite boîte à musique. J’ai des « mp3 nature » et pour me détendre j’aime bien les écouter :wink:
J’ai donc testé le df playeur version pro cité par @J-M-L :

J’ai fait un petit programme simple que j’ai téléversé sur un ATmega328p équipé d’un quartz de 16 MHz. Il y a 5 boutons poussoirs, volume +, volume - … le tout relié au module et alimenté par une batterie lipo 3.7V - 3700 mAh. L’autonomie est excellente !
Ma boîte à musique en est à la version breadboard et je tenais à vous faire part du fait que le dfplayer pro offre une excellente solution pour un lecteur de mp3. Le rendu sonore sur deux HP 4 ohms 3 watts est de bonne facture :wink:
Bonne soirée.

1 Like

cool. merci pour le retour d'info