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 .

27 comentarios:

  1. Miguel17/9/10

    hola me parece interesante tu nota, estoy haciendo algo parecido, lo que no se es como hago para no tener una sola salida, quiera tener 8 salidas cada una correspondiemte a una nota, y como entrada un pequeño teclado, cosa que si presiono dos teclas se escuchan los dos sonidos, me puedes ayudar en esa parte?
    Saludos

    ResponderEliminar
  2. Se podría probar con un cristal de 20MHz y controlando los retardos con el Timer0, aún así habría que ver si el PIC es capaz de controlar las frecuencias sin saltos.
    Recuerda que es un microcontrolador de gama baja y no es tan eficaz como un procesador o incluso algun microcontrolador de mas alta gama, pero sería cuestión de probar.

    Saludos.

    ResponderEliminar
  3. Anónimo21/11/10

    estas chavo compa, aprende .ASM

    ResponderEliminar
  4. Se podría hacer en Assembler, pero no le veo la necesidad de usarlo aquí, ya que esto es un ejemplo a modo de introducción, muy básico. Utilizando CCS C se simplifican muchas cosas, lo que ayuda a la comprensión.

    Saludos.

    ResponderEliminar
  5. Anónimo8/12/10

    Sos un grande....cuando sea mayor quiero saber la mitad de lo que ya sabés...
    Me la paso leyendo sobre pics y su programacion y no termino de armar nada, solo leo, leo y leo. No me Animo !!

    ResponderEliminar
  6. A mi me pasaba lo mismo hasta no hace mucho, sólo es cuestión de empezar por armar algo, por mas simple que sea, te darás cuenta que es mas sencillo de lo que parece a simple vista.

    Si no tienes los materiales, o no te atreves con el soldador, puedes intentarlo con Proteus (que tiene un simulador en tiempo real).

    Ánimo y suerte!

    Saludos.

    ResponderEliminar
  7. Anónimo25/7/11

    me doy cuenta de que en el link que proporcionas el esquematico no esta en proteus...pues no veo ningun archivo .dns en que program puedo ver el esquematico???

    ResponderEliminar
  8. Es que no es una simulación, es el proyecto en CCS C, o sea debes conectar un altavoz al pin B0, tres pulsadores a los pines B1, B2 y B3 del pic y quemarlo con el HEX.

    Saludos.

    ResponderEliminar
  9. no m epodrian facilitar el diagrama de conexiones...me ayudaria mucho ya que necesito armarlo urgentement
    mi mail es floresgomezanthony@yahoo.es

    ResponderEliminar
  10. La conexión la debes hacer igual que en esta entrada http://picrobot.blogspot.com/2008/12/probador-de-pic16f876.html
    Solo debes conectar un pequeño altavoz al pin B0, y tres pulsadores a los pines B1, B2 y B3

    Saludos.

    ResponderEliminar
  11. Anónimo15/11/11

    Disculpa y si quisiera poner botones para cada nota en el pic, como se haria

    ResponderEliminar
  12. Buenas, puedes modificar la parte donde aparecen todas las notas musicales para cada canción por solo la nota deseada.
    Y cambiar la parte de

    if(input(PIN_B1))PlayCancion(1);

    por

    if(input(PIN_x))PlayCancion(z);

    Donde x lo cambias por el pin de entrada y z por el numero de nota. De esta forma no podrías hacer sonar dos notas al mismo tiempo.

    Saludos

    ResponderEliminar
    Respuestas
    1. Anónimo7/11/14

      Buenas, siguiendo tu post logré sacar la marcha imperial de Star Wars! :D Ahora, cómo sería para poder reproducir dos sonidos al mismo tiempo por pines distintos? Te agradezco mucho por tus aportes.

      Eliminar
  13. Anónimo24/1/12

    Hola, esta muy interesante tu proyecto, un favor podrías colocar el link de descarga del proyecto en otro sitio diferente a megaup... gracias!!

    ResponderEliminar
  14. Anónimo17/7/12

    hola me puedes enviar el archivo por mail ? porque esta fallido y para que pic esta implementado y con que oscilador? muchas gracias muy interesante el proyecto!

    ricardo_bertello@hotmail.com

    ResponderEliminar
  15. Ya está subido el proyecto a RapidShare, disculpen las molestias.

    Saludos

    ResponderEliminar
    Respuestas
    1. Anónimo12/9/12

      Hola, cuando veo o me instruyo con personas como tü, es cuando me doy cuenta que existe Dios, gracias por tu colaboracion.

      Eliminar
  16. Anónimo27/3/13

    Muy interesante, felicidades!

    ResponderEliminar
  17. brother estoy haciendo un proyecto con pic´s nose si me puedas ayudar... bueno me estan pidiendo hacer un contador con decodificador que cuente del 0 al 99. si me podrias ayudar seria de la ptm . cdt saludos!!!

    ResponderEliminar
  18. Muy útiles tus notas. Gracias.

    Saludos.

    ResponderEliminar
  19. Como convertiste lo que esta en Qbasic para el codigo del PIC una breve explicacion por favor....

    ResponderEliminar
  20. Buenas Saulo,

    Hace tiempo que has hecho la pregunta pero hay va mi intento de contestarte.
    La verdad es que ya ni me acuerdo como hice la "traducción" de código BASIC a C, pero intentaré dar una explicación teórica de como lo hice, por si te sirve.

    Primero se carga toda la cadena con la música en una variable string.

    Después se recorre toda la variable buscando notas musicales (A,B,C, etc.), si encuentra una P (pausa), lee el número que viene a continuación y si es un símbolo mayor o menor (><) sube o baja la octava actual (una variable)

    Si encuentra una A, escribe en un archivo de texto "play (nLA_ ,4,166);".
    Donde 4, es la octava y 166 la duración.
    Si encuentra P16, escribirá "delay_ms (166);".

    Luego la "afinación" la hice manualmente, recuerdo. Cuando tuve terminado el archivo, un simple copia y pega al código C y listo.

    Espero que te haya servido.

    Saludos.

    ResponderEliminar
  21. Esta genial! me hare un piano con pic :D

    ResponderEliminar
  22. Anónimo19/8/15

    tengo un problema a la hora de agregar otra cancion al tocar el pulsador de la 4 cancion
    el sonido no para como con las 3 primeras canciones sino queda fijo que puedo hacer para solucionar este error

    ResponderEliminar
  23. una pregunta y en lenguaje ensamblador como seria soy principiante en este lenguaje pero quisiera hacerlo con ensamblador alguna idea

    ResponderEliminar
  24. Hola quisiera saber el modo de poder agregar un motor y un led a este codigo, ya que a la hora de agregar esto al codigo a mi no me jala, seria de gran ayuda su respuesta

    ResponderEliminar

Tal vez le interese: