viernes, 30 de agosto de 2013

Construcción de una caja musical con arduino III

En este artículo vamos a añadir un potenciómetro a nuestro prototipo para ajustar el volumen del decodificador MP3. Un potenciómetro es un resistor variable que normalmente posee algún tipo de control que nos permite ajustar su resistencia y por lo tanto el voltaje que leeremos por unos de los pins analógicos del microcontrolador. En la foto vemos dos tipos de potenciómetros. En uno el ajuste de la resistencia se hace mediante una rueda que se puede girar fácilmente los dedos. En el otro se hace mediante un pequeño tornillo. Obviamente para nuestra caja usaremos el primero, ya que no queremos que el usuario tenga que ajustar el volumen con un destornillador ;-)



El potenciómetro tiene tres pines, dos de ellos van a corriente y tierra, y el tercero va conectado a uno de los pines analógicos del microcontrolador. El esquema de conexión es el siguiente


Y aquí está montado en la placa de prototipo


El sketch

El código fuente modificado para que al girar la rueda del potenciómetro se ajuste el volumen es el siguiente:

#include <SPI.h>
#include <Adafruit_VS1053.h>
#include <SD.h>

#define MP3_XCS 6   // CS Registros
#define MP3_XDCS 7  // CS Datos (BSYNC)
#define MP3_DREQ 2  // Interrupción chip disponible
#define MP3_RESET 5 // Reset activo bajo
#define VOLUME_PIN A1
#define MIN_VOLUME 70


bool isSdOpened=false;
File root;
File entry;
long fileCount;
unsigned long totalBytes;
int currentVolume=MIN_VOLUME-map(analogRead(VOLUME_PIN),0,1023,0,MIN_VOLUME);
int oldVolume=currentVolume;

Adafruit_VS1053_FilePlayer musicPlayer = Adafruit_VS1053_FilePlayer(MP3_RESET, MP3_XCS, MP3_XDCS, MP3_DREQ, 10);


void setup(){
  pinMode(10,OUTPUT);
  Serial.begin(115200);

  musicPlayer.begin();

  while(!isSdOpened){
    isSdOpened=SD.begin(10);

    Serial.print("SD.begin ");
    Serial.println(isSdOpened);
  }

   musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT);
   musicPlayer.setVolume(currentVolume,currentVolume);

  root=SD.open("/");
  root.rewindDirectory();
  fileCount=0;
  entry=root.openNextFile();
  while(entry){
    fileCount++;
    Serial.print(entry.name());
    Serial.print("\t");
    if(entry.isDirectory()){
      Serial.println("Directorio");
    }
    else{
      Serial.println(entry.size(), DEC);
    }
    entry.close();
    entry=root.openNextFile();
  }
  root.rewindDirectory();
  Serial.print("Numero de ficheros: ");
  Serial.println(fileCount);
  randomSeed(analogRead(0));

  Serial.print("Volumen: ");
  Serial.println(currentVolume);

}


void loop(){

  if (! musicPlayer.playingMusic) {
    musicPlayer.softReset();
    delay(1000);
    root.rewindDirectory();

    int randNumber = random(1,fileCount+1);

    Serial.print("Numero aleatorio: ");
    Serial.println(randNumber);

    for(int i=0;i<randNumber;i++){
      entry=root.openNextFile();
      entry.close();
 
    }
    Serial.print("Fichero: ");
    Serial.println(entry.name());
    musicPlayer.startPlayingFile(entry.name());
  }
  else{
    currentVolume=MIN_VOLUME-map(analogRead(VOLUME_PIN),0,1023,0,MIN_VOLUME);
    if(currentVolume!=oldVolume){
      musicPlayer.setVolume(currentVolume,currentVolume);
      oldVolume=currentVolume;
      Serial.print("Volumen: ");
      Serial.println(currentVolume);
    }
  }
}

Comentarios al código fuente

En la instrucción currentVolume=MIN_VOLUME-map(analogRead(VOLUME_PIN),0,1023,0,MIN_VOLUME) leemos el valor de la entrada analógica y lo adaptamos al rango de valores que vamos a pasarle a la funcion setVolume(). La función analogRead(VOLUME_PIN) devuelve un valor entre 0 y 1023, pero a setVolume() le vamos a pasar un valor entre 0 y 70. La función map() devuelve un valor extrapolado de un rango a otro. En nuestro caso le pasamos un valor entre 0 y 1023 y devuelve su valor proporcional en el rango 0 a 70. Y por último restamos el valor devuelto por map() a 70 porque cuanto menor es el valor que le pasamos a setVolume() mayor es el volumen de la canción, es decir, el valor cero es el máximo volumen.
En la función loop(), cuando no se está reproduciendo la canción leemos el valor del potenciómetro y si ha cambiado ajustamos el valor con setVolume().

Con esto ya tenemos las funcionalidades básicas de nuestra caja. En la próxima entrada veremos cómo hacer que la música se reproduzca o pare cuando se abre o cierra la caja.

Artículos relacionados

 

sábado, 24 de agosto de 2013

Construcción de una caja musical con arduino II

Seguimos con la construcción de la caja musical que empezamos en Construcción de una caja musical con arduino I. Esta vez vamos a conectar el decodificador MP3 para que lea el fichero de la tarjeta SD que hemos seleccionado aleatoriamente y lo reproduzca. Para ello vamos a usar un módulo decodificador de MP3 basado en el chip VS1003/VS1053.




El esquema es el siguiente:



Y aquí lo tenemos montado en la placa de prototipo:



Como hemos visto en el esquema, el lector de tarjetas y el decodificador MP3 comparten algunas conexiones. Esto es consecuencia del uso del protocolo SPI  para la comunicación del microcontrolador con ambos dispositivos. De esta forma necesitamos menos pines para conectar varios dispositivos. Básicamente la idea es que haya un dispositivo master (el microcontrolador) y varios dispositivos esclavos (el lector de tarjetas y el decodificador MP3). El controlador elige con qué dispositivo se comunica desactivando los demás y usando las vías de comunicación comunes a todas ellas. El decodificador MP3 usa además otros cuatro pines además de los de corriente (5V y GND). La entrada XCS conectada al pin digital 6 del microcontrolador es la de Chip Select, que ha de ponerse a LOW para seleccionar el dispositivo. La entrada XDCS conectada al pin digital 7 es la de Data Chip Select, que ha de ponerse a LOW para enviar datos. La entrada DREQ conectada al pin digital 2 es la de Data Request, que estará a HIGH cuando el buffer de datos está vacío y por lo tanto podemos enviarle más. Y por último la entrada XRST conectada al pin digital 5 es la de reset, para reiniciar el dispositivo. Como veremos en el sketch, todo esto no debe preocuparnos ya que vamos a usar una biblioteca que nos permite obviar todos los detalles del protocolo SPI.

El sketch

Vamos a modificar el sketch del primer artículo de la serie para enviar el fichero seleccionado de la tarjeta al decodificador MP3. Para ello vamos a usar la biblioteca Adafruit VS1053 que se puede descargar aquí, ya que no viene por defecto en el IDE de arduino. Para instalarla hay que copiar los dos ficheros, Adafruit_VS1053.h y Adafruit_VS1053.cpp en una carpeta que crearemos donde están el resto de bibliotecas de nuestro IDE. O bien los podemos importar desde cualquier otra carpeta con la opción Sketch/Importa biblioteca.../Añadir del propio IDE de arduino. El código fuente del sketch es el siguiente:

#include <SPI.h>
#include <Adafruit_VS1053.h>
#include <SD.h>

#define MP3_XCS 6   // CS Registros
#define MP3_XDCS 7  // CS Datos (BSYNC)
#define MP3_DREQ 2  // Interrupción chip disponible
#define MP3_RESET 5 // Reset activo bajo


bool isSdOpened=false;
File root;
File entry;
long fileCount;
unsigned long totalBytes;

Adafruit_VS1053_FilePlayer musicPlayer = Adafruit_VS1053_FilePlayer(MP3_RESET, MP3_XCS, MP3_XDCS, MP3_DREQ, 10);


void setup(){
  pinMode(10,OUTPUT);
  Serial.begin(115200);

  musicPlayer.begin();

  while(!isSdOpened){
    isSdOpened=SD.begin(10);

    Serial.print("SD.begin ");
    Serial.println(isSdOpened);
  }

  musicPlayer.setVolume(10,10);

   musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT);

  root=SD.open("/");
  root.rewindDirectory();
  fileCount=0;
  entry=root.openNextFile();
  while(entry){
    fileCount++;
    Serial.print(entry.name());
    Serial.print("\t");
    if(entry.isDirectory()){
      Serial.println("Directorio");
    }
    else{
      Serial.println(entry.size(), DEC);
    }
    entry.close();
    entry=root.openNextFile();
  }
  root.rewindDirectory();
  Serial.print("Numero de ficheros: ");
  Serial.println(fileCount);
  randomSeed(analogRead(0));

}


