From the moment I got some WS2812B leds in my hands, I wanted to build a led matrix that I could control from my computer. But almost every single project that I found on the Internet used a microcontroller to translate the color data into the stream of bits required by the leds, either by bit banging or with the help of an SPI port. I found that there are some modules specifically designed for this task, but with a price tag that seems excessive to me.
Since a PC has enough power to do the translation itself, the only problem is to find an appropriate output for the bit stream. And since at least my PC has no SPI port, the next obvious candidate is the serial port.
After reading a blog post about the WS2812B timing by Josh Levine I knew that I was on the right track, specifically where he wrote:
People have used many exotic and complex methods to meet these strict requirements- including cycle counting, PWM, SPI, and even UARTs with extra inverter hardware
So yes, I’m about to use an exotic method (but not complex at all): an UART with extra inverter hardware. In that blog post he also states that the only critical parameter is the maximum width of a 0-bit pulse (500 ns). This value gives a minimum frequency that the UART has to support: 2Mbps.
Forming the bit stream
If we compare the signal required by the WS2812B with the one generated by an UART, they would seem incompatible especially because the UART signal has start and stop bits that should always be sent and thus would interfere with the intended signal. Also, the timing doesn’t appear to match the timings in the WS2812B datasheet. But we are working under the asumption that the only critical value is the bit-0 time: if bit-0 time equals x, bit-1 time would be 2x and the full period would be 3x, thus we could produce a nice signal that is still within specs.
How would this bit stream look, if it was composed of 0 and 1 bits alternated? How can we coalesce this sequence with the UART’s sequence?
Because each WS2812B bit always start with a high level and ends with a low level, we could consider that the WS2812B signal also has start and stop bits surrounding each data bit, altough with inverted levels when compared to an UART. So the same WS2812B bit stream from previous picture could be visualized as:
Since the start and stop bits are inverted with respect to the UART’s signal we invert every bit in the previous sequence to get the sequence to send over the UART:
The UART can be configured to send only 7 data bits, which gives a total of nine bits sent for each byte, and this matches three WS2812B bits. And because a single pixel requires 24 bits this means that the whole color sequence could be encoded in exactly 8 memory bytes. In the figure above the values in gray are constant so they help to build a template or map to place the color bits. Remember that the UART sends the LSB first so to visualize the real value to encode, just reverse bit positions as in the following figure:
Let’s see an example. To send the color orange (0x05ff00 in GRB), starting with the MSB we’ll divide the color bits in eight groups of three bits, and using the template above we’ll find the value to send over the UART. Remember that we have to reverse and invert the bits to match UART’s bit order and levels:
Because there are only eight three-bit permutations, a simple way to solve the bit encoding is to use a lookup table:
byte[] bitTriplets = new byte[] { 0x5b, 0x1b, 0x53, 0x13, 0x5a, 0x1a, 0x52, 0x12 };
Put everything together
To test this idea we need an USB to serial converter capable of at least 2Mbps but we can go as high as 3Mbps with 2.4Mbps being the one producing the closest timing with the one specified in the datasheet.
An ubiquitous device with this capabilities is the FT232R. So I tried first to use this module because I have one at hand and according to the datasheet you can invert the TX signal by flipping a bit in the firmware. But it turns out I have a counterfeit product so I had to invert de bits externally. A simple inverter using a general purpose transistor does the job:
With the above circuit and a simple “chasing” program I tested the idea with the FT232R and the CH340 which I also had lying around.
You can download the complete source code from the github repository.