Big, Interactive LED Art!

I thought it might be fun to learn how some of the really big, interactive, inspiring LED art was built. I’m talking about things like:

All of these great works of art were build with dedicated, smart teams, custom hardware, and amazing code. I realized I had to learn about pixel-addressable LEDs, electrical engineering, embedded microcontrollers, and a lot more.

The common denominator of these LED projects is little pixels that can show any color. Each pixel consists of three LEDs very close to each other: one red, one green, and one blue. There’s a little tiny integrated circuit for each one that reads a signal from a wire telling it what color to be, and then blinks the red, green, and blue at high speeds. Your eye doesn’t notice the blinking so it feels like you’re just seeing different colors. Here’s what a strip of them looks like, close up:

There are various versions of these pixels. If you’re getting started you may want to look at the NeoPixels and DotStar versions sold by Adafruit, but once you need thousands of pixels or variants that Adafruit doesn’t stock, you can buy them pretty cheaply from manufacturers in China (in particular Ray Wu’s store, on Aliexpress, is considered very reliable and has good customer service).

The version of chip I like is called the WS2815. If you’re interested in why, I wrote a more detailed review of the popular options.

In order to set the color of these things you need to send a digital signal with all the RGB values of the colors that you want on the entire strip. The first pixel reads the first color, shows that color itself, and sends the rest of the signal down the line to the next pixel, which does the same thing until you get to the end of the strip.

How do you make such a digital signal? You need an electrical circuit!

At the heart of the circuit is a microcontroller. This is basically just a tiny computer that is so small it doesn’t even have an operating system, but it can be programmed in languages like C++ and has a decent amount of memory. If you’ve heard of Arduino, that is a common microcontroller family that is a popular learning platform.

I built some custom hardware that can generate the signals for up to 8 different LED strips with up to 550 pixels each. It’s called Branch Controller and it looks like this:

The microcontroller I decided to use was the Teensy 3.2. If you rummage around this site you can see the various explorations I went through before I settled on the Teensy. You can see the green Teensy board in the bottom. It is plugged into MicroUSB which is where it gets power from. This USB port can also be used to download new code that you compiled on your computer, which is then stored in flash memory. One nice feature of microcontrollers is that as soon as they get power, they just start working instantly. You don’t have to wait for the operating system to boot up first like you would have with a Raspberry Pi.

What else is in that picture? In the bottom right you can see two jacks which accept standard CAT 6 cables. Each cable has eight wires in it, and it can feed data into four LED strips.

In the bottom left is a little ethernet network adapter. This basically just puts the Teensy on a local area network. You can connect to it with a web browser to configure various options. You can also run a program on a more powerful PC on the network to feed color data, over ethernet, to the controller.

This is subtle but it’s the reason why I built Branch Controller in the first place! A microcontroller is not very powerful; it can make pretty colors but it doesn’t have any kind of graphics processor (GPU) to do really fancy stuff. My theory is, you can run fancy interactive graphics programs on your powerful PC, using its GPU for high performance, and then calculate the color of every pixel and ship that, over a TCP connection, to the Branch Controller which will send it to the pixels.

Remember how I said each Branch Controller could handle up to 8 strips of 550 pixels? That turns out to be the maximum number of pixels you can drive with one Teensy 3.2 if you still want to update each pixel 60 times a second (for fast “persistence of vision” effects that look really smooth). But if you need more than 4400 pixels, no problem: just use multiple Branch Controllers! Then you are only limited by how many pixels a fast computer with a good graphics card can pump out, which is probably in the millions.

My Branch Controller has a tiny little OLED display screen on it which is handy for status information. It has a blinking green LED just so you know that it’s working. And, it has an infrared sensor which lets you control it via a remote control for frequent operations like setting the brightness.

Here’s what it all looks like in the test setup on my desk:

If you’re interested in more details, check out the github repository!

The Branch Controller Circuit Board

The plot so far: suppose you want to build a large, pixel-addressable LED installation with the following parameters:

  • Runs at 60 frames per second
  • More than 4400 LEDs
  • Fully-programmable from a PC or otherwise
  • Survives the desert

You can run a Teensy 3.2 with the FastLED library and parallel output on 8 different pins: that gets you 4416 pixels. To get more pixels than that, your best bet is to connect a bunch of Teensy’s over ethernet.

I decided to build a basic circuit board which combined:

Here’s what all that looked like on a breadboard:

I also started writing the code to run this whole thing, although that is still very much a work in progress so far. The problem I immediately hit is that the messy prototype wiring is really kinda flaky and half the time something falls out and wastes an hour of my time debugging what happened.

Since I’m going to want a PCB version of this eventually, I downloaded KiCad, which I had literally never used before, and got started. In the course of one weekend I was able to build the schematic:

… and design a prototype board:

It’s probably not the world’s fanciest circuit board, there are probably lots of ways to make it better, but it’s just a prototype and it is the first prototype board I’ve ever designed. I’ve sent it off to Osh Park to get made, which is going to take a few weeks, and then we’ll discover all the things I did wrong.

Control your projects with IR Remote Controls!

When you build electronics projects for the desert you always need some kind of basic controls. For LED projects you might want brightness controls, for example, or buttons which switch between various preset visual programs.

The trouble is that it’s very hard to come up with a reliable way to have pushbuttons and knobs which can survive with all the dust.

Last year I found two options which worked pretty well. The first option I used for DMX-controlled lighting. I used three main components:

  • a Wifi / DMX bridge; there are various options for this but I was using all ADJ lights so I got their AirStream DMX Bridge.
  • This went in a large SockitBox. Never heard of the SockitBox? It’s my favorite thing for electronics in the desert. It has enough room in it for a power strip and a nice solid clip-on plastic lid with a rubber gasket that sealed it nicely. There are little gaps, with their own rubber seals, that you can use to feed in a few outdoor-rated cables. By the way, I used SockitBoxes of every size wherever there were power strips or extension cords in our camp.
This is a large SockitBox.
  • Then I controlled the DMX system using software on an iPad, which itself was enclosed in a very tough outdoor / waterproof case.

This solution was a good start. But for my custom electronics projects, I was worried about exposing anything to anything. So I put my circuit boards into IP66 rated, sealed cases like this Polycase SK series:

SK-14-02
A Polycase case

Polycase has a whole system of knockouts and connectors you can use to get power cables in and out, and the cases come in all different sizes. I’m going to design my future circuit boards to fit perfectly in one of these cases.

Now, notice that there is an option with the SK cases to get a clear cover. The magic of that is that you can now use an infrared remote control for all your control needs. Just solder a little IR receiver, like the Vishay TSOP34438 (reviewed) (about $1), onto your circuit board:

Now you can use any kind of IR remote control, most of which are hermetically sealed and all of which cost about $2. If you search for “44 key LED controller” on Aliexpress you’ll find tons of remote controls that look like this:

These have a cheapo plastic membrane which means dust doesn’t get in, and you have a bunch of usefully-labeled buttons to control just about any aspect of your LED project.

Some useful tips on single wire protocols

I built this cute little board with 16 LED strips arranged in a star to use as a test bed for my upcoming projects. In doing so, I made a couple of mistakes and learned the hard way how to drive these very finicky strips.

I figured that all 16 strips could just share a ground. Why not? Ground is ground, right? It’s shared everywhere. So I build a little copper ring as the ground and routed that back to the Teensy controller, and used ribbon cable for all the signals.

Guess what? It didn’t work at all. There was a ton of interference between the data wires and the lights flashed like crazy.

Here’s what I learned:

  1. You have to use twisted pair GND+Signal, from each and every LED strip, all the way back to the controller. 24AWG CAT6 cable is nice for this. You can’t share the grounds like a bolshevik. It won’t work.
  2. I had been using 3.3v signals since that’s what the Teensy 3.2 puts out. I know, everyone said you have to use level shifters, but I had looked closely at the data sheets and it seemed to me like modern WS2815s are willing to take 3.3 volts. Well, they are, but you can’t transmit very far. Please put back the level shifters and run at 5 volts.
  3. And finally, I kept seeing this weird claim that you need to put a little 100 ohm resistor before the first LED. Was it to “protect” the LEDs? Or “match impedence”? Or prevent “reflections“? Who knows. I didn’t really get it either and it seemed to work without out. You know what? Put the damn resistor in there. I mean, it’s one resistor, Michael. What could it cost? Ten dollars?

So yeah. Level shift to 5v, signal+GND, resistors. Here’s what it looks like now:

Teensy 3.2 + Ethernet

Teensy 3.2 with a (standard Arduino) W5500 ethernet shield

As a part of my project to scale beyond the ~4000 pixels that Teensy 3.2 supports at 60Hz, I’m looking into ways of using multiple Teensy 3.2’s and farming out pixels to each of them. The idea would be to build a simple “Branch Controller” consisting of a Teensy 3.2, a WIZnet W5500 Ethernet adapter, and an OctoWS2811 adapter.

The first part of the project was just making sure that I could use the W5500 with Teensy 3.2. I had bought the Arduino Shield version of the W5500 ($23). The whole Arduino Shield is large and clunky and includes an SD card slot I don’t need, so I really should have used a WIZ850io ($20) which is just the ethernet adapter in a much more compact form, so I switched to that:

Teensy 3.2 wired up to a WIZnet WIX850io 10/100 BASE-T Ethernet adapter

To get the Ethernet to work with the Teensy 3.2, all I had to do was make six connections (see the picture above):

Teensy 3.2 PinsW5500 Ethernet Shield PinsWIZ850io Pins
(pinout)
10 – SCSD10SCNn
11 – MOSID11MOSI
12 – MISOD12MISO
13 – SCLKD13SCLK
GNDGNDGND
Vin (3.6 to 6.0 Volts)5V
3.3V3.3V

Then I connected the W5500 to my local area network and plugged the Teensy into the computer. From the Arduino IDE, I loaded the Ethernet > Web Server example, and uncommented the line Ethernet.init(10) in setup(). After running this I had a web server running and everything seemed to be working perfectly.

Next step: I want to see how much data I can push down to the Teensy how fast. The W5500 is a 10/100 ethernet board, but the serial protocol it’s using to talk to the board will slow that down a lot.

The first experiment I did was just a minimal web server running on the Teensy. For the client, I ran node.js calling request-promise in a loop. This was able to make about 100 HTTP requests per second, which could provide a decent frame rate, but there is no data payload yet.

Next I added some payload to try to figure out the bandwidth of a Teensy. The first experiment I did got about 100 kilobytes/sec. This could handle pixel-level data for:

555 pixelsat 60 fps
1110 pixelsat 30 fps
4416 pixelsat 7.5 fps

That’s pretty disappointing; it almost defeats the purpose of using the OctoWS2811 to drive 8 separate strands. According to Paul Stoffregen’s benchmark, I ought to be able to get about 958 kilobytes per second. Which would be enough for my needs! So there was obviously some kind of optimization I’m missing.

Looking closely at the sample web server code I was using, I noticed that it was set up to read one byte at a time, no matter how many bytes were available. A modification to the Teensy code to read blocks of bytes into a 256 byte buffer got much better results; I was able to get up to 500 kilobytes per second! Which translates to:

2777 pixelsat 60 fps
4416 pixelsat 37 fps

This is a significant improvement and probably adequate, but I wasn’t happy.

I wondered if the fact that the test computer was on WiFi instead of a wired LAN could be the bottleneck. Sure enough, moving my computer to LAN dramatically improved the throughput, and I got 1,125,093 bytes across in a second, which was faster than even Paul’s benchmark. This would translate to a frame rate of 85 frames per second with 4416 pixels, well above the 60 fps limit of the WS2812b-type LED strips!

Finally, I tried combining the Ethernet and LED code into one script (branchController) to see how the timing was. The ideal architecture, I thought, would be to open a client connection to the branchController every time you have another frame to send. The trouble with this method is that there is too much overhead to opening each connection. I only got about 20 frames per second doing it this way. Another option is to send four frames at a time (connecting 15 times per second)… this worked fine (but as the code is structured right now, probably freezes LED refresh unnecessarily while the TCP connection is happening).

What I think I’m really going to need to do is open a connection and keep it open, then stuff down one frame of data whenever I have one. That will require changing the protocol a bit so that the connection is expected to stay open, which will require rejiggering the code a bit, but I’m pretty confident that is going to work and have the performance that I expect.

Interlude: BeagleBones

BeagleBone Black

The Story So Far

