Banner publicitario de PCBWay
torres.electronico

ARDUINO: Control por VOZ y respuesta con mensajes de AUDIO (edit 2da Parte - 2025)

img-20220127-wa0011-jpeg.310472


El siguiente ejemplo, emplearemos un microcontrolador Arduino NANO, un VR3 (Voice Recognition) y un shield SD-Card, para desarrollar un simple control de luces (u otras cosas) por intermedio de nuestra VOZ, pero con un plus extra para hacerlo mas practico: "Cuando indicamos una acción, tenemos una respuesta de nuestro microcontrolador por medio de mensajes de audio "...
Para este proyecto, vamos a requerir descargar e instalar en nuestro IDE las librerias:
  • SoftwareSerial.h
  • VoiceRecognitionV3.h
  • SimpleSDAudio.h
La librería "VoiceRecognitionV3", es para controlar el shield de reconocimiento de voz, y la librería "SimpleSDAudio", sirve para poder reproducir los audios en formato "WAV" que están alojados en la tarjeta SD.
En la imagen anterior, podemos ver que en mi caso particular, como no tenia un micrófono común, implemente un micrófono inalámbrico, y como amplificador para escuchar los mensajes de audio, use un parlante bluetooth con entrada auxiliar...
Como no tengo una voz de locutor, los audios los genere con el traductor de google que permite descargarlos.

Estos audios, hay que editarlos con el siguiente editor on-line: Convert audio to WAV

En teoría, según la librería, tenía que convertir los audios en 8bits-mono-16000Khz... Al compilar, cuando reproducía el audio, se escuchaba acelerado.
Se me ocurrió convertirlos en wav de 8bits-mono-16000Khz y al compilar, al momento de reproducir audio, se escuchó perfecto!!!
El circuito para este proyecto, es el siguiente:

ctrlvoice_sd-png.310473


CSS:
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//Ejemplo: Ctrl VOZ con respuesta audible - prof.martintorres@educ.ar - ETI Patagonia
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
#include <SoftwareSerial.h>
#include "VoiceRecognitionV3.h"
#include <SimpleSDAudio.h>
/**
  Connection
  Arduino    VoiceRecognitionModule
   2   ------->     TX
   3   ------->     RX
*/
VR myVR(2, 3);   // 2:RX 3:TX, you can choose your favourite pins.

uint8_t records[7]; // save record
uint8_t buf[64];

int Ralarma = 5;
int Rcocina = 6;
int Rcomedor = 7;
int Rliving = 8;
//int Rdesactivar = 9;

#define encender   (0)
#define apagar     (1)
#define alarma     (2)
#define cocina     (3)
#define comedor    (4)
#define living     (5)
#define desactivar (6)

void printSignature(uint8_t *buf, int len)
{
  int i;
  for (i = 0; i < len; i++) {
    if (buf[i] > 0x19 && buf[i] < 0x7F) {
      Serial.write(buf[i]);
    }
    else {
//      Serial.print("[");
//      Serial.print(buf[i], HEX);
//      Serial.print("]");
    }
  }
}

void printVR(uint8_t *buf)
{
 // Serial.println("VR Index\tGroup\tRecordNum\tSignature");

 // Serial.print(buf[2], DEC);
 // Serial.print("\t\t");

  if (buf[0] == 0xFF) {
//    Serial.print("NONE");
  }
  else if (buf[0] & 0x80) {
 //   Serial.print("UG ");
 //   Serial.print(buf[0] & (~0x80), DEC);
  }
  else {
 //   Serial.print("SG ");
 //   Serial.print(buf[0], DEC);
  }
//  Serial.print("\t");

//  Serial.print(buf[1], DEC);
//  Serial.print("\t\t");
  if (buf[3] > 0) {
    printSignature(buf + 4, buf[3]);
  }
  else {
//    Serial.print("NONE");
  }
//  Serial.println("\r\n");
}

