/*
 * Copyright (c) 2010 Melanie Rhianna Lewis
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

/*
 * main.c
 *
 * This is the single source file for my programmable earrings.  This source
 * is targetted at ATTiny25/45 micro-controllers and is intended to be
 * compiled using avr-gcc.
 */

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

/*
 * Enable the code that varies the output dependent upon the voltage
 * level on ADC 1.
 */
#define SUPPORTS_ADC 1

/*
 * Defines a mask for each of the LED output pins.
 */
#define REDLED   _BV(PB0)
#define GREENLED _BV(PB1)
#define BLUELED  _BV(PB2)

/*
 * Defines the amplitude of the global intensity sin wave.
 */
#define PULSE_MAX 50

/*
 * A simple sin wave (varies between 0 and PULSE_MAX).  This is used to
 * govern the over all 'heart beat' pulse of the LED.  I.e. the global
 * intensity.
 */
uint8_t  pulse_pattern[] = { 25, 35, 42, 45, 48, 49, 50, 49, 48, 45, 42, 35, 25, 15, 8, 5, 2, 1, 0, 1, 2, 5, 8, 15, 0xff };

/*
 * The index in to the pulse pattern array, the current count value and
 * the total to count to respectively.
 */
uint8_t  pulse_index;
uint16_t pulse_count;
uint16_t pulse_total;

/**
 * The LED data.
 *
 * The total time a colour is on is total x ctotal.  So with timer
 * interrupts going off every 100us if total is 50 and ctotal is 20
 * then the colour will be on for 0.1 sec.
 */
typedef struct _led_data {
	uint8_t level;   /* Number of timer ints for *on* */
	uint8_t count;   /* Number of timer ints in this cycle so far */
	uint8_t total;   /* Number of timer ints for a cycle */
	uint8_t ccount;  /* Number of cycles so far */
	uint8_t ctotal;  /* Total number of cycles */
	uint8_t index;   /* Index in to the pattern */
	uint8_t scale;   /* Adjustment value fixed point */
} led_data;

/* The individual LED data */
volatile led_data red, green, blue;

/* Used by the timer ISR */
volatile uint8_t leds, portb;

/* Simple sin wave */
uint8_t pattern[] = { 25, 35, 42, 45, 48, 49, 50, 49, 48, 45, 42, 35, 25, 15, 8, 5, 2, 1, 0, 1, 2, 5, 8, 15, 0xff };

#ifdef SUPPORTS_ADC

/* The last light dependent resistor reading */
volatile uint8_t ldrValue = 0;

/**
 * Depending upon the value presented at the Analog input the scaling
 * of the red, green and blue channels is adjusted.  So, for example,
 * a bright light results in all three channels being driven fully,
 * but a dim light results in just red.
 */
inline void handleADC(void) {
	/* Check if conversion complete */
	if ((ADCSRA & _BV(ADSC)) == 0) {
		/* Read the value and start another conversion. */
		ldrValue = ADCH;
		ADCSRA |= _BV(ADSC);

		/* Change the timer speed */
		OCR1A = 255 - (ldrValue);

		if (ldrValue > 0xC0) {
			red.scale   = 0x80;
			green.scale = 0x80;
			blue.scale  = 0x80;
		} else
		if (ldrValue > 0xA0) {
			red.scale   = 0x80;
			green.scale = 0x80;
			blue.scale  = 0x20;
		} else
		if (ldrValue > 0x80) {
			red.scale   = 0x80;
			green.scale = 0x20;
			blue.scale  = 0x80;
		} else
		if (ldrValue > 0x60) {
			red.scale   = 0x80;
			green.scale = 0x20;
			blue.scale  = 0x20;
		} else
		if (ldrValue > 0x40) {
			red.scale   = 0x20;
			green.scale = 0x20;
			blue.scale  = 0x20;
		} else
		if (ldrValue > 0x20) {
			red.scale   = 0x20;
			green.scale = 0;
			blue.scale  = 0x20;
		} else {
			red.scale   = 0x20;
			green.scale = 0;
			blue.scale  = 0;
		}
	}
}
#endif

/**
 * Since there is only one PWM channel on the ATTiny*5 series and we need
 * three (one for each colour channel) PWM is done in software.
 *
 * Each timer tick the 'count' for the channel is incremented.  If the
 * 'count' is less than or equal to the 'level' for that channel, the LED
 * is off.  If the 'count' is greater than the 'level' the LED is on.  The
 * 'count' wraps at 'total'.  So 'level' controls the mark/space ratio
 * of the pulse being fed to the LED.
 *
 * 'ccount' and 'ctotal' are used to adjust the rate of moving through
 * the PWM pattern.  Once a full mark/space cycle is complete 'ccount'
 * is incremented.  When 'ccount' exceeds 'ctotal' the channel moves on to
 * the next value in the 'pattern' array.  I.e. for a channel, the next item
 * in the 'pattern' array is chosen after 'ctotal' PWM cycles.
 *
 * The 'level' value is calculated using the PWM pattern, the pulse pattern
 * and the scale (calculated from the analog value).  This code is repeated
 * for each channel.
 */
