Mostrando entradas con la etiqueta Sonido. Mostrar todas las entradas
Mostrando entradas con la etiqueta Sonido. Mostrar todas las entradas

Generar música con PIC

Antes que nada debo aclarar que no estamos hablando de música con la calidad un CD, ni stereo, ni nada por el estilo, es mas, dudo que tenga alguna calidad, lo digo para no crear falsas expectativas.

En esta ocasión vamos a interpretar alguna melodía utilizando la técnica que vimos en "Generar sonido con PIC" y sólo lo haremos a modo didáctico, ya que es muy útil para comprender como el microcontrolador administra los tiempos y como se utilizan los puertos. Este ejercicio bien podría reemplazar al ya mítico parpadeo de un LED con PIC16F84A ya que es, en teoría, el mismo principio pero con el agregado del control de la frecuencia.

Ahora bien, aquí viene lo mas interesante, investigando un poco me enteré de como es esto de las notas al encontrar este artículo: Frecuencias de las notas musicales , en el que se explica la fórmula para obtener la frecuencia de cada nota musical. Una de las fórmulas, la mas sencilla para llevar a cabo en un programa, es esta:

Con esta fórmula pude sacar las frecuencias para las octavas 0 a la 6, que son las que mejor se ejecutan en el PIC, mas arriba o mas abajo ya es molesto o inaudible. Dichas frecuencias estan en la siguiente tabla:

0123456
DO32,7065,40130,81261,62523,251046,502093,00
DO#34,6469,29138,59277,18554,361108,732217,46
RE36,7073,41146,83293,66587,321174,652349,31
RE#38,8977,78155,56311,12622,251244,502489,01
MI41,2082,40164,81329,62659,251318,512637,02
FA43,6587,30174,61349,22698,451396,912793,82
FA#46,2492,49184,99369,99739,981479,972959,95
SOL48,9997,99195,99391,99783,991567,983135,96
SOL#51,91103,82207,65415,30830,601661,213322,43
LA55,00110,00220,00440,00880,001760,003520,00
LA#58,27116,54233,08466,16932,321864,653729,31
SI61,73123,47246,94493,88987,761975,533951,06
Tabla 1. Frecuencia en Hz de cada nota musical.

En esta otra tabla están representadas los microsegundos necesarios entre estado alto y bajo para generar dichas frecuencias:

0123456
DO15289,027644,513822,251911,12955,56477,78238,89
DO#14430,917215,453607,721803,86901,93450,96225,48
RE13620,976810,483405,241702,62851,31425,65212,82
RE#12856,486428,243214,121607,06803,53401,76200,88
MI12134,906067,453033,721516,86758,43379,21189,60
FA11453,825726,912863,451431,72715,86357,93178,96
FA#10810,975405,482702,741351,37675,68337,84168,92
SOL10204,205102,102551,051275,52637,76318,88159,44
SOL#9631,484815,742407,871203,93601,96300,98150,49
LA9090,904545,452272,721136,36568,18284,09142,04
LA#8580,674290,332145,161072,58536,29268,14134,07
SI8099,074049,532024,761012,38506,19253,09126,54
Tabla 2. Microsegundos de pausa correspondiente a medio periodo de cada nota musical.

Observando la última tabla con atención se ve que la nota de la octava siguiente es igual a la octava actual dividido por 2, o con la equivalencia en CCS C una rotación a la derecha.
De esto se deduce que teniendo un array con los valores de las diferentes notas se puede ir rotando a la derecha tantas veces como octavas queramos subir para obtener la nota y octava precisas.

En C, la función encargada de hacerlo se ve así:

#define Speaker PIN_B0 #define nDO 0 // DO #define nDO_ 1 // DO# #define nRE 2 // RE #define nRE_ 3 // RE# #define nMI 4 // MI #define nFA 5 // FA #define nFA_ 6 // FA# #define nSOL 7 // SOL #define nSOL_ 8 // SOL# #define nLA 9 // LA #define nLA_ 10 // LA# #define nSI 11 // SI int16 FreqNota[12]={ // retardos entre estado alto // y bajo para generar las notas 15289, // DO 14430, // DO# 13620, // RE 12856, // RE# 12134, // MI 11453, // FA 10810, // FA# 10204, // SOL 9631, // SOL# 9090, // LA 8580, // LA# 8099 // SI }; void Play(int nota, int octava, int16 duracion){ int16 fn; int16 mS_Transcurridos=0; // Contadores necesarios // para controlar la duración int16 CiclosL=0; // Contandor de uS fn=FreqNota[nota]; // Define los retardos para generar // la frecuencia de cada nota fn>>=(octava); // Adapta la frecuencia a la octava actual // haciendo una rotación // a la derecha por octava do{ output_high(Speaker); // Genera la frecuancia delay_us(fn); // con los retardos mientras CiclosL+=(fn); // aumenta el contador de // ciclos transcurridos output_low(Speaker); // en dos partes para repartir el delay_us(fn); // trabajo entre estado alto y bajo. CiclosL+=(fn); // CiclosL+=25; // Compensador. while(CiclosL>999){ // Se queda en el bucle mientras CiclosL // sea menor a 1000 (1 mS) CiclosL-=1000; // Le resta 1000 a CiclosL mS_Transcurridos++; // y le suma 1 a mS_Transcurridos. CiclosL+=25; // Compensador. } }while (duracion>mS_Transcurridos); // Repite el bucle hasta que haya // pasado el tiempo indicado. }

Bueno, sabiendo como ejecutar las notas musicales ahora es tiempo de interpretar una melodía.

Como aclaré antes de música no tengo conocimientos, pero buscando alguna melodía a interpretar en el PIC recordé que en BASIC (el antíguo) había una función llamada PLAY y que interpretaba las notas musicales con el PC Speaker. Buscando en Google encontré un artículo titulado DJ QBASIC donde hay diez canciones conocidas. Ahora lo que resta es adaptar el código BASIC a CCS con la función PLAY para C que vimos en la entrada "Generar sonido con PIC", y es cuando vemos otro pequeño inconveniente, las notas musicales en BASIC están en el sistema de notación musical inglés y nosotros usamos el latino, en la WikiPedia encontré este artículo donde hablan de eso y se muestra la equivalencia: Escala musical .

En base a eso hice este ejemplo que interpreta una melodía según se pulse una tecla, si se pulsa la tecla conectada a RB1 suena "Pop Corn", si se pulsa RB2 suena "Ecuador" y si se pulsa RB3 suena "The lion sleep tonight".