void setup(void) {
  /** initialize */
myVR.begin(9600);
 Serial.begin (115200);//Velocidad del puerto serial
  // SdPlay.setSDCSPin(10); // Habilitar SÓLO si su tarjeta SD no está en el pin número 4
 SdPlay.init(SSDA_MODE_HALFRATE | SSDA_MODE_MONO | SSDA_MODE_AUTOWORKER);

 Serial.println("-LISTO!-");


  pinMode(Ralarma, OUTPUT);
  pinMode(Rcocina, OUTPUT);
  pinMode(Rcomedor, OUTPUT);
  pinMode(Rliving, OUTPUT);
  //pinMode(Rdesactivar, OUTPUT);


////////////////////////////////////////////

  if (myVR.clear() == 0) {
    Serial.println("Comandos cargados:");
  } else {
 //   Serial.println("No se encuentra el modulo de reconocimiento de voz V3");
 //   Serial.println("Por favor verificar la conexion y haga reset del equipo");
    while (1);
  }

 if (myVR.load((uint8_t)encender) >= 0) {
  //  Serial.println("encender");

  }

   if (myVR.load((uint8_t)apagar) >= 0) {
  //  Serial.println("apagar");

  }
 
  if (myVR.load((uint8_t)alarma) >= 0) {
   // Serial.println("alarma");

  }

  if (myVR.load((uint8_t)cocina) >= 0) {
   // Serial.println("cocina");

  }

  if (myVR.load((uint8_t)comedor) >= 0) {
    //Serial.println("comedor");

  }

  if (myVR.load((uint8_t)living) >= 0) {
    //Serial.println("living");

  }

 if (myVR.load((uint8_t)desactivar) >= 0) {
    //Serial.println("desactivar");
 }
}

void loop()
{
  int ret;
  ret = myVR.recognize(buf, 50);
  if (ret > 0) {
    switch (buf[1]) {
      case alarma:
        if (digitalRead(Ralarma) == 0) {
          digitalWrite(Ralarma, HIGH);
          Serial.println("Alarma Encendida");
          SdPlay.setFile("alari.wav");//con setFile vamos a cargar el archivo que queremos reproducir
          SdPlay.play(); // play reproduciomos el archivo
          delay(1400);
          SdPlay.stop();
             }
        else {
          digitalWrite(Ralarma, LOW);
          Serial.println("Alarma Apagada");
          SdPlay.setFile("alaro.wav");//con setFile vamos a cargar el archivo que queremos reproducir
          SdPlay.play(); // play reproduciomos el archivo
          delay(1400);
          SdPlay.stop();
             }
        break;
       case cocina:
        if (digitalRead(Rcocina) == 0) {
          digitalWrite(Rcocina, HIGH);
          Serial.println("Iluminacion Cocina Encendida");
          SdPlay.setFile("lucesi.wav");//con setFile vamos a cargar el archivo que queremos reproducir
          SdPlay.play(); // play reproduciomos el archivo
          delay(1400);
          SdPlay.stop();
             }
        else {
          digitalWrite(Rcocina, LOW);
          Serial.println("Iluminacion Cocina Apagada");
          SdPlay.setFile("luceso.wav");//con setFile vamos a cargar el archivo que queremos reproducir
          SdPlay.play(); // play reproduciomos el archivo
          delay(1400);
          SdPlay.stop();
             }
break;
       case comedor:
        if (digitalRead(Rcomedor) == 0) {
          digitalWrite(Rcomedor, HIGH);
          Serial.println("Iluminacion Comedor Encendida");
          SdPlay.setFile("lucesi.wav");//con setFile vamos a cargar el archivo que queremos reproducir
          SdPlay.play(); // play reproduciomos el archivo
          delay(1400);
          SdPlay.stop();
             }
        else {
          digitalWrite(Rcomedor, LOW);
          Serial.println("Iluminacion Comedor Apagada");
          SdPlay.setFile("luceso.wav");//con setFile vamos a cargar el archivo que queremos reproducir
          SdPlay.play(); // play reproduciomos el archivo
          delay(1400);
          SdPlay.stop();
                 
         }
break;
       case living:
        if (digitalRead(Rliving) == 0) {
          digitalWrite(Rliving, HIGH);
          Serial.println("Iluminacion Living Encendida");
          SdPlay.setFile("lucesi.wav");//con setFile vamos a cargar el archivo que queremos reproducir
          SdPlay.play(); // play reproduciomos el archivo
          delay(1400);
          SdPlay.stop();
             }
        else {
          digitalWrite(Rliving, LOW);
          Serial.println("Iluminacion Living Apagada");
          SdPlay.setFile("luceso.wav");//con setFile vamos a cargar el archivo que queremos reproducir
          SdPlay.play(); // play reproduciomos el archivo
          delay(1400);
          SdPlay.stop();
            }
break;


         /**case desactivar:
        if (digitalRead(foco2) == 0) {
          digitalWrite(foco2, HIGH);
          Serial.println("Foco2 encendido ;)");
          tmrpcm.play("3.wav");
        }
        else {
          digitalWrite(foco2, LOW);
          Serial.println("Foco2 Apagado ;)");
        }

        break;

      default:
        Serial.println("Record function undefined");
        break;
    }
*/
    /** voice recognized */
    printVR(buf);
  }
}
}

