I've been trying to use the popular PID library for a simple device to heat a piece of copper. I'm using a mechanical relay for switching so I've started by using the example code for relay output. The only additions I've made are to initialize an Adafruit thermocouple breakout, input a fixed setpoint of 37, and added a Serial print to monitor the current input and output values.
Initially, things seemed to be working fine and I've just been charting the temperature and gradually increasing Kp while leaving Ki and Kd at 0 which I think is fairly standard practice for manual tuning a new PID system. Eventually though as Kp got up around 100 the Output started going NaN and the relay would just stay on and skyrocket well past the setpoint. The Input print would also show NaN occasionally, but more frequently the Output goes NaN.
The regular behavior of the PID when the NaN error isn't occurring also seems a bit off. Even with a Kp as high as 80 the controller won't quite reach the 37C setpoint, and takes 5 minutes or more to go from room temp to 30C, and at no point does the "on" portion of the 5 second window exceed 800 - 900 ms, and is at it's highest on launch and immediately begins tapering down.
I'm not posting a schematic here because I know the wiring is fine - I'm comfortable with that side of things, I just don't do a lot of programming and I'm not sure how to go about debugging further.
#include <SPI.h>
#include "Adafruit_MAX31855.h"
#include <PID_v1.h>
#define PIN_INPUT 0
#define RELAY_PIN 13
#define MAXDO 3
#define MAXCS 4
#define MAXCLK 5
Adafruit_MAX31855 thermocouple(MAXCLK, MAXCS, MAXDO);
//Define Variables we'll be connecting to
double Setpoint, Input, Output;
//Specify the links and initial tuning parameters
double Kp=30, Ki=0, Kd=0;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
int WindowSize = 5000;
unsigned long windowStartTime;
void setup()
{
windowStartTime = millis();
//initialize the variables we're linked to
Setpoint = 37;
//tell the PID to range between 0 and the full window size
myPID.SetOutputLimits(0, WindowSize);
//turn the PID on
myPID.SetMode(AUTOMATIC);
pinMode(RELAY_PIN, OUTPUT);
Serial.begin(9600);
}
void loop()
{
Input = thermocouple.readCelsius();
myPID.Compute();
if (millis() - windowStartTime > WindowSize)
{ //time to shift the Relay Window
windowStartTime += WindowSize;
Serial.print("Out: ");
Serial.println(Output);
Serial.print("In: ");
Serial.println(Input);
}
if (Output < millis() - windowStartTime) digitalWrite(RELAY_PIN, LOW);
else digitalWrite(RELAY_PIN, HIGH);
}
An Example of Output/Input values with Kp 30 and setpoint 37 from the print console (prints every 5s):
Out: 187.50
In: 30.75
Out: 187.50
In: 30.75
Out: 187.50
In: 30.75
Out: 187.50
In: 30.75
Out: 187.50
In: 30.75
Out: 187.50
In: 30.50
Out: 195.00
In: 30.50
Out: 195.00
In: 30.50
Out: 195.00
In: 30.50
Out: 195.00
In: 30.50
Out: 195.00
In: 30.50
Out: 202.50
In: 30.25
Out: 195.00
In: 30.50
Out: 202.50
In: 30.25
Out: 202.50
In: 30.25
Out: 202.50
In: 30.25
Out: 202.50
In: 30.25
Out: 202.50
In: 30.25
Out: 210.00
In: 30.00
Out: 210.00
In: 30.00
Out: 210.00
In: 30.00
Out: 210.00
In: 30.00
Out: 210.00
In: 30.00
Out: 210.00
In: 30.00
Out: 210.00
In: 30.00
Out: 217.50
In: 29.75
Out: 217.50
In: 29.75
Out: 217.50
In: 29.75
Out: 217.50
In: 29.75
As you can see the performance is pretty flaccid with a plenty powerful heater and what I would think is a relatively high Kp. Doubling the Kp, when things aren't NaN'ing out, only improves this modestly.