[TUT] [C] Basic Leds & Charlieplexing, mild beginner

Go To Last Post
11 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Basic Leds & Charlieplexing
(c) M. de Ruijter, 2009

AvrFreaks Appreciation Paragraph
First of, a word of thanks, I've only been working with AVR's as a hobby for a short time, but it's due to this forum that I can actually get stuff done. Many thanks go out to abcminiuser, clawson, valusoft, zbaird, JohanEkdahl, ka7ehk and the others for pointing out the many rookie mistakes I made. I am sure there is plenty of new mistakes and ignorance is the text below, but what can I say? It works for me :)

Warning
Please consider that English is not my primary language. Furthermore, I have not had anybody proofread this, so there will be errors, both textual and factual. Also consider that I am nowhere near as knowledgeable as some of the other people on this forum, so do not take all I write as gospel or chiptruth, it's just the trial and error results of me trying to drive leds.

Introduction
One of the many usefull, and, fun things one can accomplish with a microcontroller is driving leds. Flashing leds is one of the most satisfying things a beginning developer can do. It's simple, it's fun and it's very basic.

I still consider myself quite the beginner in the area of AVR's, however, I've had the benefit of some excellent tutorials and help on this forum, and would like to try and give a little back. And so, this is the story of me building my very first functional component. I hope it is helpfull to some, it was very helpfull for me to write.

This tutorial assumes you know the basic 101 things, if not, go read it here

In the later part of the tutorial, I will reuse the timer code from Dean's, aka abcminiuser, excellent tutorial on on AVR Timers, read it here.

The case
We are building a power gauge out of leds, a basic 6 led bar that can indicate a remaining charge in 6 steps, from full, all six leds active, to empty, all six leds inactive and every state in between

The basics
We are using an AtMega16, for no reason then the fact that Dean uses that same micro for his tutorials. Consider it a tribute. (Seriously here, go read Dean's stuff, it's awesome, he is known as abcminiuser on this forum). Also, this tutorial does not focus on things like load limiting resistors and other such precautions. Finally, we are going to do this in C, I myself use the AVR Studio for my AVR programming needs in combination with WinAVR.

1. Direct Pin driven
The most simple solution to this is to use 6 pins, each pin drives a different led, making it possible to set the leds in every wich combination. We are going to use PORTB of the AtMega16, and we are using the first 6 pins of that port, that would mean pins 0 to 5.

Let's do some pseudo code to get started

INCLUDE BASICS
DEFINE FUNCTIONS

MAIN
	INITIALISE BASICS
	
	LOOP FOREVER
		SETGAUGE
	END LOOP
	
END MAIN
	
SET LEDS FUNCTION
	TURN ON SPECIFIC LEDS
END FUNCTION

This bit is very skeletal still, but gives a good idea of what we need to do. First of, since we are going to do this in C, and use the basic AVR io we are going to give a bit of structure there.

// Include the basic AVR io header
#include 	

DEFINE FUNCTIONS

// Define the main function
void main()
{
	INITIALISE BASICS
	
	LOOP FOREVER
		SETGAUGE
	END LOOP
}	
	
SET LEDS FUNCTION
	TURN ON SPECIFIC LEDS
END FUNCTION

We now have a rudementary basis in place, next, we want to make sure that the DDRB and PORTB bits are set properly for what we want. Lets take a closer look at this. First of all, it is important to realise that DDBR and PORTB both control 8 pins. Of those pins, we only want to use the first six. In our start situation, we want the data direction set to output and the value of the pins to be 0, ie off. This could quite easily accomplished by doing;

PORTB = 0x00;	// set the pins to 0
DDRB  = 0xFF;	// set the direction to be output

But, that is kind of a bad practice thing to do, this will not only set the lower 6 bits but also the last 2 bits. And those bits are out of scope for our current application, so we should leave them alone. It's better to use a bitmask and just manipulate the bits we are really after. For PORTB, we want to set the lower 6 bits to 0 so we should AND it with 1100 0000, or 0xC0 in hex and for DDRB we want to set the lower 6 bits to 1, so we should OR it with 0011 1111, or 0x3F in hex. I assume basic C knowledge in how to do this, if not, refer to the 101 tutorial on this. The link is at the top of this tutorial.

