Plug'n'play Neopixels

by p_leriche in Circuits > Arduino

1427 Views, 15 Favorites, 0 Comments

Plug'n'play Neopixels

Img_2442a.jpg
Img_2441a.jpg
Img_2440a.jpg

Don't you just love those Adafruit Neopixel rings? I have the 12, 24 and the massive 60 pixel rings, and up until now all my sketches have been configurable to run with any of them, with the size statically set at compile time with a line such as

#define PIXELS 24

So if I change the ring for a different size I have to recompile the sketch and re-upload it. But wouldn't it be nice if the sketch could automatically determine the ring size for itself, making it plug'n'play! The photos show each of the 60, 24 and 12 pixel rings in turn, being driven by the same unmodified sketch.

There are several possible ways of doing that, all relying on the fact that once all the pixels in a ring have received their RGB data, any surplus data falls out of the DOUT connection at the end of the chain to make it possible to daisy-chain further Neopixels if you like. In principle then, you could just connect DOUT to an input pin on the Arduino and count how many pixels you've stuffed before the data starts trickling out of the other end.

As always, it's easier said than done, especially if you set yourself the limitation that you really don't want to modify the standard Adafruit Neopixel library.

My first idea was to connect DOUT to an interrupt pin on the Arduino. The library functions stuff data into the Neopixels with interrupts disabled, so the interrupt (if triggered) would only be serviced once stuffing had finished. But in principle, you could instantiate successively larger rings until the interrupt didn't happen at all. Unfortunately it normally has to be instantiated in global context to make it accessible from both setup() and loop(), whereas it would have to be instantiated in setup() in order to implement the necessary logic.

An better method is to use Counter/Timer 1 in counter mode to count the pulses coming out of DOUT. This requires that you connect DOUT to Pin 5, the Counter/Timer 1 external clock pin. Each pixel requires 24 bits of data (represented by a short pulse for "0" or a long one for "1"), so all you have to do is instantiate the largest size of ring, then divide the number of pulses counted by 24. This tells you how much smaller is the ring in use (if at all), compared to the largest ring. Hence you can calculate the actual ring size and save it in a global variable for use wherever required. It doesn't matter that the Adafruit library will then be sending data to non-existent pixels.

How to Code It

The hardest part is working out how to program Counter/Timer 1 to use Pin 5 as a counter input instead of the more usual uses as a digital or analogue input or output. This requires close scrutiny of the datasheet, but luckily for you, I've done all that.

In the global context of your sketch (i.e. before setup()) you will need the following lines of code:

int pixels;
#define MAXPIXELS 60
#define PIN 8
Adafruit_NeoPixel strip = Adafruit_NeoPixel(MAXPIXELS, PIN, NEO_GRB + NEO_KHZ800);

In an existing sketch you will already have the last two lines, or something equivalent. If you are using a different type of NeoPixels or a different pin, modify these lines to match your version.

At a convenient point in setup(), add the following code:

#define COM1A 0
#define COM1B 0
#define WGM1 4 // CTC mode
#define FOC1 0
#define CS1 7 // Ext clock on rising edge

TCCR1A = (COM1A << 6) | (COM1B << 4) | (WGM1 & 3);
TCCR1B = (FOC1 << 6) | ((WGM1 & 0xC) << 1) | CS1;
strip.begin();
TCNT1 = 0;
strip.show();
pixels = PIXELS - (TCNT1 + 12)/24;
// Serial.begin(38400);
// Serial.print("Count = "); Serial.print(TCNT1);
// Serial.print(" Pixels = "); Serial.println(pixels);

You can uncomment the commented lines for debugging purposes if you like, in order to see that the bits are being counted as they come out of DOUT.

In the remainder of your sketch, use the variable pixels wherever you need the size of the ring, or where the code uses strip.numPixels().

Going Further

As I said before, it shouldn't matter that the Adafruit library will be sending data to non-existent pixels, but if this bothers you (perhaps you want to trade ring size for higher performance or free memory) then it ought to be possible to re-instantiate it with the following two lines of code, inserted to follow the code inserted in setup() in the previous step.

strip.~Adafruit_NeoPixel();<br>Adafruit_NeoPixel strip = Adafruit_NeoPixel(pixels, PIN, NEO_GRB + NEO_KHZ800);

(As before, substitute your values in place of NEO_GRB and NEO_KHZ800 if they're different.)

Unfortunately this doesn't always seem to work, possibly because dynamic memory allocation is not well suited to a device with very limited RAM such as an ATMega328-based Arduino. Indeed, there are those who regard it as a positively bad idea, and it may lead to memory fragmentation and consequently not recovering all the memory you expect.

If Pin 5 is already in use for something else and you can't reallocate it, then I'm afraid you'll have to find another method. There are 2 other counter/timers, but Counter/Timer 0 is already used for the time functions and Counter/Timer 2 doesn't have an external counter input.