/*
 *         This is a non-Arduino version of the Chronulator code.
 *
 *  Changes made to get it to compile under AVR studio:
 *   1) complete removal of the serial commands, so no setting of the meters except by time;
 *   2) prototypes taken from the arduino tools-produced .cpp. Just easy that way;
 *   3) all occurances of boolean changed to bool since bool is the standard;
 *   4) included <avr/interrupt.h> since needed for ISR definition;
 *
 *  Changes made to functionality:
 *   1) Added the compile-time switch PHASE_CORRECT to select a different PWM mode for the two out-
 *   puts. The main advantage of the different mode is that there is no output pulse at a value of 
 *   zero. This means that power-off mechanical zero calibration is sufficient for the "left end" 
 *   calibration. Since the divisor is a multiple of 510 vs 256, some extra work has to be done!
 *   2) Added MINUTES_DOWN so that reverse/upside down meters can be used. Note that this does not
 *   change the polarity of the signal, just the output-with-time direction;
 *   3) Added HOURS_DOWN similarly;
 *   4) Added TWENTY_FOUR_HOURS to select either 12 or 24 hour clock. 
 *   5) Do up-edge switch debouncing too! Then we can do what Jared says is already done, clear 
 *   seconds when minute-setting buton is released.
 *   6) Put in the second=0 statement to clear the seconds when minutes are set
 *   6) Added in another option, to do the hours "continuously"
 *   7) Added in yet another option, to do the minutes "continuously"  
 *
 *  Other changes:
 *   1) Changed all static const unsigned char variables to be pre-processor constants. (I simply 
 *   prepended the "CONS" string to the existing names.) This, surprisingly, made absolutely no 
 *   difference in the size of code produced. (These should be put back since the code looks ugly 
 *   with the preprocessor constants!) 
 *   2) Added a CONSmaximumPWM (as 240) to allow the preprocessor to calculate meter_scale_minutes
 *   and meter_scale_hours: the later is different depending on whether in 12 or 24 hour mode.
 *   3) Took out all power-mode switching code (put it back? Or make it an option?).
 *   
 *  ToDo, or consider doing: 
 *   1) Add in option to NOT need more than 12 or 60 ticks for the scale (not compat w continuous)
 *   2) Add an alarm function...
 *   3) Do per meter tick calibration? (Use EEPROM to store corrections.)
 *   4) Add a clear-all input (midnight pulse to sync to a time standard)
 *   5) Add an am/pm output (12 hr mode only?)
 *   6) control the rate of fall when going full-scale to 0
 *   7) add a seconds tick to minutes
 *   8) add a minutes tick to hours
 *   9) allow the minutes to be decremented when the button is down and the other pushed, same for
 *      hours vice-versa...this will mean that to get to the zeroing/full-scale, the other switch
 *      combinations will have to be used, i.e. down, down, first up, other up
 *   
 */ 
//protoypes:
void s1_pressed();
void s1_released();
void s2_pressed();
void s2_released();
void debounce_buttons();
void show_time();
void set_mode_show_time();
void set_mode_calibrate_zero_scale();
void set_mode_calibrate_full_scale();
void tick_hour();
void tick_minute();
void tick_second();
void add_hour();
void subtract_hour();
void add_minute();
void subtract_minute();
void update_divisor();
void tick_tick();
bool sleepModeCausesSpuriousTimer2Interrupts();
void waitForTimer2CycleToEnd();
void update_sleep_mode();
void enable_timer0();
void disable_timer0();
bool is_battery_powered();
void power_up();
void power_down();
void update_power_mode();
void initializePorts();
void initializeAnalogToMinimizePower();
void initializeTimer2For32KHzCrystal();
void setup();
void loop();

#define F_CPU 4000000

