Home Electrónica Circuitos Reproducir sonidos .WAV con PIC usando PWM [CCS+18F4550+MATLAB]

PostHeaderIcon Reproducir sonidos .WAV con PIC usando PWM [CCS+18F4550+MATLAB]

Usar puntuación: / 15
MaloBueno 

Revision 1. Septiembre 2011. English version here added

El objetivo de este artículo es explicar la manera de reproducir sonidos usando un microcontrolador PIC y la salida PWM que muchos llevan incorporada como DAC (conversor digital a analógico). El origen del sonido será un fichero .wav que ya tendremos más o menos tratado con algún programa de edición de sonido y que será introducido en la memoria del PIC previo tratamiento de los datos usando MATLAB.

La principal preocupación no es la calidad de sonido, ya que  limitaciones como la memoria del PIC o la resolución de la salida PWM hacen imposible obtener una calidad buena de sonido, pero para voces habladas y sonidos concretos será más que suficiente.

Yo he usado lo siguiente y está probado de esta forma:

  • PIC 18F4550
  • CCS PICC (compilador de C para PICs)
  • MATLAB
  • Procesador de textos
  • Un fichero .wav (16bits / 44100Hz)

 

El archivo de sonido .wav

El archivo original es bocina3.wav, es un archivo muestreado a 44100hz, 16 bits y mono. Para el que no esté demasiado familiarizado con estos términos esto quiere decir que 44100 veces cada segundo se toma el valor de la señal analogica original (el sonido) y que el valor que se lee, al pasarlo a un numero binario, se usan 16 bits.

Para este ejemplo, te recomiendo que con un programa de edicion de sonido pases a mono el fichero -salvo que seas un machote y quieras usar dos salidas PWM-, reduzcas el muestreo lo máximo posible (ahora recordaremos las limitaciones).

La norma para remuestrear (resample) para reducir la cantidad de datos esta controlada por la teoría del muestreo. Básicamente, si lo que quieres es reproducir sonidos audibles (1-20000hz) necesitarás al menos el doble de frecuencia de muestreo que la máxima frecuencia que quieras oir.

Esto es conocido como tasa de Nyquist. Básicamente lo que nos dice es que para muestrear una señal de frecuencia "f" y poder reconstruirla correctamente después, es necesario que la frecuencia de muestreo sea al menos "2 x f". De ahí sale el habitual 44100Hz. Así se evita la aparición de "alias",  señales a frecuencias que no existen en la señal original.

Si lo que quieres es producir un sonido de voz humana, las frecuencias para que se entienda el mensaje no suelen superar los 2000Hz, por lo que podrás muestrear a 4000Hz. ¿que importancia tiene esto? El tamaño del fichero! Imagina que tienes un sonido de 10 segundos que quieres meter en el microcontrolador. Vamos a calcular ahora el tamaño que tendrá dependiendo de la tasa de muestreo:

  • a 44100hz, 8 bits son: (3528000 bits) 441k
  • a 4000hz, 8 bits son: (320000 bits) 40k

Se puede ver, que el primer fichero no va a ser muy fácil de meter en la memoria del PIC. Se podría intentar con un par de memorias 24xx1025 usando el bus I2C, suponiendo que el microcontrolador fuera capaz de leer del bus y reproducir en tiempo real. Pero el segundo si sería más o menos posible dependiendo de la gama del PIC!

Crear la tabla de datos para la ROM del PIC usando Matlab.

Lo que vamos a hacer ahora es obtener los datos que vamos a cargar como variables estáticas en el controlador. Esto quiere decir, serán datos en ROM, de sólo lectura en tiempo de ejecución. Esta variable o variables contendrán el sonido. Usaremos para este ejemplo una variable de 8 bits, es decir, vamos a tener 28=256 tonos de sonido diferentes. No apto para melómanos.

