I've been trying to implement a PID temperature control system using Arduino UNO.
The input temperature is acquired from Pt100 using a analogRead with the help of transmitter. The output is supplied using MOSFET to the heater. I've also designed an interface panel and data logger in MegunoLink to monitor the temperature and output data.
The temperature code was upload separately without implementing, it worked perfect. For temperature readings, I've used an exponential filtering to smoothen the data.But, with the PID implementation, the input temperature shows weird and unstable readings.
#include <PID_v1.h>
#include "Filter.h"
#include "MegunoLink.h"
#include "CommandHandler.h" // The serial command handler is defined in here.
CommandHandler<> SerialCommandHandler;
ExponentialFilter<long> ADCFilter(25, 0);
const int MotorPin = 3;
const int HeatPin = 11;
int sensorValue = 0;
int temperature = 0;
double f1 = 0;
//double t1 = 0;
const int AnalogInput = A3;
TimePlot MyPlot("Temp");
TimePlot MyPlot2("Out");
//Tuning parameters
float kp = 250;
float ki = 0;
float kd = 0;
double Setpoint, Input, Output;
PID myPID (&Input, &Output, &Setpoint, kp, ki, kd, DIRECT);
const int sampleRate = 10;
void setup() { // initialize serial communication with computer:
Serial.begin(9600);
Setpoint = 28;
SerialCommandHandler.AddVariable(F("SetSetpoint"), Setpoint);
SerialCommandHandler.AddVariable(F("SetP"), kp);
SerialCommandHandler.AddVariable(F("SetI"), ki);
SerialCommandHandler.AddVariable(F("SetD"), kd);
pinMode (HeatPin, OUTPUT); // for heater 13
pinMode (MotorPin, OUTPUT); // for pump
myPID.SetOutputLimits(0, 255);
myPID.SetMode(AUTOMATIC);
myPID.SetSampleTime(sampleRate);
}
void tempRead() {
sensorValue = analogRead(AnalogInput);
ADCFilter.Filter(sensorValue);
temperature=map(ADCFilter.Current(),205,1023,-500,500);
f1 = temperature; // Float conversion
Input = f1/10.0; // dividing by 10
}
void loop() {
SerialCommandHandler.Process();
MyPlot.SendData("Setpoint", Setpoint);
analogWrite (MotorPin, 165); // for pump
tempRead();
MyPlot.SendData("Temperature", Input);
myPID.Compute();
analogWrite(HeatPin, Output);
MyPlot2.SendData("Output", Output);
delay(100); // delay in between reads for stability
}
I've no idea about the weird temperature readings ? It would be nice if you guys know how to solve this problem.
Thanks a lot.
Post examples of the "weird temperature readings", and explain when and how often this happens.
You can start by isolating the problem. Get rid of the exponential filter and map() function in the tempRead() function.
With a PID loop, there is no need to use the map() function or to convert to temperatures, because that will simply lead to a different choice for the PID K values.
some thoughts about PID_v1
PID_v1 has a built in blink without delay timer that skips readings until the time delay is reached.
increasing your delay between sample time will drastically affect Ki and Kd values (Ki and Kd are fractionated so that they are based on a 1 second interval but the noise of the input will be amplified at fast sample rates)
your are calculating currently at 100 times a second which I find difficult to manage most sensor readings are noisy Kp is instantaneous and the noise will be amplified, Ki isn't affected as bad, but Kd needs real change values to be effective. having such a high PID calculation rate would be used to balance a robot where you need instant detection of angle. but with temperature the physical delay will prevent you from needing this.
Suggestion 1) change** "**const int sampleRate = 10;" to 100 or even 1000
now the ZHomeSlice rule with Kp and temperature control the only way to reach setpoint with Kp only is if your output can equal zero at the same time.
temperature to setpoint error = 0 (at setpoint) times Kp (of any value) = Output (which will also equal Zero)
With every temperature application I've implemented PID on this is never the case so Kp only control will never reach setpoint there will always be an error.
So My suggestion is to start with Ki
Ki basically does the following in your case (direct control). If there is an error from setpoint at this iteration add a portion of that error to the output. with this in mind the added portion can accumulate (Windup) so resetting Ki's windup value is VERY important when starting up the PID control. you want Ki to be fast but not too fast as to overshoot way to far. so limits are necessary. you have capped yours at 0 to 255 which is excellent.
so to start try
Kp = 0;
Ki = 1;
Kd = 0;
sampleRate = 100; //10 times a second
also note that this may be a better alternative because nothing changes until myPid.Compute() returns true
if ( myPID.Compute()){
analogWrite(HeatPin, Output);
}
what you want to see is output to turn on just a little bit every cycle if it turns on too fast you will shoot all the way to 255 the temperature will overshoot and the output will shoot all the way to zero agein and again... if it turns on too slow it will take a long time to reach setpoint But it will reach setpoint. The PID values are floats so Ki can be less than 1. try 0.5 if 1 is too much.
next add a little Kp this will assist in slowing the effect of Ki as you reach setpoint. you want Ki to be effective but not overly controlling you want Kp to influence the control but only after Ki has created the proper offset to find setpoint.
Kd is a dampening routine like a shock absorber It looks at the rate of change from setpoint adding some derivative to the equation and you will find you can increase Ki's influence and still maintain control Kd basically compares last sample with this sample an creates a temporary change to the output attempting to slow down the rate of change. to much Kd and the output becomes jittery or willoccelate rapidly. add som Kd last if you even need it.
If you were to look at the 3 terms when setpoint is reached and under control after they are calculated before they get added together you would see
PTerm <3 (this value is likely caused by input noise)
ITerm > 0 and = PWM value needed to maintain setpoint
DTerm < 1 ( Kd should be small I've used Kd = 0.01 just to have a little influence)
Output = PTerm + ITerm + DTerm
sorry I mentioned resetting Ki Term is VERY important but I didn't mention how
if(!inAuto){
SkipCtr = 21; // Soft Start Skipps ki and kd for 20 cycles
integral = 0; // <<<< This is the Ki Term that needs to be reset
pre_error = 0;
return false;
}
With PID_v1 setting the PID to MANUAL mode will reset this term for you
// This will reset the Ki Term in PID_v1
myPID.SetMode(MANUAL);
myPID.Compute();
myPID.SetMode(AUTOMATIC);
With your code as long as you don't unplug your heater or create false readings while in Automatic mode your ITerm should be correctly calculated. and not wound up to 255.
I have tried changing the sample rate from 100 to 1000. I have also modified the code as you mentioned. However, the temperature readings still fluctuates very much.
Please find the attached pics of temperature readings using PID code and separate standalone code.
#include <PID_v1.h>
#include "Filter.h"
#include "MegunoLink.h"
#include "CommandHandler.h" // The serial command handler is defined in here.
CommandHandler<> SerialCommandHandler;
ExponentialFilter<long> ADCFilter(25, 0);
const int MotorPin = 3;
const int HeatPin = 11;
int sensorValue = 0;
int temperature = 0;
double f1 = 0;
const int AnalogInput = A3;
TimePlot MyPlot("Temp");
TimePlot MyPlot2("Out");
//Tuning parameters
float kp = 0;
float ki = 1;
float kd = 0;
double Setpoint, Input, Output;
PID myPID (&Input, &Output, &Setpoint, kp, ki, kd, DIRECT);
const int sampleRate = 100;
void setup() { // initialize serial communication with computer:
Serial.begin(9600);
Setpoint = 28;
SerialCommandHandler.AddVariable(F("SetSetpoint"), Setpoint);
SerialCommandHandler.AddVariable(F("SetP"), kp);
SerialCommandHandler.AddVariable(F("SetI"), ki);
SerialCommandHandler.AddVariable(F("SetD"), kd);
pinMode (HeatPin, OUTPUT); // for heater 13
pinMode (MotorPin, OUTPUT); // for pump
myPID.SetOutputLimits(0, 255);
myPID.SetMode(AUTOMATIC);
myPID.SetSampleTime(sampleRate);
}
void tempRead() {
sensorValue = analogRead(AnalogInput);
ADCFilter.Filter(sensorValue);
temperature=map(ADCFilter.Current(),205,1023,-500,500);
f1 = temperature; // Float conversion
Input = f1/10.0; // dividing by 10
}
void loop() {
SerialCommandHandler.Process();
MyPlot.SendData("Setpoint", Setpoint);
analogWrite (MotorPin, 165); // for pump
tempRead();
MyPlot.SendData("Temperature", Input);
if ( myPID.Compute()){
analogWrite(HeatPin, Output);
}
MyPlot2.SendData("Output", Output);
delay(1); // delay in between reads for stability
}
As mentioned by @jremington, Will it be due to exponential filter and map function? If we don't use the map function, how can we get the temperature readings from analog values? I've used filter because the signal is very noisy and fluctuating.
What could be the cause of this fluctuation in temperature readings in PID code?
If we don't use the map function, how can we get the temperature readings from analog values?
You multiply by the appropriate scale factor and possibly, add an offset. Post a link to the temperature sensor data sheet or web page.
I would not describe the temperature readings shown in the "Meg PID ki=1" image as noisy. The fluctuations are clearly systematic, and are either real, or they are induced by a systematic source of error. If they were obtained while the PID was running, then the PID controller was oscillating, as expected if not properly tuned.
In any case, it is extremely difficult to maintain the temperature of an object to better than 1 degree, and there is rarely any need to do so. Most sensors have measurement errors that are as large, or larger than that.
I have tried changing the sample rate from 100 to 1000. I have also modified the code as you mentioned. However, the temperature readings still fluctuates very much.
Please find the attached pics of temperature readings using PID code and separate standalone code.
...
What could be the cause of this fluctuation in temperature readings in PID code?
Thanks a ton for your help!
lets look at the myPID,Compute code
bool PID::Compute()
{
if(!inAuto) return false;
unsigned long now = millis();
unsigned long timeChange = (now - lastTime);
if(timeChange>=SampleTime) // <<<<<<<<<<<<<<<<<<< Blink Without Delay using SampleTime we set
{
/*Compute all the working error variables*/
double input = *myInput;
double error = *mySetpoint - input;
ITerm+= (ki * error);
if(ITerm > outMax) ITerm= outMax;
else if(ITerm < outMin) ITerm= outMin;
double dInput = (input - lastInput);
/*Compute PID Output*/
double output = kp * error + ITerm- kd * dInput;
if(output > outMax) output = outMax;
else if(output < outMin) output = outMin;
*myOutput = output;
/*Remember some variables for next time*/
lastInput = input;
lastTime = now;
return true;
}
else return false;
}
Note that the blink without delay when set to 1000 the PID only triggers once a second and has no affect otherwise!!!
So the Sensor noise is coming from else where.
Another test would be to simply set the PID to manual
if(!inAuto) return false; // << First line in the compute code
set the following line to MANUAL
myPID.SetMode(AUTOMATIC);
change to
myPID.SetMode(MANUAL);
after eliminating the PID calculation did that stop the problem? Does your readings still fluctuate ?
I have tried changing the PID AUTO to MANUAL, but no difference in temperature readings. Please find the attached pic of temperature readings, while the actual temperature is 22 degC.
I have removed map function and used scale factor, there was no positives from there too.
I've no idea why the temperature reading gets unstable with PID.
Really frustrated. Is there any alternatives that we can try to get a stable temperature input for PID implementation in Arduino?
Thanks a lot.
void loop() {
static unsigned long _ATimer;
tempRead();
if ((millis() - _ATimer) >= (sampleRate)) {
_ATimer = millis();
if(myPID.Compute()){
analogWrite(HeatPin, Output);
}
SerialCommandHandler.Process();
MyPlot.SendData("Setpoint", Setpoint);
analogWrite (MotorPin, 165); // for pump
MyPlot.SendData("Temperature", Input);
MyPlot2.SendData("Output", Output);
// delay(100); // delay in between reads for stability
}
}
Changed the loop function to process in the following sequence:
Read temperature sensor rapidly
After time delay with same period as PID, RUN PID compute. If PID is calculated set output
Everything else...
Hi,
Have you checked if the problem is still present if you disconnect the heater load?
Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?
In particular how you power the controller and the heater.
static unsigned long _ATimer;
tempRead();
if ((millis() - _ATimer) >= (sampleRate)) {
_ATimer = millis();
if(myPID.Compute()){
analogWrite(HeatPin, Output);
}
SerialCommandHandler.Process();
MyPlot.SendData("Setpoint", Setpoint);
analogWrite (MotorPin, 165); // for pump
MyPlot.SendData("Temperature", Input);
MyPlot2.SendData("Output", Output);
// delay(100); // delay in between reads for stability
}
}
Changed the loop function to process in the following sequence:
Read temperature sensor rapidly
After time delay with same period as PID, RUN PID compute. If PID is calculated set output
Everything else...
Try this sequence
Z
I've tried the above code, but unfortunately it didn't work too. Please find the attached temperature readings. However, the temperature readings are slower than before because of the loop.
TomGeorge:
Hi,
Have you checked if the problem is still present if you disconnect the heater load?
Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?
In particular how you power the controller and the heater.
Thanks.. Tom..
Hi Tom,
I have tried by just disconnecting heater load, there was no difference with that. Please find the attached pic of my circuit. I am using Kanthal wire for heater, powered using DC supply up to 24V and controlled using MOSFET.
Thank you.
The horizontal scale is just time in hh:mm. The pink line represents the actual temperature, while the red represents the setpoint.
I am trying to implement a temperature control system without a tank. I've got wrapped around heater on the tubes and water goes through inside. The heater is in perfect condition for now. The only problem is that temperature readings is fluctuates in PID code.
I've tried the above code, but unfortunately it didn't work too. Please find the attached temperature readings. However, the temperature readings are slower than before because of the loop.
The altered code I just gave you took hundreds of readings between each triggering of the pid loop the last reading is as random as you can get. I'm now confident your fluctuating reading is coming from an outside influence like interference from the motor coils inducing noise onto your temp sensor leads. Our the power supply isn't stable
Please try the code with the pumps powered off and the heater powered off.
Also provide the test code for the standalone sensor that doesn't have the problems.
Thanks
Z
Kp is just proportional so starting with Kp will only cause you problems when working with temperature control. You need to find a good Ki first. Ki is intended to be slow and as designed it should stage on the heat source enough to find setpoint. Adding a smaller Kp after finding setpoint will allow you to fine tune both values before adding Kd.
Because Ki is always adding a portion of the error onto the output value, it has the tenancy to "wind up" and overshoot before the error becomes negative and starts unwinding. You want Ki to find the ideal output for setpoint first so Kp doesn't have to work hard.
Z
I have turned off the motor completely even from the code. Then the temperature readings were quite stable as similar to standalone code. Finally, got to know about the problem. Thanks a lot for guiding me.
Is it because I've connected all ground (Power, Heater, Motor, and Temperature sensor) to common ground? Or Should I connect to separate ground for temperature sensor?
Hi,
You should try and connect the gnds in a star fashion, so all grnds radiate from the same point, however the sensor gnd should connect directly to the controller gnd.
Keep the sensor wiring away from other wiring.
I have connected separate ground of temperature sensor directly to micro-controller. I'm using strip board for this circuit and all other grounds were soldered on the last line vertically.
However, the results get fluctuation when I turn on the motor. Is there any possible way to include motor along with heater and temperature sensor?
What is the size of the pipe? The length of pipe wrapped with heater? Is the heater flat like tape? What is the power of the heater, Watts per meter of pipe? Is water flowing when you make the graph? If so what is the flow rate, liters per minute? What type of temperature sensor and where is it located in relation to the heater? What is the incoming water temperature?
EDIT: And what kind on pipe, copper, steel, plastic?