/* OPTIONS: */
//To turn any of these on remove the // comment characters at the start of a line. Defining as 'no' or 
//any other value does not change anything since it is the presence of the definition that is looked for.
#define PHASE_CORRECT      yes //checked
//#define MINUTES_DOWN       yes //checked
//#define HOURS_DOWN         yes
//#define TWENTY_FOUR_HOURS  yes //checked
#define CONTINUOUS_MINUTES yes //checked. Every 15 seconds a small change will happen.
//#define CONTINUOUS_HOURS   yes //checked. Every 3 (12 hr mode) or 6 minutes a small change will happen

/* INCLUDED HEADER FILES: */
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#define VERSION_RELEASE "200912280730"

typedef enum meter_mode {
  METER_MODE_SHOW_TIME = 0,
  METER_MODE_CALIBRATE_ZERO_SCALE = 1,
  METER_MODE_CALIBRATE_FULL_SCALE = 2
} meter_mode_t;

static meter_mode_t meter_mode = METER_MODE_SHOW_TIME;

#define  CONSdebounce_wait 4

static unsigned char debounce_counter_s1_up = 0;
static unsigned char debounce_counter_s2_up = 0;
static unsigned char debounce_counter_s1_dn = 0;
static unsigned char debounce_counter_s2_dn = 0;

static bool s1_active = false;
static bool s2_active = false;

//moved from below:
static unsigned char second = 0;

#define METER_M OCR2A
#define METER_H OCR2B

void s1_pressed() {
  switch( meter_mode ) {
  case METER_MODE_SHOW_TIME:
    if( s2_active ) {
      subtract_minute();
      set_mode_calibrate_zero_scale();
    } else {
      add_hour();
      show_time();
    }
    break;
    
  case METER_MODE_CALIBRATE_ZERO_SCALE:
    if( s2_active ) {
      set_mode_calibrate_full_scale();
    }
    break;
    
  case METER_MODE_CALIBRATE_FULL_SCALE:
    if( s2_active ) {
      set_mode_show_time();
    }
    break;
    
  default:
    break;
  }
}

void s1_released() {
}

void s2_pressed() {
  switch( meter_mode ) {
  case METER_MODE_SHOW_TIME:
    if( s1_active ) {
      subtract_hour();
      set_mode_calibrate_zero_scale();
    } else {
      add_minute();
	  second = 0;  //this clears second counter at push, not release of s2, it helps if continuous minutes on
      show_time();
    }
    break;
    
  case METER_MODE_CALIBRATE_ZERO_SCALE:
    if( s1_active ) {
      set_mode_calibrate_full_scale();
    }
    break;
    
  case METER_MODE_CALIBRATE_FULL_SCALE:
    if( s1_active ) {
      set_mode_show_time();
    }
    break;
    
  default:
    break;
  }
}

void s2_released() {
  if ( meter_mode == METER_MODE_SHOW_TIME ){
    second = 0;
  }
}

#define BATTERY_PRESENT_PORT (PINC)
#define BATTERY_PRESENT_BIT (_BV(PINC1))
#define BATTERY_PRESENT (BATTERY_PRESENT_PORT & BATTERY_PRESENT_BIT)

// TODO: Rename S1, S2 to S2, S3 --> HOURS, MINUTES

#define S1_PORT PINB
#define S1_BIT _BV(PINB0)

#define S2_PORT PIND
#define S2_BIT _BV(PIND7)