Un video corto de como funciona el proyecto:


PD: Recuerden que el modulo de reconocimiento de voz requiere de un entrenamiento y grabado/programacion de los comandos... acá les comparto el instructivo que implemente yo para aprender como hacer este trabajo:


2da PARTE (Editado en 2025)
Bien, como sabrán, si bien podemos grabar y almacenar hasta 255 frases, una de las limitantes del VR3 es que solo podemos trabajar con un grupo de tan solo 7 frases precargadas al iniciar nuestro programa. Si bien es una limitante el no poder trabajar con todos los comandos de voz almacenados libremente, no es un impedimento poder acceder a ellos (los 255) cuando los necesitemos. Para ello, tenemos que recurrir a tan solo unas lineas de programacion extra para limpiar los comandos seleccionados al inciciar, y luego cargar los nuevos 7, 6 u la cantidad de comandos que quieran usar.
Si vieron el video con el procedimiento de como grabar nuestros mensajes de voz, creo que ya estamos a la altura para seguir explayando como sacarle mas jugo a este modulo.
Los espacios almacenados que implemente con SISTRAIN "n" fueron los siguientes:

Código:
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// GRABACIONES:
// 0 Hey Roberta
//1 alarma
//2 Cocina
//3 patio
//4 comedor
//5 living
//6 jardin
//7 temperatura
//8 riego
//9 encender
//10 apagar
//11 apagar todo
//12 tiempo
//13 5 minutos
//14 10 minutos
//15 20 minutos
//16 30 minutos
//17 panico
//18 basta
//19 luces
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

En el setup, en la seccion donde elegimos que espacios de memoria precargar, elegimos cuales de los siete espacios queremos usar en nuestra primera parte del programa, ejemplo:


Código:
myVR.load(uint8_t (0));   //Hey Roberta esta guardado en la posicion 0
 myVR.load(uint8_t (1));  //1 alarma
 myVR.load(uint8_t (7));  //7 temperatura
 myVR.load(uint8_t (8));  //8 riego
 myVR.load(uint8_t (11)); //11 apagar todo
 myVR.load(uint8_t (17)); //17 panico
 myVR.load(uint8_t (19)); //19 luces

Con estos comandos, inicio el programa (Como no me gustas Alexa, la llame Roberta) y a medida que el VR3 va reconociendo comandos, puedo ejecutar funciones donde puedo volver a cargar otros 7 espacios de mensajes y así sucesivamente para lograr no tener la limitante de tener un programa de tan solo 7 comandos de voz.... Les muestro un extracto de mi Proyecto: "ROBERTA":love:, que básicamente es un asistente offline para automatizar / domotizar algunas funciones de nuestros hogares...

