Contrôleur LED à commande vocale avec interface tactile utilisant ESP32S3 Box 3

Voice Activated LED Controller with Touch Interface Using ESP32S3 Box 3

Contrôleur LED à commande vocale

  • Implémentation de la détection des mots d’activation à l’aide de WakeNet
  • Construire la reconnaissance des commandes avec MultiNet
  • Création d’une interface graphique tactile à l’aide de la bibliothèque LVGL
  • Lecture de commentaires audio via l’interface I2S
  • Contrôler le matériel (LED) via GPIO
S. Non Composant Quantité
1 Carte de développement ESP32-S3-BOX-3 1
2 Module LED RVB 1
3 Fils de liaison Au besoin
4 Câble USB-C (pour la programmation et l’alimentation) 1
  • ESP-IDF v5.5.2 – Cadre de développement Espressif IoT
  • Python3.12+ – Requis pour les outils ESP-IDF

Contrôleur LED à commande vocale avec interface tactile utilisant le schéma de circuit ESP32S3 Box 3

Voici l’ESP32S3-Box-3 avec la LED attachée.

Configuration matérielle du boîtier ESP32 S3

git clone https://github.com/Circuit-Digest/Voice-Activated-LED-Controller-with-Touch-Interface-Using-ESP32S3-Box-3
. $HOME/esp/esp-idf/export.sh
cd /path_to_your_project_directory
idf.py menuconfig
idf.py build
idf.py flash monitor

Contrôleur LED à commande vocale avec interface tactile utilisant la structure de fichiers ESP32S3 Box 3

Microphone   ->  I2S   ->  AFE   ->  WakeNet   ->  Wake Detection Event

Flux de détection de mots de réveil

  • WAKENET_DETECTED – Mot de réveil détecté ; commencez à écouter les commandes.
  • WAKENET_CHANNEL_VERIFIED – Chaîne vérifiée ; prêt pour la reconnaissance des commandes.
  • audio_feed_task() – Lit l’audio depuis I2S et le transmet à AFE
  • audio_detect_task() – Traite la sortie AFE et détecte les mots de réveil
  • app_sr_start() – Initialise les modèles AFE et WakeNet
Mot de réveil Langue Clé de configuration
Salut ESP

Anglais

CONFIG_SR_WN_WN9_HIESP_MULTI=y
Salut Lexin Chinois
CONFIG_SR_WN_WN9_HILEXIN_MULTI=y
Alexa Anglais
CONFIG_SR_WN_WN9_ALEXA_MULTI=y
Xiao Ai Tong Xue Chinois
CONFIG_SR_WN_WN9_XIAOAITONGXUE_MULTI=y
Ni Hao Xiao Zhi Chinois
CONFIG_SR_WN_WN9_NIHAOXIAOZHI_MULTI=y
// In app_sr_set_language() function (line ~235)
char *wn_name = esp_srmodel_filter(models, ESP_WN_PREFIX,
   (SR_LANG_EN == g_sr_data->lang ? "hiesp" : "hilexin"));
char *wn_name = esp_srmodel_filter(models, ESP_WN_PREFIX,
   (SR_LANG_EN == g_sr_data->lang ? "alexa" : "hilexin"));
char *wn_name = esp_srmodel_filter(models, ESP_WN_PREFIX, "customword");
Wake Word Detected   ->  AFE Processing   ->  MultiNet   ->  Command ID   ->  Handler Action

Flux de reconnaissance des commandes

  • ESP_MN_STATE_DETECTING – Écoute d’une commande
  • ESP_MN_STATE_DETECTED – Commande reconnue
  • ESP_MN_STATE_TIMEOUT – Aucune commande détectée dans le délai d’attente
  • Définition de commande (app_sr.c) – définit le texte et le phonème de chaque commande
  • Structure de commande (app_sr.h) – structure contenant l’ID cmd, la langue, le texte et le phonème
  • Processus de reconnaissance (audio_detect_task) – AFE traite l’audio, MultiNet analyse les morceaux, renvoie l’ID de commande via la file d’attente au gestionnaire
