PID Controller

#include <science.h>

Temperature t( PIN2 );

Stats stats;

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

class PID
{
  double kp;                  // Proportional coefficient
  double ki;                  // Integral coefficient
  double kd;                  // Derivative coefficient
  double previous_input;
  unsigned long lastTime;
  int 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 = int( Proportional + Integral + Derivative );

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

      lastTime = now;
    }
    return output;
  }
};

//
// Oscillation period is 12.67 minutes
//
// PID pid( 100, 1, 35, 98.6 );
// PID pid( 100, 1, 35, 96.8 );
// PID pid( 300, 1, 10, 98.6 );  // 160 minutes to settle
// PID pid( 400, 1, 20, 98.6 );  // 135 minutes to settle
// PID pid( 500, .5, 30, 98.6 ); // Oscillates after settling in 70 minutes
// PID pid( 350, .8, 20, 98.6 ); // Oscillates
PID pid( 340, .6, 20, 98.6 );

void
setup()
{
  Serial.begin( 115200 );
  pinMode( PIN3, OUTPUT );
  digitalWrite( PIN3, HIGH );
}

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 );
  stats.sample( f );
  Serial.print( f, 4 );
  Serial.print( ", " );
  Serial.print( stats.get_mean(), 6 );
  Serial.print( ", " );
  Serial.print( stats.get_sd(), 6 );

  int out = pid.out( f );
  analogWrite( PIN3, out );
  
  Serial.print( ", " );
  Serial.print( out );
  
  Serial.print( ", " );
  Serial.print( pid.Integral );
  
  Serial.print( ", " );
  Serial.print( pid.Target );

  Serial.println();
  delay( 300 );
}