In investigating how to drive enormous numbers of WS2812b LEDs from Arduino-style controllers at 60 fps or faster, I found that a pretty solid option is the Teensy 3.2 with the OctoWS2811 adapter board which can drive up to 4400 pixels. But what if you need more pixels? A common approach seems to be using a TCP/IP network with CAT-5 cable to connect a bunch of controllers, then using a central PC to coordinate everything.

I was about to start working on this when I thought of maybe using a Linux-style development platform (like Raspberry Pi or BeagleBone) instead of Arduino. Let’s look at some specs:

Teensy 3.2Teensy 4.0BeagleBone Black
CoreMark Benchmark Speed12623142497
RAM64K1024K512M
Flash256K2048K4GB
Price$19.80$19.95$62.38

The neat thing about the BeagleBone is that it has two PRUs. Those are tiny little 32-bit computers running at 200MHz which have access to 16 of the output pins. You can leave the PRUs in charge of sending output to the LED strips, which frees up 100% of the BeagleBone CPU for your own image processing. There’s also an insane amount of flash memory for storing images, animations, and even videos. There’s onboard ethernet so you don’t have to mess around with wiring up a W5500 to your controller.

Still, there are some disadvantages:

  • You have to wait about 10 seconds for Linux to boot up when you power up
  • Since the PRUs can only access 16 output pins, you can’t really drive insane numbers of pixels from a single BeagleBone, so you still need some kind of distribution protocol
  • BeagleBone doesn’t have FastLED and not a lot of people are using it for addressable pixels, so you’ll have to do a lot more work trying to drive pixels than you would on the Arduino-type controllers
  • And, it’s more expensive.

One obvious idea is to use BeagleBones at the center of your architecture, sending commands over Ethernet to an army of Teensy 3.2s which serve as low-cost WS2812b drivers. You can even get industrial-strength BeagleBones like this one which support Gigabit ethernet in case the 10/100 ethernet doesn’t pump enough pixels for your design.

So now the idea would be that you can run arbitrary Linux software at the center of your architecture using tons of CPU, RAM, Flash, and even all kinds of cool 3D accelerators and stuff that are built into the BeagleBone Black. We would then design a little board for the branch controllers with Teensy 3.2, W5500 ethernet, level shifters and resistors, and use one of those for each 8 WS2812b strips.

A fancier version of this board could also include a power distribution bus for the LEDs themselves. If you were driving 4400 LEDs you would theoretically need 242 amps (1210 watts) so if you were thinking “PoE” please stop thinking that.

So you need more than 4000 addressable LEDs

Maybe you want to build something for a big thing in the desert. And the desert is wide and large and your thing is going to be really, really, big. For example, the Tree of Ténéré by Zachary Smith and team has 175,000 LEDs.

A photograph of the multi-colored Tree of Tenere at night; each leaf has 7 LEDs.
Photo by Duncan Rawlinson under CC Attribution / Non Commercial License
duncan.co

That is significantly higher than the 4416 LEDs that you can drive off of a single Teensy 3.2 with an OctoWS2811 adapter and ultimately you’re going to need some kind of central controller sending signals out to multiple branch controllers that each handle a set of LEDs.

There are a bunch of ways to think about how to do this, but a good approach might be just using simple ethernet. That way you can use a lot of off-the-shelf equipment. For example, the popular WizNET W5500 can provide internet access for the branch controllers. Indeed the Tree of Ténéré people apparently used custom boards with a W5500 and a cheapo ESP32 controller. Now you can just use generic CAT5 cables and any kind of Ethernet hub you want. As the main controller, you have a single PC. It blasts pixel data out to all the branch controllers.

There are a couple of interesting design considerations:

Do you want to use Power-over-Ethernet?

It seems like it might simplify wiring a bit to deliver some power over the CAT-5 cables. The trouble is that this would not supply nearly enough power to drive the number of LEDs that each branch controller is going to drive, so you still need a separate power distribution system for the LEDs. Given that you are already distributing power for the LEDs, it doesn’t simplify the wiring as much has you might think. It seems like what the Tree people did was distribute 12V throughout the project and then had a simple low drop regulator on their custom board to drop 12v to 3.3v.

Are you sending every single pixel?

