Monday 5 May 2014

Hammer Pong #1 - Multiplexing WS2812B addressable LED strips

My 1-D Pong game was accepted to be part of an electronic art exhibition this summer.. Yay!! 1-D Pong was a project of mine from a couple of years back, which uses a 5M addressable LED string as the display surface for a game based on the classic PONG. Since the LED string is one dimensional, you can't "miss the ball" in the conventional sense so the gameplay is all about timing; you press a footswitch at exactly the right moment to return the ball. Despite being so basic it works pretty well as a game, and the pace accelerates with each return to keep it challenging.

As this will be my first gallery installation I decided I really wanted to improve and build on the original (which is, to be honest, getting a bit tatty) and do something a bit more ambitious. In speaking with the curator a few weeks back I learned that the venue has a 7 metre high ceiling, and that the exhibition would have a fairground theme. Wouldn't it be great to orient the 5m strip vertically up the wall, I thought. That got me thinking about those old "high striker" / "test your strength" fairground side shows.. you know the ones with the big hammer to whack a puck up a tube and ring a bell? Well.. combine that with the 1-D Pong and Hammer Pong was born!

Two players will whack it out against each other, hitting their foam mallets against foam block "triggers" to shoot a pulse of light up a vertical LED strip, where it will cross over and return down the opponents strip. The opponent needs to hit their block at just the right time to fire the pulse back as it reaches the base of the strip. Miss the timing and you lose.

A simple concept, but technically a little bit more challenging build than the original 1-D Pong. I will be building it over the next few weeks and will post my progress on this blog, which might be of interest to anyone who wants to know about the build after it is complete. So let's get started...

Challenge number one is that I need to drive multiple LED strips. My original 1-D Pong project was created a couple of years ago when addressable LED strips were new (and rather more expensive) and my strip used an obscure controller called a yds600 for which I had to write my own support library. Due to the way the strip required the SPI communications clock to keep "ticking" after the data was latched, to keep its PWM going, those strips would be hard to drive in multiple.

However, these days most strips use the WS2812 controller - I find these things amazing; the controller chips is actually inside the LED!!

Just think about that for a moment... a single WS2812 LED is a tiny package about 5mm square,  containing not just red, blue and green LED elements but a tiny silicon chip with a serial data controller, internal oscillator, three 8 bit PWM channels and current management circuitry. They can be had for less than 20 cents each but are more usually bought in flexible strings (usually 30 or 60 LEDs to a metre) where the LED data in and data out pins are chained together as a giant shift register so a single data pin on your microcontroller can drive them all. I think that's amazing!

Also, these days it is easy to find ready made code to drive the strips. Adafruit sell WS2812 LED assemblies under their NeoPixel brand and their Arduino library code seems pretty well written, so I have been working with that. One potentially nice point about the Neopixel library code is that it "bit-bangs" the data (the output pin is explicitly toggled on and off by code rather than using a built in serial hardware peripheral on the AVR) so the strip is not confined to specific output pins.

This means that two strips could be connected to different output pins using the NeoPixel library. However I was already getting a bit more ambitious than that... when I bought my strips I bought a job lot of ten x 5m WS2812B strips with 30LEDs/m so I could get the price down to just over US$30 a strip including shipping from China. This means I have quite a few strips to play with :)... so why not make each of my 2 players "tubes" actually made up of 3 LED strips side by side, giving each a 3x150 pixel matrix to allow some simple animations and effects, and increase the brightness... Oooh!
Ten 5 metre addressable LED strips
To run the total 6 strips needed I could use 6 output pins and 6 instances of the NeoPixel library object, or I could potentially chain the strips together and use a single pin by treating them as a single 900 (150 x 6) pixel strip. However, I think this would mean the library keeping an image of the 3 byte colour data for each of the 900 pixels in memory at the same time, which would not even fit in the Atmega328's 2k of RAM!

So... I need to be a bit creative. Firstly there isn't really a need in my game to keep an image of the display contents in memory.. All the required display content can be recalculated at every frame then dumped to the strips and latched into them. There is no need for me to refer back to the display content afterwards (e.g. collision detection is not relevant).