void debounce_buttons() {
  if( s1_active ) {
    if( S1_PORT & S1_BIT ) {
    debounce_counter_s1_up++;
      if( debounce_counter_s1_up == CONSdebounce_wait ) {
        s1_active = false;
        s1_released();
      }
	} else {
      debounce_counter_s1_up = 0;
	}
  } else {
    if( (S1_PORT & S1_BIT) == 0 ) {
      debounce_counter_s1_dn++;
      if( debounce_counter_s1_dn == CONSdebounce_wait ) {
        s1_active = true;
        s1_pressed();
      }
    } else {
      debounce_counter_s1_dn = 0;
    }
  }
  
  if( s2_active ) {
    if( S2_PORT & S2_BIT ) {
	debounce_counter_s2_up++;
      if( debounce_counter_s2_up == CONSdebounce_wait ) {
        s2_active = false;
        s2_released();
      }
	} else {
      debounce_counter_s2_up = 0;
    }
  } else {
    if( (S2_PORT & S2_BIT) == 0 ) {
      debounce_counter_s2_dn++;
      if( debounce_counter_s2_dn == CONSdebounce_wait ) {
        s2_active = true;
        s2_pressed();
      }
    } else {
      debounce_counter_s2_dn = 0;
    }
  }
}
#ifdef PHASE_CORRECT 
#define CONSticksPerSecond 64
#define CONSmakeupDivisor 255
#else
#define CONSticksPerSecond 128
#endif
#define  CONSsecondsPerMinute  60
#define  CONSminutesPerHour  60
#ifdef TWENTY_FOUR_HOURS  
#define  CONSmaximumHours  24
#define  CONSadd_minutes_to_hours 
#else
#define  CONSmaximumHours  12
#define  CONSadd_minutes_to_hours 
#endif
#define  CONSmaximumPWM  240

//initial values when reset:
static unsigned char hour = CONSmaximumHours / 2;
static unsigned char minute = CONSminutesPerHour / 2;
static unsigned char tick = 0;

#define  CONSmeter_scale_hours (CONSmaximumPWM/CONSmaximumHours) //20 if 12 hr, 10 if 24 hr
#define  CONSmeter_scale_minutes (CONSmaximumPWM/CONSminutesPerHour) //4

#define  CONSseconds_per_minute_scale_tick (60/CONSmeter_scale_minutes)
#define  CONSminutes_per_hour_scale_tick   (60/CONSmeter_scale_hours)

static unsigned char meter_m_value = 0;
static unsigned char meter_h_value = 0;

void show_time() {
#ifdef MINUTES_DOWN
#ifdef CONTINUOUS_MINUTES
  meter_m_value = CONSmaximumPWM -(minute * CONSmeter_scale_minutes +
                                   second/CONSseconds_per_minute_scale_tick);
#else
  meter_m_value = CONSmaximumPWM -(minute * CONSmeter_scale_minutes);
#endif
#else
#ifdef CONTINUOUS_MINUTES
  meter_m_value = minute * CONSmeter_scale_minutes + second/CONSseconds_per_minute_scale_tick;
#else
  meter_m_value = minute * CONSmeter_scale_minutes;
#endif
#endif

#ifdef HOURS_DOWN
#ifdef CONTINUOUS_HOURS
  meter_h_value = CONSmaximumPWM -(hour * CONSmeter_scale_hours + 
                                   minute/CONSminutes_per_hour_scale_tick);
#else  
  meter_h_value = CONSmaximumPWM -(hour * CONSmeter_scale_hours);
#endif
#else
#ifdef CONTINUOUS_HOURS
  meter_h_value = hour * CONSmeter_scale_hours + minute/CONSminutes_per_hour_scale_tick;
#else  
  meter_h_value = hour * CONSmeter_scale_hours;
#endif
#endif
}

void set_mode_show_time() {
  meter_mode = METER_MODE_SHOW_TIME;
  show_time();
}

void set_mode_calibrate_zero_scale() {
  meter_mode = METER_MODE_CALIBRATE_ZERO_SCALE;
  meter_m_value = 0;
  meter_h_value = 0;
}

void set_mode_calibrate_full_scale() {
  meter_mode = METER_MODE_CALIBRATE_FULL_SCALE;
  meter_m_value = CONSminutesPerHour * CONSmeter_scale_minutes;
  meter_h_value = CONSmaximumHours * CONSmeter_scale_hours;
}


void tick_hour() {
  if( hour < (CONSmaximumHours - 1) ) {
    hour++;
  } else {
    hour = 0;
  }
}

