Pondering a LED controller

So, a bit over 10 years ago, I made the Hat Lamp.  You can tell how long ago it was as it calls out Dick Smith part numbers for things like resistors.  (How long ago did they give that up?)

Anyway, the original still works, although my wiring is less than perfect.  I’ve thought about modernising it.  Back in 2007, addressable LEDs didn’t exist.  The project got by with nothing more than a 74HC14.  It used one gate as a pierce oscillator, a second to generate a 180° out-of-phase signal, and a third to perform “automatic” control based on the light that fell on a LDR mounted on the top of the hat.

I’ve thought about whether I modernise it.  I have access to 3D printing facilities at HSBNE, so destroying a hard hat is no longer of concern: the design I could come up with could be made to fit a hard hat without modification, meaning it would retain its safety standard qualifications.  LED technology has marched on, in 2007 a 1W LED was considered bright.  The Ay-up headlights I use when cycling are many times brighter than that.  These headlights do come with a headband accessory, which I have, but I find they’re a bit cumbersome.  They however work great on a hard hat or helmet.

That said, for WICEN activities, they’re often too bright, even on their lowest power setting.

MCUs are also cheaper today than they used to be.  And we have addressable LEDs.  Meaning that I could have the old alternating red “alien-abduction head first” pattern the old one, or any number of patterns to suit the occasion.

That said, I’m a little concerned about APA Electronic throwing their weight around.  I actually was considering the APA102s, as they use a SPI style interface which is less timing-sensitive than World Semi’s WS2812s, but really, I hadn’t made a firm choice.  Then, Pimoroni got that letter.  I have no idea whether that letter was (1) a hoax (as in, not actually sent from APA Electronic), (2) the matter settled or (3) the matter still in progress.

In any case, the patent referenced talks about synchronous interfaces.  One common gripe with the WS2812s is that the interface relies on strict timing, which is harder to do with higher-level MCUs and CPUs.  Arduinos can work them fine, but as it’s a somewhat custom serial link, you’ve got to be able to bit-bang it via GPIOs and not all systems are good at that.  Using SPI avoids that problem at the cost of an extra wire.  I wondered if there was another way.

This is what I came up with as a concept.  UARTs idle “high” when not transmitting, so the TX line can serve as a pull-up resistance when the master is not sending anything.  Some MCUs can also re-map their pins (e.g. NXP LPC81x, TI CC2538, Nordic NRF52840), others natively support half-duplex UART Rx/Tx on a single pin (e.g. Microchip ATTiny202).

That allows us to have a shared “bus” with 3 wires between each module: VDD, Data and VSS… the same as the WS2812s.  Unlike the WS2812s, this bus would be built on the UART standard, thus less sensitive to timing jitter.

The problem then is, how do you address each LED?  The WS2812 and APA102s solve this by making the whole bus function as one big shift register.  This makes the electronics simple, but has the cost that you can only communicate one way, from MCU to LED controller.  Thus, you have to maintain a framebuffer, and if you want to just change the colour of one LED, you’ve got to shift the entire framebuffer out.

Why can’t the LEDs have more brains?  How hard is it to DIY a LED controller?

The above arrangement uses the concept of “upstream” and “downstream” ports.  A bi-lateral switch is used to disconnect the “downstream” port under the control of the LED firmware.  If each slave on power up waited for some initialisation signal from the master, all could then disconnect their downstream ports, which then means the next command would only be received by the first LED module.

On telling it its assigned address, it could connect its downstream neighbour and you’d then be able to talk to those two LEDs, one of which would be at some unknown address, and the other, assigned.

This would repeat until you got to the end of the chain.  Given that the downstream LED can “hear” its upstream neighbour when it is connected, it’s not hard for the downstream LED to assume its address is one after its upstream neighbour.

Disconnection would be achieved via some sort of tri-state bi-directional buffer such as a bilateral switch.  I’ve used 4066s in the diagram, but in reality, I’d be using a single-unit version like a SN74LVC1G66.

It’s also common to consider these as matrices.  It’d be really neat, if you say had an array of say 320×240 pixels that the LEDs should use 4-byte addressing, where the lowest 9 bits was the X co-ordinate and the upper bits the Y co-ordinate.  Thus the addressing would count from 0x0000000 to 0x0000013f, then skip to 0x0000200.  That’s a simple arithmetic operation.  By connecting the next neighbour then announcing the address, this could trigger the neighbour to do the same automatically.  The master would then hear each and every pixel announce its address as it comes online.  When the messages stop, initialisation is complete.

