/*
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 ;
	}
}