Anyways, with the proper settings, the initialize code can now be added

// Include the basic AVR io header
#include 	

DEFINE FUNCTIONS

// Define the main function
void main()
{
	PORTB &= 0xC0;	// set the lower 6 bits to 0, ie off
	DDRB  |= 0x3F;	// set the lower 6 bits to 1, ie output
	
	LOOP FOREVER
		SETGAUGE
	END LOOP
}	
	
SET LEDS FUNCTION
	TURN ON SPECIFIC LEDS
END FUNCTION

Now we have the basis of our program in place. However, it won't do anything yet. Time to change that. It's good practice to place repeating code in a function. Here we are going to turn on leds, we are going to excactly the same thing 6 times, and then turn everything off again. So lets implement our SETGAUGE section a bit deeper in pseudo code again.

// Include the basic AVR io header
#include 	

DEFINE FUNCTIONS

// Define the main function
void main()
{
	PORTB &= 0xC0;	// set the lower 6 bits to 0, ie off
	DDRB  |= 0x3F;	// set the lower 6 bits to 1, ie output
	
	// Start endless loop
	while( 1 )
	{
		TURN ON LED 0
		WAIT 1 SECOND
		
		TURN ON LED 1
		WAIT 1 SECOND

		TURN ON LED 2
		WAIT 1 SECOND

		TURN ON LED 3
		WAIT 1 SECOND
		
		TURN ON LED 4
		WAIT 1 SECOND

		TURN ON LED 5
		WAIT 1 SECOND
		
		TURN OFF ALL LEDS
		WAIT 1 SECOND
	}
}	
	
SET LEDS FUNCTION
	TURN ON SPECIFIC LEDS
END FUNCTION

Hmm, that looks quite ineffecient, not to mention unreadable, let's see if we can optimize that a bit, using a for loop.

// Include the basic AVR io header
#include 	

DEFINE FUNCTIONS

// Define the main function
void main()
{
	PORTB &= 0xC0;	// set the lower 6 bits to 0, ie off
	DDRB  |= 0x3F;	// set the lower 6 bits to 1, ie output
	
	// Start endless loop
	while( 1 )
	{
		// Start a for loop, counting from 0 to 5
		for ( char i=0; i < 6; i++ )
		{
			TURN ON LED i
			WAIT 1 SECOND
		}
		
		TURN OFF ALL LEDS
		WAIT 1 SECOND
	}
}	
	
SET LEDS FUNCTION
	TURN ON SPECIFIC LEDS
END FUNCTION

Looks a lot better eh, now, I like functions. I find they make my code more readable and I like that. I am a KISS kinda guy. So I am going to make functions for pretty much everything I do. In this case, we are going to make a led turn on and off, so let's create a function for that called SetLed, we will give this function one parameter, the number of the led to switch. We will make it so, that when the led is on, it will switch if off, and when it's off, it will turn it on. Sounds easy, and it is.

// Include the basic AVR io header
#include 	

// Include the delay header
#include 

// Define the SetLed function
void SetLed( char nLed );

// Define the main function
void main()
{
	PORTB &= 0xC0;	// set the lower 6 bits to 0, ie off
	DDRB  |= 0x3F;	// set the lower 6 bits to 1, ie output
	
	// Start endless loop
	while( 1 )
	{
		// Start a for loop, counting from 0 to 5
		for ( char i=0; i < 6; i++ )
		{
			// Turn on Led i
			SetLed( i );

			// Wait 1 second
			_delay_ms( 1000 );
		}
		
		// Turn off all leds
		PORTB &= 0xC0;	// set the lower 6 bits to 0, ie off

		// Wait 1 seond
		_delay_ms( 1000 );
	}
}	

// Switch the state of a LED, from on to off, or from off to on	
void SetLed( char nLed )
{
	PORTB ^= 0x01 << nLed;
}	

