As you may recall from Steve Manley's recent column over on EE Times (see Cunning Chronograph: One Ring vs. Three Rings?), it appears that Adafruit's NeoPixel library messes up the
micros() timing function.
This isn’t all that surprising when we come to think about it; the
micros() timing function relies on interrupts, but Adafruit's NeoPixel library disables interrupts whilst it's using its
show() function to stream the data out of the Arduino microcontroller to the NeoPixels.
Having said this, the fact that the
micros() function is affected is a bit of a blow to me personally, because I was intending to use it as part of a cunning plan — indeed, a plan so cunning we could pin a tail on it and call it a weasel (as Black Adder might say).
Then I remembered that my chum Ivan was waffling on about this a few months ago. In the case of Ivan's cunning chronograph entry, he was thinking about employing a real-time operating system (RTOS), but he decided this might be overkill, so he ended up using a simple interrupt-driven scheduler called leOS (little embedded Operating System), which was created by Leonardo Miliani.
As you might imagine, having something like the Adafruit NeoPixel library disabling interrupts isn’t good news when you are using an interrupt-driven scheduler. Thus, Ivan ended up using a WS2812 library, which was created by a guy called Tim (see Tim's Blog) and “lightly tweaked” by the folks at EvilMadScientist.com. (See also my column Using WS2812-based NeoPixels in embedded systems.)
Thus, I decided to take a look at Tim's library and perform a few experiments of my own. If you are interested in playing with this library yourself, click here to download a compressed file called WS2812.zip . This contains three files called WS2812.h , WS2812.cpp , and light_ws2812.cpp , along with a folder containing some usage examples. Make sure you have exited the Arduino IDE, create a folder called WS2812 under your Arduino/libraries folder, copy the WS2812.h , WS2812.cpp , and light_ws2812.cpp files into your new WS2812 folder, and then re-launch the Arduino IDE.
First of all, I re-wired the three rings in my clock (60 pixels, 24 pixels, and 12 pixels) to form a single 96-pixel entity. Next, I created a simple program using the Adafruit NeoPixel library (click here to see the source code for this test).
This program loops around, first setting the red, green, and blue elements of each pixel to 0xAA (binary 10101010), and then turning them all off again. Even though we know there are problems, we call the
micros() function before and after we call the
show() function to stream the 0xAA data out to the NeoPixels.
We also use
Serial.println() calls to output the delay values from this test. The results were as shown below:
As I say, we already know from Steve's experiments that the results from this first test are going to be inaccurate, but at least it gives us a starting point. The fact that the output values bounce around between 856 and 866 microseconds wasn't too surprising because the
micros() function has a resolution of four microseconds (i.e., the value returned is always a multiple of four) on the Arduino Uno I'm using, and we might expect a tiny bit of “jitter” caused by the timing loops in the underlying code. However, the occasional 1884 value was something of a surprise.
Next I created an equivalent test using Tim's library (click here to see the source code for this test). The results are as shown below:
The fact that we are now talking about values in the millisecond range didn’t surprise me; I expected larger values than the previous test because this library doesn’t disable the interrupts. Nor did the fact that the results wander from 1116 to 1120 microseconds perturb me for the reasons discussed previously, but I have to admit that the occasional 92 values were a bit of a surprise.
I did wonder if buffering effects associated with the serial communications were throwing a wobbly into the system, so I tried looping around 100 times storing the results in an array, and then activating the serial communications to display the results, but things just got weirder and weirder, and eventually I said “Oh sugar!” (or words to that effect) and decided to move on with my life.
Ivan has an office in the next bay. He also has an extremely large oscilloscope (although he doesn’t like to boast) and he's not afraid to use it. I invited Ivan to join me on my quest, so he manhandled his scope over to my bay and hooked it up to my little Arduino.
The idea here is to simply loop around writing out streams of 0xAA values to the NeoPixel rings. We're using Steve's technique of directly toggling an output pin at the start and end of the
show() function, and using the 'scope to measure the duration of this function call. We started with the Adafruit NeoPixel library (click here to see the source code for this test).
I'm sure that we all know how to use an oscilloscope, but Ivan is a virtuoso in this regard. Watching him manipulate the dials and settings is like watching a professional musician coaxing the best out of his or her instrument. As we see in the screenshot below, uploading 0xAA values to the entire 96-pixel string takes 2.9ms. (If we round this up to 3ms, then even if we decide to refresh the display 60 times a second, this will consume 180ms, which leaves us with 820ms free each second to do other “stuff.”)
(Click Here to see a larger image)
Now, I must admit that I was sort of expecting Tim's “light” WS2812 library to be a tad faster than Adafruit's NeoPixel library (click here to see the source code for this test), but — as we see in the screenshot below — uploading 0xAA values to the entire 96-pixel string actually takes 3.15ms.
(Click Here to see a larger image)
That's an additional 250µs for each 96-pixel update, so if we decide to do this 60 times a second, that equates to an additional 15ms. This isn't terrible in the scheme of things, but one has a natural inclination to want to go smaller, faster, better, etc.
To be honest, I'm not too sure what to make of this result. Does Tim's library take longer because he doesn’t disable the interrupts? Unfortunately, I don’t understand the software enough to say (remember that I'm a hardware guy, and proud of it).
One other consideration is that the “direct toggle” test program using the Adafruit library has a memory footprint of 2,456 bytes with an additional 35 bytes of dynamic memory, while the equivalent code based on Tim's library occupies 2,156 bytes (that's a saving of 300 bytes) with an additional 34 bytes of dynamic memory.
Hmmm; Both libraries seem to mess up the
micros() function, which means I'll have to dispense with this function and be even more crafty and canny in order to implement my cunning plan. Tim's library leaves the interrupts active and consumes a tad less memory, while the Adafruit library disables the interrupts but executes a little faster. So, which library should I use?