/////////////////////////////////////////////////////////////////////// // // // PICMusic v.1.00 // // (c) 2010 Gerardo Ariel Ramírez. // // // /////////////////////////////////////////////////////////////////////// // // // uControlador: PIC16F876A Lenguaje: CCS C // // Xtal: 4MHz // // // /////////////////////////////////////////////////////////////////////// #include <16f876a.h> #use delay(clock=4000000) #use fast_io(all) #fuses HS #FUSES NOPUT #FUSES NOBROWNOUT #define Speaker PIN_B0 #define nDO 0 // DO #define nDO_ 1 // DO# #define nRE 2 // RE #define nRE_ 3 // RE# #define nMI 4 // MI #define nFA 5 // FA #define nFA_ 6 // FA# #define nSOL 7 // SOL #define nSOL_ 8 // SOL# #define nLA 9 // LA #define nLA_ 10 // LA# #define nSI 11 // SI int16 FreqNota[12]={ // retardos entre estado alto // y bajo para generar las notas 15289, // DO 14430, // DO# 13620, // RE 12856, // RE# 12134, // MI 11453, // FA 10810, // FA# 10204, // SOL 9631, // SOL# 9090, // LA 8580, // LA# 8099 // SI }; void Play(int nota,int octava,int16 duracion); void PlayCancion(int cancion); void main(){ set_tris_b(14); // B<3:1>: Pulsadores B0: Speaker while (true){ if(input(PIN_B1))PlayCancion(1); //Si pulso switch 1 toca // Pop Corn if(input(PIN_B2))PlayCancion(2); //Si pulso switch 2 toca // Ecuador if(input(PIN_B3))PlayCancion(3); //Si pulso switch 3 toca // The lion sleep tonight } } void Play(int nota, int octava, int16 duracion){ int16 fn; int16 mS_Transcurridos=0; int16 CiclosL=0; fn=FreqNota[nota]; // Define los retardos para generar // la frecuencia de cada nota fn>>=(octava); // Adapta la frecuencia // a la octava actual do{ output_high(Speaker); // Genera la frecuancia delay_us(fn); // con los retardos mientras CiclosL+=(fn); // aumenta el contador de // ciclos transcurridos output_low(Speaker); // en dos partes para repartir el delay_us(fn); // trabajo entre estado alto y bajo. CiclosL+=(fn); // CiclosL+=25; // Compensador. while(CiclosL>999){ // Se queda en el bucle mientras // CiclosL sea menor a 1000 (1 mS) CiclosL-=1000; // Le resta 1000 a CiclosL mS_Transcurridos++; // y le suma 1 a mS_Transcurridos. CiclosL+=25; // Compensador. } }while (duracion>mS_Transcurridos); // Repite el bucle hasta // que haya pasado el // tiempo indicado. } void PlayCancion(int cancion){ switch (cancion){ case 1: //POP CORN play (nDO ,5,166); play (nLA_ ,4,166); play (nDO ,5,166); play (nSOL ,4,166); play (nRE_ ,4,166); play (nSOL ,4,166); play (nDO ,4,166); delay_ms (166); play (nDO ,5,166); play (nLA_ ,4,166); play (nDO ,5,166); play (nSOL ,4,166); play (nRE_ ,4,166); play (nSOL ,4,166); play (nDO ,4,166); delay_ms (166); play (nDO ,5,166); play (nRE ,5,166); play (nRE_ ,5,166); play (nRE ,5,166); play (nRE_ ,5,166); play (nDO ,5,166); play (nRE ,5,166); play (nDO ,5,166); play (nRE ,5,166); play (nLA_ ,4,166); play (nDO ,5,166); play (nLA_ ,4,166); play (nDO ,5,166); play (nSOL_ ,4,166); play (nDO ,5,166); break; case 2: //ECUADOR play (nLA ,3,100); delay_ms (200); play (nMI ,3,100); delay_ms (200); play (nDO ,4,100); delay_ms (100); play (nSI ,3,100); delay_ms (100); play (nRE ,4,100); delay_ms (100); play (nSI ,3,100); delay_ms (100); play (nSOL ,3,100); delay_ms (100); play (nLA ,3,100); delay_ms (200); play (nMI ,3,100); delay_ms (200); play (nDO ,4,100); delay_ms (100); play (nSI ,3,100); delay_ms (100); play (nRE ,4,100); delay_ms (100); play (nSI ,3,100); delay_ms (100); play (nSOL ,3,100); delay_ms (100); play (nDO ,4,100); delay_ms (200); play (nSOL ,3,100); delay_ms (200); play (nMI ,4,100); delay_ms (100); play (nRE ,4,100); delay_ms (100); play (nMI ,4,100); delay_ms (100); play (nRE ,4,100); delay_ms (100); play (nSOL ,3,100); delay_ms (100); play (nDO ,4,100); delay_ms (200); play (nLA ,3,100); delay_ms (200); play (nDO ,4,100); delay_ms (100); play (nSI ,3,100); delay_ms (100); play (nDO ,4,100); delay_ms (100); play (nSI ,3,100); delay_ms (100); play (nSOL ,3,100); break; case 3: //The lion sleep tonight play (nDO ,3,125); delay_ms (250); play (nRE ,3,125); delay_ms (125); play (nMI ,3,125); delay_ms (250); play (nRE ,3,125); delay_ms (250); play (nMI ,3,125); play (nFA ,3,125); delay_ms (250); play (nMI ,3,125); delay_ms (125); play (nRE ,3,125); delay_ms (250); play (nDO ,3,125); delay_ms (250); play (nRE ,3,125); play (nMI ,3,125); delay_ms (250); play (nRE ,3,125); delay_ms (125); play (nDO ,3,125); delay_ms (250); delay_ms (125); play (nMI ,3,125); delay_ms (125); play (nRE ,3,500); break; } }

