The making of my Tomorrowland LedHat
Last weekend I went to Tomorrowland 2013. I got the ticket months ago, in February. Ever since, I had been thinking about building something to stand out, something involving LEDs and an Arduino. As usual, I only decided what to build the day before the festival! I went to the mall and found a hat in discount. This would become my Tomorrowland LedHat 🙂
The build
The plan was to attach a bunch of LED strips and control them with an Arduino. Not only that, I wanted them to react to the music! A quick look through my electronic components collection made me wonder if I could do something with an LM324 op-amp. Le Goog pointed me into the direction of a movie by Afrotechmods. He showed a simple circuit to build an op-amp comparator. It filters the microphone input, amplifies it and compares the output voltage to a threshold. I quickly built this on my breadboard, added a potentiometer to set the sensitivity and voilà , I had a simple beat detector.
Next I attached the op-amp output to an analog input of an Arduino Mini Pro. Any Arduino would work, this was what I had laying around. It needed to fit in my hat, so my Arduinos Uno or Mega weren’t options. With 5V supplied to the op-amp, the maximum output I saw was about 3.8V. The analog pin translates a 0 to 5V input to an integer between 0 and 1024. Each loop the Arduino reads the pin value and compares it to a threshold. I chose 500 as the limit (about 2.5V).
The next step was controlling the LEDs. Attaching the LED strips directly to Arduino output pins isn’t a good idea. The Arduino can only handle that much amps. Furthermore my LED strips work on 12V, something the output pins can’t supply anyway. The ULN2003 Darlington array is built for this kind of applications. Supply 5V to one end and it will switch 12V on the other. It has 7 such gates and can sink a total of 500 mA, more than enough.
By the time I had everything tested thoroughly on a breadboard and soldered it up on perfboard, it was already late in the evening. While my girlfriend was sewing everything in my hat, I started coding. Luckily I didn’t have to start from scratch! I could recycle my Discotheque() code. Quite some things had to be rewritten though. The function had to be called based on microphone input and everything about the remote control had to go. Also the sequence of LEDs was different. My quadcopter didn’t use all ULN2003 pins. Now I did, so I had to program some new patterns. Patterns consist of different steps. Each step is a 7 bit integer. The binary number tells which LEDs should be on or off. Each time the function is called (ideally on the beat), it executes the next step. I repeat every pattern three times before it goes to the next one.
And here you can see me using it in Tomorrowland the next day:
I had the battery, a 2200 mAh 3S lipo, in my back pocket, with a wire running over my back to the hat. I had 4 batteries with me, with a total of 6600 mAh. I didn’t check in advance how much current the hat would need, but I ran it for about 3 hours and the battery still had 11.5V. A fully charged lipo has 12.6V and can be discharged to 9V maximum, so it can run PLENTY of hours on just one battery this size!
The code
Tomorrowland.ino
/*
Tomorrowland rules!
Copyright (c) 2013 Niek Hombrouckx
*/
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include "LedDiscotheque.h"
int MicPin = A0;
void setup() {
pinMode(2, OUTPUT); // White
pinMode(3, OUTPUT); // Red
pinMode(4, OUTPUT); // Red
pinMode(5, OUTPUT); // Green
pinMode(6, OUTPUT); // Green
pinMode(7, OUTPUT); // Blue
pinMode(8, OUTPUT); // Blue
digitalWrite(2, LOW);
digitalWrite(3, LOW);
digitalWrite(4, LOW);
digitalWrite(5, LOW);
digitalWrite(6, LOW);
digitalWrite(7, LOW);
digitalWrite(8, LOW);
Serial.begin(9600);
}
void loop(){
if (analogRead(MicPin) > 500) { // && deltaTime > 300
Discotheque();
//delay(100);
}
}
LedDiscotheque.h
/*
Led Discotheque by Nikotine.
v.1.0
*/
#ifndef _LED_DISCOTHEQUE_H_
#define _LED_DISCOTHEQUE_H_
#define mSizeof(x) sizeof(x)/sizeof(int)
#define NUMBER_OF_LEDS 7 // 7 outputs on ULN2003
unsigned long previousTime = 0;
unsigned long currentTime = 0;
unsigned long deltaTime = 0;
int figurenumber = 0;
int figureversion = 1; // current amount of times a certain pattern has been played
int figuresize = 0;
int figurestep = 2;
int amountoffigures = 0;
int MaxBPM = 180.0;
// Eerste cijfer is het aantal milliseconden tussen elke stap. Bij 0 wordt er naar de volgende stap gegaan wanneer de functie wordt aangeroepen
// Tweede cijfer is het aantal iteraties (figureversion)
//Blue2, Blue1, Green2, Green1, Red2, Red1, White
int LedCircle[] = {
0,
4,
B0000010,
B0001000,
B0100000,
B0010000,
B1000000,
B0001000,
B0000100,
B0010000,
B0000100,
B0001000,
B1000000,
B0010000,
B0100000,
B0001000
};
int LedPolice[] = {
50,
10,
B0000110,
B0000000,
B0000110,
B0000000,
B0000110,
B0000000,
B0000110,
B0000000,
B0000001,
B0000000,
B0000001,
B0000000,
B0000001,
B0000000,
B1100000,
B0000000,
B1100000,
B0000000,
B1100000,
B0000000,
B1100000,
B0000000,
B0000001,
B0000000,
B0000001,
B0000000
};
int LedAdditiveCircle[] = {
0,
3,
B0000010,
B0001010,
B0101010,
B0111010,
B1111010,
B1111110,
B1111010,
B0111010,
B0101010,
B0001010,
B0000010,
B0000000
};
int LedRRGGBB[] = {
0,
4,
B0000011,
B0000010,
B0000101,
B0000100,
B0001001,
B0001000,
B0010001,
B0010000,
B0100001,
B0100000,
B1000001,
B1000000
};
int LedOppositeCircle[] = {
0,
4,
B0100010,
B0011000,
B1000100,
B0100010,
B0011000,
B1000100,
B0100010,
B0011000,
B1000100,
B0100010,
B0011000,
B1000100
};
int LedPinkBeat[] = {
0,
7,
B1100110,
B0000001,
B1100110,
B0000001,
B1100110,
B0000001,
B1100110,
B0000001
};
int LedGreenDevil[] = {
35,
25,
B0011000,
B0000000,
B0011000,
B0000000,
B0011000,
B0000000,
B0011000,
B0000001,
B0011001,
B0000001,
B0011000,
B0000000,
B0011000,
B0000000,
B0011000,
B0000000
};
int* figurename[] = {
LedCircle,
LedPolice,
LedAdditiveCircle,
LedRRGGBB,
LedOppositeCircle,
LedPinkBeat,
LedGreenDevil
};
int sizeoffigures[] = {
mSizeof(LedCircle),
mSizeof(LedPolice),
mSizeof(LedAdditiveCircle),
mSizeof(LedRRGGBB),
mSizeof(LedOppositeCircle),
mSizeof(LedPinkBeat),
mSizeof(LedGreenDevil)
};
void LedsActiveren() {
byte data = figurename[figurenumber][figurestep];
for (byte y=0; y<NUMBER_OF_LEDS; y++) {
if (data & (1<<y)) {
digitalWrite(2+y, HIGH);
}
else {
digitalWrite(2+y, LOW);
}
}
}
void Discotheque() {
amountoffigures = sizeof(figurename)/sizeof(int); // aantal verschillende patronen
figuresize = sizeoffigures[figurenumber]; // grootte van huidige figuur, bestaande uit steps
//Serial.println(amountoffigures);
//figurenumber = previousfigurenumber +1;
//figuresize = sizeof(figurename[figurenumber]) / sizeof(int);
currentTime = millis();
deltaTime = currentTime - previousTime;
Serial.print("figurenumber: ");
Serial.print(figurenumber);
Serial.print(" | iteratie: ");
Serial.println(figureversion);
if (figurename[figurenumber][0] == 0) {
float StepDelay = (60.0/MaxBPM/2*1000.0); // MaxBPM / 2 omdat je binnen een beat de leds aan en uit wil
if (deltaTime > StepDelay) {
Serial.print("deltaTime: ");
Serial.print(deltaTime);
Serial.print("/");
Serial.println(StepDelay);
LedsActiveren();
previousTime = currentTime;
figurestep++;
if (figurestep > figuresize-1) {
figurestep = 2;
figureversion++;
if (figureversion > figurename[figurenumber][1]) {
figureversion = 1;
figurenumber++;
if (figurenumber > amountoffigures-1) {
figurenumber = 0;
}
}
}
}
}
else {
for (figureversion=1;figureversion<figurename[figurenumber][1]+1;figureversion++) {
Serial.print("figurenumber: ");
Serial.print(figurenumber);
Serial.print(" | iteratie: ");
Serial.println(figureversion);
for (figurestep=2; figurestep<figuresize;figurestep++) {
LedsActiveren();
delay(figurename[figurenumber][0]);
}
}
figureversion = 1;
figurestep = 2;
figurenumber++;
if (figurenumber > amountoffigures-1) {
figurenumber = 0;
}
}
}
#endif // _LED_DISCOTHEQUE_H_
Room for improvement
Considering the LedHat came from concept in my head to a product in just one day, it worked relatively well. But it can be improved considerably!
- The LM324 has four op-amps, but I only needed two of them, so you save some space by using an LM358.
- This device needs both 5V for the microphone and Arduino, and 12V for the LEDs. Technically the Arduino could run on 12V (using the RAW input), but I didn’t want it to run hot on top of my head. I used a 5A switching UBEC from Hobbyking to convert 12V to 5V. It’s a great device, but it’s not an elegant solution. I tried to use an LM317 but I got some crazy results, the solder fumes must have confused my mind…
- It works great in my home with music coming out of my stereo. The beats are well defined and there is no background noise. On a festival you’ve got not only the screaming crowd, but a MUCH bigger stereo 🙂 The microphone was swamped constantly and I couldn’t get the potentiometer set sensitively enough to get distinct beats. Remember this is only doing some basic filtration, it reacts to sound, not to just bass. A better filter should be used.
- Alternatively, I could replace R4 and R5 by one potentiometer to change the amplification of the microphone input. I’ll have to test this. EDIT: I don’t think this will work after all. In fact, quite the opposite, as the wave amplitude will be smaller, it will be even more difficult to detect beats.
- During testing at home, I noticed that one beat would sometimes be detected as several spikes. This made the animation go to quickly, so to filter that out, I only had the function be called every 300 ms maximum. This is about 180 BPM, which is drum&bass stuff, not the kind of festival I was going to 🙂 This had another advantage, by coincidence actually: while the microphone was being swamped by sound in Tomorrowland, and no distinct beats could be detected, the animation would just continue, with 300 ms between each step. If I hadn’t done that, the LEDs would just stop moving, which would have been a boring hat.
- The function could be greatly improved by doing some BPM calculation. For example, measure the time between two high peaks and calculate a rolling average BPM. When a part of music doesn’t have distinct beats, the program can continue using the BPM it calculated…
- I had 7 patterns, but I needed more! And strobes! And smoke and lasers! 😀
I’ve got a year before the next Tomorrowland festival, which I will most certainly attend!! Let’s see what I can come up with…
Top idee!