The ATTiny85 is a lovely little microcontroller, but it is not exactly generous with GPIO. That is usually fine until a small project grows one more LED, button or sensor than you planned for and you suddenly find yourself counting pins.
Charlieplexing is a neat way around that when the problem is LEDs. With three GPIO pins on the ATTiny85, I can control six of them without adding a shift register or moving to a larger microcontroller. There are compromises, particularly around brightness and timing, but for status lights and other small indicators it is a surprisingly useful trick.
Six LEDs from three pins
The useful bit is that a GPIO pin does not have only two useful states. It can drive high, drive low, or be configured as an input and effectively get out of the way in a high-impedance state. Charlieplexing combines that with the fact that an LED only conducts in one direction.
Take two GPIO pins and connect a pair of LEDs between them in opposite directions. If the first pin is high and the second is low, one LED conducts. Reverse the polarity and the other LED conducts instead. The third pin stays high impedance so it does not provide another current path.
With n pins, that gives a theoretical maximum of:
n × (n - 1)
So three pins gives:
3 × (3 - 1) = 6 LEDs
For the ATTiny85, that means the six LEDs are connected across the three pins as directional pairs:
- A → B
- B → A
- A → C
- C → A
- B → C
- C → B
That directionality is the whole reason the arrangement works. A single pair of wires can address two different LEDs because changing the polarity changes which one is able to conduct.
Tiny chip, mildly unreasonable number of lights. I’m quite happy with that.
Wiring is only half of it
Of course, the ATTiny85 is not actually driving all six LEDs continuously. It is lighting them one at a time and moving between them quickly enough that the individual flashes blur together visually.
I use a timer interrupt for the refresh rather than relying on the main loop. That keeps the multiplexing regular regardless of what the rest of the program happens to be doing, which matters if you want the display to look steady rather than change its mind whenever the main loop gets busy.
For each refresh, I first return all three LED pins to INPUT, putting them into their high-impedance state. I can then choose the LED I want to light, make its source pin an output driven HIGH, and make its sink pin an output driven LOW. On the next timer interrupt the pins are cleared again and the process repeats for the next LED.
That initial high-impedance step matters. If one of the unused pins is accidentally left driving high or low, it can create an unintended path through another LED and you start getting ghosting, odd brightness changes or several LEDs doing things you did not actually ask them to do.
The timer also gives a convenient place to handle brightness if I need it. Because each LED is only active for part of the refresh cycle anyway, its apparent brightness is already determined partly by duty cycle, and additional PWM-style control can be layered on top if the project needs it.
Where I’d use it
The obvious cost of charlieplexing is brightness. With six LEDs sharing the display time, each individual LED is only being driven for a fraction of the refresh cycle, so this is never going to behave like six independently driven LEDs that can all sit at full brightness indefinitely. The refresh also has to be fast enough to avoid visible flicker, while still leaving enough on-time for each LED to be useful.
Current limiting does not disappear either. The LEDs still need suitable resistors, and the ATTiny85 GPIO limits still apply just as they would in a conventional circuit. Charlieplexing saves pins; it does not repeal Ohm’s law.
For small status indicators, tiny battery-powered projects, wearables or anything else where GPIO is scarce and maximum brightness is not particularly important, I like it. Three pins for six individually addressable LEDs is a good trade when the alternative is adding another IC simply because the microcontroller ran out of legs.
If I needed a large number of LEDs, high brightness, or genuinely independent simultaneous control, I would stop being clever and use a proper LED driver. There is a point where saving another GPIO pin stops being worth the increasingly interesting wiring.
For six LEDs on an ATTiny85, though, charlieplexing sits comfortably on the useful side of that line.