void loop(){

  if (! musicPlayer.playingMusic) {
    musicPlayer.softReset();
    delay(1000);
    root.rewindDirectory();

    int randNumber = random(1,fileCount+1);

    Serial.print("Numero aleatorio: ");
    Serial.println(randNumber);

    for(int i=0;i<randNumber;i++){
      entry=root.openNextFile();
      entry.close();
 
    }
    Serial.print("Fichero: ");
    Serial.println(entry.name());
    musicPlayer.startPlayingFile(entry.name());
  }

}

Comentarios al código fuente

La primera línea que hemos añadido es #include <Adafruit_VS1053.h> para poder usar las funciones de acceso al decodificador MP3. En la instrucción Adafruit_VS1053_FilePlayer musicPlayer = Adafruit_VS1053_FilePlayer(MP3_RESET, MP3_XCS, MP3_XDCS, MP3_DREQ, 10) estamos inicializando el decodificador MP3 con los valores de los pines a los que lo hemos conectado. Y finalmente musicPlayer.startPlayingFile(entry.name()) reproduce el fichero. Hemos añadido el if (! musicPlayer.playingMusic) porque la función de reproducción devuelve el control al programa inmediatamente, lo cual es importante si queremos hacer más cosas mientras se reproduce la canción como es nuestro caso. Pero esto ya lo veremos en la tercera entrega donde conectaremos un potenciómetro para controlar el volumen al que se reproducirá la canción.

Artículos relacionados

miércoles, 14 de agosto de 2013

Construcción de una caja musical con arduino I

Empiezo el blog con una serie de artículos en los que construiré una caja musical que al abrirla reproduzca un fichero mp3 aleatorio almacenado en una tarjeta SD. Para ello añadiremos la circuitería necesaria a una caja de madera.
Vamos a empezar conectando un módulo lector de tarjetas SD a un microcontrolador Arduino uno. Nuestro módulo lector de tarjetas es un YL-30.



La conexión de los pines al microcontrolador es el siguiente:

Pin CS (Chip Select) al pin digital 10
Pin MOSI (Master Out Slave In) al pin digital 11
Pin MISO (Master In Slave Out) al pin digital 12
Pin SCK (System Clock) al pin digital 13
Pin +5V al pin 5V
Pin GND al pin GND


Es importante no cambiar la conexión de los pines ya que en el sketch vamos a usar la biblioteca SD de arduino, que a su vez usa la SPI,para acceder al sistema de ficheros de la tarjeta. Esta biblioteca usa estos pines para comunicarse con el módulo YL-30.

Y aquí lo tenemos montado en la placa de prototipo



El sketch

Una vez montado nuestro prototipo tenemos que crear un sketch que generará un número aleatorio y seleccionará el fichero de la tarjeta que corresponda al número generado. Para ello usaremos el entorno de programación de arduino.

#include <SD.h>

bool isSdOpened;
File root;
File entry;
long fileCount;


void setup(){
  pinMode(10,OUTPUT);
  isSdOpened=SD.begin(10);
  Serial.begin(9600);
  Serial.print("SD.begin ");
  Serial.println(isSdOpened);
  root=SD.open("/");
  root.rewindDirectory();
  fileCount=0;
  entry=root.openNextFile();
  while(entry){
    fileCount++;
    Serial.print(entry.name());
    Serial.print("\t");
    if(entry.isDirectory()){
      Serial.println("Directorio");
    }
    else{
      Serial.println(entry.size(), DEC);
    }
    entry.close();
    entry=root.openNextFile();
  }
  root.rewindDirectory();
  Serial.print("Numero de ficheros: ");
  Serial.println(fileCount);
  randomSeed(analogRead(0));

}

void loop(){
  root.rewindDirectory();

  int randNumber = random(1,fileCount+1);

  for(int i=0;i<randNumber;i++){
    entry=root.openNextFile();
    entry.close();
 
  }
  Serial.print("Numero aleatorio: ");
  Serial.println(randNumber);
  Serial.print("Fichero: ");
  Serial.println(entry.name());
  delay(10000);
}

Más información sobre el lenguaje de programación de arduino aquí. Está basado en C y básicamente lo que hace es ejecutar una vez la función setup y repetir indefinidamente la función loop.

Comentarios al código fuente