If you are really using 175,000 pixels with 24 bits of data at 60Hz, that’s about 252Mbps. After adding the overhead of ethernet with real protocols, you might be able to crank this out using Gigabit ethernet but the W5500 is apparently limited to 100Mbps (100baseT). Unless you want to get really complicated and run multiple ethernet segments, you’re going to have to take shortcuts.

Luckily, we have a pretty good computer sitting at each of the branches, so there are a lot of ways to do that. We can send a packet down to each branch controller containing arbitrary parameters and let the controller decide how to map that onto its pixels.

For example, on the Tree project, which had 7 LEDs per leaf, you might just decide it’s OK for each leaf to be a single color. Then you could send custom pixel data for each leaf. You could also use other arbitrary custom compression schemes that are appropriate for your particular project.

Next Steps

Here’s what I’m working on next:

  • get an Ethernet switch and something to test W5500
  • build some software that distributes data from PCs to branch controllers

Useful links:

APA102c versus WS2815 LED Strips

If you’re just joining me, I’m trying to redo the LED lighting on this 46′-tall antenna:

My earlier experiments were all about speed, trying to increase the frame rate from last year’s abysmal 17 Hz to something better than 60 Hz. I decided I could easily double the number of LEDs and use single Teensy 3.2 with the OctoWS2811 adapter, and get 72Hz.

Honestly I could stop right now, but I was still interested in doing some testing with APA102c LEDs. These are what Adafruit calls DotStars. The big difference is that they have an extra pin for a clock signal.

Here are some of the pros and cons of the different options for LED strips that you might consider for blinky lights in the desert:

WS2812bWS2815APA102C
5V strips, which need power injection occasionally to avoid discoloration at the end of the stripUses 12V for power. (The signal is still 5V). This means you don’t have to inject power as often into the middle of the strip to prevent color loss.5V strips, which need power injection occasionally to avoid discoloration at the end of the strip
If one LED fails, the rest of the strip will not work.Provides a “backup path” for data that skips an LED. That means if a single LED fails, the rest of the strip continues to function.

Note that the backup feature is not always helpful; a completely break in the strip will still kill your strip.
If one LED fails, the rest of the strip will not work.
Slow PWM refresh rate (400Hz); doesn’t look great in video and doesn’t provide persistence-of-visionFast refresh rate (2KHz) looks great in video camerasEven faster refresh rate (4KHz – 20KHz) — provides amazing color dithering at lower brightnesses
Slow protocol with 800kHz data rate

If you want 60Hz refresh rates you are limited to 550 pixels
Slow protocol with 800kHz data rate

If you want 60Hz refresh rates you are limited to 550 pixels
Arbitrarily fast data rate of at least 24MHz

You’re probably limited by the speed of your controller and code
Only need one conductor for data.

