lunes, 27 de enero de 2014

Arduino reproductor audio



Arduino reproductor de voz mediante tarjeta SD

wav - 8bits -11KHz

xsetaseta@gmail.com

El arduino da muchas satisfaciones, la documentación que tiene y sus librerías le hacen totalmente manejable para distintos montajes, es camaleónico. Creo que su secreto es la documentación de su microcontrolador y la facilidad de programación de su entorno. Su entorno de programación basado en java es  un  tanto simple, y quizas sea esa la base de su éxito.
En este montaje realizo un reproductor de voz. En una tarjeta SD almaceno ficheros wav en un formato de 8bits y con  una frecuencia de 11KHz. Mediante la consola serie escribo el nombre del fichero a reproducir, y el arduino lo reproduce si existe.
Una de las pocas cosas que no dispone el arduino es un conversor de D/A de serie, no conozco el motivo, los  micros PIC tampoco lo llevan, debe ser un  problema de fabricación para rebajar costes. Lo mas fácil para hacer un conversor D/A sería mediante una serie de resistencias, esto implica un circuito de 16 resistencia.

Como queremos hacer un circuito lo mas sencillo posible, utilizaremos la técnica de modulación por anchura de pulso PWM (Pulse-Width Modulation) .  Consiste como dice el hombre, modular el ancho del pulso para conseguir un voltage de salida dependiendo de la anchura del pulso. En la salida colocamos un condensador a modo de filtro para conseguir una salida analógica mas real.


Una vez explicada la teoría del conversor D/A por ancho de pulso, vamos a implementarlo mediante el microcontrolador ATMEGA328.
Existen varias librerías que realizan la reproducción de un fichero wav mediante el arduino como por ejemplo TMRpcm. Pero resultó que funcionaba en el arduino mega pero no en el  arduino diecimila, no se el motivo.
 Ante esta situación decidí realizar toda la programación, y de esta forma aprender un poco de PWM.

Para la generación de la frecuencia patrón del  PWM he utilizado el timer1 con salida al Pin 9 de arduino.
Se genera una frecuencia de 31KHz por medio del timer1. Viene muy bien explicado en la página web:
http://ravc00cs.blogspot.com.es/2012/07/pwm.html

Página 134 ATmega328
Se colocan los registros del timer1 con los siguientes valores:
TCCR1A = 0x81; // seleccion del contador. PWM, Phase Correct, 8-bit  
TCCR1B = 0x01; // prescaler = 1, clkI/O/1 (No prescaling)

TIMSK1=0;      //habilita cualquier interrupción.
Ahora ya tenemos una señal de 31KHz en la salida 9 del arduino que podemos modificar el ancho del pulso cambiando el valor del registro   OCR1A .

Para poder generar en tiempo real sonido a una tasa de muestras de 11KHz, debemos modificar el registro OCR1A 11000 veces por segundo. Esto lo realizamos generando una interrupción  11000 veces por segundo, esto lo conseguimos mediante el timer2 del ATMEGA328.
Página 158 ATmega328
Se colocan los registros del timer2 con los siguientes valores:
  TCCR2A = 0;// set entire TCCR2A register to 0
  TCCR2B = 0;// same for TCCR2B
  TCNT2  = 0;//initialize counter value to 0
  OCR2A = 182;// = (16*10^6) / (11000*8) - 1 (must be <256)  11khz 
  TCCR2A |= (1 << WGM21);
// turn on CTC mode 
  TCCR2B |= (1 << CS21); 
// Set CS21 bit for 8 prescaler    
  TIMSK2 |= (1 << OCIE2A);
// enable timer compare interrupt

En el vector de interrupción ISR(TIMER2_COMPA_vect) colocamos el programa que modifica el registro OCR1A, según el valor que nos proporciona en cada instante el fichero wav.

Como la lectura de la SD requiere un tiempo, y los datos para actualizar el registro OCR1A deben ser inmediatos, debemos crear un doble buffer donde almacenar los datos para que su disponibilidad sea inmediata.

//SETA43 21/01/2014
// xsetaseta@gmail.com
// reproductor de wav desde tarjeta SD
// wav 11kHz mono
// Arduino Diecimila

#include <SD.h>
File dataFile;

String inputString = "";  
char fileplay[20] ;
boolean stringComplete = false;  

byte valor;
byte bufer0[100];
byte bufer1[100];
int din=0;
int dout=0;
byte nf=0;
byte vacio=1;
byte fin=1;