Si no lo quieres copiar, lo quieres modificar o lo quieres ya compilado, te puedes bajar el proyecto completo haciendo click en este link .

Generar sonido con PIC

En los tiempos que corren estamos acostumbrados a vivir rodeados de tecnología, hoy en día cualquier movil supera en millones de operaciones por segundo al primer ordenador que tuve, hasta un PIC puede funcionar a mas velocidad que un 80286, pero hubo un tiempo en que un teléfono movil no estába al alcance de cualquiera, y en esa época no existían aparatos capaces de brindarnos la experiencia multimedia que hoy damos por sentado de que es así. Y estamos hablando de poco menos de 20 años atras, en esa epoca por lo menos los PC normales, no disponían de una placa de sonido y cualquier juego ejecutaba a duras penas un, a veces, desesperante pitido a modo de música.

Bueno, es por eso que en un arrebato de nostalgia se me ocurrió que podría emular esas "dulces" melodías con un PIC y es por eso que ahora tu estás leyendo esto. Inmediatamente despues me acordé de algo muy importante y es que no tengo ni la mas remota idea de música. Entonces decidí comenzar por el lugar mas obvio, o sea por el principio, fue cuando deduje que para que haya música primero tiene que haber sonido, entonces lo primero que hay que hacer es que el PIC emita algo de sonido controlado, ya habrá tiempo de generar notas musicales.

Primero hay que tener un concepto aunque sea básico de lo que el sonido es, en principio el sonido es un golpe en un medio, en nuestro caso el aire y, dependiendo de la frecuencia de ese "golpe", el sonido producido será mas agudo o mas grave; El oido humano puede percibir sonidos con una frecuencia que está comprendida entre los 20Hz y los 20KHz, en otras palabras que una persona puede escuchar frecuencias de esos golpes desde unas 20 veces por segundo hasta unas 20 mil aproximadamente. Entonces aquí ya tenemos un dato: para generar el sonido con el PIC debemos conectar un altavoz a una salida del microcontrolador y hacer alternar su estado de alto a bajo a una frecuencia determinada. Cuanto mas rápido se varíe el estado mas agudo será el sonido y viceversa.

Para obtener la duración del periodo (estado alto o bajo) para una frecuencia f solo hace falta usar la siguiente formula:

tP = 1
f

Para generar un sonido dentro de los margenes perceptibles, digamos de unos 650Hz realizaremos la siguiente operación:

tP = 1
650
= 0,0015385

El resultado obtenido es el tiempo, en segundos, que debe tardar cada periodo para producir un sonido de 650Hz. En este caso es 1538uS que es la medida que nosotros necesitamos para operar con el PIC, recuerda que un PIC con un cristal corriendo a 4 MHz realiza una operacion cada 1uS.

Cada 1538uS o, lo que es lo mismo, cada 1,538mS se debe producir un cambio de estado del la salida del microcontrolador para producir un sonido a 650Hz, entonces lo que necesitamos ahora es dividir ese valor en 2, una mitad para el estado alto y la otra mitad en estado bajo.

Con todo esto tenemos que: 1538 / 2 = 769, si traducimos esta teoría en lenguaje C quedaría de esta forma:

#include <16f876a.h>          // Tipo de microcontrolador
#use delay(clock=4000000)     // Delay con xtal de 4MHz
#use fast_io(all)             // I/O todas rápidas, de esta forma cuando se
                              // escribe en los puertos, no se configura el tris
                              // acelerando el proceso.  
#fuses HS                     // FUSES cristal HS >=4MHz 
#FUSES NOPUT                  // No power up Timer, no espera a estabilizar la tension 
#FUSES NOBROWNOUT             // No Brown out reset, no se resetea si cae la tension  
#define Speaker   PIN_B0      // Altavoz conectado a RB0  

