Controlling Two LEDs per uC pin

7. Code

All the following code assumes the following:

Obviously, ports and timings can be changed to suit.

Solution 1

Firstly, here's a very simple example; just turn on LED_0A:

// turn on LED 0A... this is attached to PIN0 of port D
// that's done by setting the pin as output and high
PORTD = 0b00000001 ;
DDRD = 0b00000001 ;

Result: LED_0A is on. All other LEDs are off.

The counterpart is to just turn on LED_0B:

// turn on LED 0B... this is attached to PIN0 of port D
// that's done by setting the pin as output and low
PORTD = 0b00000000 ;
DDRD = 0b00000001 ;

Result: LED_0B is on. All other LEDs are off.

Repeatedly swapping between the two examples above (with a small delay between) can be accomplished as follows;

// turn on both LED 0A and 0B by flickering between the two
while(1)
{
	// turn on 0A
	PORTD = 0b00000001 ;
	DDRD = 0b00000001 ;
	_delay_ms(1);

	// turn on 0B
	PORTD = 0b00000000 ;
	DDRD = 0b00000001 ;
	_delay_ms(1);
}

Result: The illusion that both LED_0A and LED_0B are on].

Solution 2

The above examples aren't really very useful. If you need to be in this loop to flicker the LEDs, then your microcontroller isn't going to be doing anything else. There are much better things it could be doing (controlling nuclear reactor cores, for example).

A more elegant solution here is to use a regularly timed interrupt to switch between showing all the A LEDs and all the B LEDs. A helper function pre-calculates the settings required and stores them in a global variable for the interrupt to use.

The main loop of the program then no longer needs to be involved with flickering the LEDs, it just calls the helper function whenever it wants to show a different pattern on the LEDs. The interrupt quietly gets on with flickering LEDs in the background.

The following code uses this interrupt + helper method to demonstrate a simple 16 LED chaser.

/*
Copyright (c) 2008, Nigel Batten.
Contactable at <firstname>.<lastname>@mail.com

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

	1.	Redistributions of source code must retain the above copyright
		notice, this list of conditions and the following disclaimer.
	2.	Redistributions in binary form must reproduce the above copyright
		notice, this list of conditions and the following disclaimer in the
		documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.

===============================================================================
A demonstration of controlling 16 LEDs from 8 pins.
Nigel Batten
September 2008

Written for a 'factory setting' Mega8 (highbyte: 0xd9, lowbyte:  0xe1)
e.g. running on 1Mhz internal RC oscillator.

(Don't forget that your programmer may need to be on it's "slow" setting)

requires the following:
A timer with an overflow interrupt

*/

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

// define the processor speed if it's not been defined at the compiler's command line.
#ifndef F_CPU
#define F_CPU 1000000
#endif

// global variables to hold the input/output state required for each set of LEDs; A and B
volatile uint8_t g_Slice_A_DDRD ;
volatile uint8_t g_Slice_B_DDRD ;

// procedure headers
static void inline led_init( void ) ;
static void led_encode( uint16_t leds );

int main (void)
{
uint16_t leds = 0; // 16 bits. One per LED.

	/*
	// turn on LED 0A... this is attached to PIN0 of port D
	// that's done by setting the pin as output and high
	PORTD = 0b00000001 ;
	DDRD  = 0b00000001 ;
	while(1);
	*/
	
	/*
	// turn on LED 0B... this is attached to PIN0 of port D
	// that's done by setting the pin as output and low
	PORTD = 0b00000000 ;
	DDRD  = 0b00000001 ;
	while(1);
	*/
	
	/*
	// turn on both LED 0A and 0B by flickering between the two
	while(1)
	{
		// turn on 0A
		PORTD = 0b00000001 ;
		DDRD  = 0b00000001 ;
		_delay_ms(1);
		
		// turn on 0B
		PORTD = 0b00000000 ;
		DDRD  = 0b00000001 ;
		_delay_ms(1);
	}
	*/


	// initialise the LEDs...
	led_init();
	led_encode( leds ) ;
	
	// start the interrupts running...
	sei();

	// now a (really simple) demonstration...
	// sweep a lit LED through each position.
    while(1)
    {
		_delay_ms( 500 ) ;
		if (leds == 0)
		{
			leds = 0b1 ;// start the sweep from the start.
		}
		else
		{
			leds <<= 1 ; // shift the lit LED up one position.
		}
		led_encode( leds ) ; // change this to ~leds if you want to invert the display.
	}
    return(0);
}

// set up the timer
void led_init( void )
{
	// set Timer0's prescaler to 8. It will then overflow every 256 * 8 CPU cycles.
	// That's 488 times per second with a 1Mhz clock.
	TCCR0 = ((0<<CS02)|(1<<CS01)|(0<<CS00)) ; // use clock prescaler of 8 for timer0
	TIMSK |= (1 << TOIE0) ; // turn on the overflow interrupt for timer0
}

// encode the 16-bit value representing which LEDs are lit into the port settings required.
// These port settings will be used by the interrupt handler to toggle between the two banks of LEDs.
void led_encode( uint16_t leds )
{
uint8_t ddr_A = 0 ;
uint8_t ddr_B = 0 ;

	uint16_t ledbitpos = 0b1 ;
	uint8_t pinbitpos = 0b1 ;
	while ( ledbitpos != 0 )
	{
		if (leds & ledbitpos) ddr_A |= pinbitpos ;
		ledbitpos <<= 1 ;
		if (leds & ledbitpos) ddr_B |= pinbitpos ;
		ledbitpos <<= 1 ;
		pinbitpos <<= 1 ;
	}
	g_Slice_A_DDRD = ddr_A ;
	g_Slice_B_DDRD = ddr_B ;
}

// timer 0 overflow handler
ISR( TIMER0_OVF_vect )
{
static uint8_t s_slice = 0 ;

	// toggle the slice counter determine wether to show the A or B leds...
	s_slice ^= 0xff ;
	if (s_slice)
	{	// light any A LEDs required...
		DDRD = g_Slice_A_DDRD ;
		PORTD = g_Slice_A_DDRD ; // A's are lit when the pin is 1
	}
	else
	{	// light any B LEDs required...
		PORTD = 0b00000000 ; // B's are lit when the pin is 0
		DDRD = g_Slice_B_DDRD ;
	}
}

Thank you for reading this tutorial. We hope it was useful.

We appreciate any feedback - you can email us here at batsocks.co.uk using our "feedback" email account.

You can find more stuff that we've written in the Read Me section.

The following files are available for download:

Version Date File Size
Two LEDs per uC pin tutorial 2.0 2009-01-08 (PDF file) tut_led16_02.pdf 258.5 kB
1.0 2008-10-16 (PDF file) tut_led16_01.pdf 223.1 kB
example code 1.0 2008-10-10 (.c file) code_bm_led_168_10.c 4.7 kB