// Command definition array  (app_sr.c)
static const sr_cmd_t g_default_cmd_info[] = {
   {SR_CMD_LIGHT_ON,  SR_LANG_EN, 0, "turn on light",  "TkN nN LiT", {NULL}},
   {SR_CMD_LIGHT_OFF, SR_LANG_EN, 0, "turn off light", "TkN eF LiT", {NULL}},
};
typedef enum {
   SR_CMD_LIGHT_ON,
   SR_CMD_LIGHT_OFF,
   SR_CMD_MY_NEW_CMD,    //  Add your command enum
   SR_CMD_MAX,
} sr_user_cmd_t;
static const sr_cmd_t g_default_cmd_info[] = {
   {SR_CMD_LIGHT_ON,     SR_LANG_EN, 0, "turn on light",  "TkN nN LiT", {NULL}},
   {SR_CMD_LIGHT_OFF,    SR_LANG_EN, 0, "turn off light", "TkN eF LiT", {NULL}},
   {SR_CMD_MY_NEW_CMD,   SR_LANG_EN, 2, "my new command", "mI nU kMnd", {NULL}},  //   Add
};
case SR_CMD_MY_NEW_CMD:        //   Add your handler
   ESP_LOGI(TAG, "My new command executed!");
   // Your action here
   break;
idf.py build flash monitor
// app_sr.h - enum
SR_CMD_FAN_ON,
SR_CMD_FAN_OFF,
SR_CMD_SET_BRIGHTNESS_HIGH,
SR_CMD_SET_BRIGHTNESS_LOW,
// app_sr.c - command definitions
{SR_CMD_FAN_ON,                  SR_LANG_EN, 2, "turn on fan",      "TkN nN fN",    {NULL}},
{SR_CMD_FAN_OFF,                 SR_LANG_EN, 3, "turn off fan",     "TkN eF fN",    {NULL}},
{SR_CMD_SET_BRIGHTNESS_HIGH,     SR_LANG_EN, 4, "brightness high",  "brItns hI",    {NULL}},
{SR_CMD_SET_BRIGHTNESS_LOW,      SR_LANG_EN, 5, "brightness low",   "brItns lO",    {NULL}},
sr_cmd_t new_cmd = {
   .cmd     = SR_CMD_MY_NEW_CMD,
   .lang    = SR_LANG_EN,
   .id      = 10,
   .str     = "my command",
   .phoneme = "mI kMnd"
};
app_sr_add_cmd(&new_cmd);
app_sr_update_cmds();   // Update MultiNet command list
  • app_sr_add_cmd() – Ajouter une nouvelle commande
  • app_sr_modify_cmd() – Modifier une commande existante
  • app_sr_remove_cmd() – Supprime une commande
  • app_sr_remove_all_cmd() – Efface toutes les commandes
  • app_sr_update_cmds() – Mettre à jour MultiNet avec la liste de commandes actuelle
  • Pilote d’affichage – Contrôleur LCD ILI9341 (320×240), interface SPI, format couleur RGB565, rendu accéléré matériellement.
  • Pilote tactile – Contrôleur tactile capacitif GT911 via I2C, avec support multi-touch (une seule touche utilisée dans ce projet).
  • Intégration LVGL – LVGL s’exécute dans une tâche dédiée avec double buffering pour un rendu fluide. Les événements tactiles sont gérés via le pilote d’entrée LVGL.