void tick_minute() {
  if( minute < (CONSminutesPerHour - 1) ) {
    minute++;
  } else {
    minute = 0;
    tick_hour();
  }
}

void tick_second() {
  if( second < (CONSsecondsPerMinute - 1) ) {
    second++;
  } else {
    second = 0;
    tick_minute();
  }
}

void add_hour() {
  if( hour < (CONSmaximumHours - 1) ) {
    hour++;
  } else {
    hour = 0;
  }
}

void subtract_hour() {
  if( hour > 0 ) {
    hour--;
  } else {
    hour = CONSmaximumHours - 1;
  }
}

void add_minute() {
  if( minute < (CONSminutesPerHour - 1) ) {
    minute++;
  } else {
    minute = 0;
  }
}

void subtract_minute() {
  if( minute > 0 ) {
    minute--;
  } else {
    minute = CONSminutesPerHour - 1;
  }
}

#ifdef PHASE_CORRECT
static unsigned char makeup = 0;
static unsigned char ticks_per_second = CONSticksPerSecond;

void update_divisor(){
  makeup++;
  //create a 0...254 counter, so 255 states
  if ( makeup == CONSmakeupDivisor -1) {
    makeup = 0;
  }
  //Roughly every 4th time we wait for one more tick. The actual number is 64/255, so 
  // the complete divisor is 64+(64/255). The PHASE_CORRECT mode used divides the 32768 Hz
  // clock by 510, not 512 as might be expected. And since 64+(64/255) x 510 = 32768, this 
  // is the correct divisor.
  ticks_per_second = ((0x03&makeup) == 0)? CONSticksPerSecond : CONSticksPerSecond-1  ;
}

void tick_tock() {
  //this version uses a variable divisor, mostly 64 but sometimes 65.
  if( tick == ticks_per_second ) {
    tick = 0;
    tick_second();
	update_divisor();
    if( meter_mode == METER_MODE_SHOW_TIME ) {
      show_time();
    }
  } else {
    tick++;
  }
}
#else
//as before
void tick_tock() {
  if( tick < (CONSticksPerSecond - 1) ) {
    tick++;
  } else {
    tick = 0;
    tick_second();
    if( meter_mode == METER_MODE_SHOW_TIME ) {
      show_time();
    }
  }
}
#endif

bool sleepModeCausesSpuriousTimer2Interrupts() {
  switch( SMCR & (_BV(SM2) | _BV(SM1) | _BV(SM0)) ) {
  case (_BV(SM1) | _BV(SM0)):
  case (_BV(SM0)):
    // Power-save or ADC Noise Reduction
    return true;
    
  default:
    return false;
  }
}

void waitForTimer2CycleToEnd() {
  while(ASSR & (_BV(OCR2AUB) | _BV(OCR2BUB)));
}


ISR(TIMER2_OVF_vect) {
  METER_M = meter_m_value;
  METER_H = meter_h_value;

  tick_tock();

  //update_power_mode();
  
  debounce_buttons();
  
  // Ensure TOSC cycle will not cause extra interrupts.
  // a. Write a value to TCCR2x, TCNT2, or OCR2x. 
  // b. Wait until the corresponding Update Busy Flag in ASSR returns to zero. 
  // c. Enter Power-save or ADC Noise Reduction mode.

  // Can't re-enter power save mode until the TOSC1 cycle that woke us is
  // complete. Use the writes to OCR2A/B, performed earlier in this interrupt
  // routine, to indicate when the cycle is over. The ASSR OCR2xUB flags will
  // clear when the cycle is over.
  if( sleepModeCausesSpuriousTimer2Interrupts() ) {
    waitForTimer2CycleToEnd();
  }
}

