#define F_CPU 8000000
#include <avr/interrupt.h>
#include <avr/io.h>
#include <stdint.h>
#include <avr/sleep.h>

/*
 * ADC0: input voltage * 0.0476
 * ADC1: output voltage * 0.0476
 * PD3/OC2B: switch pwm
 * PD2: led (active low)
 */

volatile uint16_t v_in_mv  = 0;
volatile uint16_t v_out_mv = 0;
#define MIN_VOLTAGE 5700   /* Disable switch when input voltage below this.  */
#define MAX_VOLTAGE 20000  /* Disable switch when output voltage above this. */
#define MPP_VOLTAGE 6300   /* Control switch to make input voltage this.     */
#define DEADBAND    40     /* How far voltage must be off to adjust pwm.     */

/* #define SERIAL_DEBUG */

/* Note that MAX_VOLTAGE is not intended to be a well-regulated output voltage;
 * it exists mainly to protect the output capacitors from getting melted if
 * a battery is not connected.  The converter topology is capable of charging
 * them well past their limits. */

#define LED_ON  (PORTD &= ~(1<<2), DDRD  |=  (1<<2))
#define LED_OFF (PORTD |=  (1<<2))

/* Converts ADC output to millivolts. */
#define ADC_CONVERT(ADC) ((uint16_t)((((uint32_t)(ADC)) * 1478400) >> 16))

void adc_init() {
  DDRC = 0;                            /* disable port c output drivers      */
  DIDR0 = 0x3F;                        /* disable port c digital inputs      */
  ADCSRA = (1<<ADEN)|(1<<ADIE) | 0x07; /* enable adc with int and %128 scale */
  ADCSRB = 0;
  ADMUX = (1<<REFS1)|(1<<REFS0);       /* select 1.1v internal reference     */
  ADCSRA |= (1<<ADSC);
}

SIGNAL(SIG_ADC) {
  static uint8_t adc_ch = 0;
  static uint8_t postscale = 0;
  static uint8_t blink = 0;
  static uint8_t charging = 0;   
  /* alternate measureing input and output voltages */
  if (adc_ch == 0) {
    v_in_mv = ADC_CONVERT(ADCW);
    adc_ch = 1;
  } else if (adc_ch == 1) {
    v_out_mv = ADC_CONVERT(ADCW);
    adc_ch = 0;
  }

  if (blink) {
    LED_ON;
    --blink;
  } else {
    LED_OFF;
  }
 
  /* every 100 interrupts, adjust the pwm value */
  if (--postscale == 0) {
    postscale = 100;
    if (v_in_mv > MIN_VOLTAGE && v_out_mv < MAX_VOLTAGE) {  
      /* adjust pwm if input voltage is too high or low */
      if (v_in_mv < (MPP_VOLTAGE-DEADBAND) && OCR2B > 0) {
        --OCR2B;
      } else if (v_in_mv > (MPP_VOLTAGE+DEADBAND) && OCR2B < 224) {
        ++OCR2B;
      }

      /* set pwm enabled */
      DDRD |= 1<<3;
      TCCR2A = (1<<COM2B1)|(0<<COM2B0) | (1<<WGM21)|(1<<WGM20);
      TCCR2B = (0<<WGM22) | (0x07 & 0x01);

      /* blink to tell the world we're charging */
      if (++charging == 50) {
        charging = 0;
        blink = 10;
      }
    } else {
      charging = 0;
      /* set pwm disabled and switch off */
      DDRD |= 1<<3;
      TCCR2A = TCCR2B = 0;
      OCR2B = 0;
      PORTD &= ~(1<<3);
    }
  }
  
  /* start the next conversion */
  ADMUX = (ADMUX & 0xF8) | (0x07 & adc_ch);
  ADCSRA |= (1<<ADSC);
}

#ifdef SERIAL_OUTPUT

#define BAUD 9600 
#define UBRRSET (F_CPU/8/BAUD-1)

void serial_init() {
  DDRD &= ~(1<<0);
  DDRD |=   1<<1;
  UBRR0H = (uint8_t) (UBRRSET >> 8);
  UBRR0L = (uint8_t) UBRRSET;
  UCSR0A = (1<<U2X0);
  UCSR0B = (1<<RXEN0)|(1<<TXEN0);
  UCSR0C = (1<<UCSZ00)|(1<<UCSZ01);
}

void putch(char c) {
  while(!(UCSR0A & (1<<UDRE0)));
  UDR0 = c;
}

void putstr(const char * str){
  while(*str) putch(*str++);
}

void put_mv(uint16_t d) {
  putch(d / 10000 + '0');
  d = d % 10000;
  putch(d / 1000 + '0');
  d = d % 1000;
  putch('.');
  putch(d / 100 + '0');
  d = d % 100;
  putch(d / 10 + '0');
}
#endif

int main(void){  
  adc_init();
  sei();

#ifdef SERIAL_DEBUG
  serial_init();
  while(1) {
    put_mv(v_in_mv);
    putstr(" ");
    put_mv(v_out_mv);
    putstr("\n");
  }
#else
  while(1) {
    set_sleep_mode(SLEEP_MODE_IDLE);
    sleep_enable();
    sleep_cpu();
  }
#endif

  return 0;
}