La primera instrucción de la función setup pinMode(10,OUTPUT)  pone el pin al que hemos conectado el pin CS del módulo SD en modo salida. Sin esta instrucción las demás funciones de acceso a los ficheros de la tarjeta no funcionarán.
La siguiente instrucción isSdOpened=SD.begin(10) inicializa el módulo lector para que podamos usarlo. Por defecto usa el pin 10 del microcontrolador, así que si lo hemos montado tal como indica el esquema la podemos pasar sin parámetros.
Siguiendo con la función setup, una vez inicializado el módulo abrimos el directorio raiz con la instrucción root=SD.open("/"). La siguiente instrucción root.rewindDirectory() lo que hace es volver al primer fichero del directorio y en teoría no es necesaria cuando acabas de abrir un directorio, pero parece ser que hay un bug que provoca errores si no se rebobina el directorio al abrirlo. Una vez abierto el directorio, lo recorremos en el bucle while para contar el número de ficheros y poder sacar así el número aleatorio dentro del rango correcto.
Un detalle muy importante a tener en cuenta es que los experimentos se hacen con gaseosa, es decir, no uses la tarjeta SD de tu cámara cuyas fotos aún no has pasado al ordenador para hacer las pruebas porque lo puedes lamentar amargamente. Y lo digo por las instrucciones entry=root.openNextFile() y entry.close(), que hacen lo que indica su nombre, abrir y cerrar ficheros. El peligro, que he tenido ocasión de comprobar, es que una operación de apertura sin su correspondiente cierre puede corromper el fichero.
En la instrucción randomSeed(analogRead(0)) inicializa el generador aleatorio de números para que la función random() no genere siempre la misma secuencia de números aleatorios. El uso de la función analogRead(0) como parámetro de randomSeed() es porque necesitamos inicializar el generador de números aleatorios con un número diferente cada vez. En un ordenador lo haríamos pasando como parámetro a randomSeed() la hora del sistema, pero nuestro microcontrolador no tiene reloj así que lo que hacemos es leer de un pin que no está conectado a nada cuyo valor es imprevisible. El problema es que la función analogRead() devuelve un valor entre 0 y 1023, con lo cual es bastante probable que con el uso se repitan secuencias de números, pero como tampoco vamos a generar claves de cifrado no creo que suponga mucho inconveniente.
En la función loop() lo que hacemos es generar un número aleatorio entre 1 y el número de ficheros con int randNumber = random(1,fileCount+1) y accedemos a él con el bucle for.
La salida del sketch por el puerto serie es la siguiente:

SD.begin 1
01-BOB~1.MP3 3368960
02-BOB~1.MP3 3897344
03-BOB~1.MP3 4413505
04-BOB~1.MP3 6598656
05-BOB~1.MP3 7415808
06-BOB~1.MP3 5928960
07-BOB~1.MP3 3074048
08-BOB~1.MP3 4009984
09-BOB~1.MP3 3710976
10-BOB~1.MP3 3268608
11-BOB~1.MP3 3045376
12-BOB~1.MP3 5967872
13-BOB~1.MP3 6866944
14-BOB~1.MP3 4851712
15-BOB~1.MP3 6514688
16-BOB~1.MP3 7540736
17-BOB~1.MP3 3913728
18-BOB~1.MP3 7213056
Numero de ficheros: 18
Numero aleatorio: 17
Fichero: 17-BOB~1.MP3
Numero aleatorio: 8
Fichero: 08-BOB~1.MP3
Numero aleatorio: 16
Fichero: 16-BOB~1.MP3
Numero aleatorio: 8
Fichero: 08-BOB~1.MP3
Numero aleatorio: 1
Fichero: 01-BOB~1.MP3
Numero aleatorio: 10
Fichero: 10-BOB~1.MP3
Numero aleatorio: 5
Fichero: 05-BOB~1.MP3
Numero aleatorio: 8
Fichero: 08-BOB~1.MP3
Numero aleatorio: 7
Fichero: 07-BOB~1.MP3
Numero aleatorio: 12
Fichero: 12-BOB~1.MP3
Numero aleatorio: 14
Fichero: 14-BOB~1.MP3

Los más viejos del lugar habrán reconocido la estructura 8+3 de los ficheros que se usaba en windows 95 y 98. La biblioteca SD de arduino sólo soporta los sistemas de ficheros FAT16 y FAT32, pero para el caso que nos ocupa es más que suficiente.

En la próxima entrega conectaremos el módulo reproductor de MP3 al prototipo que hemos creado y modificaremos el sketch para que reproduzca el fichero que hemos seleccionado aleatoriamente.