So, here we have our SetLed function, it will bit flip the pin associated with the given parameter, nLed (yes, I am the habit of adding n's to variables and s to strings etc, it's my way ok :) ?). Notice that I've also changed the for loop to call the SetLed function, added the _delay_ms(1000) function (and it's needed header) to the code to wait 1 second after each led switch and a reset of the lower 6 bits of PORTB after all leds have been set. If you compile this little bit of code, you end up with a binary that will, if you hook up some leds to the rights pins, slowly increase the number of light leds at a rate of one per second. Pretty neat eh?

2. Improving the function
What we have now is neat, sure. But not excactly what we need. It's fine if I want to set the powerbar in nice 1 step increments, but kinda hard to use if I want to set the powerlevel to a random value. So, it's easy to go from 1 to 2, but going from 1 to 6 and then from 6 back to 4 is only possible with a lot of manual coding. What we really want is a way to just say, "Eh, powergauge! Display a value 4!". This will involve, of course, a bit of coding. Let's take our final snippet from chapter 1 and add another bit of pseudo code.

We are going to remove the for loop, and replace it with a random loop. Also, we are going to add another function, SetPowerGauge.

// Include the basic AVR io header
#include 	

// Include the delay header
#include 

// Define the SetLed function
void SetLed( char nLed );

// Define the main function
void main()
{
	PORTB &= 0xC0;	// set the lower 6 bits to 0, ie off
	DDRB  |= 0x3F;	// set the lower 6 bits to 1, ie output
	
	// Start endless loop
	while( 1 )
	{
		SELECT RANDOM VALUE, RANGING FROM 0 to 6
		SET POWERGAUGE TO VALUE
		
		// Wait 1 seond
		_delay_ms( 1000 );
	}
}	

// Switch the state of a LED, from on to off, or from off to on	
void SetLed( char nLed )
{
	PORTB ^= 0x01 << nLed;
}	

FUNCTION SETPOWERGAUGE
	SET POWERGAUGE TO ASSIGNED LEVEL	
END FUNCTION

First, the random bit, that is easy. C provides us with all sort of lovely stuff for that, you don't even have to think much. Just us the rand function, wich takes a lower and upper boundry, and gives back a pseudo random number. Good enough for us. Let's define the new function while we are at it. And add a bit of pseudo code to it, to ease implementation a bit.

// Include the basic AVR io header
#include 	

// Include the delay header
#include 

// Define the SetLed function
void SetLed( char nLed );

// Define the SetPowerGauge function
void SetPowerGauge( char nPower );

// Define the main function
void main()
{
	PORTB &= 0xC0;	// set the lower 6 bits to 0, ie off
	DDRB  |= 0x3F;	// set the lower 6 bits to 1, ie output
	
	// Start endless loop
	while( 1 )
	{
		char i;
		// Get random value for i
		i = rand() % 7; );
		
		// Set the powergauge to that value
		SetPowerGauge( i );
		
		// Wait 1 seond
		_delay_ms( 1000 );
	}
}	

// Switch the state of a LED, from on to off, or from off to on	
void SetLed( char nLed )
{
	PORTB ^= 0x01 << nLed;
}	

// Set the power gauge to the assigned level
void SetPowerGauge( char nPower )
{
	CLEAR LEDS
	POWER UP NEEDED LEDS
}

Acutally, not much needs to be done here, we start with clearing all the leds that are on, and then activate the ones we are interested in, we can do that with a for loop, but instead of counting to 6, we are going to count the the powerlevel provided.

// Include the basic AVR io header
#include 	

// Include the delay header
#include 

// Define the SetLed function
void SetLed( char nLed );

// Define the SetPowerGauge function
void SetPowerGauge( int nPower );

// Define the main function
void main()
{
	PORTB &= 0xC0;	// set the lower 6 bits to 0, ie off
	DDRB  |= 0x3F;	// set the lower 6 bits to 1, ie output
	
	// Start endless loop
	while( 1 )
	{
		int i;
		// Get random value for i
		i = rand() % 7;
	
		// Set the powergauge to that value
		SetPowerGauge( i );
		
		// Wait 1 seond
		_delay_ms( 1000 );
	}
}	

