Hoy es día 4 de Mayo ¡el día de StarWars! Y vamos a celebrarlo con un proyecto divertido, hacer sonar la banda imperial en un procesador como Arduino.
Pero como hacer sonar simplemente un altavoz o un buzzer que iba a sabernos a poco, en su lugar vamos a usar un motor paso a paso como “instrumento musical”.
En realidad, no solo podéis usar un motor paso a paso. Casi cualquier cosa que haga ruido y podáis controlar (servo, motores DC…) puede serviros.
De hecho, en YouTube encontraréis auténticas virguerías, con disqueteras, con discos duros, con latas… en serio, buscar, hay cosas alucinantes.
Nosotros no vamos a dedicarle tanto tiempo, y nos conformamos con un motor. Pero si queréis ampliarlo a más instrumentos, la base es la misma.
Marcha imperial en Arduino
Vamos a empezar el caso más sencillo, que es hacer sonar la marcha imperial con un buzzer o altavoz pasivo, como vimos en esta entrada.
El núcleo de nuestro programa va a ser la clase ImperialMarch, que contiene la melodía de la marcha imperial. Tiene la siguiente forma.
typedef void(*PlayNoteCallback)(int note, int duration);
class ImperialMarch
{
public:
PlayNoteCallback playNoteCallback;
void playSong()
{
firstSection();
secondSection();
firstVariant();
secondSection();
secondVariant();
}
void playNote(int note, int duration)
{
playNoteCallback(note, duration);
delay(20);
}
private:
const int c = 261;
const int d = 294;
const int e = 329;
const int f = 349;
const int g = 391;
const int gS = 415;
const int a = 440;
const int aS = 455;
const int b = 466;
const int cH = 523;
const int cSH = 554;
const int dH = 587;
const int dSH = 622;
const int eH = 659;
const int fH = 698;
const int fSH = 740;
const int gH = 784;
const int gSH = 830;
const int aH = 880;
void firstSection()
{
playNote(a, 500);
playNote(a, 500);
playNote(a, 500);
playNote(f, 350);
playNote(cH, 150);
playNote(a, 500);
playNote(f, 350);
playNote(cH, 150);
playNote(a, 650);
delay(500);
playNote(eH, 500);
playNote(eH, 500);
playNote(eH, 500);
playNote(fH, 350);
playNote(cH, 150);
playNote(gS, 500);
playNote(f, 350);
playNote(cH, 150);
playNote(a, 650);
delay(500);
}
void secondSection()
{
playNote(aH, 500);
playNote(a, 300);
playNote(a, 150);
playNote(aH, 500);
playNote(gSH, 325);
playNote(gH, 175);
playNote(fSH, 125);
playNote(fH, 125);
playNote(fSH, 250);
delay(325);
playNote(aS, 250);
playNote(dSH, 500);
playNote(dH, 325);
playNote(cSH, 175);
playNote(cH, 125);
playNote(b, 125);
playNote(cH, 250);
delay(350);
}
void firstVariant()
{
playNote(f, 250);
playNote(gS, 500);
playNote(f, 350);
playNote(a, 125);
playNote(cH, 500);
playNote(a, 375);
playNote(cH, 125);
playNote(eH, 650);
delay(500);
}
void secondVariant()
{
playNote(f, 250);
playNote(gS, 500);
playNote(f, 375);
playNote(cH, 125);
playNote(a, 500);
playNote(f, 375);
playNote(cH, 125);
playNote(a, 650);
delay(650);
}
};
Como vemos, en primer lugar tiene la definición de las frecuencias para las notas. A continuación, tenemos la “partitura” en sí, en forma de nota y duración de la misma.
Para sacar la melodía lo normal es que partiéramos de un fichero Midi, y exportáramos un instrumento. Otra fuente bastante buena son los antiguos politonos (si, eso de los móviles viejos). Pero esto da para entrada aparte.
El otro punto importante de nuestra clase es la función de callback playNoteCallback(note, duration), que contiene la función que usara la clase para hacer sonar una nota.
Ahora, para usar nuestra clase en programa principal haríamos lo siguiente.
#include <./imperialMarch.hpp>
ImperialMarch imperialMarch;
void setup()
{
imperialMarch.playNoteCallback = beep;
}
void loop()
{
imperialMarch.playSong();
delay(500);
}
Donde, como veis, hemos asociado la función beep() de Arduino a nuestra función de callback. ¿A que queda un código cortito y mono? Pues ya estaría, así de fácil.
¿Por qué la función de callback?
A estas alturas alguno se puede preguntar. ¿Para qué narices metes una clase y una función de callback? Con lo fácil que es hacer sonar un buzzer.
Pues aparte de para intentar mantener nuestro código lo menos guarro posible, principalmente porque podemos reusar la clase ImperialMarch para otras fuentes de sonido.
Por ejemplo, si queremos llevar este código un M5StickC basado en el ESP32, el código sería el siguiente:
#include <M5StickC.h>
#include <./imperialMarch.hpp>
ImperialMarch imperialMarch;
void playNote(int note, int duration)
{
ledcWriteTone(ledChannel, note);
delay(duration);
ledcWriteTone(ledChannel, 0);
}
void setup()
{
M5.begin();
ledcSetup(ledChannel, freq, resolution);
ledcAttachPin(servo_pin, ledChannel);
ledcWrite(ledChannel, 256);
imperialMarch.playNoteCallback = playNote;
}
void loop()
{
imperialMarch.playSong();
delay(500);
}
Es decir, básicamente podemos usar el mismo código, simplemente cambiando la acción a ejecutar para hacer sonar una nota.
Aquí tenéis el resultado.
¡Haz que suene ese paso a paso!
A estas alturas ya debería verse el final. Podemos meter cualquier cosa que haga ruido en nuestra función playNoteCallback, si podemos controlar su frecuencia y duración.
Por ejemplo, un motor hace un ruido más agudo cuanto más alta es la velocidad (hasta que deja de ser audible). Así que podemos jugar con su velocidad para que haga una melodía.
El caso de control de un motor paso a paso es de los más sencillos, especialmente si usamos un driver como el A4988 o similar, como vimos en esta entrada.
#include <./imperialMarch.hpp>
const int STEPPER1_STEP = 0;
const int STEPPER1_DIR = 0;
ImperialMarch imperialMarch;
void playNote(int note, int duration)
{
auto start_time = millis();
auto step_time = 1000000 / note;
while (millis() - start_time < duration)
{
digitalWrite(STEPPER1_STEP, HIGH);
delayMicroseconds(100);
digitalWrite(STEPPER1_STEP, LOW);
delayMicroseconds(step_time - 100);
}
}
void setup()
{
pinMode(STEPPER1_DIR, OUTPUT);
pinMode(STEPPER1_STEP, OUTPUT);
digitalWrite(STEPPER1_DIR, HIGH);
digitalWrite(STEPPER1_STEP, LOW);
imperialMarch.playNoteCallback = playNote;
}
void loop()
{
imperialMarch.playSong();
delay(500);
}
Y si lo probamos queda algo así.
Que ya os digo que en directo es mucho mejor que en video, y que no me canso de ver girar el motorcito más rápido y más despacio a medida que hace las distintas notas de la melodía.
Como vemos, un proyecto sencillo que hará felices a los fans de Darth Vader y de cualquier friky que se precie y que, además, podemos hacer con los peques de la casa.
¿Os animáis a hacer algo parecido con un servo? ¿O con un motor DC? ¿O con más instrumentos? Si lo hacéis y queréis dejarnos un comentario, estáis invitados.
Os dejo todo el código en Github. Que la fuerza os acompañe ¡nos vemos en la siguiente entrada!
Descarga el código
Todo el código de esta entrada está disponible para su descarga en Github.