// halloween.c
// for NerdKits with ATmega168
// hevans@nerdkits.com

#define F_CPU 14745600

#include <stdio.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
//#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

// PIN DEFINITIONS:
//
// AIN1/PD7 -- sensor
// OC0B/PD5 -- PWM output

uint16_t timer_val = 0;
float avg_old_timer = 0;
float avg_timer_diff = 0;
uint32_t timer_accum = 0;
float volatile avg_timer;
uint8_t volatile avg_timer_diff_ready = 0;
uint16_t avg_counter = 0;
ISR(ANALOG_COMP_vect) {

  //grab the current timer value
  timer_val = TCNT1;

  PORTD |= (1<<PD7); //pull it high
  DDRD |= (1<<PD7); //make inverter input - output pin
  //delay_us(2); //wait for it to charge

  if(avg_counter == 1000){
    avg_timer = (float)timer_accum / 1000.0;
    avg_timer_diff = avg_timer - avg_old_timer;
    avg_old_timer = avg_timer;
    avg_timer_diff_ready = 1;
    timer_accum = 0;
    avg_counter = 0;
  } else {
    timer_accum = timer_accum + timer_val;
    avg_counter++;
  }

  //clear the interrupt flag just in case
  ACSR &= ~(1<<ACI);
  
  //make it an input pin again
  DDRD &= ~(1<<PD7);
  PORTD &= ~(1<<PD7);
  
  //clear the timer
  TCNT1 = 0;

}


void init_timer(){
  //turn on timer1, no prescaling
  TCCR1B |= (1<<CS10);
}

void init_pwm_timer(){
  //setup fast PWM mode
  //max value 0xFF
  TCCR0A = (1<<WGM01) | (1<<WGM00) | (1<<COM0B1);
  //enable the timer, no prescale
  TCCR0B = (1<<CS00);
  //make OC0B an output pin
  DDRD |= (1<<PD5);
  PORTD &= ~(1<<PD5);
  
}

void init_analog_comparator(){

  //turn the bandap reference voltage
  ACSR |= (1<<ACBG);
  //AC interrutp enable
  ACSR |= (1<<ACIE);
  //AC interrupt fire on rising output edge
  ACSR |= (1<<ACIS1);
  ACSR |= (1<<ACIS0);
  //Turn on Analog Compare Output
  ACSR |= (1<<ACO);
  //Turn off Analog Compare Disable
  ACSR &= ~(1<<ACD);

}

int main() {
  
  sei();
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;


  //init_pin_change_interrupt();

  //set PC5 as output pin
  DDRC |= (1<<PC5);
  //kick it
  //PORTC |= (1<<PC5);


  //set PD7 output
  DDRD |= (1<<PD7);
  PORTD |= (1<<PD7); 

  init_analog_comparator();
  init_timer();
  init_pwm_timer(); 
  delay_ms(500);
  
  //let the games begin
  PORTD &= ~(1<<PD7);
  DDRD &= ~(1<<PD7);

  delay_ms(500); //wait for everything to be ready
  
  //average over a bunch of samples to grab a baseline
  uint8_t i = 0;
  float baseline = 0;
  uint32_t baseline_tmp = 0;
  for(i=0;i<10;i++){
    while(avg_timer_diff_ready == 0) {
      //wait untill ready 
    }
    baseline_tmp = baseline_tmp + avg_timer;
    avg_timer_diff_ready = 0;
  }
  baseline = baseline_tmp / 10;

  //flash LED to indicate ready
  PORTC |= (1<<PC5);
  delay_ms(100);
  PORTC &= ~(1<<PC5);

  uint8_t on_mode = 0;
  uint8_t on_count = 0;
  uint8_t off_count = 0;
  float avg_timer_min = 0;
  float new_pwm = 0;
  float full = (float)baseline + 0.05*(float)baseline;
  float floor = (float)baseline + 0.008*(float)baseline;
  while(1) {
    if(avg_timer_diff_ready){

      //This code is here to adjust the baseline value over time as many 
      //factors can change the capacitance slowly (including kids bumping your bowl)
      if(!on_mode){
        baseline = avg_timer*.003 + baseline*.997;
        full = (float)baseline + 0.05*(float)baseline;
        floor = (float)baseline + 0.008*(float)baseline;
         //detect a sudden rise in the capacitance, on_mode will not adjust the baseline
         //this is done to prevent "adjusting" to a lingering hand
        if(avg_timer > full){
          on_mode = 1;
        }
      } else {
        on_count++;
        if(avg_timer < floor){
          on_mode = 0;
          on_count = 0;
        } else if(on_count > 200) { //been in on mode for long enough, readjust the baseline to the current level
          on_mode =0;
          on_count = 0;
          baseline = avg_timer;
          full = (float)baseline + 0.05*(float)baseline;
          floor = (float)baseline + 0.008*(float)baseline;
        }
      }

      //set the new PWM frequency 0 - 255
      new_pwm = (255.0/(float)(full-floor)*(avg_timer-floor))*.2 + new_pwm*.8;
      if(new_pwm > 255){
        new_pwm = 255;
      } else if (new_pwm < 0) {
        new_pwm = 0;
      }
      OCR0B = new_pwm;
      //In fast PWM mode the output is set at BOTTOM and cleared on compare match.
      //In the case where OCR0B == BOTTOM, this results in a tiny spike at the beggining
      //of the PWM cycle. This was unacceptable for this application, so the PWM pin is
      //disconnected when PWM is being set to 0.
      if(new_pwm == 0){
        TCCR0A &= ~(1<<COM0B1);
      } else {
        TCCR0A |= (1<<COM0B1);
      }      
      avg_timer_diff_ready = 0;
      printf_P(PSTR("%.2f,%.2f,%.2f,%.2f\r\n"),avg_timer,baseline,new_pwm);
    }
  }
  
  return 0;
}
