On the perils of trying to pack your bits too tightly
While superficially, it might seem like a good idea to try to pack your bits in as tightly as possible. You want to update your string as rapidly as possible to reduce transmission time. Unfortunately, this can actually cause some hairy problems that get worse as your strings get longer.
Remember how signal reshaping will take a short bit and stretch it out to make it into an “ideal” bit? If you try to be cute and pack your bits in as tightly as you can, eventually these stretched bits are going to use up the gap before the next bit and violate the VDL minimum. When this happens, you see a glitch on the string.
You might think that as long as you keep your bits at least as long as the “ideal” size, then they will not get stretched and you will avoid glitches. Unfortunately, the “ideal” bit size varies from pixel and even changes over time for a single pixel depending on temperature and voltage
Heck, you can even have cases where a chip will turn tightly packed 1-bits into 0-bits thanks to signal shaping. Take at look at this capture…
Here we were trying to to squeeze our 1-bits to be as small as possible while still meeting the letter of the law. The bottom blue trace shows a perfectly valid 560ns wide 1-bit (spec says 700ns ±150ns = 550ns min) coming in, but the purple trace shows it going out the other side now reshaped to a 376ns wide 0-bit. Why did it do that? Maybe it was hot. Maybe it was just having a bad day. It doesn’t really matter because if this happened to you, what you would see is that all of the LEDs past this one went black – a symptom that would surely have you wasting lots of time looking for a power or signal break. And the longer your strings, the wider the range of behavior like this you are likely to see.
Give your 1-bits and your gaps a little room the stretch their legs and it will save you all kinds of intermittent and hard to debug problems
The moral of the story: Give your 1-bits and your gaps a little room to stretch their legs and it will save you all kinds of intermittent and hard to debug problems. You don’t want to stretch your 0-bits, however, because then they might become 1-bits (see T0H maximum), instead you can stretch the gap after the 0-bit to give it some room to expand into. For some simple bit-pampering code, check out…
One of the things that makes NeoPixels so useful is that you can connect huge numbers of them end to end and they will faithfully relay your data on down the line. According to the Englishish datasheet, “Built-in signal reshaping circuit, after wave reshaping to the next driver, ensure wave-form distortion not accumulate”.
To see how this actually works, we need to go NSA on a couple of Neopixels and intercept their communications…
I’ve placed a wiretap on the trace that connects the data-out from the near NeoPixel to the data-in of the far one.
I stuck a photo-detector on a NeoPixel to get an insider’s look at exactly how they do their PWM…
Looking at the photo-detector output on a scope at a 500us timescale, the PWM of the LED shows up perfectly – it is running at about 500hz (~2ms per cycle).
Here is the LED showing the lowest brightness of 0x01…
…and at half brightness of 0x80…
…and finally full brightness of 0xFF…
We can see that the LED is never continuously on. Even when the color is set to full brightness, the LED still turns off for about 100us at the end of each PWM cycle. This means that there will always be some flicker with a Neopixel, although nothing noticeable to human eyes.
This PWM clock runs independently from the data coming in. This has some practical consequences. It means that there is up to ~2ms of jitter between when you latch new data and when it actually shows up on the LED. Here are a couple of extreme examples…
Here we see the LED come (top trace) on almost immediately after the data stops (middle trace). We just got lucky that the PWM cycle was ending just as we finished sending data.
This time there is delay of >1.5ms (1,500,00ns!) between the end of the data and the LED lighting up. We just missed the end of the previous PWM cycle and so had to wait for the next one.
- the shortest flash you can make with a NeoPixel is ~2ms. This rules out using an array of NeoPixels as a high-speed strobe flash.
- if you try to show a frame of pixels for less than 2ms, there is a real chance that you might completely miss a PWM cycle and they will never actually be displayed at all. This practically limits your maximum frame rate before frames start getting dropped.
- since each pixel has a free-running PWM clock,. there will always be up to ~2ms of time between resets when some pixels in the same string will be displaying new data while others are still displaying old data. This could cause problems like motion tearing and dithering if you take a photo of a changing Neopixel display using a 1/500 second or shorter exposure time.
So now we can detail exactly a frame refresh works…
- Every time the data signal falls from high to low, a countdown timer starts. The data line going from low to high stops the countdown.
- About ~6us after the reception of the last bit, the countdown timer expires and the newly received color data is latched into an internal buffer.
- At the end of the next asynchronous PWM cycle (which could be anytime in the next ~2ms), the chip grabs the data from the buffer and starts displaying it on the LED.
Q: Couldn’t I use a photo-detector or current probe to lock into the PWM clock and then make sure my data always ends just before the next PWM cycle starts?
A: Keep in mind that every pixel has its own independent PWM clock and they are all free-running and all have different periods due to manufacturing and environmental differences. So you probably will only be able to get your data to be optimally timed for a single pixel in the string.
There is an easier way to drive NeoPixels using code that…
- is simple to understand
- easy to change without breaking
- allows indefinitely long pixel strings
- addresses the root cause of signal reshaping glitches
- needs only a trivial amount of memory regardless of string length
Here is a demo of a 1,000+ pixel string being driven by a vintage Arduino DueMillinove at about 30 frames per second…