


Homemade New Year's Eve LED Ball Drops(This article was supposed to be written a lot earlier, but food poisoning had a discouraging effect on the author.) Every New Year's Eve, up to a million visitors crowd Times Square NYC to see a massive crystal and LED ball descend at the stroke of midnight. The ball has grown increasingly large and complex over the past few years, now 12 feet in diameter and over 32,000 LEDs. While an impressive sight, it's not easy to drop by Times Square when you live on the West Coast. And seeing the ball drop on TV, 3 hours early, just isn't the same. So why not build your own LED ball? Dropping our own LED ball is a 4-year macetech LLC tradition (well, four drops, and three elapsed years to be precise). The first ShiftBrite LED Modules were produced in early 2008. By the end of 2008, we were doing higher volume production and had founded a new company, macetech LLC. ShiftBrites are a small PCB with an RGB LED and a PWM controller, capable of 1023 levels of brightness for each of the red, green, and blue channels. They can be chained together with 6-pin cables, and sent color commands by a microcontroller. Only 4 data pins are needed to fully control up to 255 pixels in a single chain.
The ball itself had to be something other than the original drinking straw construction, since my then-roommate/current-cofounder's cat had located the 2008/2009 ball. The cat already loved chewing wires, straws, and zip ties all on their own... So new construction was based on the Sparkleball. A simple, yet geometrically interesting shape made from plastic drinking cups. It's in fairly widespread use by Christmas and some wedding decorators...simply attach a lot of drinking cups into a sphere, fill with lights, and hang it someplace. There are a lot of ways to go about fastening the cups together, the type of lights to use, the size of the cups, etc. For this ball, I used 120 16-ounce plastic cups. The Arduino and ShiftBars were attached to the top inner surface of the ball, and the Satellite Modules hung down to the center. We were at a friends house and couldn't set up a pole, so we threw a rope over a tree branch.
First, the (messy) code from last year's ball (should work with any ShiftBrite/ShiftBar/MegaBrite/OctoBar project): Put this code in your main sketch. #include <math.h> #include <avr/pgmspace.h> #include "rgbdefs.h" #define clockpin 13 // CI #define enablepin 10 // EI #define latchpin 9 // LI #define datapin 11 #define NumLEDs 20 unsigned long SB_CommandPacket; int SB_CommandMode; int SB_BlueCommand; int SB_RedCommand; int SB_GreenCommand; int currentpattern = 2; int channelnumber = 0; int cyclecount = 0; byte fadecount = 0; byte spincount = 0; float spinspeed = 4; byte speedvalue = 0; int channelvalue[4] = {0}; int spinpos = 0; int oldred = 0; int oldgreen = 0; int oldblue = 0; int newred; int newgreen; int newblue; int LEDChannels[NumLEDs][3] = {0}; PROGMEM prog_uchar Gradients[4][24] = { {0,0,0, 25,25,0, 50,50,0, 100,100,0, 150,150,0, 200,200,0, 255,255,0, 0,0,0}, {255,0,0, 255,0,0, 255,0,0, 255,0,0, 0,0,255, 0,0,255, 0,0,255, 0,0,255}, {0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,128,0, 0,255,0, 0,255,0, 0,255,0}, {255,0,0, 255,255,0, 0,255,0, 0,255,255, 0,0,255, 255,0,255, 0,0,0, 0,0,0} }; byte PlasmaGradient[8][3] = {0}; byte treerows[11] = {1, 1, 3, 3, 5, 5, 5, 7, 7, 7, 7}; void SelectGradient(int gradient) { for (int d = 0; d < 8; d++) { PlasmaGradient[d][0] = pgm_read_byte_near(Gradients[gradient] + d * 3); PlasmaGradient[d][1] = pgm_read_byte_near(Gradients[gradient] + d * 3 + 1); PlasmaGradient[d][2] = pgm_read_byte_near(Gradients[gradient] + d * 3 + 2); } } void setup() { pinMode(datapin, OUTPUT); pinMode(latchpin, OUTPUT); pinMode(enablepin, OUTPUT); pinMode(clockpin, OUTPUT); SPCR = (1<<SPE)|(1<<MSTR)|(0<<SPR1)|(1<<SPR0); digitalWrite(latchpin, LOW); digitalWrite(enablepin, LOW); Serial.begin(9600); randomSeed(analogRead(0)); } void SB_SendPacket() { if (SB_CommandMode == B01) { SB_RedCommand = 127; SB_GreenCommand = 127; SB_BlueCommand = 127; } SPDR = SB_CommandMode << 6 | SB_BlueCommand>>4; while(!(SPSR & (1<<SPIF))); SPDR = SB_BlueCommand<<4 | SB_RedCommand>>6; while(!(SPSR & (1<<SPIF))); SPDR = SB_RedCommand << 2 | SB_GreenCommand>>8; while(!(SPSR & (1<<SPIF))); SPDR = SB_GreenCommand; while(!(SPSR & (1<<SPIF))); } void WriteLEDArray() { SB_CommandMode = B00; // Write to PWM control registers for (int h1=0;h1<NumLEDs;h1++) { SB_RedCommand = LEDChannels[NumLEDs-h1-1][0]; SB_GreenCommand = LEDChannels[NumLEDs-h1-1][1]; SB_BlueCommand = LEDChannels[NumLEDs-h1-1][2]; SB_SendPacket(); } delayMicroseconds(3); PORTB |= (1 << 1); PORTB |= (1 << 2); delayMicroseconds(3); PORTB &= ~(1 << 2); PORTB &= ~(1 << 1); SB_CommandMode = B01; // Write to current control registers for (int z = 0; z < NumLEDs; z++) SB_SendPacket(); delayMicroseconds(3); PORTB |= (1 << 1); delayMicroseconds(3); PORTB &= ~(1 << 1); } void fadeall(int rate, int fromred, int fromgreen, int fromblue, int tored, int togreen, int toblue) { for (int i = 0; i < 17; i++) { for (int j1 = 0; j1 < NumLEDs; j1++) { LEDChannels[j1][0] = (fromred * (16 - i) + tored * i)/4; LEDChannels[j1][1] = (fromgreen * (16 - i) + togreen * i)/4; LEDChannels[j1][2] = (fromblue * (16 - i) + toblue * i)/4; } WriteLEDArray(); delay(rate); } } void randomstrobe(int rate, int blinks) { for (int i = 0; i<blinks; i++) { int randomLEDx = random(0,NumLEDs); LEDChannels[randomLEDx][0] = 1023; LEDChannels[randomLEDx][1] = 1023; LEDChannels[randomLEDx][2] = 1023; WriteLEDArray(); delay(rate); for (int j1 = 0; j1 < NumLEDs; j1++) { LEDChannels[j1][0] = 0; LEDChannels[j1][1] = 0; LEDChannels[j1][2] = 0; } delay(rate); } } void noisy(int blinks, int rate, int red, int green, int blue) { for (int i = 0; i<blinks; i++) { for (int j1 = 0; j1 < NumLEDs; j1++) { int rndval = random(0,10); LEDChannels[j1][0] = rndval*red/10; LEDChannels[j1][1] = rndval*blue/10; LEDChannels[j1][2] = rndval*green/10; } WriteLEDArray(); delay(rate); } } void fillall(int red, int green, int blue) { for (int i = 0; i < NumLEDs; i++) { LEDChannels[i][0] = red; LEDChannels[i][1] = green; LEDChannels[i][2] = blue; } } void blinkall(int rate, int blinks, int red, int green, int blue) { for (int i = 0; i<blinks; i++) { for (int j1 = 0; j1 < NumLEDs; j1++) { LEDChannels[j1][0] = red; LEDChannels[j1][1] = green; LEDChannels[j1][2] = blue; } WriteLEDArray(); delay(rate); for (int j1 = 0; j1 < NumLEDs; j1++) { LEDChannels[j1][0] = 0; LEDChannels[j1][1] = 0; LEDChannels[j1][2] = 0; } WriteLEDArray(); delay(rate); } } // Mapping functions int MapToTree(int row, int col, int red, int green, int blue) { int newindex = 51; if ((row == 0) || (row == 1)) { if (col == 0) { newindex = row;; } } else if (row == 2) { if (col < 3) { newindex = 4 - col; } } else if (row == 3) { if (col < 3) { newindex = col + 5; } } else if (row == 4) { if (col < 5) { newindex = 12 - col; } } else if (row == 5) { if (col < 5) { newindex = col + 13; } } else if (row == 6) { if (col < 5) { newindex = 22 - col; } } else if (row == 7) { if (col < 7) { newindex = col + 23; } } else if (row == 8) { if (col < 7) { newindex = 36 - col; } } else if (row == 9) { if (col < 7) { newindex = col + 37; } } else if (row == 10) { if (col < 7) { newindex = 50 - col; } } LEDChannels[50-newindex][0] = red; LEDChannels[50-newindex][1] = green; LEDChannels[50-newindex][2] = blue; } void ctree(int rate, int cycles) { for (int i = 0; i < cycles; i++) { fillall(0, 800, 0); MapToTree(0,0,1023,800,0); for (int j = 0; j < 10; j++) { int randval = random(0,50); LEDChannels[randval][0] = 1023; LEDChannels[randval][1] = 0; LEDChannels[randval][2] = 0; } WriteLEDArray(); delay(rate); } } void filltree(int rate, int red, int green, int blue) { for (int i = 10; i >= 0; i--) { for (int j = 0; j < treerows[i]; j++) { MapToTree(i, j, red, green, blue); } WriteLEDArray(); delay(rate); } } RGBType GradientMap(int color) { float GradientRed = 0; float GradientGreen = 0; float GradientBlue = 0; int GradientIndex1 = color/32; int GradientIndex2 = color/32 + 1; int GradientRatio = color % 32; if (GradientIndex2 > 7) GradientIndex2 = 0; GradientRed = (PlasmaGradient[GradientIndex1][0] * (32 - GradientRatio) + PlasmaGradient[GradientIndex2][0] * (GradientRatio)) / 8160.0; GradientGreen = (PlasmaGradient[GradientIndex1][1] * (32 - GradientRatio) + PlasmaGradient[GradientIndex2][1] * (GradientRatio)) / 8160.0; GradientBlue = (PlasmaGradient[GradientIndex1][2] * (32 - GradientRatio) + PlasmaGradient[GradientIndex2][2] * (GradientRatio)) / 8160.0; RGBType RGB; RETURN_RGB(GradientRed, GradientGreen, GradientBlue); } RGBType HSV_to_RGB( HSVType HSV ) { // H is given on [0, 6] or UNDEFINED. S and V are given on [0, 1]. // RGB are each returned on [0, 1]. float h = HSV.H, s = HSV.S, v = HSV.V, m, n, f; int i; RGBType RGB; if (h == UNDEFINED) RETURN_RGB(v, v, v); i = floor(h); f = h - i; if ( !(i&1) ) f = 1 - f; // if i is even m = v * (1 - s); n = v * (1 - s * f); switch (i) { case 6: case 0: RETURN_RGB(v, n, m); case 1: RETURN_RGB(n, v, m); case 2: RETURN_RGB(m, v, n) case 3: RETURN_RGB(m, n, v); case 4: RETURN_RGB(n, m, v); case 5: RETURN_RGB(v, m, n); } } RGBType Plasma(int i, float cntOffset, float frequency, float centerX) { for(int x = 0; x < NumLEDs; x++) { float color = ((1.0 + sin(abs(i - x - centerX) / frequency + cntOffset*3.14159/180)))/2.0; HSVType plasHSV; plasHSV.H = color*6.0; plasHSV.S = 1.0; plasHSV.V = 1.0; return HSV_to_RGB(plasHSV); } } int cntHeart = 0; float cntHeartOffset = 0; // counter for rotating plasma void runAnimLoops() { RGBType mapColor; cntHeartOffset += 4; if (cntHeartOffset >= 360) cntHeartOffset = 0; for (int i = 0; i < NumLEDs; i++) { mapColor = Plasma(i, cntHeartOffset, 8.0, 0); LEDChannels[i][0] = mapColor.R*1023; LEDChannels[i][1] = mapColor.G*1023; LEDChannels[i][2] = mapColor.B*1023; } WriteLEDArray(); } void loop() { int reps; switch (random(0,6)) { case 0: for(int i = 0; i<(random(300,600)); i++) { runAnimLoops(); } break; case 1: blinkall(random(15,45), random(5,20), random(0,10)*100, random(0,10)*100, random(0,10)*100); break; case 2: noisy(random(10,30), random(30,120), random(0,10)*100, random(0,10)*100, random(0,10)*100); break; case 3: randomstrobe(random(1,25),random(50,130)); break; case 4: reps = random(3,10); for (int i = 0; i < reps; i++) { newred = random(10)*20; newgreen = random(10)*20; newblue = random(10)*20; fadeall(20,oldred,oldgreen,oldblue,newred,newgreen,newblue); oldred = newred; oldgreen = newgreen; oldblue = newblue; } break; case 5: reps = random(3,10); for(int i = 0; i < reps; i++) { fadeall(10,255,0,0,0,255,0); delay(250); fadeall(10,0,255,0,0,0,255); delay(250); fadeall(10,0,0,255,255,0,0); delay(250); } fadeall(40,255,0,0,0,0,0); break; case 6: ctree(100,400); break; case 7: for (int i = 0; i < 5; i++) { filltree(25, 1023, 0, 0); filltree(25, 1023, 1023, 0); filltree(25, 0, 1023, 0); filltree(25, 0, 1023, 1023); filltree(25, 0, 0, 1023); filltree(25, 1023, 0, 1023); filltree(25, 1023, 1023, 1023); filltree(25, 0, 0, 0); } break; } delay(1); } Make a new tab in your sketch, name it rgbdefs.h, and put this code inside: #define RETURN_HSV(h, s, v) {HSV.H = h; HSV.S = s; HSV.V = v; return HSV;} Building the pole: It's not too difficult. We used two pieces of 10 foot EMT conduit from Home Depot. The coupling isn't very strong, so we braced it with a 1x2 and some gaffer tape...you can't really see it in the dark. :) The most important thing to set the bottom end of the pole carefully. You don't want it to kick out at any time. Put in inside the center of an old tire, jam it deep into the ground, etc. We strapped it to the side of an existing brick fence. Next, you'll need guy wires...nothing too strong needed, just some twine. Use two or three guy wires to stabilize the top of the pole. You'll also need a pulley at the top, and some light rope or strong twine. Suspend the ball from the twine and make sure you can easily raise and lower it before setting everything up. You won't have time to fix it if there's a problem at midnight! Wiring and Power: Depending on the LED modules used, the power supply can vary. If using Satellite 001's, you would be using a 12V supply. If using ShiftBrites or MegaBrites, you would use between 5.5V and 9V. A Meanwell 7.5V, 8A supply is a pretty commonly available option, plenty of power. Get your LEDs and Arduino all fastened inside the ball, leaving only a power cable running down from the ball to your power supply. Usually, you will want the ball hanging from the rope, and the power cable hanging straight below the ball to the ground. If you fasten the power cable well enough, it's also something extra to pull on in case the ball doesn't want to come down by gravity alone. Dropping the Ball: Maybe you want to set up an automatic motorized timer system, but the simplest things often work best! Plus, you're only doing this once a year. Let other people count off the seconds, and start lowering the ball. Don't let go of the rope, and pace yourself so the ball reaches the ground at the zero count. Hurray, it's a new year!
Submitted by Garrett on Fri, 12/30/2011 - 03:17. |
Recent Comments
|
I don't think that is what
I don't think that is what is meant by ringing in the new year. How did you get it? I'm pretty sure I've had it at least three times--twice after eating raw food (fruit, sandwich) from local non-chain restaurants which I never revisited, and once in France. Was it the Camembert or did the hotel staff dip the drinking glasses in horse manure? Believe me, it's a tossup. Oh, sorry, bad choice of words....