/* -----------------------------------------------------------------------
 * Title:    Farfadet (Sprite)
 * Author:   Sofian Audry (info@sofianaudry.com) and Samuel St-Aubin (samuel.st-aubin@sympatico.ca)
 * Date:     21.05.2008
 * Hardware: ATtiny13v
 * -----------------------------------------------------------------------*/
 
// These parameters control the "personality" of the device
// Try to set them differently for each one of them
#define BASE_PERIOD 750 // Controls the pitch. Smaller values mean higher pitch. Try to stay in [50, 3000]
#define SEELIE true     // Seelies' pitch increases when someone approaches while unseelies' pitch decreases

////////////////////////////////////////////////////////////////////////////

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

#define PIEZZO PB4                     // Define PIEZZO ext output pin on PB2

int i;                              // 8 bits integer

#define output_low(port,pin) port &= ~(1<<pin)
#define output_high(port,pin) port |= (1<<pin)
#define set_input(portdir,pin) portdir &= ~(1<<pin)
#define set_output(portdir,pin) portdir |= (1<<pin)

typedef unsigned char boolean;
#define true 1
#define false 0

//#define min(a,b) ((a)<(b)?(a):(b))
//#define max(a,b) ((a)>(b)?(a):(b))
//#define abs(x) ((x)>0?(x):-(x))

// The number of times timer 0 has overflowed since the program started.
// Must be volatile or gcc will optimize away some uses of it.
volatile unsigned long timer0_overflow_count;

SIGNAL(SIG_OVERFLOW0)
{
	timer0_overflow_count++;
}

unsigned long millis()
{
	// timer 0 increments every 64 cycles, and overflows when it reaches
	// 256.  we would calculate the total number of clock cycles, then
	// divide by the number of clock cycles per millisecond, but this
	// overflows too often.
	//return timer0_overflow_count * 64UL * 256UL / (F_CPU / 1000UL);
	
	// instead find 1/128th the number of clock cycles and divide by
	// 1/128th the number of clock cycles per millisecond
	return timer0_overflow_count * 64UL * 2UL / (F_CPU / 128000UL);
}

void delay(unsigned long ms)
{
	unsigned long start = millis();
	
	while (millis() - start < ms)
		;
}

/* Delay for the given number of microseconds.  Assumes a 16 MHz clock. 
 * Disables interrupts, which will disrupt the millis() function if used
 * too frequently. */
void delayMicroseconds(unsigned int us)
{
	uint8_t oldSREG;

	// calling avrlib's delay_us() function with low values (e.g. 1 or
	// 2 microseconds) gives delays longer than desired.
	//delay_us(us);

#if F_CPU >= 16000000L
	// for the 16 MHz clock on most Arduino boards

	// for a one-microsecond delay, simply return.  the overhead
	// of the function call yields a delay of approximately 1 1/8 us.
	if (--us == 0)
		return;

	// the following loop takes a quarter of a microsecond (4 cycles)
	// per iteration, so execute it four times for each microsecond of
	// delay requested.
	us <<= 2;

	// account for the time taken in the preceeding commands.
	us -= 2;
#else
	// for the 8 MHz internal clock on the ATmega168

	// for a one- or two-microsecond delay, simply return.  the overhead of
	// the function calls takes more than two microseconds.  can't just
	// subtract two, since us is unsigned; we'd overflow.
	if (--us == 0)
		return;
	if (--us == 0)
		return;

	// the following loop takes half of a microsecond (4 cycles)
	// per iteration, so execute it twice for each microsecond of
	// delay requested.
	us <<= 1;
    
	// partially compensate for the time taken by the preceeding commands.
	// we can't subtract any more than this or we'd overflow w/ small delays.
	us--;
#endif

	// disable interrupts, otherwise the timer 0 overflow interrupt that
	// tracks milliseconds will make us delay longer than we want.
	oldSREG = SREG;
	cli();

	// busy wait
	__asm__ __volatile__ (
		"1: sbiw %0,1" "\n\t" // 2 cycles
		"brne 1b" : "=w" (us) : "0" (us) // 2 cycles
	);

	// reenable interrupts.
	SREG = oldSREG;
}

void init() {
	// this needs to be called before setup() or some functions won't
	// work there
	sei();
	
	// timer 0 is used for millis() and delay()
	timer0_overflow_count = 0;
}

//void delay_us(uint8_t us) {
//  uint16_t delay_count = F_CPU / 17500;
//  volatile uint16_t i;
//  
//  while (ms != 0) {
//    for (i=0; i != delay_count; i++);
//    ms--;
//  }
//}
// 
// this is just a program that 'kills time' in a calibrated method
void delay_ms(uint8_t ms) {
  uint16_t delay_count = F_CPU / 17500;
  volatile uint16_t i;
  
  while (ms != 0) {
    for (i=0; i != delay_count; i++);
    ms--;
  }
}
//void delay_ms(uint8_t ms) {
//  uint16_t delay_count = F_CPU / 17500;
//  volatile uint16_t i;
//  
//  while (ms != 0) {
//    for (i=0; i != delay_count; i++);
//    ms--;
//  }
//}

unsigned int analogRead() {
  unsigned int low, high;
  
  ADCSRA |= (1 << ADEN);          // Analog-Digital enable bit
  ADCSRA |= (1 << ADSC);          // Discard first conversion

  while (ADCSRA & (1 << ADSC));  // wait until conversion is done

  ADCSRA |= (1 << ADSC);         // start single conversion

  while (ADCSRA & (1 << ADSC))  // wait until conversion is done

  ADCSRA &= ~(1<<ADEN);             // shut down the ADC
  
  //----------Show ADCH Byte in PIEZZO variable brightness indicator---------
	low = ADCL;
  high = ADCH;
  
  return (high << 8) | low;
}

int value;
int last_value;

#define THRESHOLD 3

int motion() {
  int motion;
  value = analogRead();
  motion = value - last_value;
  last_value = value;
  return motion;
}

void analogInit() {
  // Analog read initialization.
  ADCSRA |= (1 << ADEN) |          // Analog-Digital enable bit
            (1 << ADPS1) |         // set prescaler to 8    (clock / 8)
            (1 << ADPS0);          // set prescaler to 8    (clock / 8)

  ADMUX |= // (1 << ADLAR) |         // AD result store in (more significant bit in ADCH)
            (1 << MUX1) | (1 << MUX0);           // Choose AD input ADC3 (BP 3)
}

#define TIME_STEP 100

void playSquare(unsigned int period, unsigned int times)
{
  period++;
  period /= 2;
  while (times--) {
    output_high(PORTB, PIEZZO);
    delayMicroseconds(period);
    output_low(PORTB, PIEZZO);
    delayMicroseconds(period);
  }
}


int main(void)
{
  unsigned int m;
  unsigned int v;
  analogInit();
  
  set_output(DDRB, PIEZZO);              // Set output direction on PIEZZO
    
  output_low(PORTB, PIEZZO);
  
  motion(); // call one time to initialize the values
  
  for (;;)
  {
      if (TIME_STEP)
        delay_ms(TIME_STEP);
      
      m = motion();
      if (abs(m) > THRESHOLD) {
        v = analogRead();
        v = (SEELIE ? v : 1023-v) / 2;
        // Play a square wav slightly modified by the light reading
        playSquare(BASE_PERIOD + v, v);
      }
  }
  
  return 0;
}