Also the WS2812 data protocol is very timing critical, so the transmit code needs to be tight and run without interruption (I am pleased to see Adafruit coded the important parts in assembly language in their library). This means that it is not possible to send data to multiple strips in parallel, you must send the full set of data to each strip in turn (unless they are chained together and a single send can address all strips, but this needs the full 900 pixel content to be buffered for send, so is a no-no)

So, I decided I need a way to update one strip at a time, making sure I required only a single 150 pixel buffer in memory. I guess two options are possible
  • Dynamically reconfigure the NeoPixel library to direct the data from a single library object and 150 pixel buffer to 6 different digital output pins in turn, re-rendering image into the data buffer for each new strip. This would need to use 6 output pins. It may also need some changes to the library code to allow the output pin to be efficiently reconfigured on the fly. 
  • Use a digital multiplexer IC (I have some TC4514BP's available, which should fit the bill) to connect a single output pin to each of the 6 strips in turn, re-rendering image into the data buffer for each new strip. This would need to use 4 output pins (one for the data and 3 for the selection between strips). I'd need to make sure the multiplexer chip would not skew the output pulses (which for the WS2812 are timing critical). I thought this approach could work without changes to the library code, but read on...

I went for the second option... some simple tests on a breadboard showed it worked, but I did need to modify the NeoPixel library code to allow the data output to be inverted (so that when it would usually output HIGH it output a LOW and vice-versa). I'll explain the reason...

The 4514 digital multiplexer has 16 outputs (more than I need, but I have a load of these chips lying about so..). You have 4 Address pins which you use to select which of the 16 outputs will be set to a HIGH value (all the other 15 show LOW).

There is also an Enable pin which must be held low for the selected output to show HIGH. If the Enable pin is HIGH then all of the 16 outputs are LOW. This allows the Enable pin to be used to toggle the selected output pin between LOW and HIGH, BUT the logic is inverted. If you want the selected output to be HIGH then Enable must be LOW and vice-versa. Therefore if we want the output from the NeoPixel library to drive the Enable pin we either need some kind of Inverter chip, or we can hack the library code to enable inverting of the logic. I went for the second option :)

TC4514BP 1-of-16 Digital Multiplexer

Some simple tests on breadboard and it seems to be working fine. I was concerned about the gate lag on the multiplexer impacting the time-critical WS2812 protocol (ICs have a small but possibly significant "propagation delay" between changing an input and seeing the output change) however as long as the delay is symmetrical and rising and falling edges are delayed by a similar amount of time then it should not be a problem for my application. The data sheet shows this is within the WS2812 +/-150ns tolerance.

Another good thing about this multiplexer is that unselected outputs are driven LOW rather than floated. This means that the WS2812 "Latch" command (pulling the data line low for an extended period of time) can take place after we have deselected the strip and moved on to the next one, making things ever so slightly faster.

Simple test with 2 strips

In case you want to use the same approach, here is the change I made to the NeoPixel library. First off I added a flag field activeLow (I wanted to make it switchable in case I needed to toggle during trouble shooting)
class Adafruit_NeoPixel {
:
 private:
#ifdef __AVR__
uint8_t
    activeLow;       // Set to 1 to use ACTIVE LOW output pulses    
#endif
};

I initialise the flag to 1
Adafruit_NeoPixel::Adafruit_NeoPixel(uint16_t n, uint8_t p, uint8_t t) : numLEDs(n), numBytes(n * 3), pin(p), pixels(NULL)
  ,type(t)
#ifdef __AVR__
  ,port(portOutputRegister(digitalPinToPort(p))),
   pinMask(digitalPinToBitMask(p)),
   activeLow(1)
#endif
The library implementation uses conditional compilation directives for the many Arduino boards, so you need to look through and find several places where the bit masks lo and hi are defined. This is a pre-calculation of values used later to toggle specific bits in the port registers. hi is usually set to the current port value with the target bit set and lo to the value with the bit cleared. We simply calculate these in the opposite sense if activeLow is needed
 if(activeLow)
        {
          lo = PORTD |  pinMask;
          hi = PORTD & ~pinMask;
        }
        else
        {
          
          hi = PORTD |  pinMask;
          lo = PORTD & ~pinMask;
        }



No comments:

Post a Comment