// Switch the state of a LED, from on to off, or from off to on	
void SetLed( char nLed )
{
	PORTB ^= 0x01 << nLed;
}	

// Set the power gauge to the assigned level
void SetPowerGauge( int nPower )
{
	// Clear the leds, set the lower 6 bits to 0, ie off
	PORTB &= 0xC0;
	
	// Start a for loop, counting from 0 to nPower
	for ( char i = 0; i < nPower; i++ )
	{
		// Turn on Led i
		SetLed( i );
	}
}

Notice that, in contrary to before, we do not wait a second after setting each led, instead we set all the leds we are interested in at once, and then wait a second. Also notice that I use a 'magic' number, ie 7 for the random generator. Bad practice really, but good enough for this example. Now we have our power gauge. It's set a new random value between 0 and 6 inclusive every second. Lighting up just the right leds, you can of course modify to your hearts content. Doing all sorts of stuff, such as dividing the thing up to a percentage scale. But hey, this is just the start.

3. Charlieplexing
Flashing a single LED using a single pin, that is easy, everyone can do that. And flashing mulitple leds, using multiple pins, that is also not to hard. The cute things come by when you have 3 pins available and want to control 6 leds for instance. That is what we are going to try and accomplish. For this we will be using a technique called charlieplexing, Wikipedia has a nice article on it. But the basis is simple, we will cover it during the course of this tutorial.

Be remindend tough, this is pure a learning experience thing, there are downsides to charlieplexing and multiplexing in general, also there are dedicated IC's you can use that take all the hassle away from you. But hey, there is mountain here, let's climb it.

Charlieplexing is basicly making use of the fact that the pins on a microcontroller can be three things, they can be high, they can be low and they can be disconnected. Combine that with the fact that leds are directional, ie diode's, and you can hook two leds up to two pins and control them independently. It sound complex, but really, it is not. Let's ook at the very most basic example. Courtesy of wikiepdia, we have a picture.

As you can see, there are two leds hooked up, seemingly parrelel. But not really, the diodes are positioned such, that when pin 1 is high and pin 2 low, led 1 will burn, and when pin 2 is high and and pin 1 is low, led 2 will burn. If both are low or both are high, no leds will burn.

Still, it's no real improvement, we can now control 2 leds with 2 pins. But, if we expand that image, and hook up leds like this.

We now have 6 leds hooked up to 3 pins. This will, howerver, introduce some difficulty in controlling the leds. For each led to light, 3 pins need to be manipulated, instead of just the one from the previous example. One pin needs to be high, one pins needs to be low, and one pin needs to disconnected from the circuit. It's out of the scope of this tutorial to discuss the science behind the electrical, but, I encourage you to read and understand the Wikipedia article on this subject. It's quite insightfull.

All we need to know for now is two things. One, our current SetLed function will need to change, and two (take my word on this one) we can only light one led at the time. More on that later.

First things first, we are going to change our SetLed function to properly light the selected led.

// Include the basic AVR io header
#include 	

// Include the delay header
#include 

// Define the SetLed function
void SetLed( char nLed );

// Define the SetPowerGauge function
void SetPowerGauge( int nPower );

// Define the main function
void main()
{
	PORTB &= 0xC0;	// set the lower 6 bits to 0, ie off
	DDRB  |= 0x3F;	// set the lower 6 bits to 1, ie output
	
	// Start endless loop
	while( 1 )
	{
		SET LEDS 1 THROUGH 6
	}
}	

// Switch the state of a LED, from on to off, or from off to on	
void SetLed( char nLed )
{
	DISCONNECT PIN
	SET PIN TO HIGH
	SET PIN TO LOW
}	

// Set the power gauge to the assigned level
void SetPowerGauge( int nPower )
{
	// Clear the leds, set the lower 6 bits to 0, ie off
	PORTB &= 0xC0;
	
	// Start a for loop, counting from 0 to nPower
	for ( char i = 0; i < nPower; i++ )
	{
		// Turn on Led i
		SetLed( i );
	}
}