(Nice option to use twisted pairs with GND + DATA, so, for example, CAT-6 can carry four lines of data
Only need one conductor for data.

(Nice option to use twisted pairs with GND + DATA, so, for example, CAT-6 can carry four lines of data
Requires separate conductors for data and clock.
Currently $12/meter/144pixelsCurrently $15/meter/144pixelsCurrently $19/meter/144 pixels

By the way, these things change all the time. The manufacturers are kind of unreliable and often update their products without notice. They especially love to flip the color order. I’ve bought the exact same product one month later (to replace a failed strip) and discovered that the new version had Red and Green flipped so I couldn’t really replace the failed strip. Be careful and test things yourself!

Next, I want to test how fast the APA102c really is. Can I really drive thousands of pixels with just with a single pin, no OctoWS2811? And do they really look better? I decided to wire up 4-meter strings of each to a Feather M4 and see how they did.

In terms of frame rate, as expected, I got faster refresh with the APA102. In a test with 576 pixels, I got an update rate of 52Hz with the WS2815 and 167Hz with the APA102.

In terms of how things looked: at full brightness, there was really no difference. But once the brightness got below 128 you started to notice that the APA102 did a much better job of rendering dim colors. In fact at very low brightnesses like 16 the WS2815 had basically dropped out completely but the APA102 was still showing pretty much the same hues as it showed at full brightness.

In conclusion, for bright outdoor applications, there is a lot to like about the WS2815, especially the fact that you can do less power injection and the resilience to single-pixel failures. For more subtle, indoor applications where you might run at lower brightness, APA102 might be preferable. I’m going to stick with the WS2815 for the antenna.

Measuring WS2812b Frame Rates with an Oscilloscope

As a part of trying to speed up the animation on my antenna, which uses 1800 addressable WS2812b-type LEDs, I’m trying a bunch of experiments. I wanted to figure out a reliable way to measure the actual frame rate I’m getting (i.e., updates per second).

My first, janky idea was just to measure things with a stopwatch, but that felt kind of primitive. I was pretty sure that I couldn’t use the clock on the controller, because I thought a lot of WS2812b libraries might be disabling interrupts and interfere with the real-time clock.

The idea I came up with was using an oscilloscope.

To do this I just found an unused pin, and pulsed it right before I called FastLED.show():

  ...

  digitalWrite(10, HIGH);
  delay(1);
  digitalWrite(10, LOW);
  
  FastLED.show();

That was the easiest thing ever. The oscilloscope did all the calculations for me and measured the frequency perfectly. Here you can see that with 1800 LEDs, I’m getting an actual refresh rate of 16.9 Hz.

(The WS2812b protocol takes 30μs per pixel, 1800 pixels should take about 54ms to send. As you can see from the screen grab, it actually took 59.18ms when you factor in all my other code).

Just to make sure I’m not insane, while still using the Feather M4, I modified the code to send 450 pixels instead of 1800:

Now I’m getting a frame rate above 61 Hz, which is very respectable.

Finally I tried the hardware that looks the best right now: Teensy 3.2 + OctoWS2811 using the FastLED library. Even pushing 450 pixels through all 8 strands, I get an even better frame rate of 72 Hz, which is almost exactly the theoretical limit of the WS2812b. If you want a 60 Hz refresh rate, you can put 550 LEDs on each strip for a total of 4400 pixels per Teensy 3.2.

What about the Teensy 4.0?

The Teensy 4.0 development board, by PJRC

Oh man, this thing looks amazing.

No sooner had I soldered pins on the Teensy 3.2 than a newer Teensy 4.0 arrived in the mail. The new Teensy is insanely more capable for the same price and in the same form factor. It’s 10-15 times faster on benchmarks, has 16 times as much RAM and 8 times the flash. It uses the new ARM Cortex M7. This is a 600 MHz chip. This is basically a Pentium III-class processor, with tons of memory, tons of IO, and all kinds of amazing things.

Teensy 3.2Teensy 4.0
CoreMark Benchmark Speed1262314
RAM64K1024K
Flash256K2048K
Price$19.80$19.95

I would love to use this development board to drive tons and tons of WS2812b-type LEDs, but I just can’t get it to work.

First:

  1. The Teensy 4.0 uses different pins for parallel output than the 3.2. I am pretty sure that means that it will not work with the OctoWS2811 library, which has not been updated.
  2. That would be OK, because FastLED seems to have new native support for parallel output on the Teensy 4.0, and FastLED is a much better library than OctoWS2811.
  3. But, because the pins have changed, the OctoWS2811 adapter board won’t work with the Teensy 4.0 (even though it is the same form factor), so I think I would be on my own in terms of providing level-shifting, resistors, and RJ-45 connectors.
  4. Another problem with the Teensy 4.0 is that a lot of its output pins seem to be in the form of tiny pads on the bottom (digital pins 24-33). These might be ok to use in an emergency but seem like a real mess in real projects.
The bottom of the Teensy 4.0 includes a stunning number of annoying little solder pads
The bottom of the Teensy 4.0 includes a stunning number of annoying little solder pads

I prototyped a little circuit that level shifts eight of the pins (19,18,14,15,17,16,22,23) from 3.3v to 5.0v:

Unfortunately, this is just not working for me no matter what I try. I even tried using an oscilloscope to figure out where it’s going wrong, but at this point, I’m in way over my head. From the current discussion on Reddit I see a lot of other people having problems so I’m pretty much ready to give up on Teensy 4.0 for a while until someone smarter than me figures out how to make it work.

(By the way, by my calculations, even the Teensy 3.2 is powerful enough to drive eight strips of 500-1000 LEDs at 30Hz- 60Hz. Putting any more WS2812b-type LEDs on a Teensy 4.0 would actually just reduce the frame rate due to limitations in the protocol. So there’s no compelling case for a Teensy 4.0 right now.)