Saturday, May 2, 2020

0000 0000 0010 1101

Charlieplexing all those lights

In an earlier blog I looked at extending the miserly 5 available GPIO pins on an ATTiny13 to drive 8 LEDs using a 74HC595 shift register IC. At the time I mentioned a possible alternative charlieplexing solution and how it might be "fun" to try to drive more LEDs than the pins would nominally allow.

The target for full charlieplexing is N*(N-1) lights from N pins. I thought that I would start with 4 pins and therefore 4*3=12 LEDS. It was very confusing and ended up a bit of a mess (although it did work!). Eventually I sat down with a tablet and sketched out a logical representation of the desired circuit.
Oh yeah, that makes so much more sense!

Sure, it looks great (?) but then I need to arrange the LEDs in a line so that addressing them in some meaningful way is possible. The next Frankenstein diagram was a possible rearrangement as follows:
Wiring courtesy of a local starling

I wondered for a brief moment if there was a more efficient way to wire this up (there is), but to be honest I thought I could spend WAY too much time optimising the circuitry and not enough time soldering and looking at the blinking lights. And isn't that the fun part?

The ATTiny13 pins are labelled 1-4 on the diagram above which is shorthand for PB1-PB4. I chose these specific pins because when they are in use (e.g. as outputs in this case), and in consultation with the datasheet, they all line up in a row nicely:


DDRB = 0b00011110


Writing the code was then much simpler than my earlier efforts and using the diagram above as a guide (full code available on github), I was able to map the code to the circuit in a more logical way. The final code (in Arduino "C") uses 902 bytes, 88% of the available program storage space on that tiny guy. It runs five different effects with a bit of gap, then loops back indefinitely.

The LEDs are addressed in a 12x2 array (12 leds with 2 properties, port direction and port state) and after settling on that arrangement I just needed to write some loopy loops to access each LED.

If I needed one LED lit then it was easy, but for two or more LEDs the program needs to oscillate between them so quickly that the human eye/brain combo believes that they are both continuously lit

In the video below I focus on lighting LED "h" (the eighth LED from the right) which needs PB1 and PB3 set to OUTPUT, and a port state of HIGH for PB3 and LOW for PB1.

DDRB = 0b00001010
PORTB = 0b00001000


You can see this exact pattern described in the array shown in the code below labelled "LED h". There were a couple of other small bits in the code that you might find interesting, and it is all commented if you wish to take a deeper dive.

const byte leds12 [12][2] =

{
    {0b00000110, 0b00000010}, // LED a
    {0b00000110, 0b00000100}, // LED b
    {0b00001100, 0b00000100}, // LED c
    {0b00001100, 0b00001000}, // LED d
    {0b00011000, 0b00001000}, // LED e
    {0b00011000, 0b00010000}, // LED f
    {0b00001010, 0b00000010}, // LED g
    {0b00001010, 0b00001000}, // LED h
    {0b00010100, 0b00000100}, // LED i
    {0b00010100, 0b00010000}, // LED j
    {0b00010010, 0b00000010}, // LED k
    {0b00010010, 0b00010000}, // LED l
};

void lightemup (byte portdirection, byte portstate, int mydelay) {
    DDRB = portdirection;
    PORTB = portstate;
    delay(mydelay);

}

lightemup(leds12[0][0], leds12[0][1], 50);

The end result looks a bit of a mess on a breadboard...


...but blinks real nice. Note that the purists might see that I have rotated the ATTiny13 180 degrees from the diagrams above - I was under the illusion that might make the final wiring easier but I'm not sure that is true!



No comments:

Post a Comment