It's very common for tech companies to have booths at trade shows to show off what they have and how their products can benefit your application, and Digilent is no exception. With the ECEDHA conference coming up, Digilent Marketing Director Larissa Swanland decided we needed something big and flashy. We carry weatherproof strips of the WS2812 addressable LEDs in 1m lengths with 30 LEDs on each strip, so what better way to grab your attention than to build a display with 30 strips!
Yep, that's 900 individually addressable LEDs. This is gonna be awesome!
A small disclaimer - this was designed and built on the fly with some materials and tools at hand. That means that it is wide open for interpretation and redesign. The main challenge was figuring out the power for the LEDs, so do with it what you will, just be safe in your power connections and use of tools.
Step 1: Hardware
The initial design of this array is to play a version of Snake (see GIF at the top of the first page), though it will display any image that is 30X30 pixels. The list of materials and tools is related to that goal. It
- 30 1m strips of WS2812 addressable LEDs, 30 LEDs per meter
- chipKIT uC32 microcontroller
- 5V, 75A power supply - Each strip at 100% power (white light) required 1.4A, and 10 strips in parallel was consistent with that at 14A. 1.4*30=~42A. We went a little overboard with 75A, but at least we know we won't have power supply issues.
- various lengths of wire for connections - I went with 12-14 AWG wire, for reasons explained below.
For the hand controller:
- RJ45 Pmod connector pair
- 4 momentary push buttons
- 4 1k? resistors
- short wire lengths, solder, bare PCB, metal standoffs with screws, small acrylic panel
The optional materials are dependent on how you want to mount and display your array.
- 1/4" (6.35mm) clear, colorless acrylic sheet (plexiglass) - We want the display on top of a table at a booth, so it can't be opaque. I split the board into 3 panels with 10 strips each. Since I wanted the space between pixels to be the same in all directions, I measured the spacing between 10 LEDs and found it to be 13 1/8 " (33.3cm). Thus, 2 panels at 13 1/8" X 42" (33.3cm X 106.7cm) and the third panel 18" X 42" (45.7cm X 106.7cm). The third allows about 6" (15.2cm) for mounting the chipKIT, promotional materials, etc. I also needed some small scrap pieces, so ask your acrylic supplier if they have any they are willing to give you.
- 6 friction catches for cabinets. The spring was clipped to reduce the tension, making it easier to connect the panels
- super glue - hot glue will not stick to the weatherproof silicone sheath around the LED strips
- acrylic cement
- 1/" (6.35mm) particle board
- flat black enamel paint
The tools are more dependent on the mounting material chosen, but most will be needed however you decide to do it.
- saw - I used a band saw and a hand saw at various points
- sandpaper and sanding block
- drill press with 1/2" (12.7mm) and 1/8" (3.2mm) drill bits - a hand drill will work, but the holes are ugly
- rotary tool with cutoff wheel
Step 2: Designing
The data pin on each strip only runs in one direction, so I initially planned on one giant string of 900 LEDs. I first tested a few strips with a simple code to just turn them on at 100% white light, then strung them together end to end. That didn't work so well as the first strip used 1.4A but the last strip only received about 500mA, making it noticeably dimmer. Well, that's not going to work.
I decided to power the strips in parallel from two main bus lines on each panel, but then connect the data pins in series. The strips come with a connector on each end that connects power as well as data lines to allow for serial connection, but they also have a second set of bare wires for power connections. I used these bare wires to connect them in parallel to the bus lines across the bottom of the panel and the connectors to pass the data signal down the line. Since the data must be serial, the strips must be connected end to end. This means that when you look at the display, the data comes in from the lower left and runs up the first strip, then down the second, up the third, etc. in a zig-zag pattern across the board. As far as the power is concerned, the strips are totally ambiguous with how you connect the +5V and GND, so this worked perfectly. I built a proof of concept on the table with just ten strips and at full power it was really starting to hurt my eyes and I had to wear my sunglasses.
That was the hard part of the whole design, just getting the strips all powered evenly. The rest was just a matter of getting it all onto a board.
Step 3: Building
Using one strip as a guide, I marked all four edges of the acrylic with the gaps between each LED so that I could center each LED on the board where it needed to go to make a square array. I also spaced the first strip lengthwise so that the first and last LEDs on it were the same distance away from the bottom and top edges of the panel. The weatherproof casing and even the strips themselves are not all perfectly 1m in length, nor is the distance from the end of the strip to the first LED consistent between strips, so just lining up one end of each strip would not have resulted in a square array. I needed to use the LEDs themselves for spacing.
Once I determined the distance from the top and bottom, I marked where the wires were coming out of the end of the strip, but back in toward the center of the strip about 1/2" (12.7mm). The connectors for the data are about 1/2" wide, so the hole I would need to drill through the acrylic was going to be pretty big. By moving the hole back about 1/2" it allows for the strip to cover most of the hole, giving a cleaner look. I also used the standing drill press and some clamps to give a much cleaner cut to the holes. Trying to do it by hand on a test piece was not reliable and it looked terrible.
After the holes were drilled, it was time to lay down the strips and glue them in place. Using my edge marks as a guide for placement, I carefully laid out each strip on one board. I tested hot glue on a strip on a test piece and while it stuck fine to the acrylic, the silicone casing came right off the hot glue with very little effort. Since this board will be getting handled quite a bit, I thought it best to make sure the strips were well adhered to the board, so I tested some super glue and it worked perfectly. Some of the strips had a slight bulge or bend, so in addition to a dab of glue on each end and in the middle, these strips required small amounts of additional glue in strategic places to ensure that they were placed straight and square. After all the strips are straight and glued, the edge markings can be sanded off.
With the LEDs glued, I had to make and attach the power buses. For each bus, I used a length of 12AWG wire as long as the panel is wide plus enough to reach the power supply. This resulted in the panels all having different total lengths, but the part attached to each panel was identical. It was a little tedious, but I sliced and removed little gaps into the insulation of the bus wires at intervals of one LED, leaving the insulation in place between each connection. The wire was laid down on the back of the panel and the bare wires on the LED strips that had been pushed through from the front were measured, cut, stripped, and then soldered to the bus wire. Once all ten supply wires were soldered to the supply bus wire, the whole wire was glued down. This process was repeated for the ground bus, but the buses were placed so that the gaps were staggered between the supply and ground buses to minimize shorting. On the other end of the LED strips at the top of the panel, where there is no need for bus wires, the bare wires were trimmed as close to the silicone casing as possible to prevent shorting on that end. The bus wires and the AC supply cable were all fitted with spade connectors to connect to the terminal blocks on the 75A power supply.
Step 4: Building continued...
Next came the connectors between panels. I couldn't have any panel connectors on the front and I didn't want to use a draw latch on the back like the kind you see on watertight cases and such. With a draw latch only on the back, the panels would be pulled too tightly together in the back, creating a bowed effect to the whole display. This couldn't happen, so after some looking and a suggestion from the lady at the hardware store, I went with the tension latches. I used three latches per seam, so six in total. Now the issue was how to attach them to the panels. They came with screws of course, but these were way too long to use on 1/4" acrylic. After figuring out where to put them and trimming the internal spring to reduce the tension (I was afraid too much tension could break them off the acrylic), I drilled screw holes for the larger piece that contained the spring into the panel, just deep enough to not go all the way through the panel. I then used a rotary tool with a cutoff wheel to trim the screws so that they would not go through the acrylic panel. For the other piece of the latch, I had to build small blocks out of acrylic to attach them as they mount perpendicular to the panel. Using some acrylic cement and some scrap pieces of acrylic cut to fit the base of the smaller piece of the latch, these blocks were cemented in place on the back of the panel. I then drilled holes into these blocks and used screws, slightly trimmed this time, to attach the second piece to the adjacent panel. So far this mounting solution has worked, though after some handling it is possible that these mounting blocks may break off of the panels.
Now that the panel was finished and working (code development was concurrent with board construction), it needed a stand, which was a simple collapsible design that is nice and compact for shipping. Using some scrap particle board that I had lying around, I simply leaned the panels against the edge of the table, figured it was a good viewing angle, and made some marks on the board. Each stand has four pieces, two legs and two braces. The spacing between the legs for the two smaller panels places them in between the 3rd and 4th LED strip from each side with 4 strips in between them, or about 6 1/2" (16.5cm). The spacing on the third stand is about 5" wider since that panel is about 5" wider than the other two. I used a simple mated-notch design to build the stands, wherein the legs and braces have corresponding notches, all cut to a depth of 1/2 the width of the brace. These notches line up when assembled, making the stand simple yet strong (I would recommend using something a bit stronger than particle board though). Once completed, the stands were given a coat of flat black enamel paint.
The last thing to build was the game controller. I started by placing the push buttons in a standard up/down/left/right diamond pattern. I then soldered a 6-pin, 90deg angled header to the board to allow for attaching the female RJ45 Pmod. +5V and GND will be coming directly from the Max32 through the RJ45 Pmods. One side of each of the four buttons is tied to the other four pins on the header, with pull down resistors for each button, attached between those same header pins and GND, as well. +5V is attached to the other side of each button. The signal back to the Max32 is driven to GND (logic LOW) by the pull-down resistor until the button is pressed, at which point the pin is driven to +5V (logic HIGH). This design is easily reversed if you want a logic LOW to trigger the code to do something. Once the board was all soldered together, the solder points on the bottom were a little sharp. I decided to make a bottom cover so I cut a piece of acrylic to the same size, sanded the edges of both top and bottom pieces, and screwed some metal standoffs between them. The controller is just the right size, large enough to fit in one hand but easy to use with only one hand.
Now it's time to take a look at the code, so I'll hand this I'ble off to JColvin91.
Step 5: Software introduction
While all the hardware is definitely needed to make our array of 900 LEDs look professional, receive the appropriate amount of power, and even work correctly, without anything digitally telling the WS2812 strips what color(s) they should be, our entire setup becomes very (No offense intended, brmarcum)... anticlimactic.
All of these LEDs are run with code that utilizes Marshall Wingerson's PICxel library which is designed for the Digilent chipKIT boards, specifically for the Uno32 and the uC32. The code itself for the Snake game was written by Tommy Kappenman, an intern at Digilent.
Some of you may be wondering, could I run the code on my particular system board? The answer (as it frequently is) is maybe. There are libraries for both chipKIT boards (which I referenced above) and Arduino boards if you use the library provided by Adafruit in the Adafruit Neopixel Uberguide, although I personally have not dug into Adafruit's library enough to be able to compare the two libraries.
The larger issue that you would run into is memory. The chipKIT library keeps track of each of the 900 LEDs dynamically with 3 bytes per LED in RGB mode or 4 bytes per LED in HSV mode. At 900 LEDs, this equates to requiring anywhere from 2.7 kB to 3.6 kB of RAM.
A chipKIT Uno32 has 16 kB of RAM available and a uC32 has twice as much RAM, so this is no issue on the chipKIT end.
Step 6: PICxel library- how does it work?
But let's move past that and get into the more interesting bit of the software: the code itself. Again, as I did not personally write any of the code, this is going to be more of a board overview illustrating some of the various points of the code works.
We'll check out the main driving force that allows our particular 30x30 array of LEDs to run as planned: the PICxel library. This library is surprisingly simple and straightforward to use.
To start out and initialize the array of all 900 LEDs we first call the construction for the PICxel class:
PICxel name_of_the_set_of_LEDs( how_many_LEDs_we_have, which_pin_the_data_is_being_sent_from_to_the_LEDs, color_mode);
Here we can provide how LEDs we are running (900), which pin the data is going to be used to connect to the data line on the LEDs (pin 3 in our case), and the desired color mode. Marshall's library supports two different color modes: GRB (RGB in a different order), and HSV. Our snake game happens to be using HSV since that allowed more flexibility for the user to choose both a color and the brightness.
We can get the LEDs lit up and running by first calling in our setup the
function and then choose the settings of the LEDs and subsequently update the strip of LEDs with our new values.
For HSV color mode, you can set the hue, saturation, and value. For those of you that might not know, these values roughly translate to the color, how "bold" a particular color is instead of being washed out, and how "bright" a color is instead of being dark, respectively. These are all set with the following function:
name_of_the_set_of_LEDs.HSVsetLEDColor(which_LED_out_of_your_set_you_want_to_change, hue, saturaturation, value);
This function will only set (but not yet update) the values on just one LED, so it's recommend to use a for loop to change multiple LED values.
To update the LEDs so that they all show their new settings (or old if that particular LED was not updated), simply issue the following command:
The refresheLEDs() function utilizes port manipulation and assembly code to make for a tight library so very little time is wasted in sending out the new information to the WS2812 LEDs at their designated data rate.
You can check out a screen shot of the HSV demo for this library in the picture for this step.
Step 7: The snake game
But really, I think the snake game is the coolest bit of coding behind this LED panel; mostly because it is the most interactive. For those of you that might not know, the snake game is a type of game that became generally popular on the old Nokia phones where you controlled a "snake" that ran around on your screen, much like the image you see in this step (taken from telegraph.co.uk)
The object of the game is to have your snake eat a piece of food that would appear randomly somewhere on your screen. If you successfully ate the food by having your snake run into it, your snake would grow longer in length. The catch and what made the game tricky was that (at least in the version I played) you could not run your snake into the wall of your screen or into itself, otherwise it would die. The game also got progressively faster paced as you successfully ate more and more food. Basically, it was one of those addicting games that seem super simple in principle, but are a lot harder in reality (kinda like 2048 or Flappy Bird).
The way that Tommy designed this snake game was by taking advantage of Structs and Linked Lists. By keeping track of where the head was currently located on the 30 x 30 display, he would set an "enable" parameter to indicate that particular LED would be the snake's brighter color rather than the background color. His code keeps track of how long is snake is and then only sets the (previous) tail of the snake back to the background color once his loop determines that the entire length of the snake from head to tail is shown on the screen.
On each loop through, the code checks to see if it is time to update the values on the screen based the difficulty. The difficulty is the amount of delay between each time you visually see the snake move on the LED board. As you eat more food, this delay decreases (along with the length of the snake increasing), increasing the game speed and difficulty.
Step 8: Code and parting thoughts
You can find the code to the snake game (currently designed for Digilent's uC32) in the zip file at the bottom of this step. Similarly, a zip file for the PICxel library can be retrieved from here, with the corresponding I'ble by Marshall over here. The free MPIDE software used to program chipKITs can be found from here.
Naturally, we're not limited to just playing the snake game; we can display still images on the screen (like in Step 3 with the Seahawks logo) or create other games like Conway's Game of Life.
To learn more about the board and it's construction check Digilent's YouTube video!
If you have any questions or comments for brmarcum or me (JColvin91) please feel free to give us a comment below! Remember to check out the Digilent Blog and have fun! :)