Abrimos Matlab y cargamos en un vector los datos del muestreo. Recomentado ejecutar "help wavread" en Matlab si no sabes lo que significa nada de lo que hay entre corchetes. Básicamente introduces el vector de datos destino, la frecuencia de muestreo, el número de bits a utilizar...

  • [datos,Fs,NBits,Opts]=wavread('d:\bocina3.wav')

El único problema es que los datos varían entre -1 y 1 y no nos sirve, los queremos entre 0 y 255 (8 bits). Así que ejecutamos las siguientes instrucciones:

  • datos=datos*127

El valor 127 es suponiendo que el máximo o mínimo valor del vector de datos es 1 o -1. Si no lo es, este valor puede ser un poco mayor para maximizar la resolución de la conversión. Si no sabes o no te apetece saber de lo que hablo, usa 127.

  • datos=datos+127
  • datos=fix (datos)

Ahora grabamos el fichero de valores (0-255) en formato CSV (valores separados por comas).  Anota la dimensión de la variable datos en MATLAB antes de cerrar. Es el número de datos dentro del vector "datos" que hemos generado con nuestro sonido. Lo vamos a necesitar más adelante en el compilador de C para definir el tamaño del  vector estático.

  • csvwrite('d:\datos.csv',datos);

Ya está! Ahora sólo hace falta abrirlo con un procesador de textos y hacer un apaño!. Verás que hay un montón de numeros. Ese es el sonido con sus valores en digital. Para comprobar que no nos hemos equivocado en el proceso puedes ver que los valores oscilan entre 0-255. Sino, algo has hecho mal.

Abre el fichero .csv y con la opción de reemplazar sustituye los saltos de linea por "," y crea un texto que debería ser más o menos así:

Const char datos[X]={127,127,127,127,....tus datos...,125,54};

* El valor de X es el valor que anotaste del tamaño de la variable "datos" en Matlab

Esto que hacemos aquí es definir como variable "datos" estática "const", es decir se guardará como sólo lectura en ROM que tiene generalmente más capacidad que la RAM-, el sonido en formato digital.

Ya puedes copiar la linea en el CCS-PICC. Si el sonido es muy muy pequeño puedes definir la variable sin el "const" y guardarla en RAM, pero normalmente esta memoria es mucho más pequeña que la ROM y que el fichero a guardar, así que debes usar "const" para que se guarde como datos de ROM. Ojo, no podrás modificarla en tiempo de ejecución!!

Puedes descargarte el ejemplo de como debería quedar antes de copiarlo y pegarlo en el CCS PICC. Hay que copiarlo con un valor por linea, ya que sino, el editor de texto del CCS se puede quedar colgado!!

Programa ejemplo y configuración del PIC

main.c

#USE RS232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)
#use delay(clock=8000000)

Const char datos[1000]={127,127,127,127,....tus datos...,125,54};

int16 i;
void main()
{

setup_adc_ports(NO_ANALOGS|VSS_VDD);
setup_adc(ADC_OFF);
setup_spi(SPI_SS_DISABLED);

setup_timer_1(T1_DISABLED);

SETUP_CCP1(CCP_PWM_HALF_BRIDGE|CCP_PWM_L_L|CCP_SHUTDOWN_AC_F);
setup_wdt(WDT_OFF);

setup_timer_2(T2_DIV_BY_1,110,1);

setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);

while(1==1){
setup_ccp1(CCP_CAPTURE_FE);
delay_ms(3000);
SETUP_CCP1(CCP_PWM_HALF_BRIDGE|CCP_PWM_L_L|CCP_SHUTDOWN_AC_F);
for (i=0;i<1000;i++) {set_pwm1_duty(datos[i]);}
for (i=0;i<1000;i++) {set_pwm1_duty(datos[i]);}
for (i=0;i<1000;i++) {set_pwm1_duty(datos[i]);}
}
}

main.h  (configuración):

#include <18F4550.h>
#device adc=8