A major problem with asynchronous communications is figuring out the baud rate.  Luckily, LIN has solved that problem.  No, I won’t actually use the LIN protocols, I’ll just use its sync frame, support for which is built into many MCU UART modules.  LIN uses a header which includes a BREAK followed by the 0x55 byte which helps the slave figure out the correct baud rate being used.  If I use that same sequence, I get autobauding for free.

So putting this together, how would this work?  Let’s assume everything has just been reset.  Each protocol frame would be bounded by a header and a trailer, the header being based on the LIN standard.  Not sure what the trailer will look like at this point, maybe a CRC.

  1. On power-on, the slaves all link their downstream ports.  (Thus if a controller crashes, you lose just that one pixel.  It also allows all slaves to receive the initial configuration commands.)
  2. The master then tells the slaves to commence a roll-call.  The instruction would be made up of:
    1. The “commence roll-call” op-code (1 byte)
    2. The length in bytes of the addresses to be used (1 byte; call its value L)
    3. The number of bits in the address representing a single row (1 byte; call this D)
    4. The number of pixels per row (L bytes, call this M)
  3. The slaves immediately disconnect their downstream ports then respond back with
    1. The “OK” op-code
  4. Since the head of the line would have disconnected every other slave, the master only hears one “OK” response.  The master performs the following computation:
    • Address = (2^(8L)) – 1
  5. The master starts the roll-call off by sending the following on the bus.
    1. The “Address announcement” op-code (1 byte)
    2. The address it calculated (L bytes)
  6. Since just the first slave is connected, it hears this.  It connects its downstream neighbour, then with the received address, it performs the following algorithm:
    • Address = UpstreamAddress + 1
    • If (Address & ((2^D)-1) > M:
      • Address = ((Address >> D) + 1) << D
  7. With its new address, and the immediate neighbour connected, it sends
    1. The “Address announcement” op-code (1 byte)
    2. The address it calculated (L bytes)

Ad infinite um, until the last in the chain announces its address.

The master of course hears all, including its own traffic.  As an example, if we considered a 320×200 pixel panel with 32-bit addressing; thus L=4, D=9 and M=320, it would hear this:

  • HEADER OP_ROLL_CALL L=4 D=9 M=320 TRAILER: Begin roll-call
  • HEADER RES_OK TRAILER: Slaves ready for roll-call
  • HEADER OP_ADDR_ANN ADDR=0xffffffff TRAILER: Master “my address is 0xffffffff”
  • HEADER OP_ADDR_ANN ADDR=0x00000000 TRAILER: First pixel “my address is 0x00000000”
  • HEADER OP_ADDR_ANN ADDR=0x00000001 TRAILER: Second pixel “my address is 0x00000001”
  • etc
  • HEADER OP_ADDR_ANN ADDR=0x0000013f TRAILER: 320th pixel “my address is 0x0000013f”
  • HEADER OP_ADDR_ANN ADDR=0x00000200 TRAILER: 321st pixel “my address is 0x00000200”
  • etc

At the end, everybody knows their address, including the master (which is derived from the address length; its address is “all ones”), and because each slave connected its neighbour, everyone can communicate together.

Operation codes could be implemented that allow a pixel to be set to a given colour, or to report its present colour.  Since they all know how to interpret the address to form co-ordinates, it’s possible for the master to send a command that says “fill rectangle (X1,Y1)-(X2,Y2) with colour C”, all pixels hear this simultaneously and the action is performed.

Or better yet, “pixels in area (X1,Y1)-(X2,Y2), copy the colour from the neighbour to your right”, which would allow for scrolling text displays.  The pixels would know immediately who to ask, and could have an “agreed upon” order in which to perform operations.  Thus (X1,Y1) would know to ask (X1+1,Y1) for its colour, copy that to its own output, then tell (X1,Y1+1) to perform its step.  (X1,Y2) would know that once it copied its colour from (X1+1,Y2), it needs to poke (X1+1,Y1).  Finally (X2,Y2) would know to tell the master that the operation is complete.

Blitting can also be done.  You know the operation, you know how to obtain the input data, the rest can be done independent of the master MCU.

The microcontrollers don’t need a lot of brains to do this, nor does the master for that matter, it’s distributed brains that get the job done.  The part I’m thinking of for this is the Microchip ATTiny202, which can be bought for under 60c a piece and features hardware UART and up to 4 PWM channels.

For sure, add in the bilateral switch, some passives, a PCB and a RGB LED and you’ve blown more money than the competition, but in this case, you’ve got a fully programmable LED controller with open-source firmware, that’s not patent encumbered.

It might be a little while before this MCU is available, Mouser reckon they’ll have them late October, which is fine I can wait.  Until then, plenty of time to research the problem.