Heating Element with PWM and PID control

Hi all,

I know my way around basic coding and electronics stuff but my current project is getting a bit too complex for me. Before doing something stupid it would be great to get some feedback on my problem.

The basic need is to heat a liquid (about 200ml) to a certain temperature with a precision as close to ±0,1° as possible.
I included the following PID Library but am having troubles with the wiring/ schematics.

My idea is to use a Mosfet (IRLZ34N) and pwm to control the voltage from a desktop power supply to the heating element.
Would it be smarter to use it as a relay (on/off) instead?

Could anybody please have a look at the wiring if I am missing something?
Any help is highly appreciated!

I have also attached the current code:

#include <AutoPID.h>
#include <DallasTemperature.h>
#include <OneWire.h>

#define TEMP_PROBE_PIN 13
#define Heater_PIN A5
#define TEMP_READ_DELAY 800 //can only read digital temp sensor every ~750ms

//pid settings and gains
#define OUTPUT_MIN 0
#define OUTPUT_MAX 255
#define KP 50
#define KI 0
#define KD 0

//Dallas Tempsensor
#define TEMPERATURE_PRECISION 12


unsigned long current = 0;
unsigned long last = 0;
int mode;

double temperature, setPoint = 75, outputVal;

OneWire oneWire(TEMP_PROBE_PIN);
DallasTemperature temperatureSensors(&oneWire);

//input/output variables passed by reference, so they are updated automatically
AutoPID myPID(&temperature, &setPoint, &outputVal, OUTPUT_MIN, OUTPUT_MAX, KP, KI, KD);

unsigned long lastTempUpdate; //tracks clock time of last temp update

//call repeatedly in loop, only updates after a certain time interval
//returns true if update happened
bool updateTemperature() {
  if ((millis() - lastTempUpdate) > TEMP_READ_DELAY) {
    temperature = temperatureSensors.getTempFByIndex(0); //get temp reading
    lastTempUpdate = millis();
    temperatureSensors.requestTemperatures(); //request reading for next time
    return true;
  }
  return false;
}


void setup() {
  pinMode(Heater_PIN,OUTPUT);
  Serial.begin(9600);
  
  temperatureSensors.begin();
  temperatureSensors.requestTemperatures();
  while (!updateTemperature()) {} //wait until temp sensor updated

  //if temperature is more than 4 degrees below or above setpoint, OUTPUT will be set to min or max respectively
  myPID.setBangBang(4);
  //set PID update interval to 4000ms
  myPID.setTimeStep(4000);
  
  last = millis();
}

void loop() {

  updateTemperature();
  myPID.run(); //call every loop, updates automatically at certain time interval
  analogWrite(Heater_PIN, outputVal);
  
  Serial.print ("tmp");
  Serial.println( temperature);
  
  Serial.print ("pwm");
  Serial.println(outputVal);
}

Calling for such high precision, +/- 0.1 degree, You most likely need a PID control.
Also make sure that heat distribution in the fluid is performed well in order to avoid temperature gradients. You also need to measure the temperature in a representative place, not in a hot spot, not in a Cold spot.

The fritzing looks okay with the exception that pin A5 cannot do PWM, it must one of the pins marked with the tilde “~” character (3,5,6,9,10,11) and yes, you absolutely want PWM, not on-off control.

What actual temperature do you want to maintain (don’t want to assume the 75 in the code)? The difference between that setpoint and the surrounding ambient will matter. Are you looking for 0.1 degree absolute accuracy or just repeatability? Unfortunately, there is no chance of that absolute accuracy with that sensor, not sure about repeatability. You’ll certainly need to stir the liquid as well.

Define your needs. is that C or F ?

Temperature control that close will be very difficult. very difficult indeed.

First, Forget D. the process is way too slow for that to be of much use.
Second, you have a thermal mass, heat gain and heat loss.
If you can calculate the heat loss in joules, you can determine the needed heat gain in joules and deliver that. HIGHLY RECOMMENDED

as was mentioned, your heat at the heater will be temperature + some 20(50?) degrees.
your temperature at the skin of the vessel will be temperature less 2 to 3F degrees.
the mixer/stirrer in the unit will add some tiny bit of heat, not enough to account for unless you are going to super-insulate the vessel.

without a stirrer the temperatures will be different due to the convection currents caused by the heating element.

furthermore, for sensors, you should have a sensor that is (as a rule of thumb) 4 to 10 times more sensitive than the process you want to measure. that means, you need to resolve better than 1/4 of 1/10 of one degree-F. the DS18B20 can only resolve a bit over 0.1 degree F so, it is not possible to control a setting to a higher accuracy than the resolution of your sensor. You need a different sensor.
Even if you were to smooth the signal, you would never be under control as you would be chasing averages of mathematical results and not real time process variables.
With a DS18B20, in a TO-92 as you show would have a high response time, the waterproof versions in the small cans are way too slow.

My suggestion is to try to get is to be within 1 degree C. to start.
Your choice to PWM the heater will deliver power in slices. if you can match those slice to the heat loss, match your heater size to the heat loss, then you might get your PWM to a useful 50%
if you are tying to control in the 2% ranges, you have less range of control.

