Creating a Charlieplexed LED Grid to Run on ATTiny85
by benbrandt22 in Circuits > Microcontrollers
43953 Views, 126 Favorites, 0 Comments
Creating a Charlieplexed LED Grid to Run on ATTiny85
This instructable was inspired by my first AVR microcontroller project that I've been working on for some time now. I wanted to start learning more about the AVR microcontroller and see how much I could do with the minimum amount of hardware... no extra chips, simple components, etc... just my AVR and my code so I could experiment with a simple set of hardware.
I decided to start with one of the smallest AVRs available, the ATTiny85 with only 8 pins. With only 5 output pins available to me (using 6 would make me lose other chip functionality), I decided to experiment with Charlieplexing. Charlieplexing allows you to control many LEDs with very few output pins, by wiring up an LED in both directions to every possible pair-combination of the pins. In my case, 5 pins would allow control of up to 20 LEDs (5 x (5-1)). This would only require the ATTiny, LEDs, 5 resistors, and a power source.
More info on Charlieplexing: http://en.wikipedia.org/wiki/Charlieplexing
I decided to start with one of the smallest AVRs available, the ATTiny85 with only 8 pins. With only 5 output pins available to me (using 6 would make me lose other chip functionality), I decided to experiment with Charlieplexing. Charlieplexing allows you to control many LEDs with very few output pins, by wiring up an LED in both directions to every possible pair-combination of the pins. In my case, 5 pins would allow control of up to 20 LEDs (5 x (5-1)). This would only require the ATTiny, LEDs, 5 resistors, and a power source.
More info on Charlieplexing: http://en.wikipedia.org/wiki/Charlieplexing
Planning Out the Wiring
Conceptually, it's not too difficult to understand how to wire up charlieplexing from some of the simpler diagrams available online (see diagram below courtesy of Wikipedia). However, I wanted to arrange 20 LEDs in a tight grid, and managing all the different connections & polarities seemed like it would get complicated as I scaled up from the 2 or 3 pin examples. Most importantly, to avoid mistakes when assembling, I wanted all my LEDs to be aligned the same direction on the board.
In the next step you'll see how the wiring works out for a larger grid of LEDs.
In the next step you'll see how the wiring works out for a larger grid of LEDs.
Running Bus-lines to All LEDs in the Grid
After some thinking and sketching, I came up with my concept. In this project we're working with 5 pins. Lets call them A, B, C, D and E.
Let's just focus on pin A for a moment. If pin A is set to a positive voltage, it could control up to 4 different LEDs connected to ground on pins B through E. So I should have one line connecting the 4 positive ends (Anodes) of LEDs to pin A. This same concept extends to all the other pins. If Pin B was positive, it could control 4 others on pins A, C, D, and E. With this in mind, I planned out a positive "bus-line" for each column of 4 LEDs. These are shown in blue in the diagram below.
The negative (anode) connections can be handled on the other side of the board, as bus-lines between each row, feeding to LEDs on either side of the line as needed. These are shown in red in the diagram below.
All that's left is to connect the respective bus-lines to each other so they all connect back to the same 5 pins. These connections are shown in green in the diagram below.
Finally, a resistor is placed between each line and the microcontroller to limit current through the LEDs.
While it's a little complicated to solder by hand, it can be managed. Just go slowly and double check your connections as you go.
Let's just focus on pin A for a moment. If pin A is set to a positive voltage, it could control up to 4 different LEDs connected to ground on pins B through E. So I should have one line connecting the 4 positive ends (Anodes) of LEDs to pin A. This same concept extends to all the other pins. If Pin B was positive, it could control 4 others on pins A, C, D, and E. With this in mind, I planned out a positive "bus-line" for each column of 4 LEDs. These are shown in blue in the diagram below.
The negative (anode) connections can be handled on the other side of the board, as bus-lines between each row, feeding to LEDs on either side of the line as needed. These are shown in red in the diagram below.
All that's left is to connect the respective bus-lines to each other so they all connect back to the same 5 pins. These connections are shown in green in the diagram below.
Finally, a resistor is placed between each line and the microcontroller to limit current through the LEDs.
While it's a little complicated to solder by hand, it can be managed. Just go slowly and double check your connections as you go.
Charlieplexing in Software - Getting Started
In my project, I used the 4x5 grid to run a Conways Game of Life simulation. However, before we get that complex, let's cover some basics of the software to show how we light LEDs in this charlieplexed setup.
First, I've defined my pins A through E and specified which bit on PORTB they will be referring to. This makes it easier to refer to Line A through E later in the code:
#define LINE_A 0 //Pin 5 (PB0) on ATtiny85
#define LINE_B 1 //Pin 6 (PB1) on ATtiny85
#define LINE_C 2 //Pin 7 (PB2) on ATtiny85
#define LINE_D 3 //Pin 2 (PB3) on ATtiny85
#define LINE_E 4 //Pin 3 (PB4) on ATtiny85
In order to light any one of the 20 LEDs, we need to configure our 5 pins a different way for each LED. To light one LED, we need one pin set to an output with a high voltage, one pin set to an output with a ground voltage, and all the other pins need to be set to inputs to prevent current flow.
To make it simpler, we'll set up some arrays to store all the configurations for DDRB (which sets the input/output modes of each pin) and PORTB (which sets the high/low voltage of each pin).
//DDRB direction config for each LED (1 = output)
const char led_dir[20] = {
( 1<<LINE_A | 1<<LINE_E ), //LED 0
( 1<<LINE_B | 1<<LINE_E ), //LED 1
( 1<<LINE_C | 1<<LINE_E ), //LED 2
( 1<<LINE_D | 1<<LINE_E ), //LED 3
( 1<<LINE_E | 1<<LINE_D ), //LED 4
( 1<<LINE_A | 1<<LINE_D ), //LED 5
( 1<<LINE_B | 1<<LINE_D ), //LED 6
( 1<<LINE_C | 1<<LINE_D ), //LED 7
( 1<<LINE_D | 1<<LINE_C ), //LED 8
( 1<<LINE_E | 1<<LINE_C ), //LED 9
( 1<<LINE_A | 1<<LINE_C ), //LED 10
( 1<<LINE_B | 1<<LINE_C ), //LED 11
( 1<<LINE_C | 1<<LINE_B ), //LED 12
( 1<<LINE_D | 1<<LINE_B ), //LED 13
( 1<<LINE_E | 1<<LINE_B ), //LED 14
( 1<<LINE_A | 1<<LINE_B ), //LED 15
( 1<<LINE_B | 1<<LINE_A ), //LED 16
( 1<<LINE_C | 1<<LINE_A ), //LED 17
( 1<<LINE_D | 1<<LINE_A ), //LED 18
( 1<<LINE_E | 1<<LINE_A ) //LED 19
};
//PORTB output config for each LED (1 = High, 0 = Low)
const char led_out[20] = {
( 1<<LINE_A ), //LED 0
( 1<<LINE_B ), //LED 1
( 1<<LINE_C ), //LED 2
( 1<<LINE_D ), //LED 3
( 1<<LINE_E ), //LED 4
( 1<<LINE_A ), //LED 5
( 1<<LINE_B ), //LED 6
( 1<<LINE_C ), //LED 7
( 1<<LINE_D ), //LED 8
( 1<<LINE_E ), //LED 9
( 1<<LINE_A ), //LED 10
( 1<<LINE_B ), //LED 11
( 1<<LINE_C ), //LED 12
( 1<<LINE_D ), //LED 13
( 1<<LINE_E ), //LED 14
( 1<<LINE_A ), //LED 15
( 1<<LINE_B ), //LED 16
( 1<<LINE_C ), //LED 17
( 1<<LINE_D ), //LED 18
( 1<<LINE_E ) //LED 19
};
Finally, we have one simple function to make this work, "light_led"
void light_led(char led_num) { //led_num must be from 0 to 19
DDRB = led_dir[led_num];
PORTB = led_out[led_num];
}
void leds_off() {
DDRB = 0;
PORTB = 0;
}
By calling light_led with a number of 0 to 19, we can light the desired LED. From here we can build more complexity into the software to store a 4x5 grid and display it in lights.
First, I've defined my pins A through E and specified which bit on PORTB they will be referring to. This makes it easier to refer to Line A through E later in the code:
#define LINE_A 0 //Pin 5 (PB0) on ATtiny85
#define LINE_B 1 //Pin 6 (PB1) on ATtiny85
#define LINE_C 2 //Pin 7 (PB2) on ATtiny85
#define LINE_D 3 //Pin 2 (PB3) on ATtiny85
#define LINE_E 4 //Pin 3 (PB4) on ATtiny85
In order to light any one of the 20 LEDs, we need to configure our 5 pins a different way for each LED. To light one LED, we need one pin set to an output with a high voltage, one pin set to an output with a ground voltage, and all the other pins need to be set to inputs to prevent current flow.
To make it simpler, we'll set up some arrays to store all the configurations for DDRB (which sets the input/output modes of each pin) and PORTB (which sets the high/low voltage of each pin).
//DDRB direction config for each LED (1 = output)
const char led_dir[20] = {
( 1<<LINE_A | 1<<LINE_E ), //LED 0
( 1<<LINE_B | 1<<LINE_E ), //LED 1
( 1<<LINE_C | 1<<LINE_E ), //LED 2
( 1<<LINE_D | 1<<LINE_E ), //LED 3
( 1<<LINE_E | 1<<LINE_D ), //LED 4
( 1<<LINE_A | 1<<LINE_D ), //LED 5
( 1<<LINE_B | 1<<LINE_D ), //LED 6
( 1<<LINE_C | 1<<LINE_D ), //LED 7
( 1<<LINE_D | 1<<LINE_C ), //LED 8
( 1<<LINE_E | 1<<LINE_C ), //LED 9
( 1<<LINE_A | 1<<LINE_C ), //LED 10
( 1<<LINE_B | 1<<LINE_C ), //LED 11
( 1<<LINE_C | 1<<LINE_B ), //LED 12
( 1<<LINE_D | 1<<LINE_B ), //LED 13
( 1<<LINE_E | 1<<LINE_B ), //LED 14
( 1<<LINE_A | 1<<LINE_B ), //LED 15
( 1<<LINE_B | 1<<LINE_A ), //LED 16
( 1<<LINE_C | 1<<LINE_A ), //LED 17
( 1<<LINE_D | 1<<LINE_A ), //LED 18
( 1<<LINE_E | 1<<LINE_A ) //LED 19
};
//PORTB output config for each LED (1 = High, 0 = Low)
const char led_out[20] = {
( 1<<LINE_A ), //LED 0
( 1<<LINE_B ), //LED 1
( 1<<LINE_C ), //LED 2
( 1<<LINE_D ), //LED 3
( 1<<LINE_E ), //LED 4
( 1<<LINE_A ), //LED 5
( 1<<LINE_B ), //LED 6
( 1<<LINE_C ), //LED 7
( 1<<LINE_D ), //LED 8
( 1<<LINE_E ), //LED 9
( 1<<LINE_A ), //LED 10
( 1<<LINE_B ), //LED 11
( 1<<LINE_C ), //LED 12
( 1<<LINE_D ), //LED 13
( 1<<LINE_E ), //LED 14
( 1<<LINE_A ), //LED 15
( 1<<LINE_B ), //LED 16
( 1<<LINE_C ), //LED 17
( 1<<LINE_D ), //LED 18
( 1<<LINE_E ) //LED 19
};
Finally, we have one simple function to make this work, "light_led"
void light_led(char led_num) { //led_num must be from 0 to 19
DDRB = led_dir[led_num];
PORTB = led_out[led_num];
}
void leds_off() {
DDRB = 0;
PORTB = 0;
}
By calling light_led with a number of 0 to 19, we can light the desired LED. From here we can build more complexity into the software to store a 4x5 grid and display it in lights.
Charlieplexing in Software - Displaying an Image
Next in our code-building journey we're going to set up a 4x5 grid in memory. By changing the values in this grid, and then running a draw routine, our image will be displayed in LED lights.
Here we'll set up our variable to hold the 20 values (pixels) for our LED image. The default is shown below (all 0's). This would display as a dark image, with all LEDs turned off, but we can change this in software.
char led_grid[20] = {
000 , 000 , 000 , 000 , 000 ,
000 , 000 , 000 , 000 , 000 ,
000 , 000 , 000 , 000 , 000 ,
000 , 000 , 000 , 000 , 000
};
I'm allowing this variable to store more than just 1's and 0's, but larger numbers too. I want to make my LEDs dimmer at times. So I've decided for this project a value of 100 will be full-brightness, and 0 will be off.
The following function will loop through all 20 positions in the led_grid and if the value of each pixel is above zero, the appropriate LED will be lit. Since you can only light one LED at a time, the image must be re-drawn constantly, and fast enough that you can't notice the strobing/flickering with your eyes. Typically this won't be a problem with the speeds you can run on a microcontroller.
void draw_frame(void){
char led, bright_val, b;
for ( led=0; led<=19; led++ ) {
//software PWM
bright_val = led_grid[led];
for( b=0 ; b < bright_val ; b+=4 ) { light_led(led); } //delay while on
for( b=bright_val ; b<100 ; b+=4 ) { leds_off(); } //delay while off
}
}
Here we'll set up our variable to hold the 20 values (pixels) for our LED image. The default is shown below (all 0's). This would display as a dark image, with all LEDs turned off, but we can change this in software.
char led_grid[20] = {
000 , 000 , 000 , 000 , 000 ,
000 , 000 , 000 , 000 , 000 ,
000 , 000 , 000 , 000 , 000 ,
000 , 000 , 000 , 000 , 000
};
I'm allowing this variable to store more than just 1's and 0's, but larger numbers too. I want to make my LEDs dimmer at times. So I've decided for this project a value of 100 will be full-brightness, and 0 will be off.
The following function will loop through all 20 positions in the led_grid and if the value of each pixel is above zero, the appropriate LED will be lit. Since you can only light one LED at a time, the image must be re-drawn constantly, and fast enough that you can't notice the strobing/flickering with your eyes. Typically this won't be a problem with the speeds you can run on a microcontroller.
void draw_frame(void){
char led, bright_val, b;
for ( led=0; led<=19; led++ ) {
//software PWM
bright_val = led_grid[led];
for( b=0 ; b < bright_val ; b+=4 ) { light_led(led); } //delay while on
for( b=bright_val ; b<100 ; b+=4 ) { leds_off(); } //delay while off
}
}
Charlieplexing in Software - Adding Complexity
At this point we have a way to create an image in memory and display it on our charlieplexed LED grid. From here you can use your imagination and experiment with your programming code.
For my project, I chose to create a Conways Game of Life simulation. I create a random configuration of LEDs and let them run through each generation until it dies, stays steady, or loops. Once any of these is detected, it resets and starts over. I've included my code in the attached file if you'd like to review, but I would encourage you to experiment and come up with your own animations.
More information on my project can be found on my blog:
http://b2ben.blogspot.com/search/label/TinyChuck5
Thanks for reading!
For my project, I chose to create a Conways Game of Life simulation. I create a random configuration of LEDs and let them run through each generation until it dies, stays steady, or loops. Once any of these is detected, it resets and starts over. I've included my code in the attached file if you'd like to review, but I would encourage you to experiment and come up with your own animations.
More information on my project can be found on my blog:
http://b2ben.blogspot.com/search/label/TinyChuck5
Thanks for reading!