inline void handleLEDs(void) {
	/* All off to start */
	leds = 0;

	/* Handle red */
	red.count ++;

	/* Mark or space */
	if (red.count <= red.level) {
		leds |= REDLED;
	}

	/* Have we completed a PWM cycle? */
	if (red.count > red.total) {
		red.count = 0;

		/* Move through the channel intensity pattern */
		red.ccount ++;
		if (red.ccount > red.ctotal) {
			red.ccount = 0;

			if (0xff == pattern[++red.index]) {
				red.index = 0;
			}
		}

		/* Calculate the channel's new intensity level */
		red.level = (uint8_t)(((uint16_t)pattern[red.index] * (uint16_t)pulse_pattern[pulse_index]) / PULSE_MAX);
		red.level = (uint8_t)(((uint16_t)red.level * (uint16_t)red.scale) / 0x80);
	}

	/* Handle green */
	green.count ++;

	/* Mark or space */
	if (green.count <= green.level) {
		leds |= GREENLED;
	}

	/* Have we completed a PWM cycle? */
	if (green.count > green.total) {
		green.count = 0;

		/* Move through the channel intensity pattern */
		green.ccount ++;
		if (green.ccount > green.ctotal) {
			green.ccount = 0;

			if (0xff == pattern[++green.index]) {
				green.index = 0;
			}
		}

		/* Calculate the channel's new intensity level */
		green.level = (uint8_t)(((uint16_t)pattern[green.index] * (uint16_t)pulse_pattern[pulse_index]) / PULSE_MAX);
		green.level = (uint8_t)(((uint16_t)green.level * (uint16_t)green.scale) / 0x80);
	}

	/* Handle blue */
	blue.count ++;

	/* Mark or space */
	if (blue.count <= blue.level) {
		leds |= BLUELED;
	}

	/* Have we completed a PWM cycle? */
	if (blue.count > blue.total) {
		blue.count = 0;

		/* Move through the channel intensity pattern */
		blue.ccount ++;
		if (blue.ccount > blue.ctotal) {
			blue.ccount = 0;

			if (0xff == pattern[++blue.index]) {
				blue.index = 0;
			}
		}

		/* Calculate the channel's new intensity level */
		blue.level = (uint8_t)(((uint16_t)blue.level * (uint16_t)blue.scale) / 0x80);
	}

	/* Move through the global intensity pattern */
	pulse_count++;
	if (pulse_count > pulse_total) {
		pulse_count = 0;

		if (0xff == pulse_pattern[++pulse_index]) {
			pulse_index = 0;
		}
	}

	/* Mask off.  Leds are sunk and not sourced. */
	portb = PORTB & ~(REDLED | GREENLED | BLUELED);
	PORTB = portb | (leds ^ (REDLED | GREENLED | BLUELED));
}

/**
 * The timer 1 OCA compare interrupt handler.
 *
 * This is called when timer 1 reaches the value set in the OCA register.
 */
ISR (TIMER1_COMPA_vect) {

	/* Reset the timer to zero */
	TCNT1 = 0;

#ifdef SUPPORTS_ADC
	/* handle the ADC conversion */
	handleADC();
#endif

	/* handle the LEDs */
	handleLEDs();
}

/**
 * The hardware initialisation function.
 *
 * This function sets up the IO pins used to drive the LEDs, the ADC and
 * timer 1.
 */
void initHardware(void) {
	/* Configure the digital outputs that drive the LED */

	/* Set the PINS as output */
	DDRB  = (REDLED | GREENLED | BLUELED);
	/* Set the initial value */
	PORTB = 0;

#ifdef SUPPORTS_ADC
	/* Configure the ADC */

	/* Disable the digital input on the ADC3 pin */
	DIDR0  = _BV(ADC3D);
	/* Set to use ADC3, left adjust and VCC as the voltage reference */
	ADMUX  = _BV(ADLAR) | _BV(MUX1) | _BV(MUX0);
	/* Enable ADC and set pre-scale of 128 */
	ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);
#endif

	/* Configure the timer */

	/* Reset to 0 after match, pre-scale of 8 */
	TCCR1 = _BV(CTC1) | _BV(CS12);
	/* No PWM stuff */
	GTCCR = 0;
	/* Set timer to 0 */
	TCNT1 = 0;
	/* Compare to 100 (Gives a 100us clock) */
	OCR1A = 100;
	/* Enable OC1A interrupt */
	TIMSK = _BV(OCIE1A);

	/* Enable all the interrupts */
	sei();
}

/**
 * Initialises the LED data structures and the global intensity data.
 */
void initLeds(void) {
	/* Initialise the data for the Red LED */
	red.count    = 0;
	red.total    = 50;
	red.ccount   = 0;
	red.ctotal   = 20;
	red.index    = 0;
	red.scale    = 0x80;
	red.level    = pattern[red.index];

	/* Initialise the data for the Green LED */
	green.count  = 0;
	green.total  = 50;
	green.ccount = 0;
	green.ctotal = 10;
	green.index  = 4;
	green.scale  = 0x80;
	green.level  = pattern[green.index];

	/* Initialise the data for the Blue LED */
	blue.count   = 0;
	blue.total   = 50;
	blue.ccount  = 0;
	blue.ctotal  = 15;
	blue.index   = 8;
	blue.scale   = 0x80;
	blue.level   = pattern[blue.index];

	/* Initialise the data for the RED LED */
	pulse_index = 0;
	pulse_count = 0;
	pulse_total = 3900 / (sizeof(pulse_pattern) - 1);
}

/**
 * The main function.  Everything is done in interrupt contexts so once the
 * hardware has been initialised the processor just sleeps until it is
 * awakened by an interrupt.  This should never return.
 */
int main(void) {

	initLeds();
	initHardware();

	/* Start ADC conversion */
	ADCSRA |= _BV(ADSC);

	for (;;) {
		sleep_mode();
	}

	return -1;
}


