Categories
Arduino Electronics Programming

RGB Led Light Panel

Original Project Thread: /https://www.aussiearcade.com/topic/96389-rgb-led-light-panel/

I’d seen some examples of similar things like this before so started doing some research on what it takes to make something like it. I tend to be obsessive when I start something and went all in. I based my project on the one outlined here: https://www.brainy-bits.com/post/mak…h-256-rgb-leds

Like the author above I made mine a 16 x 16 array which is the general size of most early arcade sprites until tech got better and it went to 32 x 32 and then even more. 16 x 16 was practical from a sizing perspective and especially since this was my first attempt. You’re not limited in your sizing but would need to modify your materials list to suit anything different. That gives a whopping 256 individually addressable lights.

Materials List
  • 1 x Arduino Mega 2560 (You can use a UNO but get far less storage so your call)
  • 1 x 5 meter Roll of WS2812B Non-Waterproof LED Strip (These are individually addressable)
  • 1 x 240v to DC 5V 10A power Supply
  • 1 x Kmart Anko 12″ x 12″ Shadow Box Frame
  • 1 x Can of Frosted Glass Spraypaint
  • 1 x Sheet of 5mm Art Foamboard (A1 size)
  • 3 x 20cm Male-Male Dupont Wires for Breadboard (Optional)
  • 15 x LED Strip Light Connector Cables (Optional)
Arduino Mega 2560:

There are projects out there that use a Raspberry Pi and other types of boards but the Arduino is really cheap and reliable. Also the code uses Flash Ram to store the data. I used the Mega as it has more memory than the UNO. Differences between the two:

UNO Flash 32k bytes compared to only 2k bytes of SRAM
The Arduino MEGA has 256k bytes and 8k bytes of SRAM

Whatever your choice, you will need to modify your code to suit.

Power Supply

The 5 volt 10 Amp power supply is pretty beefy but given the large number of lights to be powered it’s required.

The generally accepted method for calculating power requirements is:

You need to know the required current (Amps or milliamps) for the LED’s. Typically, you can estimate 20mA (0.02 Amps) for one regular LED or 60mA for an RGB LED.

In this build we have 256 LED’s. If they’re all lit at 100% brightness and white in color, then the total power consumption would be around 16A. (60mA x 256) / 100 = 15.36Amps. Remember these are 5 volt NOT 12 volt led’s

Since I’m only using 15% brightness and not all the LED’s will be lit up white at any time, I’m using a 5V 10A power supply which is more than enough for my purposes. (I’m sure people with far more experience can chime in here)

Panel Cutting

If you’re lucky enough to have a router, cnc or laser cutter you could create your lighting matrix board using those. You could even in theory print one with a 3D Printer. Either way, to look good it needs to be very accurate as you’ll be lighting this from behind so every defect is immediately noticeable.

I used a laser cutter but its only a low power (15 watt) model. Fortunately the work area is 400mm x 430mm and I needed 306mm x 306mm so perfect. I originally wanted to use soft timber for my array but my cutter is too low powered and the amount of passes required scorched the timber too much. A little experimentation with settings and materials found that black 5mm foam board works perfectly.

I used Lightburn Software to create the cutting templates. Lightburn has a handy array creation function, so I set two burn paths. One for the full square and then another for the 256 squares representing CRT pixels.

Note I said two templates as I made one for mounting the LED’s and a top one for the CRT pixel representation just like in the linked original project although he has 3 layers and I only have 2. The Anko Frame is only so deep but it still worked very well.

LED Bottom Template

The squares are 6mm to seat the LED’ in snugly.

Since the core is foam and foam contracts under heat I had to experiment on distance between each square on the top pixel layer so that it didn’t burn through to the other side. If this happens light will bleed into the cell next to it ruining the effect.

Distances ended up being 14.5mm squares with a distance between the centre of each LED being 16.8mm. This is the STD distance between each LED in a 5 meter WS2812B roll that I’ve found. Wall thickness is roughly 2.3mm.

Based on my laser cutter, material and settings each panel took 1 hour to cut. It is set and forget though 😉

Here’s the cutter in action!

LED Top Panel

Here’s the final result!

Construction

The LED strips are 3 pin. 1 x 5volt, 1 x Ground and 1 x data. This is the WS2812B spec. One downfall of this is that if a single LED or resistor fails on a strip, anything after it will stop working. The WS2813 spec LED’s avoid this by having 4 pins, doubling the data link. If a unit fails, it juts won’t light up anymore but the rest will. This of course would still be insightly but at least it will still work.