Let's get to work. For each led we need to figure out what needs to set, so lets make a table for that. We will include the Led, the high and low pin, the disconnected pin and the resulting bit mask to apply.

Led    | Pin High | Pin Low | Pin Disc. | PORTB | DDRB |
0      | 0        | 1       | 2         | 0x01  | xFB  |
1      | 1        | 0       | 2         | 0x02  | xFB  |
2      | 1        | 2       | 0         | 0x02  | xFE  |
3      | 2        | 1       | 0         | 0x04  | xFE  |
4      | 0        | 2       | 1         | 0x02  | xFD  |
5      | 2        | 0       | 1         | 0x04  | xFD  |

As the first step in the function is to clear the current pins, these masks can easily AND'en or OR'ed. Let's implement. Also, notice that since we now only use 3 pins, our start masks have changed.

// Include the basic AVR io header
#include 	

// Include the delay header
#include 

// Define the SetLed function
void SetLed( char nLed );

// Define the SetPowerGauge function
void SetPowerGauge( int nPower );

// Define the main function
void main()
{
	PORTB &= 0xF8;	// set the lower 3 bits to 0, ie off
	DDRB  |= 0x07;	// set the lower 3 bits to 1, ie output
	
	// Start endless loop
	while( 1 )
	{
		// Start a for loop, counting from 0 to 5
		for ( char i = 0; i < 6; i++ )
		{
			// Turn on Led i
			SetLed( i );
			_delay_ms(1000);
		}
	}
}	

// Switch the state of a LED, from on to off, or from off to on	
void SetLed( char nLed )
{
	// Clear existing status, we can only drive on led at a time
	PORTB &= 0xF8;	// set the lower 3 bits to 0, ie off
	DDRB  |= 0x07;	// set the lower 3 bits to 1, ie output

	switch ( nLed )
	{
		case 0:	
			DDRB  &= 0xFB;  // Disconnect the nr. 2 pin
			PORTB |= 0x01;  // Set pin 0 to high and pin 1 to low
			break;
		
		case 1:	
			DDRB  &= 0xFB;  // Disconnect the nr. 2 pin
			PORTB |= 0x02;  // Set pin 1 to high and pin 0 to low
			break;
		
		case 2:	
			DDRB  &= 0xFE;  // Disconnect the nr. 0 pin
			PORTB |= 0x02;  // Set pin 1 to high and pin 2 to low
			break;				

		case 3:	
			DDRB  &= 0xFE;  // Disconnect the nr. 0 pin
			PORTB |= 0x04;  // Set pin 2 to high and pin 1 to low
			break;							
						
		case 4:	
			DDRB  &= 0xFD;  // Disconnect the nr. 1 pin
			PORTB |= 0x01;  // Set pin 0 to high and pin 2 to low
			break;
		
		case 5:	
			DDRB  &= 0xFD;  // Disconnect the nr. 1 pin
			PORTB |= 0x04;  // Set pin 2 to high and pin 0 to low
			break;				
	}
}	

// Set the power gauge to the assigned level
void SetPowerGauge( int nPower )
{
	// Clear the leds, set the lower 6 bits to 0, ie off
	PORTB &= 0xC0;
	
	// Start a for loop, counting from 0 to nPower
	for ( char i = 0; i < nPower; i++ )
	{
		// Turn on Led i
		SetLed( i );
	}
}

This will now, one by one, switch leds off and on, running from led 0 to led 5. Pretty cool eh, we drive 6 leds with only 3 pins. This is what charlieplexing is about.

4. Timers & Fooling
We now can switch leds off and on, but sadly, only one at a time. That is an inherent flaw with this technique, but one that we can overcome with a little cheating. We are going take advantage of the fact that our human perceptions are not that fast. Or rather, the AVR is a whole lot faster. You see, we don't need the led to be one constantly to look like it's on constantly. If the led is flashing really fast, it will appear to the human eye as tought it is lit constantly. So, let's make it so that we light a led for say, 10 milliseconds, then light the next led for 10 ms, and so on. When the last leds has lit, we start with the first led again.