void main(){
   
   set_tris_b(2); // RB<7:2> Salida RB1 entrada (Pulsador) RB0 Salida (Speaker)
   
   do{                              // Bucle infinito
      
      while(input(PIN_B1)){         // Mientras se presione el
                                    // interruptor conectado a RB1.
                                    
         output_high(Speaker);      // Altavoz encendido
         delay_us(769);             // espera 769 uS
         
         output_low(Speaker);       // Altavoz apagado
         delay_us(769);             // espera 769 uS
      }
      
   }while(true);
}

Este ejemplo lo único que hace es emitir un sonido, ni agudo, ni grave, cuando se activa el pulsador conectado a RB1.

Nuestro PIC acaba de emitir su primer sonido!

Ahora podremos crear una funcion que emita un sonido con la frecuencia y duracion deseados.
El siguiente ejemplo hace sonar una sirena cuando se pulsa un interruptor en RB1.

/////////////////////////////////////////////////////////////////////////////
//                                                                         //
//                            PICSirena 1.00                               //
//                   (c) 2010 Gerardo Ariel Ramírez.                       //
//                                                                         //
/////////////////////////////////////////////////////////////////////////////
//                                                                         //
//       uControlador: PIC16F876A            Lenguaje: CCS C               //
//               Xtal: 4MHz                                                //
//                                                                         //
/////////////////////////////////////////////////////////////////////////////


#include <16f876a.h>          // Tipo de microcontrolador
   
#use delay(clock=4000000)     // Delay con xtal de 4MHz
#use fast_io(all)             // I/O todas rápidas, de esta forma cuando se
                              // escribe en los puertos, no se configura el tris
                              // acelerando el proceso.

#FUSES HS                     // FUSES cristal HS >=4MHz
#FUSES NOPUT                  // No power up Timer, no espera a estabilizar la tension
#FUSES NOBROWNOUT             // No Brown out reset, no se resetea si cae la tension

#define Speaker   PIN_B0      // Altavoz conectado a RB0

void Sound(int16 frecuencia, int16 duracion);

void main(){
   int t;
   set_tris_b(2);
   
   while(true){      //Bucle infinito
   
      if (input(PIN_B1)){                 // Si se activa el pulsador
      
         for (t=0;t<30;t++)               // Bucle ascendente incrementando la
            sound(((int16)t*15)+1600,20); // frecuencia del sonido
         for (t=30;t>0;t--)               // Bucle decrementando
            sound(((int16)t*15)+1600,20); // la frecuencia del sonido
      
      }
   }
}

void Sound(int16 frecuencia, int16 duracion){
   
   int16 mS_Transcurridos=0;
   int16 CiclosL=0;
   int16 uS;
   int32 tmp;
   
   if (frecuencia>=20&&frecuencia<=20000){ //si la frecuancia se encuentra entre
                                           // los margenes de 20Hz y 20 KHz se ejecuta
      tmp=100000;                          // de los contrario no.
      tmp/=frecuencia;           // convierte los Hz a microsegundos para la pausa
      tmp*=5;   
      uS=tmp;
      do{
         output_high(Speaker);   // Genera la frecuancia deseada
         delay_us(uS);           // con los retardos mientras
         CiclosL+=(uS);          // aumenta el contador de ciclos transcurridos
         output_low(Speaker);    // en dos partes para repartir el 
         delay_us(uS);           // trabajo entre estado alto y bajo.
         CiclosL+=(uS);          // 
         CiclosL+=25;            // Compensador.
         
         while(CiclosL>999){     // Se queda en el bucle mientras CiclosL sea
                                 // menor a 1000 (1 mS)
            CiclosL-=1000;       // Le resta 1000 a CiclosL 
            mS_Transcurridos++;  // y le suma 1 a mS_Transcurridos.
            CiclosL+=25;         // Compensador.
         }
      }while (duracion>mS_Transcurridos);// Repite el bucle hasta que haya pasado el
                                         // tiempo indicado.
   }
}

Desde este link te puedes bajar el proyecto y el hex.

Bueno, hasta aquí hemos llegado, ahora resta por hacer que el PIC reproduzca alguna nota musical e interprete alguna canción, pero eso lo veremos en Generar música con PIC.


Tal vez le interese: