// sousvide.c
// for NerdKits with ATmega168
// http://www.nerdkits.com/

#define F_CPU 14745600

#include <stdio.h>
#include <math.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:
//
// PB2 -- servo output signal (OC1B)
// PC0 -- temperature sensor 0 analog input
// PC1 -- temperature sensor 1 analog input

#define MIN(x, y) ( ((x)<(y)) ? (x) : (y) )
#define MAX(x, y) ( ((x)>(y)) ? (x) : (y) )
#define BOUNDED(x, minim, maxim) ( MAX((minim),MIN((x),(maxim))) )

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

double adc_fivevee;

void adc_init() {
  // assume +5V really is 5.0
  adc_fivevee = 5.0;

  // set analog to digital converter
  // for external reference (5v), single ended input ADC0
  ADMUX = 0 | (1<<REFS0);

  // set analog to digital converter
  // to be enabled, with a clock prescale of 1/128
  // so that the ADC clock runs at 115.2kHz.
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);

  // fire a conversion just to get the ADC warmed up
  ADCSRA |= (1<<ADSC);
}

uint16_t adc_read() {
  // set ADSC bit to get the *next* conversion started
  ADCSRA |= (1<<ADSC);

  // read from ADC, waiting for conversion to finish
  while(ADCSRA & (1<<ADSC)) {
    // do nothing... just hold your breath.
  }
  // bit is cleared, so we have a result.   

  // read from the ADCL/ADCH registers, and combine the result
  // Note: ADCL must be read first (datasheet pp. 259)
  uint16_t result = ADCL;
  uint16_t temp = ADCH;
  result = result + (temp<<8);
  
  return result;
}

void adc_select_channel(uint8_t mux) {
  // select desired channel
  ADMUX = mux | (1<<REFS0);
  
  // do one dummy read
  adc_read();
}

double adc_average(uint16_t count) {
  double foo = 0.0;   
  uint16_t i;
  uint32_t total = 0;
  for(i=0; i<count; i++) {
    total += adc_read();
  }
  foo = total;
  return foo / ((double) count);
}

double sampleToFahrenheit(double sample) {
  // conversion ratio in DEGREES/STEP:
  // (5000 mV / 1024 steps) * (1 degree / 10mV)
  //	^^^^^^^^^^^		 ^^^^^^^^^^
  //     from ADC		  from LM34
  return sample * (adc_fivevee * 1000.0 / 1024.0 / 10.0);  
}

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

#define PWM_MIN 1300
#define PWM_MAX 4450
#define PWM_START 4450

void servo_pwm_set_raw(uint16_t x) {
  OCR1B = x;
}

void servo_pwm_init() {
  // setup Timer1 for Fast PWM mode, 16-bit
  // COM1B1 -- for non-inverting output
  // WGM13, WGM12, WGM11, WGM10 -- for Fast PWM with OCR1A as TOP value
  // CS11 -- for CLK/8 prescaling
  
  DDRB |= (1<<PB2); // PB2 output
  OCR1A = 36864;        // sets PWM to repeat pulse every 20.0ms
  servo_pwm_set_raw(PWM_START);
  TCCR1A = (1<<COM1B1) | (1<<WGM11) | (1<<WGM10);
  TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS11);
  
  // each count is 8/14745600 = 0.5425us.
  // so 1.0ms = 1843.2
  //    1.5ms = 2764.8
  //    2.0ms = 3686.4
  //   20.0ms = 36864
}

double dimmer_power = 0.0;
uint16_t dimmer_power_raw = 0;

void dimmer_set_power(double x) {
  // x is a power fraction between 0 and 1
  if(x < 0.0) {
    x = 0.0;
  } else if(x > 1.0) {
    x = 1.0;
  }
  
  // however, our regression can't handle x < 0.1
  if(x < 0.1) {
    x = 0.1;
  }
  
  double y = 1183.45 + -1394.18 * log(x);
  
  uint16_t z = (uint16_t) y;
  if(z > PWM_MAX) {
    z = PWM_MAX;
  } else if(z < PWM_MIN) {
    z = PWM_MIN;
  }
  
  servo_pwm_set_raw(z);

  dimmer_power = x;
  dimmer_power_raw = z;
}

double dimmer_get_power() { return dimmer_power; }
uint16_t dimmer_get_power_raw() { return dimmer_power_raw; }

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

int main() {
  // start up the LCD
  lcd_init();
  FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
  lcd_home();

  // start up the Analog to Digital Converter
  adc_init();  
  
  // start up the servo
  servo_pwm_init();
  dimmer_set_power(0);

  // start up the serial port
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

  // holder variables for temperature data
  double bandgap;
  double temp_chan0, temp_chan1;
  char tc;
  uint8_t heartbeat_count = 0;
  
  // inner means inner control loop (hull temp)
  double inner_const = 0.30; // power applied for zero error
  double inner_scale = 0.05;  // power applied for temperature offset (power fraction per degree F)
  double inner_setpoint = 200.0;
  double inner_error = 0.0;
  double inner_control = 0.0;
  
  // outer means outer control loop (core temp)
  double outer_const = 50.0; // degrees F when core temp is dead on
  double outer_scale = 10.0; // degrees F per degrees F of core temp error
  double outer_setpoint = 140.0;
  double outer_error = 0.0;
  double outer_control = 0.0;
  
  uint8_t servo_update_count = 0;
  
  while(1) {
    // read potentiometer
    adc_select_channel(5);
    outer_setpoint = 80.0 + 140.0 * (adc_average(100) / 1024.0);
  
    // calibrate ADC
    adc_select_channel(0x0E); // binary 1110
    adc_average(10); // throw away some readings
    bandgap = adc_average(100);
    adc_fivevee = 1024.0 * 1.099774569828667 / bandgap;
  
    // read sensors
    adc_select_channel(0);
    temp_chan0 = sampleToFahrenheit(adc_average(2500));
    adc_select_channel(1);
    temp_chan1 = sampleToFahrenheit(adc_average(2500));
    
    // calculate new controls
    outer_error = (outer_setpoint - temp_chan0);
    outer_control = (outer_error * outer_scale) + outer_const;
    inner_setpoint = outer_control + temp_chan0;
    inner_error = (inner_setpoint - temp_chan1);
    inner_control = (inner_error * inner_scale) + inner_const;
    
    // update servo?
    if(servo_update_count++ == 9) {
      servo_update_count = 0;
      dimmer_set_power(inner_control);
    }
    
    // write message to serial port
    printf_P(PSTR("%7.2f\t"), temp_chan0);
    printf_P(PSTR("%7.2f\t"), temp_chan1);
    printf_P(PSTR("%5.3f\t"), dimmer_get_power());
    printf_P(PSTR("%5.3f\t"), outer_setpoint);
    printf_P(PSTR("%5.3f\t"), outer_const);
    printf_P(PSTR("%5.3f\t"), outer_scale);
    printf_P(PSTR("%5.3f\t"), outer_control);
    printf_P(PSTR("%5.3f\t"), inner_setpoint);
    printf_P(PSTR("%5.3f\t"), inner_const);
    printf_P(PSTR("%5.3f\t"), inner_scale);
    printf_P(PSTR("%5.3f\t"), inner_control);
    printf_P(PSTR("\r\n"));
    
    // update LCD
    lcd_line_one();
    fprintf_P(&lcd_stream, PSTR(" NerdKits Sous Vide "));
    lcd_line_two();
    if(heartbeat_count == 0) { lcd_write_data(' '); lcd_write_data(' '); lcd_write_data(' '); }
    else if(heartbeat_count == 1) { lcd_write_data(0xA5); lcd_write_data(' '); lcd_write_data(' '); }
    else if(heartbeat_count == 2) { lcd_write_data(' '); lcd_write_data(0xA5); lcd_write_data(' '); }
    else if(heartbeat_count == 3) { lcd_write_data(' '); lcd_write_data(' '); lcd_write_data(0xA5); }
    if(++heartbeat_count == 4) heartbeat_count = 0;
    lcd_write_data(' ');
    lcd_write_data(0xDF); // degree sign
    fprintf_P(&lcd_stream, PSTR("F  CORE   HULL "));
    lcd_line_three();
    fprintf_P(&lcd_stream, PSTR(" SetPt%7.2f%7.2f"), outer_setpoint, BOUNDED(inner_setpoint, -99.99, 999.99));
    lcd_line_four();
    fprintf_P(&lcd_stream, PSTR("Actual%7.2f%7.2f"), BOUNDED(temp_chan0, -99.99, 999.99), BOUNDED(temp_chan1, -99.99, 999.99));
    
    // monitor inputs
    if(uart_char_is_waiting()) {
      tc = uart_read();
      if(tc == 'b') {
        printf_P(PSTR("? "));
        scanf_P(PSTR("%f"), &outer_const);
        printf_P(PSTR("\r\n"));
      } else if(tc == 'c') {
        printf_P(PSTR("? "));
        scanf_P(PSTR("%f"), &outer_scale);
        printf_P(PSTR("\r\n"));
      } else if(tc == 'd') {
        printf_P(PSTR("? "));
        scanf_P(PSTR("%f"), &inner_const);
        printf_P(PSTR("\r\n"));
      } else if(tc == 'e') {
        printf_P(PSTR("? "));
        scanf_P(PSTR("%f"), &inner_scale);
        printf_P(PSTR("\r\n"));
      }
    }
    
  } 


  return 0;
}