I strongly suggest you read up on Dave's timer tutorial, because I am just going to take his code as a given and presume you understand how it works.

Let's pseudocode!

// Include the basic AVR io header
#include 	

// Include the delay header
#include 

// Define the SetLed function
void SetLed( char nLed );

// Define the SetPowerGauge function
void SetPowerGauge( int nPower );

// define an array to hold the led status
DEFINE LEDARRAY

// Define the main function
void main()
{
	PORTB &= 0xF8;	// set the lower 3 bits to 0, ie off
	DDRB  |= 0x07;	// set the lower 3 bits to 1, ie output
	
	SETUP TIMER CIRCUITRY
	
	// Start endless loop
	while( 1 )
	{
		int i;
		// Get random value for i
		i = rand() % 7;
	
		// Set the powergauge to that value
		SetPowerGauge( i );
		
		// Wait 1 seond
		_delay_ms( 1000 );
	}
}	

TIMERCODE FUNCTION
	IMPLEMENT TIMER CODE
END TIMERCODE FUNCTION

// Switch the state of a LED, from on to off, or from off to on	
void SetLed( char nLed )
{
	// Clear existing status, we can only drive on led at a time
	PORTB &= 0xF8;	// set the lower 3 bits to 0, ie off
	DDRB  |= 0x07;	// set the lower 3 bits to 1, ie output

	switch ( nLed )
	{
		case 0:	
			DDRB  &= 0xFB;  // Disconnect the nr. 2 pin
			PORTB |= 0x01;  // Set pin 0 to high and pin 1 to low
			break;
		
		case 1:	
			DDRB  &= 0xFB;  // Disconnect the nr. 2 pin
			PORTB |= 0x02;  // Set pin 1 to high and pin 0 to low
			break;
		
		case 2:	
			DDRB  &= 0xFE;  // Disconnect the nr. 0 pin
			PORTB |= 0x02;  // Set pin 1 to high and pin 2 to low
			break;				

		case 3:	
			DDRB  &= 0xFE;  // Disconnect the nr. 0 pin
			PORTB |= 0x04;  // Set pin 2 to high and pin 1 to low
			break;							
						
		case 4:	
			DDRB  &= 0xFD;  // Disconnect the nr. 1 pin
			PORTB |= 0x01;  // Set pin 0 to high and pin 2 to low
			break;
		
		case 5:	
			DDRB  &= 0xFD;  // Disconnect the nr. 1 pin
			PORTB |= 0x04;  // Set pin 2 to high and pin 0 to low
			break;		
	}
}	

// Set the power gauge to the assigned level
void SetPowerGauge( int nPower )
{
	// Clear the leds, set the lower 6 bits to 0, ie off
	PORTB &= 0xC0;
	
	// Start a for loop, counting from 0 to nPower
	for ( char i = 0; i < nPower; i++ )
	{
		// Turn on Led i
		SetLed( i );
	}
}

As you can see, we brought back the random setting of the powergauge, and introduced some basic timer things and an array to hold the status of our leds. This can probably be done more elegant, but for educational purpuses, for now, it will do fine. Let's look at an implementation of that code.

// Include the basic AVR io header
#include 	

// Include the delay header
#include 

// Include the interrupt header
#include 

// Define the SetLed function
void SetLed( char nLed );

// Define the SetPowerGauge function
void SetPowerGauge( int nPower );

// define an array to hold the led status
char aLedArray[6];
char nCurrentLed;

