Sous Vide

#include <science.h>
#include <avr/io.h>
#include <avr/interrupt.h>

Temperature t( PIN3 );
enum { heater = 9, zerocrossdetector = PIN2 };

Stats stats;

enum
{
  SAMPLE_TIME_IN_MS = 300,
  MILLISECONDS_PER_SECOND = 1000,
  OUTMIN = 0,
  OUTMAX = 255,
  PULSE = 4
};

class PID
{
  double kp;                  // Proportional coefficient
  double ki;                  // Integral coefficient
  double kd;                  // Derivative coefficient
  double previous_input;
  unsigned long lastTime;
  long output;

public:

  double Integral;
  double Target;

  PID( double Kp, double Ki, double Kd, double target )
  {
    Integral = 0;
    previous_input = target;
    output = 0;

    Target = target;

    double seconds_per_sample = double( SAMPLE_TIME_IN_MS )
      / MILLISECONDS_PER_SECOND;

    kp = Kp;
    ki = Ki * seconds_per_sample;
    kd = Kd / seconds_per_sample;

    lastTime = millis() - SAMPLE_TIME_IN_MS;        
  }

  int
  out( double input )
  {
    unsigned long now = millis();
    unsigned long timeChange = now - lastTime;

    if( timeChange >= SAMPLE_TIME_IN_MS )
    {
      double error = Target - input;

      Integral += ki * error;

      double Proportional = kp * error;
      double Derivative = kd * (previous_input - input);

      previous_input = input;

      if( Integral > OUTMAX )
        Integral = OUTMAX;
      else if( Integral < OUTMIN )
        Integral = OUTMIN;

      output = long( Proportional + Integral + Derivative );

      if( output > OUTMAX )
        output = OUTMAX;
      else if( output < OUTMIN )
        output = OUTMIN;

      lastTime = now;
    }
    return output;
  }
};

unsigned long int crossings = 0;
unsigned long int compares = 0;
unsigned long int overflows = 0;

void
zeroCrossingInterrupt()

  TCCR1B = 0x04;               // start timer with divide by 256 input
  TCNT1 = 0;                   // reset timer - count from zero
  crossings++;
}

ISR(TIMER1_COMPA_vect)
{
  digitalWrite( heater, HIGH );  // set triac gate to high
  TCNT1 = 65536 - PULSE;       // trigger pulse width
  compares++;
}

ISR(TIMER1_OVF_vect)
{
  digitalWrite( heater, LOW ); // turn off triac gate
  TCCR1B = 0x00;             // disable timer stop unintended triggers
  overflows++;
}

PID pid( 6, .5, 40, 133 );

void
setup()
{
  Serial.begin( 115200 );

  pinMode( zerocrossdetector, INPUT );        // zero cross detect
  digitalWrite( zerocrossdetector, HIGH );    // enable pull-up resistor
  pinMode( heater, OUTPUT );                  // triac gate control
  digitalWrite( heater, LOW );

  //
  // set up Timer1 
  // (see ATMEGA 328 data sheet pg 134 for more details)
  //
  OCR1A = 100;      // initialize the comparator
  TIMSK1 = 0x03;    // enable comparator A and overflow interrupts
  TCCR1A = 0x00;    // timer control registers set for
  TCCR1B = 0x00;    // normal operation, timer disabled

  attachInterrupt( 0, zeroCrossingInterrupt, RISING );    
  // IRQ0 is pin 2. Call zeroCrossingInterrupt on rising signal
}

void
leading_spaces( double num )
{
  if( num < 10 )
    Serial.print( "  " );
  else if( num < 100 )
    Serial.print( " " );
  Serial.print( num );
}

void
loop()
{
  int num = t.how_many_sensors();

  if( num <= 0 )
  {
    Serial.println( "No sensors" );
    delay( 2000 );
    return;
  }
  
  t.read( 0 );

  double f = t.fahrenheit( 0 );

  if( f < -100 )      // Sometimes we get spurious readings of -196 degrees
    return;
    
  stats.sample( f );

  int out = pid.out( f );

  OCR1A = out;                        // Set heater temperature
  
  Serial.print( f, 4 );
  Serial.print( ", " );
  Serial.print( stats.get_mean(), 6 );
  Serial.print( ", " );
  Serial.print( stats.get_sd(), 6 );

  Serial.print( ", " );
  leading_spaces( out );
  
  Serial.print( ", " );
  leading_spaces( pid.Integral );
  
  Serial.print( ", " );
  Serial.print( pid.Target );

/*
  Serial.print( ", o=" );
  Serial.print( overflows );

  Serial.print( ", cr=" );
  Serial.print( crossings );

  Serial.print( ", co=" );
  Serial.print( compares );
*/

  Serial.println();

  delay( 300 );
}