For construction, you need to cut the LED strip into lengths of 16 LED’s. The strips have 3 copper points between each light. You cut down the middle for each cut giving you enough surface area to achieve a connection.

You’ll layout and connect the strips in a backwards and forwards pattern snaking down the panel until complete. Like below.

————–>

<————–

————–>

<————–

Now if you’re like me and solder like a gorilla, the optional quick connects are recommended. This allows a solder free connection from one link to the next.

Here is the result.

Since the Arduino is powered by 5 volt USB, like the project site linked I spliced a spare old USB cable to fit both into the Arduino AND the power supply adapter provided. The power supply can now power both the Arduino and the light strips at the same time.

I taped down the strips as I went as they tend to curl up, especially when the joiners are attached. I ended up saving the tape as a good solution to keeping it all in place.

Connect the 5volt and Ground wires to the provided harness connector on the 1st strip. The data wire, goes straight into Pin 3 on the Arduino which is the data output pin we’re using in the program.

Shadow Box

The shadow box is essentially a deep photo frame. I picked a Kmart brand Anko 12″ (30cm) x 12″ version

Take the glass out and spray one side with the frosted glass spray paint. Only give it a light coat so that it doesn’t run and coats evenly. I gave it 4 coats and waited an hour between each. This gave the perfect result.

Once ready assemble all the parts into the frame.

Frosted Glass first, followed by Top Large Grid, then the RGB LED grid with everything attached. You can then re-attach the back with some padding to hold everything in place if required.

I still need to work out the best method for being having the power supply cable neatly integrated.

Programming

Last but not least we need to provide the Ardunio with something to display onto your brand new light panel. Since my original intent was classic arcade game sprites, my goal was to create as many as I liked.

The code uses an array of #rgb colour values like is often used for building websites. You define the colour for each LED. You can get tricky with other values such as brightness, repetition, patterns, fading etc but lets start with a static colour.

Looking at the code below you can see the RGB values organised in a 16 x 16 array of 256 values.

Colour Codes:

0x000000 = Black or for RBG LEDS turn off.
0xcccccc = Silver or Grey
0x0066cc = A shade of blue
0xff0000 = A shade of Red

// Create the array of retro arcade characters and store it in Flash memory
const long DigDug01[] PROGMEM =
{
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000,
0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x0066cc, 0x000000, 0x0066cc, 0x000000, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000,
0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0x000000, 0x0066cc, 0x000000, 0x0066cc, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000,
0x000000, 0x000000, 0x000000, 0x0066cc, 0x0066cc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0xff0000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0xff0000, 0xff0000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x0066cc, 0x0066cc, 0xcccccc, 0x000000, 0x000000,
0x000000, 0xff0000, 0xff0000, 0xff0000, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0x000000,
0x000000, 0x000000, 0xff0000, 0xff0000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0x0066cc, 0x0066cc, 0x0066cc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0xff0000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0x000000, 0x000000,
0x000000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000
};

However making these by hand is not only difficult and confusing but damn tedious. So I started with sprite sheets I sourced from https://www.spriters-resource.com/arcade/

I thought the grid pattern mandated an easy solution in using Excel to create the sprite maps. So using the sprite sheets as a reference I then created using colours in excel, replicas of the sprites:

But then I wanted an automated way to generate the RGB code values above so created a Macro that grabs the referenced cell colour and displays it. I also used the Excel CONCAT formula to add the 0x prefix and the trailing comma so all I had to due was cut and paste the values straight into the Arduino IDE.

Now I can get really tricky and make the spreadsheet create the entire needed code but I’m not up to that yet, so in the meantime it’s semi manual. The sprites aren’t much other than a still pictures sequenced to simulate motion. This technique uses stop motion effectively so for any movements you need to create a sprite for each change. String them together much like a cartoon and you have your movement.

In the Dig Dug example there are only 2 sprite sheets I’ve created and I alternate between them to simulate movement exactly how it looks on an arcade monitor. In the code I call Sprite Sheet 1 to display and then sprite sheet 2 and tell it to repeat 8 times.

// Put DigDug first frame
for(int passtime = 0; passtime < 8; passtime++) { // The value 8 here can be changed to whatever you like but here is says run this entire sequence of code 8 times.

FastLED.clear();
for(int i = 0; i < NUM_LEDS; i++) { // Telling the program to use the number of LEDS stated at the start of the program
leds[i] = pgm_read_dword(&(DigDug01[i])); // DigDug01 here is the 1st Sprite Array Name above.
}

FastLED.show(); // Display the lights
delay(250); // 250 Millisecond delay before moving to the next below

// Put DigDug second frame
FastLED.clear(); // Clear the lights turning them off in readyness for the next frame
for(int i = 0; i < NUM_LEDS; i++) {
leds[i] = pgm_read_dword(&(DigDug02[i])); //DigDug01 here is the 2nd Sprite Array Name above
}

FastLED.show();
delay(250); 250 Millisecond delay before moving to the next
}
Important Point