// Define the main function
void main()
{
	// Clear the led array, clear the powergauge
	SetPowerGauge( 0 );

	PORTB &= 0xF8;	// set the lower 3 bits to 0, ie off
	DDRB  |= 0x07;	// set the lower 3 bits to 1, ie output
	
	// Setup timer to prescalar FCpu/64 @ 1 mhz
	TCCR1B |= ((1 << CS10) | (1 << CS11));

	// Set timer to CTC mode
	TCCR1B |= (1 << WGM12 );
	// Enable CTC interrupt
	TIMSK |= ( 1 << OCIE1A );
	// Enable global interupts
	sei();

	// Set timer to compare of 1/10 second
	OCR1A = 31250;
		
	// Start endless loop
	while( 1 )
	{
		int i;
		// Get random value for i
		i = rand() % 7;
	

		// Set the powergauge to that value
		SetPowerGauge( i );

		
		// Wait 1 seond
		_delay_ms( 10000 );	
	}
}	


// Handle timer interrupt
// This triggers every 1/10th of a second
ISR( TIMER1_COMPA_vect )
{
	// Check what led to toggle
	// Check if that led should be active
	if ( aLedArray[nCurrentLed] == 1 )
	{
		SetLed( nCurrentLed );
		
		// Increase Led Counter
		nCurrentLed++;
	}
	else
	{
		nCurrentLed = 0;
	}


	// Wrap around
	if ( nCurrentLed > 5 )
	{
		nCurrentLed = 0;
	}

}

// Switch the state of a LED, from on to off, or from off to on	
void SetLed( char nLed )
{
	// Clear existing status, we can only drive on led at a time
	PORTB &= 0xF8;	// set the lower 3 bits to 0, ie off
	DDRB  |= 0x07;	// set the lower 3 bits to 1, ie output

	switch ( nLed )
	{
		case 0:	
			DDRB  &= 0xFB;  // Disconnect the nr. 2 pin
			PORTB |= 0x01;  // Set pin 0 to high and pin 1 to low
			break;
		
		case 1:	
			DDRB  &= 0xFB;  // Disconnect the nr. 2 pin
			PORTB |= 0x02;  // Set pin 1 to high and pin 0 to low
			break;
		
		case 2:	
			DDRB  &= 0xFE;  // Disconnect the nr. 0 pin
			PORTB |= 0x02;  // Set pin 1 to high and pin 2 to low
			break;				

		case 3:	
			DDRB  &= 0xFE;  // Disconnect the nr. 0 pin
			PORTB |= 0x04;  // Set pin 2 to high and pin 1 to low
			break;							
						
		case 4:	
			DDRB  &= 0xFD;  // Disconnect the nr. 1 pin
			PORTB |= 0x01;  // Set pin 0 to high and pin 2 to low
			break;
		
		case 5:	
			DDRB  &= 0xFD;  // Disconnect the nr. 1 pin
			PORTB |= 0x04;  // Set pin 2 to high and pin 0 to low
			break;
	}
}	

// Set the power gauge to the assigned level
void SetPowerGauge( int nPower )
{
	// Clear the leds, set the lower 6 bits to 0, ie off
	nCurrentLed = 0;
	for ( char i = 0; i < 6; i++ )
	{
		aLedArray[i] = 0;
	}
	
	
	// Start a for loop, counting from 0 to nPower
	for ( char i = 1; i <= nPower; i++ )
	{
		// Add Led #i to array of ON leds
		aLedArray[i-1] = 1;
	}
}

We had to modify the powergauge function to set an array and no longer the leds. The timer function just polls that array to see what led to burn.

Notice that we included the interrupt.h file. If the timer stuff confuses you, go read Dean's stuff. He explain it really well. The interrupt function now nicely triggers the right led every 1/10th of second. Charlieplexing our powergauge.

5. Afterword
This illustrate a nice way to charlieplex leds, of course there are probably better ways, and there are downsides to charlieplexing as well. For me it was my first hands-on project with timers and leds, and I hope it paints a clear picture. Yes the C code is probably due for some optimisation, this takes about 30% of the space on an AtMega16. Yes, there are most likely parts that are unclear. I claim nowhere near the same skill level as some of the vets here, but I hope this tutorial by a beginner for beginners is somewhat usefull.

Thank you.

Now, gentleman, tell me what I did wrong.

Code, justify, code - Pitr Dubovich

Last Edited: Wed. Jul 1, 2009 - 08:39 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello,
nice reading but I didn't go completly thru it right now.