void setup()
{
  
  Serial.begin(9600);  
  inputString.reserve(50);
  
  pinMode(9, OUTPUT);
 
 // pinMode(4, OUTPUT);    // si utilizamos la salida 4 como CS de la tarjeta SD
 // if(!SD.begin(4)){    //  
   pinMode(10, OUTPUT);    // si utilizamos la salida 4 como CS de la tarjeta SD
  if(!SD.begin(10))
  {
    Serial.println("Tarjeta erronea o no insertada");    // Texto informativo
    return;
  }
  else
  {
    Serial.println("Tarjeta OK");    // Texto informativo
  }
  
cli();//stop interrupts

TCCR1A = 0x81;
TCCR1B = 0x01;
OCR1A = 255;
TIMSK1=0;

//timer interrupts
//by Amanda Ghassaei
//set timer2 interrupt at 11kHz
  TCCR2A = 0;// set entire TCCR2A register to 0
  TCCR2B = 0;// same for TCCR2B
  TCNT2  = 0;//initialize counter value to 0
  OCR2A = 182;// = (16*10^6) / (11000*8) - 1 (must be <256)  11khz
  // turn on CTC mode
  TCCR2A |= (1 << WGM21);
  // Set CS21 bit for 8 prescaler
  TCCR2B |= (1 << CS21);      
  // enable timer compare interrupt
  TIMSK2 |= (1 << OCIE2A);
sei();//allow interrupts
}//end setup


ISR(TIMER2_COMPA_vect)
{
  //timer1 interrupt 11kHz toggles
  if(nf==0) valor=bufer1[dout];
  if(nf==1) valor=bufer0[dout];
  dout++;
  if(dout>99)
    {
     vacio=1;
     dout=0;
     if(nf==1)
       nf=0;
      else
       nf=1;       
    }  
  OCR1AH = 0;
  OCR1AL = valor;     
  //PORTC= valor;
}

void loop()
{
  
  if(vacio == 1 && fin == 0)
    {     
     if(nf==0) for(din=0;din<100;din++) bufer0[din] = dataFile.read();
     if(nf==1) for(din=0;din<100;din++) bufer1[din] = dataFile.read();
     vacio=0;
     if(!dataFile.available())
       {
         Serial.println("Fin de cancion");
         for(din=0;din<100;din++){ bufer0[din]=128;  bufer1[din]=128;} //vacia bufer
         fin=1;
       }
    }
    
  if (stringComplete)
    {
     inputString.toCharArray(fileplay,19);
     fileplay[inputString.length()-1]=0; //clear \n
     if(strcmp(fileplay, "stop")  == 0)
       {
         Serial.println("Stop cancion");
         for(din=0;din<100;din++){ bufer0[din]=128;  bufer1[din]=128;} //vacia bufer
         dataFile.close();
         fin=1;         
       }
       else
       {
        inputString.toCharArray(fileplay,19);
        fileplay[inputString.length()-1]=0;
        dataFile.close();
        dataFile = SD.open(fileplay,FILE_READ);
        if (dataFile)
          {
           Serial.print("OK ");
           for(din=0;din<1000;din++) dataFile.read(); //quita 1000 bytes del wav
           fin=0;
          }
          else
          {
           Serial.print("Error ");
          }
          Serial.println(fileplay);
       }      
    inputString = "";
    stringComplete = false;
   }    
}

void serialEvent()
{
  while (Serial.available())
   {
    char inChar = (char)Serial.read();
    inputString += inChar;
    if (inChar == '\n')   stringComplete = true;
   }
}

El código fuente no está optimizado ni nada, es un ejercicio de programación rápida que funciona.
El programa no hace ninguna comprobación de tipo de fichero wav, ni de tasa de muestreo  ni de resolución de bits, solo toma el fichero de la SD, quita los 1000 primeros bytes y los vuelca en el conversor D/A a una tasa de muestreo de 11KHz.

En la salida, pin 9 del arduino,  sale la señal de audio que se filtra mediante un condensador.

La tarjeta SD funciona a 3,3V, y como el arduino funciona a 5V debemos hacer adaptar la entradas y salidas. Para no complicar el circuito y no gastarnos dinero en un adaptador, lo realizo con resistencias.
Es fácil y normalmente funciona bien.


Esquema eléctrico.


Circuito eléctrico montado en un adaptador de SD a MicroSD.


Diagrama del filtro de audio.




Circuito montado y funcionando.

Video del funcionamiento



Para la conversión y edición  de ficheros wav, utilizo el programa audacity.


Conversor de audio con audacity.

En el siguiente circuito crearé un interface desde el arduino para manejar los ficheros wav.

Saludos
Juan Galaz



Bibliografía:
http://ravc00cs.blogspot.com.es/2012/07/pwm.html
https://github.com/TMRh20/TMRpcm/wiki
http://apcmag.com/arduino-project-5-digital-audio-player.htm
http://arduino-info.wikispaces.com/Arduino-PWM-Frequency
http://hykrion.com/?q=node/153
http://arduino.cc/es/Tutorial/SecretsOfArduinoPWM