The goal for control is to put each device into a sweet-spot where it can work easily, not on the bleeding edge where it can only work if all the stars line up perfectly.

Thanks you for your feedback!

Railroader:
You also need to measure the temperature in a representative place, not in a hot spot, not in a Cold spot.

Thats's a good point. So far I have placed the sensor in the centre of the tank, but I guess I will have to try which place works best.

WattsThat:
What actual temperature do you want to maintain (don’t want to assume the 75 in the code)? The difference between that setpoint and the surrounding ambient will matter. Are you looking for 0.1 degree absolute accuracy or just repeatability? Unfortunately, there is no chance of that absolute accuracy with that sensor, not sure about repeatability. You’ll certainly need to stir the liquid as well.

I need to maintain a temperature as close to 38°C as possible. I am looking for 0,1°C accuracy but I already realized that might be too much to ask for. I am using a dc motor to stir the liquid.

dave-in-nj:
Define your needs. is that C or F ?

First, Forget D. the process is way too slow for that to be of much use.
Second, you have a thermal mass, heat gain and heat loss.
If you can calculate the heat loss in joules, you can determine the needed heat gain in joules and deliver that. HIGHLY RECOMMENDED

furthermore, for sensors, you should have a sensor that is (as a rule of thumb) 4 to 10 times more sensitive than the process you want to measure. that means, you need to resolve better than 1/4 of 1/10 of one degree-F. the DS18B20 can only resolve a bit over 0.1 degree F so, it is not possible to control a setting to a higher accuracy than the resolution of your sensor. You need a different sensor.
Even if you were to smooth the signal, you would never be under control as you would be chasing averages of mathematical results and not real time process variables.
With a DS18B20, in a TO-92 as you show would have a high response time, the waterproof versions in the small cans are way too slow.

My suggestion is to try to get is to be within 1 degree C. to start.
Your choice to PWM the heater will deliver power in slices. if you can match those slice to the heat loss, match your heater size to the heat loss, then you might get your PWM to a useful 50%
if you are tying to control in the 2% ranges, you have less range of control.

Thanks so much for your help!
The code was just an example to test the system, I want to maintain temperature of 38°C.
By calculating the heat loss, do you mean in general for the system concerning material and surface or a 'live calculation' including surrounding temperature while heating?

I will use a small DC motor without direct contact to tank itself, so I think I can neglect the influence.

You are absolutely right about the accuracy, do you have any recommendation of anode sensor more suitable for the task? The overall time I need to maintain the temperature will be around 30 min and the tank will only be insulated by its material (ABS enclosure).

Could you please elaborate a bit more on matching pwm to the heat loss? I think I get the general idea, but I am not sure how to implement it with the pid control.

I will give an update after I have done a first run.

I am running into some issues while setting the pid.
After reading some tutorials I started out with increasing kp while leaving ki and kd at 0 until I see an oscillation.
What I am wondering about is, that low values kp >100 result in a pwm output (around 50-70) not high enough to actually heat the system properly.
I therefore increased kp to about 800 and actually have received quite good results with a quite constant temp around 37,94° while set point is 38° (see attached diagram).
In all the tutorials the kp value was mostly around 0.01 up to 2 or 3 so I am wondering if I am doing something wrong?
Any input is highly appreciated!

#include <OneWire.h> 
#include <PID_v1.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS 2
#define heaterPin 6
#define enablePin 11
#define in1Pin 10
#define in2Pin 9
#define potPin A2

double Setpoint, Input, Output;

int dirChangeTime = 10000;
boolean Dir = 0;
long Time = 0;
double Kp=800, Ki=0, Kd=0;
int speed = 80; 
double lastIn = 25;

OneWire oneWire(ONE_WIRE_BUS); 
DallasTemperature sensors(&oneWire);
PID myPID(&Input, &Output, &Setpoint,Kp,Ki,Kd, DIRECT);

void setup() {

  Serial.begin(9600);
  pinMode(in1Pin, OUTPUT);
  pinMode(in2Pin, OUTPUT);
  pinMode(enablePin, OUTPUT);
  pinMode(heaterPin, OUTPUT);
  Setpoint = 38;
  sensors.begin(); 

  myPID.SetMode(AUTOMATIC);
}

void loop() {

  if((millis()-Time)>=dirChangeTime){
    Dir = !Dir;
    Time = millis();
    setMotor(speed, Dir);
  }
  setMotor(speed, Dir);
  lastIn = Input;
  
  Input = GetTemp();
    
  myPID.Compute();

  
  analogWrite(heaterPin,Output);
  Serial.print(Input);
  Serial.print(" ");
  Serial.println(Output);
}

double GetTemp(){
  double temp;
  sensors.requestTemperatures();
  temp = (double)sensors.getTempCByIndex(0); 

  if (abs(temp-lastIn)>50)
    return lastIn;
  else
    return temp;
}

void setMotor(int speed, boolean dir)
{
  analogWrite(enablePin, speed);
  digitalWrite(in1Pin, ! dir);
  digitalWrite(in2Pin, dir);
}