bsp_display_cfg_t cfg = {
   .lvgl_port_cfg  = ESP_LVGL_PORT_INIT_CONFIG(),
   .buffer_size    = BSP_LCD_H_RES * CONFIG_BSP_LCD_DRAW_BUF_HEIGHT,
   .double_buffer  = 0,
   .flags          = { .buff_dma = true }
};
bsp_display_start_with_config(&cfg);
bsp_board_init();
#include "lvgl.h"
#include "bsp/esp-bsp.h"
bsp_display_lock(0);            // Lock for thread safety
lv_obj_t *scr  = lv_scr_act();  // Get current screen
// Create a button
lv_obj_t *btn  = lv_btn_create(scr);
lv_obj_set_size(btn, 100, 50);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
// Add label
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "Click Me");
// Add click callback
lv_obj_add_event_cb(btn, on_button_click, LV_EVENT_CLICKED, NULL);
bsp_display_unlock();
static void on_touch_event(lv_event_t *e)
{
   lv_event_code_t code = lv_event_get_code(e);
   lv_obj_t       *obj  = lv_event_get_target(e);
   switch (code) {
   case LV_EVENT_PRESSED:
       lv_obj_set_style_bg_color(obj, lv_color_hex(0x0000FF), 0);
       break;
   case LV_EVENT_RELEASED:
       lv_obj_set_style_bg_color(obj, lv_color_hex(0x00FF00), 0);
       break;
   case LV_EVENT_CLICKED:
       light_ctrl_toggle();   // Perform action
       break;
   default: break;
   }
}
  • LV_EVENT_CLICKED – Toucher relâché après avoir appuyé
  • LV_EVENT_PRESSED – Toucher enfoncé
  • LV_EVENT_RELEASED – Toucher relâché
  • LV_EVENT_LONG_PRESSED – Appui long détecté
lv_img_set_src(img_obj, "/spiffs/image.bin");
// Screen 1: Main
lv_obj_t *main_screen = lv_obj_create(NULL);
// ... add widgets ...
// Screen 2: Settings
lv_obj_t *settings_screen = lv_obj_create(NULL);
// ... add widgets ...
// Navigate
void goto_settings(lv_event_t *e) { lv_scr_load(settings_screen); }
void goto_main(lv_event_t *e)     { lv_scr_load(main_screen);     }
WAV File   ->  Memory Buffer   ->  I2S Write   ->  Codec   ->  Speaker
  • sr_echo_init() – Charge les fichiers WAV de SPIFFS vers la mémoire
  • sr_echo_play() – Lit un segment audio via I2S
  • bsp_i2s_write() – Écrit les données audio sur I2S (fonction BSP)