void initializePorts() {
  // Configure I/O pins for lowest power.
  
  // PB0: I, pullup: Switch (DDB0=0, PB0=1)
  // PB1: I: OC1A PWM output (DDB1=1, PB1=0)
  // PB2: I: OC1B PWM output (DDB2=1, PB2=0)
  // PB3: O: OC2A PWM output, "minutes" meter (DDB3=1, PB3=0)
  // PB4: I:
  // PB5: I:
  // PB6: I: TOSC1 (crystal) (DDB6=0, PB6=0)
  // PB7: I: TOSC2 (crystal) (DDB7=0, PB7=0)
  DDRB = _BV(DDB2) | _BV(DDB1);
  PORTB = _BV(PB0);

  // PC0: I:
  // PC1: I, pullup: Battery plug present (DDC1=0, PC1=1)
  // PC2: I:
  // PC3: I:
  // PC4: I:
  // PC5: I:
  // PC6: I: RESET
  // PC7: I: (no pin)
  DDRC = 0;
  PORTC = _BV(PC1);

  // PD0: I: RXD Serial RX (DDD0=0, PD0=1)
  // PD1: O: TXD Serial TX (DDD1=1, PD1=1)
  // PD2: I:
  // PD3: O: OC2B PWM output, "hours" meter (DDD3=1, PD3=0)
  // PD4: I:
  // PD5: O: OC0B PWM output, LED (DDD5=1, PD5=0)
  // PD6: O: OC0A PWM output, LED (DDD6=1, PD6=0)
  // PD7: I, pullup: Switch (DDD7=0, PD7=1)
  DDRD = 0;
  PORTD = _BV(PD7);

  // Do not disable internal pull-up resistors on ports.
  MCUCR &= ~_BV(PUD);
}

void initializeAnalogToMinimizePower() {
  // Turning off analog stuff saves 120uA:
  ACSR |= _BV(ACD); // Disable analog comparator.
  //ACSR &= ~_BV(ACBG);	// Select analog input as reference (instead of bandgap).
  //DIDR1 = _BV(AIN1D) | _BV(AIN0D); // Disable digital input buffers on analog inputs.
  ADMUX &= ~(_BV(REFS1) | _BV(REFS0)); // Turn off AREF, internal Vref.
  ADCSRA &= ~_BV(ADEN); // Disable ADC.
}

void initializeTimer2For32KHzCrystal() {
  power_timer2_enable();

  // Timer 2 async setup (section 17.9)
  // a. Disable the Timer/Counter2 interrupts by clearing OCIE2x and TOIE2.
  TIMSK2 = 0;
  
  // b. Select clock source by setting AS2 as appropriate.
  ASSR = _BV(AS2);
  
  // b(2). Wait for clock to come up.
  _delay_ms(1000);
  
  // c. Write new values to TCNT2, OCR2x, and TCCR2x.
  TCNT2 = 0;
  OCR2A = 0;
  OCR2B = 0;
#ifdef PHASE_CORRECT
  //the phase correct mode has the advanage of zero output at zero count
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
  TCCR2B = _BV(CS20);
#else
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
  TCCR2B = _BV(CS20);
#endif
  // d. To switch to asynchronous operation: Wait for TCN2xUB, OCR2xUB, and TCR2xUB.
  while( ASSR & (_BV(TCN2UB) | _BV(OCR2AUB) | _BV(OCR2BUB) | _BV(TCR2AUB) | _BV(TCR2BUB)) );
  
  // e. Clear the Timer/Counter2 Interrupt Flags.
  TIFR2 = 0;
  
  // f. Enable interrupts, if needed.
  TIMSK2 = _BV(TOIE2);

  DDRB |= _BV(DDB3);
  DDRD |= _BV(DDD3);
}


void setup() {
  clock_prescale_set(clock_div_2);
  set_sleep_mode(SLEEP_MODE_PWR_SAVE);

  cli();

  initializePorts();
  
  initializeAnalogToMinimizePower();

  power_all_disable();
  
  initializeTimer2For32KHzCrystal();
  
  sei();
}

void loop() {
  sleep_mode();

}

int main(void){
  //init();
  setup();
  for (;;)
   loop();
  return 0;
}
