Charlieplexing


It is really amazing how many LEDs can be controlled with only a few ports using the Charlieplexing method (proposed by by Charlie Allen, 1995). In this example we are using d8 to d12 as they are mapped to PORT B, bits 0 to 4 which makes handling them in the interrupt procedure much easier.

When you take a close look at the schematics you will find it easy to place the LEDs on a breadboard.


(Click to enlarge)

You can also solder the LEDs, the resistors, and a multi-pin connector to a soldering strips grid board with only a few additional connections to make. As a result you get a "Charlie-Shield".

The picture above shows the state after soldering the 8-pin connector, the wires, two of the resistors and one of the LEDs.
Some of the bars have to be cut. This is done by drilling with a 0.1" drill only to destroy the copper bar.

To program you have to set both the data register (bits[]) and the data direction register (dirs[]). For each LED you need to set two of the port bits as OUTPUT and one of them HIGH, the other one LOW.

#define FILENAME "Charlie5f"
/* 20 LEDs connected to Pin-8 to Pin-12 = PORT-D, bits 0 to 4
Version with matrix "ways", without CASE
More than one LED can flash at one time.
        +----+----+----+----+----+               
        |  0 |  1 |  2 |  3 |  4 |
        +----+----+----+----+----+               
        |  5 |  6 |  7 |  8 |  9 | 
        +----+----+----+----+----+               
        | 10 | 11 | 12 | 13 | 14 | 
        +----+----+----+----+----+               
        | 15 | 16 | 17 | 18 | 19 |
        +----+----+----+----+----+               
PORT B bit-0 =  d8 = Controller-Pin-14
PORT B bit-1 =  d9 = Controller-Pin-15
PORT B bit-2 = d10 = Controller-Pin-16
PORT B bit-3 = d11 = Controller-Pin-17
PORT B bit-4 = d12 = Controller-Pin-18 */
const byte rows = 4;
const byte cols = 5;
const byte N = rows * cols;
const byte N1 = cols;
const byte WAYS[][2] = { {0,4}, {3,0}, {2,3}, {1,2}, {0,1},
                         {4,0}, {0,3}, {3,2}, {2,1}, {1,0},
                         {3,4}, {1,3}, {4,1}, {2,4}, {0,2},
                         {4,3}, {3,1}, {1,4}, {4,2}, {2,0} };

You also need to initialize an interrupt sevice routine:

void setTimer() {
  TCCR2B = 3; // Timer2 Settings: Timer Prescaler /32, mode 0
  TIMSK2 = 1; // Timer2 Overflow Interrupt Enable
  // 16.000.000 / 32 / 256 = 1953 Interrupts per second  
}

declare a video RAM:

boolean ways[N1][N1];

and implement the interrupt sevice routine:

ISR(TIMER2_OVF_vect) { // Timer 2 interrupt service routine 
  static byte i;
  byte dirs = 1 << i;
  PORTB = dirs;  // that is the positive, all the other ones are negative
  // enable only the port bits that are needed:
  for (byte j = 0; j < N1; j++) if (ways[i][j]) dirs = dirs | (1 << j);
  DDRB = dirs;  
  if (++i >= N1) i = 0; // next pin to set positive 
}

(If you are using d13 (the internal Arduino LED) you should mask bit d5 out.)
Now you can easily switch on or switch off each LED in your loop routine by calling this procedure
void setLED(byte nr, boolean on) {
  byte anode   = WAYS[nr][0];
  byte cathode = WAYS[nr][1];
  ways[anode][cathode] = on;
}

That's all ...

Well, not really. If your project needs the SPI pins for other devices you have to use PORT-D or even PORT-C (the analog pins). When using PORT-D you might get in trouble with pin-0 and pin-1 as they are used for RX and TX. So the ISR has to do some bit manipulation.

In theory you can use up to 8 bits for Charlie-plexing but as the time slot for each LED is getting shorter and the circuit is getting more and more complex you should not exceed six pins. Also, if one of the LEDs fails (open or short-cut) it will be difficult to find the one.




contact: nji(at)gmx.de