typedef enum {
   AUDIO_WAKE,   // Wake word detected tone
   AUDIO_OK,     // Command recognised tone
   AUDIO_END,    // Timeout / end tone
   AUDIO_MAX,
} audio_segment_t;
// Load WAV from SPIFFS  -> PSRAM
static esp_err_t load_wav_to_mem(audio_segment_t seg, const char *path)
{
   FILE *fp = fopen(path, "rb");
   if (!fp) return ESP_ERR_NOT_FOUND;
   fseek(fp, 0, SEEK_END);
   long sz = ftell(fp);
   fseek(fp, 0, SEEK_SET);
   s_audio[seg].buf = heap_caps_malloc(sz, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
   s_audio[seg].len = (size_t)sz;
   fread(s_audio[seg].buf, 1, sz, fp);
   fclose(fp);
   return ESP_OK;
}
typedef enum {
   AUDIO_WAKE,
   AUDIO_OK,
   AUDIO_END,
   AUDIO_CUSTOM_1,   //   Add your segment
   AUDIO_CUSTOM_2,
   AUDIO_MAX,
} audio_segment_t;
spiffs/
├── echo_en_wake.wav
├── echo_en_ok.wav
├── echo_en_end.wav
├── custom_sound_1.wav     Add here
└── custom_sound_2.wav
ESP_RETURN_ON_ERROR(
   load_wav_to_mem(AUDIO_CUSTOM_1, "/spiffs/custom_sound_1.wav"),
   TAG, "load custom1 wav failed");
sr_echo_play(AUDIO_CUSTOM_1);
idf.py build flash
Paramètre Valeur
Exemples de tarifs 8, 16, 22,05, 44,1, 48 kHz
Profondeur de bits 16 bits (recommandé)
Canaux Mono ou Stéréo
Format WAV PCM non compressé
# Convert to 16 kHz, 16-bit, mono WAV
ffmpeg -i input.mp3 -ar 16000 -acodec pcm_s16le -ac 1 output.wav
# Convert to 16 kHz, 16-bit, stereo WAV
ffmpeg -i input.mp3 -ar 16000 -acodec pcm_s16le -ac 2 output.wav
Fonction Description
bsp_codec_set_fs()
Définir la fréquence d’échantillonnage du codec, la profondeur de bits et le mode de canal
bsp_codec_volume_set()
Régler le niveau de volume (0-100)
bsp_codec_mute_set()
Couper ou réactiver le codec audio
bsp_i2s_write()
Écrire le tampon de données audio sur la sortie I2S
bsp_codec_dev_stop()
Arrêtez le périphérique codec
bsp_codec_dev_resume()
Reprendre le périphérique codec
  • 16 kHz, 16 bits, mono -> ~32 Ko par seconde
  • 16 kHz, 16 bits, stéréo -> ~64 Ko par seconde
  • 44,1 kHz, 16 bits, stéréo -> ~ 176 Ko par seconde
  • Utiliser PSRAM pour les tampons audio (MALLOC_CAP_SPIRAM)
  • Précharger les sons fréquemment utilisés en mémoire
  • Diffusez de longs fichiers audio à partir de SPIFFS en morceaux de 4 Ko
void play_long_audio_stream(const char *wav_path)
{
   FILE *fp = fopen(wav_path, "rb");
   if (!fp) return;
   fseek(fp, 44, SEEK_SET);     // Skip WAV header
   uint8_t chunk[4096];
   size_t  bytes_read;
   while ((bytes_read = fread(chunk, 1, sizeof(chunk), fp)) > 0) {
       size_t bytes_written = 0;
       bsp_i2s_write((char *)chunk, bytes_read, &bytes_written, portMAX_DELAY);
   }
   fclose(fp);
}

1. Ouvrez le fichier : main/app/app_led.c

2. Trouvez cette ligne (autour de la ligne 15) :

#define SINGLE_LED_GPIO  GPIO_NUM_40
#define SINGLE_LED_GPIO  GPIO_NUM_38
idf.py build flash monitor
cd /path/to/esp32-box3-voice-led-project
. $HOME/esp/esp-idf/export.sh
idf.py menuconfig
idf.py build
idf.py flash monitor

1. Prononcez le mot de réveil : « Salut ESP » (parlez clairement, à environ 1 mètre de l’appareil).

2. Attendez le retour audio – vous entendrez un son de confirmation.

3. Prononcez la commande : « Allumez la lumière » ou « Éteignez la lumière ».

4. Observez : la LED change d’état, l’écran se met à jour et le retour audio est joué.

  • Parlez plus fort et plus clairement, à 0,5-1 mètre de l’appareil.
  • Réduisez le bruit de fond.
  • Vérifiez le moniteur série pour détecter les erreurs d’initialisation AFE.
  • Vérifier la polarité des LED
  • Vérifiez la connexion GPIO 40.
  • Testez avec un multimètre : le GPIO doit lire 3,3 V lorsqu’il est allumé.
  • Assurez-vous que ESP-IDF v5.5.2 est correctement installé.
  • Courir . $HOME/esp/esp-idf/export.sh avant la construction.
  • Effectuez une reconstruction complète : idf.py fullclean && idf.py build.

L’écran tactile ne répond pas

  • Vérifiez le moniteur série pour les messages d’initialisation LVGL.

Aucun retour audio

  • Assurez-vous que les fichiers WAV se trouvent dans le répertoire spiffs/ avant de créer.
  • Vérifiez le volume du haut-parleur (un ajustement physique peut être nécessaire).
  • Vérifiez l’initialisation I2S dans les journaux série.

Retrouvez l’histoire de Raspberry Pi dans cette vidéo :

YouTube video

  • ESP32-S3 Carte de Développement Écran Tactile LCD 4,3 pouces, Écran RGB 800×480 65K avec étui, Prise en charge Wi-Fi 2,4 GHz et Bluetooth 5, Processeur LX7 32 bits double cœur
  • TUOPUONE ESP32-S3 Smart 86 Box Carte de développement intégrée 4" 480 × 480 pixels, interface tactile RVB 5 points