"Blinker!" Megabrite/Shifty VU LED Wall Project

This is my first Arduino (or even electronics) project ever. It uses an Arduino Mega, 65 macetech MegaBrites, an 4x20 LCD display, 4 rotary encoders, a power supply and a lot of wire.

I started working on this back in September and the first version was a balloon blob that we used for a Halloween party. It had 13 MegaBrites which were controlled from a panel. It was a total mess. The whole thing came down halfway through the night. The electrical connections were unstable which forced me to have to fiddle with it a few times - in the dark - pain in the ass. But it helped me figure out where the weaknesses were and I was able to correct them for the wall panel version.

I can't post any links or images of my project until I've made at least one post. Hopefully I can post in my reply to this.

Video of control panel / LCD display

I finished the four 16 light panels a couple days before New Year's Eve and managed to get it hung in our room at a warehouse party in Toronto. I spent so much time building the panels and working out the base functions of the controller that I didn't get much time to write programs. In total there are currently 12 selectable programs - two chaser effect variants, a couple randoms, two VUs, blink and a couple plasma effects. I'm working on more effects and think I could eventually have around 40 - 50 - most of them "beat" effected.

Video of Volume Units program (makes pretty basic use of the Shifty VU)


Video of Random program

And a few build pics:

I have some questions about performance and this is probably just my inexperience with the platform and with code optimization so hopefully it can be made faster.

  1. When I went from 48 lights to 64 lights I saw a huge decrease in performance. From ~ 25ms / loop ~ 45ms / loop. I probably just need to optimize my code but are there better platforms to be working on for this other than arduino? It would be nice to get refreshes down below 20ms/loop.

  2. My 4X20 green LCD screen needs to have delays added whenever a refresh is done otherwise it goes all wonky and this slows the whole light panel down when I'm changing parameters in the program because it's updating the LCD screen. I guess the same as question 1 – are there any platforms that are multi-threaded or is there a way to get around this "refresh" wait?

I eventually want to move up to 128 lights but I'm not sure how I'm going to do it with the current performance I'm getting. Is it possible to run arduino's in parallel maybe? I could hook up 2 or 4 and have them work together maybe.

I dunno. Let me know your thoughts.

Thanks,
R

One final thing. This forum and Garrett Mace were an amazing resource for me during the entire development of this project. I couldn't have done it without you guys. Thanks!

Looks great!

I've been scratching my head over the performance change, and I'll probably ask to see your code at some point. But it strikes me that algorithms don't always scale linearly. For example, maybe moving to 64 lights meant that some of your mathematics got pushed from 8-bit math into 16-bit, or 16-bit into 32-bit, etc. Or you crossed a barrier in how much you can get done between timer interrupts, so it has to put what you were doing on the stack and get back to it later.

If I take a look at some of your code I might have a few ideas to streamline some of the processing. The 80 MegaBrite wall we made seemed to update pretty fast, I remember something like 30ms, though I didn't need that much.

Please put the code into code bracket things---the button looks like a # in the post editing page.
It looks a lot nicer that way, and you don't have to do a bunch of scrolling.

#include "Functions.h"
#include "Color.h"
#include "Controls.h"
#include "MegaBrite.h"
#include "Screen.h"
#include "Programs.h"
#include "ShiftyVU.h"

MegaBrite megaBrite;
ShiftyVU shifty;
Programs progs;
Screen screen;
Controls controls(progs);

int mode = 0; //0 - off, 1 - on, 2 - pulse

double beatSegments[9] = {1.00/16.00,1.00/8.00,1.00/4.00,1.00/2.00,1.00,2.00,4.00,8.00,16.00};

int currentProgram = 0;
int effectTime = 0;
int millisecondsPerBeat = 60000 / 128;
int effectDuration = millisecondsPerBeat;
int pulseDuration = millisecondsPerBeat * beatSegments[2];
int currentLED = 0;
int color;
int fadeArray[NUMLEDS];
bool effecting = false;
bool pulsing = false;
long refreshDif = 0;
long refreshTimer = millis();
long timer;
long ms;
long palette[PALETTEARRAY];
long plasma[SCREENHEIGHT][SCREENWIDTH];
ColorRGB colorFrom;
ColorRGB colorOff = {0, 0, 0};

void setup() {
   Serial.begin(19200);
   randomSeed(analogRead(0));

   controls.setup();

   for(int x = 0; x < NUMLEDS; x++)
       fadeArray[x] = -1;
}