C:
///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////
void loop()
    {
     // - Mapa de grabaciones -
     //0  Hey Roberta
     //1  alarma      -> 9 encender / 10 apagar   (SALIDA= Sirena)
     //7  temperatura -> LM35 + DFPLAYER mini
     //8  riego       -> 12 tiempo /13 5 minutos / 14 10 minutos / 15 20 minutos / 16 30 minutos / 9 encender /10 apagar (SALIDA= Riego)
     //17 panico      -> 18 basta   (SALIDA= Sirena)
     //19 luces       -> 2 Cocina -> 9 encender / 10 apagar  (SALIDA= Cocina)
     //               -> 3 patio  -> 9 encender / 10 apagar  (SALIDA= Patio)
     //               -> 4 comedor-> 9 encender / 10 apagar  (SALIDA= Comedor)
     //               -> 5 living -> 9 encender / 10 apagar  (SALIDA= Living)
     //               -> 6 jardin -> 9 encender / 10 apagar  (SALIDA= Jardin)
     //11 apagar todo   -> Ponemos en estado bajo todos los pines / funciones
  
     int ret;
     ret = myVR.recognize(buf, 50);
     if(ret>0)
       {
        if(buf[1] == 0)
          {
           Apagar_todo();
           digitalWrite(HEY_ROBERTA, HIGH);
           HEY_ROBERTA = true;
           Serial.println("Hey Roberta");
           digitalWrite(Te_Escucho, HIGH);
           Serial.println("Esperando orden");
           TE_ESCUCHO = true;
           myVR.clear();            //Primero debemos borrar los comandos anteriores
                                           // y luego cargar los comandos a implementar
           myVR.load(uint8_t (1));  //1 alarma
           myVR.load(uint8_t (7));  //7 temperatura
           myVR.load(uint8_t (8));  //8 riego
           myVR.load(uint8_t (11)); //11 apagar todo
           myVR.load(uint8_t (17)); //17 panico
           myVR.load(uint8_t (19)); //19 luces
          }
       if(HEY_ROBERTA)
         { 
         //El buffer inicial con los espacios de mensajes precargados es de 7 posiciones (0 a 6)
          if(buf[1] == 1)    // Si VR3 escucha "Alarma", envia el numero de espacio que reconocio ("1")
            {
             ...bla bla bla
             TE_ESCUCHO = true;
            }
 
      if(buf[1] == 7)      //Temperatura
        {
         decirTemperatura();
         delay(1000); // Antirebote básico   
        }
     
     if(buf[1] == 17)                                   //Reconoce la palabra y nos envia el valor del espacio almacenado
        {                            
        ....bla bla bla
        }
     if(buf[1] == 19)                                   //Reconoce la palabra y nos envia el valor del espacio almacenado
        {                            
        ....bla bla bla
       }
      if(buf[1] == 8){                              //Reconoce la palabra y nos envia el valor del espacio donde esta    
     //guardada. Precargamos 5 comandos para poder establecer el set de tiempo para programar el tiempo
    //de riego
      
        Serial.println("Que tiempo desea regar?");
        SET_TIEMPO_RIEGO = true;
        myVR.clear();
        myVR.load(uint8_t (12));                     //Seteamos 5minuto
        myVR.load(uint8_t (13));                     //Seteamos 10 minutos
        myVR.load(uint8_t (14));                     //Seteamos 15 minutos
        myVR.load(uint8_t (15));                     //Seteamos 20 minutos
        myVR.load(uint8_t (16));                     //Seteamos 25 minutos
        myVR.load(uint8_t (9));                      //encender
        myVR.load(uint8_t (10));                     //apagar

        if ((buf[1] == 12)
          {
            tiempo=5;                 //variable para el temporizador
            BanderaRIEGO=1;
            Regar();
          }
   
      if ((buf[1] == 13)
          {
            tiempo=10;                 //variable para el temporizador
            BanderaRIEGO=1;
            Regar();
          }
 
    }
//cuando ejecuta el salto a la funcion "Regar", como cargamos anteriormente los espacios
//12 tiempo /13 5 minutos / 14 10 minutos / 15 20 minutos / 16 30 minutos / 9 encender /10 apagar, ahora espera que le digamos el tiempo y de ahi saltara a otra funcion para indicarle que inicie (SALIDA= Riego)
void Regar()
      {
       Serial.println("Iniciar/Apagar Riego?");
    
 if ((buf[1] == 9)
          {
          // aca ponemos en alto el pin de salida del riego
           }
 if ((buf[1] == 10)
          {
          //aca ponemos en bajo el pin de salida del riego y precargamos los espacios del inicio
          }
     //la bandera es para que sepa donde esta y a donde volver
       }
    }


Bien, entiendo que simplifique bastante el programa :silbando: , pero me gustaria que salga de ustedes un poco de creatividad tambien :no: .... Si pongo todo el programa, como ya vi en anteriores ocasiones, copian y pegan en otro lugar sin mencionar quien fue el salamin con patas que los desarrollo :facepalm: ...
Bien, la primera parte estrella de esta nueva actualizacion ya la comente; Ahora vamos a la segunda parte estrella, que basicamente fue mejorar la manera en que contestaba con audios las ordenes impartidas... Migre todo a un pequeño modulo reproductor de mp3, el "dfPLAYER mini" y como veia que contestarme cosas basicas como "Luces encendidas / Luces apagadas" ya me parecía pre-arcaico, decidí darle mas funciones para sacarle jugo a la nueva adquisición: "Le di a Roberta la opción de poder decirme la temperatura con voz!!! "
Les muestro como y ustedes podran modificarlo a futuro para sus proyectos... Recordemos las siguientes lineas en el loop el programa anterior:

C:
if(buf[1] == 7)      //Temperatura
        {
         decirTemperatura();
         delay(1000); // Antirebote básico
        }

Bien, cuando reconoce la palabra, nos envia el numero de espacio donde esta almacenado y saltamos a la funcion "decirTemperatura();"
En esta funcion, lo que vamos hacer es leer el sensor LM35 de Temperatura, vamos a extraer esos valores (enteros y decimales) y luego vamos a reproducir los audios para que diga la temperatura...parece facil, peeerooooo o_O ...

C:
void decirTemperatura()
{
  int lectura_analogica = analogRead(tempPin);  // Leemos el valor del sensor
  float voltaje = (lectura_analogica * 5.0) / 1023.0;
  float temperatura_celsius = voltaje * 100.0;  // LM35: 10mV/°C → 100°C por volt


  // Separamos parte entera y decimal
  int tempEntero = (int)temperatura_celsius;
  int tempDecimal = (int)((temperatura_celsius - tempEntero) * 100);
  if (tempDecimal < 0) tempDecimal *= -1; // Asegurar positivo


  // Limitamos rango
  if (tempEntero < -1) tempEntero = -1;
  if (tempEntero > 50) tempEntero = 50;
  if (tempDecimal > 99) tempDecimal = 99;


  // --- Reproducción por DFPlayer ---
  // "Hacen"
  myDFPlayer.play(1);
  delay(1000);


  // Parte entera de la temperatura (-1 a 50)
  if (tempEntero == -1) {
    myDFPlayer.play(2); // menos un grado
  } else {
    myDFPlayer.play(3 + tempEntero); // 0003 = 0 grados, 0004 = 1 grado...
  }
  delay(1200);


  // Parte decimal (0 a 99)
  myDFPlayer.play(100 + tempDecimal);
  delay(1200);
}

El problema del DFPlayer Mini, es que numera los archivos en el orden en que están grabados, no por nombre alfabético.
Cuando tengamos la miniSD, es MUY importante copiarlos uno por uno en el orden correcto.
En mi caso use nombres de archivo tipo 0001.mp3, 0002.mp3, etc; y evite carpetas (los deje a todos en la raíz de la tarjeta). Yo se que es tedioso, mas cuando me tome el trabajo de usar un TTS para generar todos los audios por que tengo una voz de Roberta media ronca :ROFLMAO:
En fin; El orden de mensajes que estableci para esta parte es:

0001.mp3 → “Hacen” (Palabra inicial)

Las Temperaturas enteras del –1 al 50:

0002.mp3 → “menos un grado”
0003.mp3 → “cero grados”
0004.mp3 → “un grado”
0005.mp3 → “dos grados”
bla bla bla …
0053.mp3 → “cincuenta grados”

Con esto tenemos –1 hasta 50 cubierto.
El tema decimales del 0 al 99 lo resolvi de esta manera:

0100.mp3 → “con cero centésimas”
0101.mp3 → “con una centésima”
0102.mp3 → “con dos centésimas”
bla bla bla …
0199.mp3 → “con noventa y nueve centésimas”
No quiero ser reiterativo, pero ya se que es tediosa esta parte y por eso quiero enfatizar en algunas cosas... Al que le gusta el durazno, se banca la pelusa... Veamos mas detenidamente los llamados y luego un ejemplo de como esta misma parte la podemos usar para decir la hora u otro dato que quieran...

El entero se llama con:

C:
if (tempEntero == -1)
   {
    myDFPlayer.play(2);   // menos un grado
   }
   else
   {
   myDFPlayer.play(3 + tempEntero); // 0003 = cero grados, 0004 = 1 grado, ...
   }

El decimal se llama con:

C:
myDFPlayer.play(100 + tempDecimal); // 0100 = cero centésimas, ...

o sea, citemos un ejemplo par ser mas practicos:

Si el sensor marca 23.45 °C

Reproduce 0001.mp3 → “Hacen”
Reproduce 0026.mp3 → “veintitrés grados”
Reproduce 0145.mp3 → “con cuarenta y cinco centésimas”

Si el sensor marca –1.20 °C

Reproduce 0001.mp3 → “Hacen”
Reproduce 0002.mp3 → “menos un grado”
Reproduce 0120.mp3 → “con veinte centésimas”

Para el caso de implementar una RTC para decir la hora, podria ser:

C:
#include <Wire.h>
#include "RTClib.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"


RTC_DS1307 rtc;
SoftwareSerial mySerial(10, 11); // RX, TX para DFPlayer
DFRobotDFPlayerMini myDFPlayer;


#define BUTTON_PIN 2   //para este ejemplo usamos un pulsador para que diga la hora


void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP); // Pulsador a GND
  Serial.begin(9600);
  mySerial.begin(9600);


  if (!rtc.begin()) {
    Serial.println("No se encuentra RTC");
    while (1);
  }
  if (!rtc.isrunning()) {
    Serial.println("RTC detenido, ajustando fecha/hora...");
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }


  if (!myDFPlayer.begin(mySerial)) {
    Serial.println("Error DFPlayer Mini");
    while (1);
  }
  myDFPlayer.volume(25); // Volumen (0-30)
}


void loop() {
  if (digitalRead(BUTTON_PIN) == LOW) { // Pulsador presionado
    decirHora();
    delay(1000); // Antirebote básico
  }
}


void decirHora() {
  DateTime now = rtc.now();
  int hora = now.hour();
  int minuto = now.minute();


  // "Son las"
  myDFPlayer.play(1);
  delay(1200);


  // Hora
  myDFPlayer.play(2 + hora);  // porque 0002.mp3 es "cero horas"
  delay(1500);


  // "y"
  myDFPlayer.play(30);
  delay(800);


  // Minutos
  myDFPlayer.play(31 + minuto); // porque 0031.mp3 es "cero minutos"
  delay(1500);
}

Bien, estoy terminando/arrancando a dibujar la caja de los parlantes y la caja contenedora del circuito para imprimirlo en 3D. Cuando termine, se viene el VIDEO ;)


Lo que no entiendan, por favor armen un tópico en el foro para no tener que responder por privado. Su pregunta puede servir a otros miles que quizas tengan la misma duda ;)
  • IMG-20220127-WA0011.jpeg
    IMG-20220127-WA0011.jpeg
    322 KB · Visitas: 135
  • CtrlVOICE_SD.png
    CtrlVOICE_SD.png
    798.4 KB · Visitas: 124
  • Me encanta
Reactions: 426ivan y krlosss
Atrás
Arriba