#FUSES NOWDT                    //No Watch Dog Timer
#FUSES WDT128                   //Watch Dog Timer uses 1:128 Postscale
#FUSES INTRC_IO                 //Internal RC Osc, no CLKOUT
#FUSES NOPROTECT                //Code not protected from reading
#FUSES NOBROWNOUT               //No brownout reset

#FUSES NOPUT                    //No Power Up Timer
#FUSES NOCPD                    //No EE protection
#FUSES STVREN                   //Stack full/underflow will cause reset
#FUSES NODEBUG                  //No Debug mode for ICD
#FUSES NOLVP                    //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOWRT                    //Program memory not write protected
#FUSES NOWRTD                   //Data EEPROM not write protected
#FUSES IESO                     //Internal External Switch Over mode enabled
#FUSES FCMEN                    //Fail-safe clock monitor enabled
#FUSES PBADEN                   //PORTB pins are configured as analog input channels on RESET
#FUSES NOWRTC                   //configuration not registers write protected
#FUSES NOWRTB                   //Boot block not write protected
#FUSES NOEBTR                   //Memory not protected from table reads
#FUSES NOEBTRB                  //Boot block not protected from table reads
#FUSES NOCPB                    //No Boot Block code protection
#FUSES NOMCLR                     //Master Clear pin NOT enabled
#FUSES LPT1OSC                  //Timer1 configured for low-power operation
#FUSES NOXINST                  //Extended set extension and Indexed Addressing mode disabled (Legacy mode)



#FUSES ICPRT                    //ICPRT enabled

#use delay(clock=8000000)
#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)

 

Si quieres puedes descargarlos aquí: main.h main.c

 

Esquema electrónico

Para convertir una señal PWM (Modulación de ancho de pulso) en una señal analógica variable y así tener funcionando un DAC, es necesaria como mínimo una red RC a la salida del PIC. Este punto, explicado, justificado y demás, se puede encontrar en la nota de aplicación AN538 de Microchip. En ella se explica que valor hay que darle a la resistencia y condensador del filtro paso baja -formulas clásicas- y también los valores para configurar el Timer2 y los requisitos de resolución y frecuencia del conversor PWM. También se recomienda usar un filtro paso baja de orden mayor al de una red RC simple, pero para esta aplicación no lo veo muy necesario.

 

Nos damos cuenta, a la vista del esquema que tenemos una red RC (filtro paso baja) que calcularemos más adelante. La salida de esa red ya se podría conectar a un pequeño altavoz, pero el volumen es muy bajo, así que yo le he puesto un amplificador operacional no-inversor, con ganancia 10 para amplificarlo. Puede que incluso sea poco dependiendo de la aplicación que busques.

Elige un valor de C. Pongamos por ejemplo, C=0.01uF. No te fies del valor en el esquema. Para una f=20khz, C=10nF tendríamos una resistencia de 795 Ohm. Ahora usamos:

 

 

Siendo f la frecuencia máxima del sonido que queremos oir. Ya tienes R y C. Monta la red RC tal y como se muestra en el esquema, que puedes verlo a más resolución al final de la página.

Sólo queda configurar el timer2 del PIC para obtener una velocidad de reproducción acorde con la velocidad de muestreado que hubiesemos elegido. Evidentemente esto dependerá de la velocidad de muestreo, velocidad del micro, velocidad del reloj del timer, valor de desbordamiento y número de veces que se debe desbordar. Ahí es nada. Afortunadamente hay una ecuación para todo esto, aunque a veces es fácil ajustarlo a ojo.

Prueba con setup_timer_2(T2_DIV_BY_16, x, 1); Donde x es:

 

 

***Valores habituales:

Para sonidos muestreados a 22050Hz: Oscilador a 4 Mhz, Timer2(Div_by_1,125,1), R=795 Ohm, C=10nF

Para sonidos muestreados a 44100Hz: Oscilador a 8 Mhz, Timer2(Div_by_1,125,1), R=1,5KOhm, C=10nF

 

Dim lights Embed Embed this video on your site