/*!
	\file lasergame.c
 
	\brief The main source-file for the lasergame

	\section lasergamesec1 Pin-Description

	This section describes all the pins of the Attiny84 and what is connected to them in a counter clockwise manner as the pincount in the datasheet is done.
	
	<ol>
		<li> VCC	3.3V	
		<li> PB0 
		<li> PB1	Laser -> Via 1k to IRLML2502 to switch the laser on and off
		<li> PB3	Reset	10k to VCC, button to GND
		<li> PB2	INT0 -> Start Button with interrupt to GND, internal pullup active
		<li> PA7	LED 1k to GND 
		<li> PA6	LED 1k to GND	-> at OC1A possibility for PWM
		<li> PA5	Sound	-> at OC1B possibility for PWM
		<li> PA4	
		<li> PA3
		<li> PA2	LDR to VCC, 4.7k to GND	-> at ADC2, detection for the laser
		<li> PA1	( programming port with bootloader )
		<li> PA0	( 100n to GND ) 
		<li> GND	0V
	</ol>

	\section lasergamesec2 LDR-Measurement

	The LDR is a typ A905014. In bright red laser light the resistance is measured as 700 Ohm.
	In normal light the resistance is 9kOhm, and in dimmed darkness it is 300k.

	The ADC-measurement is calculated by ADC = Vin * 1024 / Vref.

	Vref could be 1.1V or VCC (3.5V).

	To gain a maximum resolution the second resistor has to be chosen to 4.7k.

	For a bright lit LDR the Vin is about 1/10th and the ADC-value around 30. For an overall lit
	LDR without Laser the LDR is about 10k, Vin = 2/3 VCC = 2.2V, ADC-value around 180. For an even
	more dimmed LDR the ADC-value rises up to 255. But the most sensitive area is around the bright lit
	LDR.


	\section lasergamesec3 Function

	After boot-up, the laser is on and the device is uncalibrated. This is the time to adjust the laser
	and all the mirrors for the game. Take care for the laser-beam as it will not stop when you look into it!
	The led and the laser is flashing fast, to reduce the chance to look into the laser for a long time.

	When the setup is finished press the start button twice with 2s pause, to start the game. The last average value
	for the LDR is set as the calibration value.
	During game the LDR is checked every 2ms. And if the reading is
	below half the calibration value the interruption-detect-flag is set and the laser beam is interrupted.
	
	When you press start the current ADC-reading is taken as calibration value, but the laser was flashing before, so this value is a bit too low. That is why after 2s you should press the start button again. Now the laser was on for 2s and the reading is much higher. This feature might also be implemented in software in the future. 




 *  \date Created on: 22.02.2012
 *  \author ak
 */


#ifndef F_CPU
// #warning "F_CPU not defined, will be set to 8MHz"
#define F_CPU 8000000UL    
#endif

//#include <avr/iotn84.h>
#include <avr/io.h>
#include <inttypes.h>
#include <stdlib.h>
#include <stdint.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <avr/interrupt.h> 
#include <avr/eeprom.h>
#include "lasergame.h"
#include "debug.h"

// private functions definition
void ADC_Init(void);
void init( void );
int main( void );
ISR(INT0_vect);
ISR(TIM0_OVF_vect);
ISR(TIM1_COMPB_vect);
void LED_flash(uint8_t count );
void CalculateAverage( void );
void alarm( void );

// comment the next line to eliminate the debug output! 
#define debugactive true;

volatile uint8_t calibrationvalue = 0, measurement[8] = { 0 }, runningaverage, interruptiondetected = 0, measurementcounter = 0 ;
volatile uint8_t adjust = 0, alarmactive = 0 ;
uint8_t  i, l, m;
volatile uint16_t ll = 0;
uint8_t sound[] PROGMEM = { 20, 200, 50, 100, 180, 10, 244 };


void init( void )
{
	// Set Inputs / Outputs and Pullups 
	LEDDDR |= (1<<LEDpin);				// Set LED pin as Output
	SOUNDDDR |= (1<<SOUNDpin);			// Set Sound pin as Output
	LASERDDR |= (1<<LASERpin);			// Set the Laserdriver pin as Output
	PORTA |= (1<<PA7);					// enable internal pullup for adjustbutton
	PORTB |= (1<<PB2);					// enable internal pullup for startbutton
#ifdef debugactive
	initdebug();						// run the init for the debug-output
#endif

	// enable INT0-Interrupt
	MCUCR = ( (1<<ISC00) | (1<<ISC01) );	// the rising edge of INT0 generates an interrupt request

	// General Interrupt Mask Register
	GIMSK = (1<<INT0) | (1<<PCIE0);		// enable Int0 and int7 interrupt
	GIFR |= (1<<PCIF0);

	// Initialise ADC, Set the registers and read first dummy.
 	ADC_Init();

// 	set_sleep_mode(SLEEP_MODE_IDLE);	// Selection of Sleep mode

	// Initialise Timer / PWMs
	// Timer 0: internal Overflow Interrupt for timing, change, control
	TCCR0B = ((1<<CS01) | (1<<CS00));		// clk/64 Prescaler, leads to a 2ms interrupt source
	TIMSK0 = (1<<TOIE0) ;					// T/Counter0 Overflow Interrupt enable

	LEDport &= ~(1<<LEDpin);		// Switch on the LED
	LASERport |= (1<<LASERpin);		// Switch on the laser
	SOUNDport |= (1<<SOUNDpin);		// switch off sound

	sei(); 	// enable interrupts

}