void loop() {

   refreshDif = millis() - refreshTimer;
   refreshTimer = millis();

   //check if param positions have changed and update LCD if so
   controls.checkKnobs();

   //check if bpm has changed
   if(controls.bpmChanged())
       millisecondsPerBeat = (int)(60000.00 / controls.bpm);

   if(controls.programChanged()) {
       
       progs.currentProgram(controls.program);
       currentProgram = progs.currentProgram();

       //generate the palette
       ColorRGB rgb;
       ColorHSV hsv;
       for(int x = 0; x < 256; x++)
       {
           ColorHSV hsv = {x, 255, 255};
           rgb = HSVtoRGB(hsv);
           palette[x] = RGBtoInt(rgb);
       }

       //setup for next effect
       switch (currentProgram) {
           //Beat
           case PROGRAM_VU_CYCLE:
               {
                   color = 0;
                   effectDuration = progs.param1();
                   break;
               }
           //Beat
           case PROGRAM_VU_FIXED:
               {
                   color = progs.param1();
                   break;
               }
           //Sine Plasma
           case PROGRAM_PLASMA_FIXED:
           case PROGRAM_PLASMA_CYCLE:
               {
                   color = -1;
                   effectDuration = progs.param2();

                   //generate the plasma once
                   for(int x = 0; x < SCREENHEIGHT; x++)
                   {
                       for(int y = 0; y < SCREENWIDTH; y++)
                       {
                           long color;
                           //straight line / b & w
                           //color = long(128.0 + (128.0 * sin(y / 8.0)));
                           //plasma like
                           color = long(
                                 128.0 + (128.0 * sin(x / 8.0))
                               + 128.0 + (128.0 * sin(y / -16.0))
                               + 128.0 + (128.0 * sin(sqrt(double((x - 128 / 2.0)* (x - 128 / 2.0) + (y - 6 / 2.0) * (y - 6 / 2.0))) / 8.0))
                               + 128.0 + (128.0 * sin(sqrt(double(x * x + y * y)) / -16.0))
                           ) / 4;
                           plasma[x][y] = color;
                       }
                   }
                   break;
               }
           //Chaser
           case PROGRAM_CHASER_FIXED:
           case PROGRAM_CHASER_CYCLE:
               {
                   effectDuration = millisecondsPerBeat * beatSegments[progs.param2()];
                   pulseDuration = millisecondsPerBeat * beatSegments[progs.param3()];
                   break;
               }
           //Random Colour
           case PROGRAM_RANDOM_FIXED:
           case PROGRAM_RANDOM_CYCLE:
           case PROGRAM_RANDOM_RAND:
               {
                   effectDuration = millisecondsPerBeat * beatSegments[progs.param2()];
                   pulseDuration = millisecondsPerBeat * beatSegments[progs.param3()];
                   break;
               }
           //Blink
           case PROGRAM_BLINK:
               {
                   color = progs.param1();
                   effectDuration = millisecondsPerBeat * beatSegments[progs.param2()];
                   pulseDuration = millisecondsPerBeat * beatSegments[progs.param3()];
                   break;
               }
           //Test
           case PROGRAM_TEST_FIXED:
           case PROGRAM_TEST_CYCLE:
               {
                   effectDuration = millisecondsPerBeat * beatSegments[progs.param2()];
                   pulseDuration = millisecondsPerBeat * beatSegments[progs.param3()];
                   break;
               }
           //Nothing - Program Not Found - This shouldn't happen
           default:
               {
                   break;
               }
       }

       pulsing = true;

       ms = millis();
   }

   //check if mode has been switched to 0 (OFF)
   if(controls.modeChanged()) {
       mode = controls.mode();
       if(mode==0)
       {
           effecting = true;
           effectDuration = (millisecondsPerBeat) * beatSegments[4];
           for (int i = 0; i < NUMLEDS; i++) {
               megaBrite.ledChannels(i, COLORFROM, megaBrite.ledChannels(i, COLORCURRENT));
           }
       }
   }
   if(mode > 0) {
       switch (currentProgram) {
           //Beat
           case PROGRAM_VU_FIXED:
           case PROGRAM_VU_CYCLE:
           {
               if(currentProgram==PROGRAM_VU_CYCLE)
               {
                   effectDuration = progs.param1();
                   color = color + effectDuration;
               }
               else if(currentProgram==PROGRAM_VU_FIXED)
               {
                   color = progs.param1();
               }
               if(color>PALETTEARRAY) {
                   color = 0;
               }

               //holy nuts i hate these strongly typed variables
               float vu = (float)shifty.adcAvg() / (float)1023;
               double temp = ((double)vu*(double)100);
               double vuRatio = temp * temp * temp * temp / 100000000;

               colorFrom = InttoRGB(palette[color]);
               colorFrom.r = int((double)colorFrom.r * vuRatio);
               colorFrom.g = int((double)colorFrom.g * vuRatio);
               colorFrom.b = int((double)colorFrom.b * vuRatio);

               for (int i = 0; i < NUMLEDS; i++) {
                   megaBrite.ledChannels(i, COLORCURRENT, colorFrom);
               }
               
               break;
           }
           //Sine Plasma
           case PROGRAM_PLASMA_FIXED:
           case PROGRAM_PLASMA_CYCLE:
           {
               if(color != progs.param1() && currentProgram==PROGRAM_PLASMA_FIXED)
               {
                   color = progs.param1();
                   //generate the palette
                   ColorRGB rgb;
                   ColorHSV hsv;
                   for(int x = 0; x < 256; x++)
                   {
                       //use HSVtoRGB to vary the Hue of the color through the palette
                       ColorHSV hsv = {color, 255, x};
                       rgb = HSVtoRGB(hsv);
                       palette[x] = RGBtoInt(rgb);
                   }
               }
               effectDuration = progs.param2();

               //the parameter to shift the palette varies with time
               long paletteShift;
               paletteShift = (long)(millis() / (double)effectDuration);

               ColorRGB current;

               //draw every pixel again, with the shifted palette color
               for(int x = 0; x < SCREENHEIGHT; x++)
               {
                   for(int y = 0; y < SCREENWIDTH; y++)
                   {
                       current = InttoRGB(palette[((long)plasma[x][y] + paletteShift) % 256]);
                       megaBrite.ledChannels(screen.pixel[x][y], COLORCURRENT, current);
                   }
               }
               //shows the LED in the panel
               megaBrite.ledChannels(NUMLEDS - 1, COLORCURRENT, current);
               break;
           }
           //Chaser
           case PROGRAM_CHASER_CYCLE:
           case PROGRAM_CHASER_FIXED:
           {
               if(currentProgram==PROGRAM_CHASER_CYCLE)
               {
                   color = color + progs.param1();
                   if(color>PALETTEARRAY) {
                       color = 0;
                   }
               }
               else
               {
                   color = progs.param1();
               }

               effectDuration = (millisecondsPerBeat) * beatSegments[progs.param2()];
               pulseDuration = (millisecondsPerBeat) * beatSegments[progs.param3()];

               timer = millis();
               effectTime = timer - ms;
               if(effectTime>effectDuration) {
                   ms = millis();
                   currentLED++;
                   if(currentLED>=SCREENWIDTH)
                       currentLED = 0;

                   for(int x = 0; x < SCREENHEIGHT; x++)
                   {
                       for(int y = 0; y < SCREENWIDTH; y++)
                       {
                           if(y==currentLED)
                           {
                               fadeArray[screen.pixel[x][y]] = 0;
                               megaBrite.ledChannels(screen.pixel[x][y], COLORFROM, InttoRGB(palette[color]));
                           }
                       }
                   }
               }

               for (int i = 0; i < NUMLEDS; i++) {
                   if(fadeArray[i]>-1) {
                       if(fadeArray[i]>pulseDuration) {
                           fadeArray[i] = pulseDuration;
                       }
                       if(fadeArray[i]<=pulseDuration) {
                           ColorRGB current;
                           current.r = quadraticEaseOut((long)(pulseDuration - fadeArray[i]), megaBrite.ledChannels(i, COLORFROM).r, colorOff.r, (long)pulseDuration);
                           current.g = quadraticEaseOut((long)(pulseDuration - fadeArray[i]), megaBrite.ledChannels(i, COLORFROM).g, colorOff.g, (long)pulseDuration);
                           current.b = quadraticEaseOut((long)(pulseDuration - fadeArray[i]), megaBrite.ledChannels(i, COLORFROM).b, colorOff.b, (long)pulseDuration);
                           megaBrite.ledChannels(i, COLORCURRENT, current);
                       }
                       if(fadeArray[i]==pulseDuration) {
                           fadeArray[i] = -1;
                       } else {
                           fadeArray[i] += refreshDif;
                       }
                   }
               }

               break;
           }
           //Random Colour
           case PROGRAM_RANDOM_FIXED:
           case PROGRAM_RANDOM_CYCLE:
           case PROGRAM_RANDOM_RAND:
           {
               currentLED = progs.param1();
               if(currentProgram==PROGRAM_RANDOM_FIXED)
               {
                   color = currentLED;
                   currentLED = 2;
               }
               else if(currentProgram==PROGRAM_RANDOM_CYCLE)
               {
                   color = color + (currentLED);
                   currentLED = 2;
                   if(color>PALETTEARRAY) {
                       color = 0;
                   }
               }
               else {
                   color = random(256);
               }
               effectDuration = (millisecondsPerBeat) * beatSegments[progs.param2()];
               pulseDuration = (millisecondsPerBeat) * beatSegments[progs.param3()];
               timer = millis();
               effectTime = timer - ms;
               if(effectTime>effectDuration) {
                   ms = millis();
                   for (int i = 0; i < currentLED; i++)
                   {
                       int newRandom = random(NUMLEDS);
                       fadeArray[newRandom] = 0;

                       megaBrite.ledChannels(newRandom, COLORFROM, InttoRGB(palette[color]));
                   }
               }
               for (int i = 0; i < NUMLEDS; i++) {
                   if(fadeArray[i]>-1) {
                       if(fadeArray[i]>pulseDuration) {
                           fadeArray[i] = pulseDuration;
                       }
                       if(fadeArray[i]<=pulseDuration) {
                           ColorRGB current;
                           current.r = quadraticEaseOut((long)(pulseDuration - fadeArray[i]), megaBrite.ledChannels(i, COLORFROM).r, colorOff.r, (long)pulseDuration);
                           current.g = quadraticEaseOut((long)(pulseDuration - fadeArray[i]), megaBrite.ledChannels(i, COLORFROM).g, colorOff.g, (long)pulseDuration);
                           current.b = quadraticEaseOut((long)(pulseDuration - fadeArray[i]), megaBrite.ledChannels(i, COLORFROM).b, colorOff.b, (long)pulseDuration);
                           megaBrite.ledChannels(i, COLORCURRENT, current);
                       }
                       if(fadeArray[i]==pulseDuration) {
                           fadeArray[i] = -1;
                       } else {
                           fadeArray[i] += refreshDif;
                       }
                   }
               }
               
               break;
           }
           //Nothing - Program Not Found - This shouldn't happen
           default:
           {
               break;
           }
       }
   }
   //None/Off
   else
   {
       if(effecting) {
           timer = millis();
           effectTime = timer - ms;
           if(effectTime>effectDuration) {
               effecting = false;
               effectTime = effectDuration;
           }

           for (int i = 0; i < NUMLEDS; i++) {
               ColorRGB current;
               current.r = quadraticEaseOut((long)(effectDuration - effectTime), megaBrite.ledChannels(i, COLORFROM).r, 0, (long)pulseDuration);
               current.g = quadraticEaseOut((long)(effectDuration - effectTime), megaBrite.ledChannels(i, COLORFROM).g, 0, (long)pulseDuration);
               current.b = quadraticEaseOut((long)(effectDuration - effectTime), megaBrite.ledChannels(i, COLORFROM).b, 0, (long)pulseDuration);
               megaBrite.ledChannels(i, COLORCURRENT, current);
           }
       }
   }

   megaBrite.writeLEDArray();
   //delayMicroseconds(50);
 
}