The Code above is going to use those colour values in strict order. Now since you HAVE to string your lights in a single unbroken chain, they will display in that order but think about it. You snaked the LEDS backwards and forwards so the true required order is not how the colours are displayed in the Excel picture. They must be in the same zig zagging pattern, so the formulas I wrote in excel do the same thing. If you look closely you can tell that Row 1 values run left to right –> and row 2 values run right to left <–

Effectively every second row is displayed in reverse!

Full code snippet just for the Dig Dug animation:

/* Arduino 256 RGB LEDs Matrix Animation Frame
* Using WS2812 LED Strips

Created by Yvan / https://Brainy-Bits.com

This code is in the public domain...

You can: copy it, use it, modify it, share it or just plain ignore it!
Thx!

*/

#include <avr/pgmspace.h> // Needed to store stuff in Flash using PROGMEM
#include "FastLED.h" // Fastled library to control the LEDs

// How many leds are connected?
#define NUM_LEDS 256

// Define the Data Pin
#define DATA_PIN 3 // Connected to the data pin of the first LED strip

// Define the array of leds
CRGB leds[NUM_LEDS];

// Create the array of retro arcade characters and store it in Flash memory
const long DigDug01[] PROGMEM =
{
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000,
0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x0066cc, 0x000000, 0x0066cc, 0x000000, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000,
0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0x000000, 0x0066cc, 0x000000, 0x0066cc, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000,
0x000000, 0x000000, 0x000000, 0x0066cc, 0x0066cc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0xff0000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0xff0000, 0xff0000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x0066cc, 0x0066cc, 0xcccccc, 0x000000, 0x000000,
0x000000, 0xff0000, 0xff0000, 0xff0000, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0x000000,
0x000000, 0x000000, 0xff0000, 0xff0000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0x0066cc, 0x0066cc, 0x0066cc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0xff0000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0x000000, 0x000000,
0x000000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000
};

const long DigDug02[] PROGMEM =
{
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000,
0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x0066cc, 0x000000, 0x0066cc, 0x000000, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000,
0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0x000000, 0x0066cc, 0x000000, 0x0066cc, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000,
0x000000, 0x000000, 0x000000, 0x0066cc, 0x0066cc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0xff0000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0xff0000, 0xff0000, 0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x0066cc, 0x0066cc, 0xcccccc, 0x000000, 0x000000,
0x000000, 0xff0000, 0xff0000, 0xff0000, 0x0066cc, 0x0066cc, 0x0066cc, 0x0066cc, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0xff0000, 0x000000,
0x000000, 0x000000, 0xff0000, 0xff0000, 0x000000, 0x000000, 0x000000, 0xcccccc, 0x0066cc, 0x0066cc, 0x0066cc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000, 0x000000, 0xff0000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xcccccc, 0xcccccc, 0xcccccc, 0xcccccc, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000
};

void setup() {
FastLED.addLeds<NEOPIXEL,DATA_PIN>(leds, NUM_LEDS); // Init of the Fastled library
FastLED.setBrightness(15);
}

void loop() {

// Put DigDug first frame
for(int passtime = 0; passtime < 8; passtime++) {

FastLED.clear();
for(int i = 0; i < NUM_LEDS; i++) {
leds[i] = pgm_read_dword(&(DigDug01[i]));
}

FastLED.show();
delay(250);

// Put DigDug second frame
FastLED.clear();
for(int i = 0; i < NUM_LEDS; i++) {
leds[i] = pgm_read_dword(&(DigDug02[i]));
}

FastLED.show();
delay(250);
}
}

Connect your Arduino to your PC, upload this code, plug it back into your frame and power it on and voila!

Note I have not fixed and sealed the back yet so you’ll see some odd shapes and light seeping through on the edges but when fixed properly it looks fantastic.

Extras

One thing I have done is to source some WiFi integrated 2560’s as I want to be able to program them over my WiFi Network instead of USB. I have not started this portion of the project yet.

Audio is another optional accessory that I considered but I think whilst very doable may become annoying. You can do this and even add a volume control if you like.

WIP Result. I’ve created around 100 sprites so far 🙂