Charlieplexing as a basis function

From Noisebridge
Jump to navigation Jump to search

Charlieplexing as a basis function[edit]

Short version: using ternary integers to encode charlieplexing as a basis function for encoding the state of an LED matrix. We can use POV for superposition to recombine these "charlievectors", and get multiple levels of grey on the array for free in our decomposition step.

Motivation and background[edit]

Motivation: getting video onto a charlieplexed array.

You can't do it because the normal way charlieplexing works is to set a single LED at a time. To turn on N LEDs requires toggling 4*N lines on the microcontroller. In this view, what you're putting onto the display is "encoded" as a set of LEDs that are on: if you want to turn on LEDs 1, 2, 3, 12, and 15, you'd encode it as { 1, 2, 3, 12, 15 }. You then walk through that list, turning them on, waiting a few milliseconds, then turn them off, and move on.

Problems with charlieplexing video[edit]

Ideally, we'd just turn on all the LEDs for a given frame of video at once, then turn them off. Sadly, we can't do that. Typical charlieplexing goes through the matrix LED by LED, turning them on, then turning them off, and moving on. If you do this fast enough, it sort of burns into the eye, a trick called "Persistence of Vision" or POV.

However, POV only works well within certain parameters: if you take too long to strobe a frame, the video stops being video. If you go too fast, the lights are really dim. On the heart, for instance, if you want to turn on all the LEDs with POV, each LED can only be 1/27th its maximum possible brightness. In addition to these two problems, the display also tends to display "flicker" due to the time it takes to strobe across all the LEDs.

A solution[edit]

What if, instead of turning on one LED at a time, we could turn on many of them? We could turn a series of single-LED writes into a shorter sequence of multi-LED writes. This greatly increases our efficiency, giving us better brightness and less chance at flicker. It also greatly increases the framerate we can achieve.

Can we turn on more than one LED at a time? Sure! Instead of turning only one or two pins to +5V or Ground, we turn multiple pins on and off. If we do this carefully, we can make patterns of multiple LEDs light up concurrently. The available set of patterns is determined by how the matrix is wired up. To figure out the patterns, we enumerate the basis in a software model, and write it out. But, first, a digression into ternary.

Charlieplexed pins have a ternary encoding[edit]

Note that every pin in a charlieplexed array is a trit, it has 3 possible states: HIGH, LOW, and HI-Z. So, we can encode all possible states of our pins as a 6-trit number. This gives us 729 possible states, which are pretty easy to enumerate: you just count to 729! Then, you take this number modulo 3, and that gives you the first trit's value: 0, 1, or 2. 0 is HI-Z, 1 is LOW, and 2 is HIGH. You then divide the number by 3 (right shift in base 3), and take the modulus to get the second trit. Rinse, repeat, to get 6 trits. This gives you a set of six pin states. These can be applied on the microcontroller itself to actually drive the pins, or used in a modelling program.

In fact, that's exactly what we do: we take a model of the LED pinout (ie: to turn on LED 1, turn pin 1 HIGH and pin 2 LOW) and check it against the pin states above. When we do this, we find that we can associate each of our 729 pin states with a set of LEDs that turn on. For example, TK state 106 turns on LEDs 1, 3, 12, and 15. To turn on just pin 2, we'd use state 11, which only turns on LED 2. We can thus encode the display of LEDs 1, 2, 3, 12, and 15 as { 106, 11 }. Before, we'd have to turn on all 5 LEDs individually, which meant that each LED could be at most 1/5 its total brightness. Now, we can do it in just two operations, letting each one be roughly half its peak brightness. Reducing the operations also reduces the problem with flicker, as there's not as much delay between the updates.

The actual process we're going through[edit]

So, then, what do we do?

1. Generate the pin diagram (you probably have this already in your C code) 2. Decide on your pin -> trits mapping function 3. Generate a basis file, mapping integers into LED states (enumerate and decode using the above two files) 4. Write microcontroller code that also implements the integer to pin states decoding. 5. Spot check your two decoding implementations, to make sure your basis is correct (the heart circuit swaps pins all over the place, which makes it surprisingly tricky to get the mapping correct). I'd suggest writing a function that just turns on each of your LEDs for half a second in sequence, which gives a very good spot check. Once that works, you're probably okay. 6. Here's the tricky bit: write a program that, given a set of LEDs to have on, turns that into a set of charlieplexed basis integers. 7. Once you've got that, you're pretty well done with the binary case: you can now output single-grey video to the device.

Greyscale for nothing[edit]

Now, what about doing multiple levels of grey? Notice that the decomposition function above turns a given set of LED states into a set of charlieplexed basis integers, and we're getting them all to turn on through POV. Well, what if we just made the decomposition work on an array of multiple-valued integers, instead of only 1s and 0s? The decomposition should be the exact same process, but just a little more expensive.

I've gotten 4 levels to mostly work, but it looks like I have a problem with my pin mappings at the endpoint there. Some LEDs which aren't supposed to be lit up show up rather bright, or half-dim: I believe I need a full encoding of the LED matrix/network to make the modelled version better. However, until then, it's not a bad first approximation.