Here's a full demo of the lights and the current programs:

you can download a higher quality version of this video here - http://www.djmikeryan.com/blinker/blinker.mkv - it's 235 MB

I really like it, I can even think of this as purposes as home lighting (of which I regularly looking around for) Was thinking of fixing something myself, but this is certainly an inspiration, you have day to day lighting and even some party lighting for when you are having a party @ home

Nice work! Man, it looks really professional too.

What are the black objects attached to the front?

Hi djmikeryan,

Good work on this project! I'm putting together a similar project, and would like to ask you a few questions about some of the issues you've probably solved already.
I started a thread for my project at...
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1278852236
You can find specifics about what I wanna do there, but a few questions for you here...

What's the most MegaBrites you've linked together in one chain? I plan to have 170 per chain. Do you think that's possible without latency problems?
I plan to show video on my setup, so a good refresh rate is crucial. Would I be able to refresh the whole chain at at least 25fps?

Cheers! :slight_smile:

Hi, I love your design and would like to add DMX capability to it. Would you please be so kind to share the complete code with us?
Thanks in advanced.
Siliconsoul.

You can change some things in the code, things like ints storing very low values, an int takes 16 bits, and doing maths on then take longer than doing maths in a uint8_t that is only 8 bits and can have a max value of 255, see if you can use it instead, and using ints over longs and things like those.

Changing the values to suite DMX is no problem. I was actually talking about the include part:
#include "Functions.h"
#include "Color.h"
#include "Controls.h"
#include "MegaBrite.h"
#include "Screen.h"
#include "Programs.h"
#include "ShiftyVU.h"

Heh, add a spectrum analyzer so it does stuff to the music:
http://bliptronics.com/item.aspx?ItemID=116

(Yeh, shameless plug, but it would look so good.....:wink: )

Still trying to work around this but without avail.

Seems like this thread kinda died... -.-