Controlling NeoPixels from a PC without a microcontroller.

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?

signal_compare

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:

start_stop_bits

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:

uart_signal_matched

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:

bit_template

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:
encoding

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:

 

Simple-Transistor-Inverter

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.

11 thoughts on “Controlling NeoPixels from a PC without a microcontroller.”

    1. Thank you Josh for taking the time to comment. It seems that I didn’t search hard enough, if I had read that blog post I could have saved many hours. I will check into the break signal for the Usb bridges that I have. Thanks again, without your analisis I couldn’t have had the idea in this post.

      Like

  1. I’d like to control a led strip of 300 from my PC, by sending it data in real time (30FPS will do).
    Is this possible to do with your code?

    I don’t have much free time, and want to use the little I have to mess with the code that generates the data, if it is possible.

    How can I get up and running as fast as I can? (I will, of course, learn everything when I’ll have free time)
    Did you maybe find a way to do it without any extra hardware?
    If not, what do I need to run 300 leds at at least 30FPS? (of course, the smoother the better)

    Thanks for doing the experiments and sharing the results with us hobbyists 🙂

    Like

    1. Noam,
      I’ve been really busy (and also I’m very lazy), so I haven’t published the second part that I planned for this blog post. But yes, even the slowest device that I tested, the CH340G can handle up to 512 leds at about 50 FPS. But for simplicity I recommend you to use the FT232R: Although expensive, you can invert the output from the firmware, and thus is the simplest set up for what you need.

      Like

      1. I succeeded!
        My FT232 was not counterfeit (checked by the link you provided) and it worked without an inverter!
        I ran a strip I had of 60 leds and it worked flawlessly, but I have a problem running it through a strip of 300 leds.
        This is what happens:
        I connect the strip, it lights up with a gradient of white to orange to red (as I don’t have enough energy, but I just wanted to check if the animation works before I get a supply)
        And when I ran your code, the amount of leds I declared in the variable “pixelCount” go black, and the rest of the leds get the remaining energy (e.g If I use a pixelCount of 250 (out of 300), 250 leds will turn off, and 50 leds will be fully white).

        Do you have any idea what I am doing wrong?

        Thank you for your reply (even though you are a self-declared “lazy”).

        Like

      2. Hello,
        You really have to either invert the signal from firmware or with an external inverter.
        I think the timing is correct as the number of leds you have defined are being turned off, but you’re seeing a reset pulse, since the iddle TX line is low and this is interpreted as a reset by the pixels.
        Invert the line. It works:

        Like

  2. As I wrote, I did invert the signal, from firmware, with the FT232, and it did work with a strip of 60 leds, but it doesn’t work with a strip of 300 leds.
    Then, what am I still doing wrong?

    Like

    1. Sorry. I thougt you didn’t change it. It might be an issue with the baud rate. Have you tried different values? I found that if you mix different pixel types you might get errors. Try with lower baud rates. 2Mbps or 2.4Mbps.

      Liked by 1 person

      1. You were right about this too, I added this line to your code:
        refValues[i] /= 16;

        And any funky behaviors were gone.
        Thanks again.

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s