/*!
	\brief Interrupt Service Routine for the external interrupt 0-pin PB2 (start button)

 	 By pressing this button, the sensor reading is set as a reference.

 */
ISR(INT0_vect)
{

//	cli();									// Disable interrupts

	calibrationvalue = runningaverage;		// set the new calibration value.
	if(alarmactive == 0 )
		{
		LASERport |= (1<<LASERpin);				// switch on the laser continously
		}
#ifdef debugactive
	debug(calibrationvalue);				// debug the measurement
#endif
	if(adjust == 0) adjust = 1;				// if adjust is not yet finished, finish it now.
	sei();									// reenable interrupts
}


/*!
 *	\brief Interrupt Service Routine for the timer-0 Overflow
 */
ISR(TIM0_OVF_vect)
{

	cli();

	// make sure the counter is never bigger than 5
	measurementcounter = measurementcounter%5;

	// write the current measurement into the array and increment counter
	measurement[++measurementcounter] = ADCH;

/*	if(calibrationvalue < runningaverage)
	{
		calibrationvalue = runningaverage;		// set the new calibration value.
	}
*/

	if( 0 != adjust )	// 	when adjust is active, skip the interrupt
	{

		if(measurement[measurementcounter] < (runningaverage-20)  ) // < (calibrationvalue-30))
		{
			alarm();
			LASERport &= ~(1<<LASERpin);	// Switch off the laser
		}

	}

	sei();
}

/*!
	\brief Timer1 compare B interrupt, used for generate a sound 
	
*/
ISR(TIM1_COMPB_vect)
{
	if(l++>250)
	{

		OCR1A = pgm_read_byte(sound+m);
		m++;
		m = m%5;

		l=0;
	}

}
	

/* ADC initialisieren */
void ADC_Init(void) 
{
  uint16_t result;
  
  ACSR = (1<<ACD);								// Analog Comparator disable, saves energy
  ADMUX = (1<<MUX1); 							// use VCC as reference, work on channel 2 (PA2)
 
  ADCSRA = (1<<ADPS2) | (1<<ADPS0) | (1<<ADATE); 	// ADPS2,0 frequency-div set to 32, ADATE select auto trigger enable
  ADCSRB = (1<<ADLAR);								// activate auto trigger source: free running mode, ADLAR left adjust, 8 MSB in one byte ADCH
  ADCSRA |= (1<<ADEN);                  			// activate ADC
  
  // now the ADC continously measures and converts the voltage on pin PA2 and puts the result to the ADCH-Register
  
  ADCSRA |= (1<<ADSC);                  // start the conversion
  while (ADCSRA & (1<<ADIF));          // wait while the conversion is active
  result = ADCH;						// read the result, this is needed to finish the first conversion.
}

/*!
 *	\brief Generate a sound or do something really incredible
 */
void alarm( void )
{
	// activate the OC1B PWM for the Soundgenerator and update it in the timer interrupt	
	// Timer 1: PWM generation for LED and Sound
	alarmactive = 1;
	TCCR1A = (1<<COM1B0); // | (1<<WGM10) | (1<<WGM11) ; 	// Clear OC1B on compare match, set at bottom
	TCCR1B = (1<<WGM12)  | (1<<CS11) ;
	TIMSK1 |= (1<<OCIE1B);
	MCUCR = 0;	// disable further INT0-Interrupt
	OCR1A= 155;								// Top of the PWM, max. 0x03FF
}



void LED_flash(uint8_t count )
{
	for(i=0;i<count;i++)	// flash the led, count times
	{
		LEDport ^= (1<<LEDpin);
		_delay_ms(50);
		LEDport ^= (1<<LEDpin);
		_delay_ms(50);
	}
}

void CalculateAverage( void )
{
	// sum up the last four values and divide by 4
	runningaverage = ( (measurement[4] + measurement[1] + measurement[2] + measurement[3])>>2 );
}

int main ( void )
{
	// initialise the device
	init();

	// endless loop, nearly all functions are called from interrupts, not much to do here.
	for(;;)
	{
		CalculateAverage();

		LEDport ^= (1<<LEDpin);		// toggle the led

		// this if-case is true while the device is still in the adjust mode
		if(adjust == 0)
		{
			LASERport |= (1<<LASERpin);
			_delay_ms(10);	// drive the laser in a 10% duty-cycle. When it accidentially hits your eye it will at least stop after 10ms in the worst case.
			LASERport &= ~(1<<LASERpin);
			_delay_ms(90);	// give your eye time to close the lid.


		}
		else	// this is during game
		{
			// this _delay should be replaced by a sleep command
			_delay_ms(500);	
		}

	}
	return 0;
}

