Miniature 8x8 WS2812B Arrays
Addressable LEDs are taking over LED lighting. It's not hard to understand why - they're cheap, can output a huge array of colors, and an entire strip can be driven with a single microcontroller pin. That last one is very important if you're driving long strings or large arrays - charlieplexing an 8x8 (64) RGB LED array or strip would require at least 15 or 16 GPIO pins.
When it comes to LED arrayes however, it seems that MAX7219 arrays are still very popular. These have been around forever and are the plastic-encapsulated 8x8 LED arrays with a MAX7219 (or clone) controller on board that you communicate with via a three or four wire serial interface. They're cheap and easy to use, but restricted to a single color LED and require more data lines than WS2812B style LEDs. Most protogen heads seem to use these types of displays, an 8x16 for the eyes and an 8x24 for the mouth segments.
So, I decided to make a slightly smaller, slimmer, and RGB alternative for the MAX7219 arrays, using a PCB-mounted array of WS2812B style RGB LEDs. Preferably without using huge amounts of power, or huge numbers of data lines.
Designing the Arrays
First things first, I wanted to find an addressable LED that was in a small package - much smaller than the traditional WS2812B-5050 packages you'll find in cheap LED strips. I also wanted them to be low power - a standard WS2812B has about a 0.6mA sleep current with all LEDs off, which would be 38mA of sleep current for an 8x8 array when not displaying anything at all. I ended up settling on the OPSCO SK6805-EC14, a 5 volt, 1.4x1.4mm SMD package that is tiny, and has half the sleep current (0.3mA) of a standard WS2812B.
The PCB is about 26.5mm square and was designed to tile perfectly - i.e. allowing two displays to be used together without a noticeable gap/seam between the two arrays. It's a standard two-layer board in 1mm black FR4 material. Black soldermask + white silkscreen across the entire back both avoids reflecting specific colors (e.g. green or red) and does a good job blocking light bleed-through on the back of the PCB.
Total height of the board with components should be about 1.5mm - thinner than a loonie. Compare this to a typical MAX7219 module, which are usually 32x32mm and >10mm tall. It's a thinner, smaller, and lighter board with better resolution, at the expense of a much higher sleep or standby current than the MAX7219. Total weight of the assembled array is just 1.74g.
Making this a two-layer board caused some extra difficulties in design. I was also trying to avoid via-in-pad, because epoxy filling + capping costs extra. One side effect of this is that I had to rotate LEDs (i.e. around the end of rows and in alternating rows) to make the routing fit properly. It also requires a lot of via stapling for the grounds. It would probably be possible to make an even smaller array with more efficient routing using a 4-layer board and via-in-pad. I also had to account for some tooling holes that are required by the manufacturer - if these were made in a v-cut panel the holes could be removed.
In terms of circuitry, there isn't much required. Every two LEDs share a single 220nF decoupling capacitor each (so 32 220nF capacitors total), and the VIN rail has a 4.7uF capacitor to provide some bulk decoupling for the entire array. The DIN pin for the first LED in the array has a 330Ω resistor to protect whatever it is connected to, along with a 5V TVS diode to protect the LED DIN pin from ESD damage. Pads for VIN, DIN, DOUT (of the last LED in the array and labelled DIO) and GND are placed on the back of the array.
The LEDs themselves are by far the largest part of the board cost. Ten boards * 64 LEDs per board = 640 LEDs total; the cost from LCSC for the SK6805-EC14 in 500+ quantity is $0.0879 USD each. This works out to about $52.74 USD total for 10 arrays for the LED cost alone. More than five dollars of LEDs per array might seem steep, but it's not too out of line with the cost of 1m of 60 LED/m led strip. The other components cost basically nothing in comparison.
A complete schematic is available here: WS2812B 8x8 Array Schematic. It's pretty boring - just a long chain of SK6805-EC14 LEDs, a bunch of decoupling capacitors, and some token protection for the input.
WS2812B Protocol
There's many flavours of addressable LEDs now but the majority of them are using the same (or so close as to be cross-compatible) communication method that WorldSemi established with the WS2812. It's become so ubiquitous that "WS2812" has become the generic term for single-wire addressable LEDs, even though most of them aren't made by WorldSemi anymore. It's a bit of a weird protocol - 0 and 1 bits are defined as ratios of high and low signal times. Zero bits have a shorter "high" signal period followed by a longer "low" signal period, while 1 bits have the opposite timings. Resets hold the data line low for a set time period.
Somehwere on the datasheet for the LEDs you'll typically find a transmission timing chart that specifies some min/max/average values for the high and low time periods. Chips tend to differ a bit in timing specifications from one to the other but compatibility issues seem rare - most manufacturers assume a zero-bit high time and one-bit low time of ~300ns (the tightest timing in the protocol), a total time per bit of ~1.2us, and a reset low time >200us. WorldSemi's original WS2812B datasheet shows every timing has a window of at least 100ns, if not substantially more. These days with a lot of microcontrollers running in the double or triple digit MHz achieving that kind of timing isn't difficult. It might be a problem if you're using a low-power setup with a reduced clockrate (e.g. an internal 1MHz internal RC oscillator or similar).
To tell a WS2812B LED what color to display, a 24-bit code is sent to the LED's DIN (Data In) pin, consisting of 8 bits of green, 8 bits of red, and 8 bits of blue. The onboard controller will automatically set the PWM of the RGB LEDs to try to match the color (although this color matching might be rather poor depending on the LED). Multiple 24-bit codes can be sent one after the other - the LED will take the first one, and then echo the remaining 24-bit packets out over it's DOUT (Data Out) pin. This allows you to just daisy-chain each WS2812B LED together, DOUT to DIN, and pass a different color to each LED in the chain.
You can do some basic math to calculate the refresh rate of a WS2812B strip or array. The protocol takes about 1.25us per bit, or a transfer rate of 800Kbit/s. Each LED requires 24 bits of color data, times 64 LEDs means 1536 bits of data. 1536 bits divided by 800Kbit/s = 0.00192s, or about 1.92 milliseconds, plus about 200us for the latch time due to sending a reset at the end of the data. This works out to roughly 2.12ms to draw a frame - about a 470 Hz refresh rate. Two arrays together (DOUT of first array connected to DIN of the second) would have (128 * 24 / 800000) + 200us = approximately 4ms frame delay, or 250 Hz refresh rate. Even with all ten arrays connected in series (640 LEDs), the refresh rate would still be a respectable 50 Hz - more than enough for most animations or patterns.
Testing the Boards
I soldered some wires onto one of the arrays and plugged them into an ESP32 dev module I had laying around - VIN to the 5V USB power rail, GND to GND, and DIN to GPIO 13. I opted to use FastLED library that I used previously. It handles all the communication, you just have to tell it how many LEDs you have and what pin you're using. Then drawing to the array is as easy as assigning a hex color i.e. 0xFF0000 to an LED[i] in the LED array, and then calling show() to display the updated colors. I ran through tests at off (0x000000), full brightness (0xFFFFFF), each of the individual color channels, and some of the FastLED examples like Fire2012 and Pacifica.
I was rather worried that the 3.3V logic of the ESP32 wouldn't be enough to drive the DIN of the LED - the SK6805 lists a 0.65*VDD minimum voltage for a "high" signal. 5V * 0.65 = 3.25V minimum voltage, which is really right on the edge of a 3.3V signal. Maybe 50mv is more than enough room, or maybe I'm just lucky, but the array didn't seem to have a problem with a 3.3V signal. Still, I'd probably use a level shifter on these to drive them at 5V instead for the sake of reliability.
Since the array is basically a strip that has been folded several times, lighting the next LED in the array produces a light that snakes back and forth across the board. If you want to do X, Y style addressing, you'll need to write a function to convert a given X, Y value to a specific LED index that takes into account the snaking back and forth on alternate rows.
The arrays look pretty good. They're shockingly bright, and the small footprint means higher pixel density so they look a little crisper than a 7219 style array. There's a little bit of color banding because of the orientation of the LED packages - the red and green LEDs line up between rows, creating reddish and greenish bands. It's not super noticeable unless you're driving all the pixels of array to a bright yellow or white hue. They probably need some software color correction but they produce some very vivid, nice colors and I didn't notice any PWM flicker (although faster cameras might - the SK6805 datasheet says it uses 4.5KHz PWM).
Overall, a successful test run - no arrays blew up, no magic smoke released. Things definitely got a little hot (more on that in a moment), but the array can survive at least short excursions to its maximum output, and barely warms up when the output intensity is turned down and only some pixels are on. Even at 15% intensity the array is quite bright and readable. I wish I had managed to squeeze a level converter for DIN onto the board though, then I think it would be just about perfect.
Power Consumption & Heat
The SK6805-EC14 is listed as having a standby (or "static") current of 0.3mA, and up to 5mA of current per RGB channel depending on brightness (much lower than the 20mA per color channel that larger WS2812B chips use). Thats 15.3mA per LED at max power, times 64 LEDs, meaning a total power consumption of about 980mA at 5V, or 4.9 watts. Lighting the entire array in any single full-brightness channel (RGB) should be (5mA + 0.3mA) * 64 = 340mA, or 1.7 watts.
Power consumption of the array depends on both the number of pixels currently lit as well as the brightness of the color being displayed. For displays where most of the pixels are on most of the time, you'll need to reduce the brightness to reduce power consumption. Displays that have most pixels off most of the time, like a scrolling text display, can use brighter colors since there are fewer lit pixels overall.
I took some measurements with a multimeter and found that these calculated estimates are pretty close to the actual performance. Here's the results of the tests, all performed at 5V and using the entire array of 64 LEDs:
- 0xFFFFFF (White) - 1.0A
- 0x000000 (Off) - 19mA
- 0xFF0000 (Red) - 350mA
- 0x00FF00 (Green) - 350mA
- 0x0000FF (Blue) - 350mA
- Fire2012 Demo - 320mA to 430mA
- Pacifica Demo - 60mA to 80mA
While testing at full brightness the array begins getting hot almost immediately, and I'm sure it would probably cook the LEDs if left on like that for an extended period. Since the board is tiny and weighs almost nothing there is very little heatsinking capability, so any extra power being dissipated will cause rapid heating. That is the reason why high power COB LEDs usually come on an aluminum PCB and have a heatsink attached.
I don't think this is much of a problem though - I never designed these to be floodlights. They're plenty bright enough at low intensities. For displays, animations, text, etc you can probably comfortably run these under 100mA without the LEDs appearing dim and barely any heat. If you want to make similar arrays, I'd recommend capping the maximum output power in software to something like 25% or less.
Some Conclusions
The arrays basically met all of my expectations. They're bright, they have reasonable framerates, and when the brightness is turned down they don't consume too much power. They still draw way more current than something like a charlieplexed display (especially at idle), but that's just the cost of running a tiny controller in every LED.
It's definitely not the smallest 8x8 RGB array in the world, but it's small enough to be uncommon. I found an Adafruit product that has almost identical dimensions, a touch smaller at 25.4x25.4mm (1"): Adafruit DotStar High Density 8x8 Grid. The $25 dollar price tag suddenly makes me feel a bit better about my BOM cost for all those LEDs, although the DotStar LEDs that Adafruit is using are fancier (higher data/PWM speeds, separate clock signal line, not WS2812B protocol). For specialty stuff like this it might actually be economically feasible to design and manufacture it rather than buy it (certainly not for normal LED strips though).
These will probably reappear in a synth head project some time in the future, and I might end up ordering some more in the future depending on how it goes.