Quote:
Now, gentleman, tell me what I did wrong.
Quote:
For PORTB, we want to set the first 6 bits to 0 so we should AND it with 1100 0000, or 0xC0...

   PORTB &= 0x03;   // set the first 6 bits to 0, ie off

needs to be &= ~0x03 or &= 0xC0 to fit the text.
It would be a little bit better if you talk about the lowest 6 bits because the first six bits could be B7..B2 if you write a byte.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've changed that, guess I wasn't that used to talking about bit like that anymore.

Thank you.

Code, justify, code - Pitr Dubovich

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello,
that's fine but did you miss the ~ before 0x03?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yup, I missed it at points. In the start of the text I am using 0x03 while in the working example I used 0xC0, just didn't change it in the final text. My bad.

Thanx.

Code, justify, code - Pitr Dubovich

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This was a very useful read for me, I sat down and hand coded some of this and went through it step by step to understand the method. I am still very "green" so the pseudocode examples were very useful.

Thanks for your work!
Will

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Eloque ( M. de Ruijter):
Thank you very much for the tutorial. I just bought a STK500 board and am using it with a Tiny2313 processor. I was having a hard time getting started using C. Your tutorial has helped a lot. Something I notice and am confused about though. For me, your program that is suppose to light the leds one by one works the opposite of how it is advertised. That is, it starts with all of them lit, and turns them off one by one. I was able to make it work as advertised by assumming that a bit on in PORTB represents an "off" LED, and vice versa. In other words, the exact opposite of what you state and your code suggests. I've put the modified code below. I'm a newbie at firmware (I program PCs in C# for a living) but I'm guessing this is a simple problem and is telling me I have something wired backwards or some such thing. Any ideas?

Again, thank you very much and I hope you continue producing tutorials.

Sincerely,
Andrew Walker

MODIFIED CODE
// Define the main function
int main()
{
PORTB |= ~0xC0; // NOTICE DIFFERENCE FROM ORIGINAL CODE
DDRB |= 0x3F; // set the lower 6 bits to 1, ie output

// Start endless loop
while( 1 )
{

// Start a for loop, counting from 0 to 5
for ( char i=0; i < 6; i++ )
{
// Turn on Led i
SetLed( i );

// Wait 1 second
_delay_ms( 1000 );
}

// Turn off all leds
PORTB |= ~0xC0; // NOTICE DIFFERENCE FROM ORIGINAL CODE

// Wait 1 seond
_delay_ms( 1000 );

}
}

// Switch the state of a LED, from on to off, or from off to on
void SetLed( char nLed )
{
PORTB = PORTB & ( ~(0x01 << nLed) ); // // NOTICE DIFFERENCE FROM ORIGINAL CODE
}

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

works the opposite of how it is advertised. That is, it starts with all of them lit, and turns them off one by one. I was able to make it work as advertised by assumming that a bit on in PORTB represents an "off" LED, and vice versa. In other words, the exact opposite of what you state and your code suggests.

This is all to do with the circuitry of the STK500 and the way the LEDs are driven. It does mean that they are active low so that '1' means off and '0' means on.

Well done for working out that to invert the behaviour you needed an &~ rather than an | - you're clearly well on your way up the learning curve!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Good point, I wasn't using an STK500 but a simulator and a real board at the time of writing. In my sims it worked as I stated.

Also, due to work and private related issues I had to put my own efforts on AVR aside a bit. Time n time again time is scarce. Basicly, always asume what clawson says to be true, that works for me most of the time.

Code, justify, code - Pitr Dubovich

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for this great tutorial..........I really liked it..thanks a lot!
I found some typing error in section two(2. Improving the function )

// Get random value for i
      i = rand() % 7; ); 

Sorry it's not a big thing but for a newbee it might trouble....once again thanks for this great tutorial!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sorry sir, if i ask some silly question. but, i'm get stuck with rotating led. can you share the simple code for rotating led?? i prefer to use sensor, so i can control what character is going to pop up.

thank you very much..