How to make temperature rate increase and decrease with time for a PID temperature control for heater ( AC load )

Hello everyone,

I am working on a PID temperature control that increases and decreases temperature rate slowly within the time frame. The code below is based on electronoobs pid temperature control that uses zero cross method to control temperature rate through a triac, that's not the problem here but here is the link http://electronoobs.com/eng_arduino_tut39.php so you can get the general it and see the schematic. Here is what the code below is about, the user enters setpoint of temperature with a keypad and then can change it with push buttons +1 or -1 degrees. The setpoint is what the heater should be, and with the thermocouple attached to it the arduino will get the feedback through max6675 and compare the two values and the PID will fix the error with it values if there is any.

I want to set time with keypad (30 sec , 1 min , 1.30 sec, 2min) and then set temperature rate (1 degree , 2 degrees, etc) and temperature will increase or decrease following the degree per time rate until it reaches it setpoint of temperature.

Can you help me in code form and not just some vague phrases it would be much appreciated. I looked all over the internet non stop for weeks without anything that resembles to what I am looking for.

#include <SPI.h> 
#include <Keypad.h>
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include "max6675.h"


LiquidCrystal_I2C lcd(0x27,16,2);

//Inputs and outputs
int firing_pin = 3;
int increase_pin = 11;
int decrease_pin = 12;
int zero_cross = 8;
int thermoDO = 4; // so
int thermoCS = 5; //
int thermoCLK = 6; // sck

MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);

const byte rows = 4; /* four rows */
const byte columns = 4; /* four columns */
/* define the symbols on the buttons of the keypads */
char hexaKeys[rows][columns] = {
  {'0','1','2','3'},
  {'4','5','6','7'},
  {'8','9','A','B'},
  {'C','D','E','F'}
};

byte row_pins[rows] = {26, 27, 28, 29}; /* connect to the row pinouts of the keypad */
byte col_pins[columns] = {22, 23, 24, 25}; /* connect to the column pinouts of the keypad */

/* initialize an instance of class Keypad */

Keypad keypad_key = Keypad( makeKeymap(hexaKeys), row_pins, col_pins, rows, columns); 



//Keypad variables
char degkey[3]; // degree we enter with key
int i=0;
char key_pressed=0;

//Variables
int last_CH1_state = 0;
bool zero_cross_detected = false;
int firing_delay = 7400;
int maximum_firing_delay = 7400;

unsigned long previousMillis = 0; 
unsigned long currentMillis = 0;
int temp_read_Delay = 500;
int real_temperature = 0;
int setpoint = 0;
bool pressed_1 = false;
bool pressed_2 = false;

//PID variables
float PID_error = 0;
float previous_error = 0;
float elapsedTime, Time, timePrev;
int PID_value = 0;

//PID constants
int kp = 203;   int ki= 7.2;   int kd = 1.04;
int PID_p = 0;    int PID_i = 0;    int PID_d = 0;

void setup() {
    lcd.init();
    lcd.backlight();
    
  pinMode (firing_pin,OUTPUT); 
  pinMode (zero_cross,INPUT); 
  pinMode (increase_pin,INPUT); 
  pinMode (decrease_pin,INPUT);   
  PCICR |= (1 << PCIE0);    //enable PCMSK0 scan                                                 
  PCMSK0 |= (1 << PCINT0);  //Set pin D8 (zero cross input) trigger an interrupt on state change.
  PCMSK0 |= (1 << PCINT3);  //Set pin D11 (increase button) trigger an interrupt on state change.
  PCMSK0 |= (1 << PCINT4);  //Set pin D12 (decrease button) trigger an interrupt on state change.


} 

void loop() {
  
  key_pressed = keypad_key.getKey();

   if(key_pressed=='#')

   setpoint==setpoint; // # will setpoint to initial 0

  if (key_pressed)

  {

    degkey[i++]=key_pressed;

    lcd.print(key_pressed);

      }
      
    if(i==3)

  {

    delay(500);

    for(int j=0;j<3;j++)

    setpoint==degkey; // setpoint = the number we entered thats below 3 digits 
    
    lcd.clear();

      lcd.setCursor(0, 0); 

      lcd.print("Set Temperature");

      lcd.setCursor(0, 1);
      
      lcd.print(setpoint);

      delay(5000);

   lcd.clear();
   
  }
    
  currentMillis = millis();           //Save the value of time before the loop
   /*  We create this if so we will read the temperature and change values each "temp_read_Delay"
    *  value. Change that value above iv you want. The MAX6675 read is slow. Tha will affect the
    *  PID control. I've tried reading the temp each 100ms but it didn't work. With 500ms worked ok.*/
  if(currentMillis - previousMillis >= temp_read_Delay){
    previousMillis += temp_read_Delay;              //Increase the previous time for next loop
    real_temperature = thermocouple.readCelsius();  //get the real temperature in Celsius degrees

    PID_error = setpoint - real_temperature;        //Calculate the pid ERROR
    
    if(PID_error > 30)                              //integral constant will only affect errors below 30ºC             
    {PID_i = 0;}
    
    PID_p = kp * PID_error;                         //Calculate the P value
    PID_i = PID_i + (ki * PID_error);               //Calculate the I value
    timePrev = Time;                    // the previous time is stored before the actual time read
    Time = millis();                    // actual time read
    elapsedTime = (Time - timePrev) / 1000;   
    PID_d = kd*((PID_error - previous_error)/elapsedTime);  //Calculate the D value
    PID_value = PID_p + PID_i + PID_d;                      //Calculate total PID value

    //We define firing delay range between 0 and 7400. Read above why 7400!!!!!!!
    if(PID_value < 0)
    {      PID_value = 0;       }
    if(PID_value > 7400)
    {      PID_value = 7400;    }
    //Printe the values on the LCD
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Set: ");
    lcd.setCursor(5,0);
    lcd.print(setpoint);
    lcd.setCursor(0,1);
    lcd.print("Real temp: ");
    lcd.setCursor(11,1);
    lcd.print(real_temperature);
    previous_error = PID_error; //Remember to store the previous error.
  }

  //If the zero cross interruption was detected we create the 100us firing pulse  
  if (zero_cross_detected)     
    {
      delayMicroseconds(maximum_firing_delay - PID_value); //This delay controls the power
      digitalWrite(firing_pin,HIGH);
      delayMicroseconds(100);
      digitalWrite(firing_pin,LOW);
      zero_cross_detected = false;
    } 
}
//End of void loop
// |
// |
// |
// v
//See the interruption vector


//This is the interruption routine (pind D8(zero cross), D11(increase) and D12(decrease))
//----------------------------------------------

ISR(PCINT0_vect){
  ///////////////////////////////////////Input from optocoupler
  if(PINB & B00000001){            //We make an AND with the state register, We verify if pin D8 is HIGH???
    if(last_CH1_state == 0){       //If the last state was 0, then we have a state change...
      zero_cross_detected = true;  //We have detected a state change! We need both falling and rising edges
    }
  }
  else if(last_CH1_state == 1){    //If pin 8 is LOW and the last state was HIGH then we have a state change      
    zero_cross_detected = true;    //We haev detected a state change!  We need both falling and rising edges.
    last_CH1_state = 0;            //Store the current state into the last state for the next loop
    }

    if(PINB & B00001000){          //We make an AND with the state register, We verify if pin D11 is HIGH???
      if (!pressed_1)
      {
        setpoint = setpoint + 1;   //Increase the temperature by 5. Change this with your value if you want.
        delay(20);
        pressed_1 = true;
      }
    }
    else if (pressed_1)
    {
      pressed_1 = false;
    }

    if(PINB & B00010000){          //We make an AND with the state register, We verify if pin D12 is HIGH???
      if (!pressed_2)
      {
        setpoint = setpoint - 1;   //Decrease the temperature by 5. Change this with your value if you want.
        delay(20);
        pressed_2 = true;
      }
    }
    else if (pressed_2)
    {
      pressed_2 = false;
    }
  
}

A PID is not helpful in temperature control because of the long time lags. Use either a more sophisticated algorithm with more temperature sensors or revert to a two-state controller with hysteresis.

The long time is not a problem for me because I am already working on a system that increases or decreases the temperature slowly. Do you have an idea on how to control temperature rate with time ?

See #2. Have temperature sensors at the heater and one or more places of the cab.

A good and rather typical example of Arduino PID temperature control is a reflow oven for soldering PCBs. The protocol features a timed ramp up, hold, ramp down, etc.

A few moments spent with your favorite search engine will find a number of DIY projects/tutorials, usually starting with a toaster oven. Try "arduino diy reflow oven"

Pure lag in PID for temperature control is beneficial (easy to control). The response of the system or time constant (tau) can be days or longer. What hurts PID control is to much dead time. As dead time increases, PID controllability decreases. If dead time >= 2 tau, then PID control is useless.

The PID controller will control the temperature rate with time appropriately once tuned.
Have you tried using a PID library?
For PID control, have a look at QuickPID
For autotuning, have a look at sTune and examples using the MAX6675

With both, its possible to automatically tune the PID on-the-fly (see the plot below). Example

Example plot showing optimum output SSR control, which has the effect of reducing overshoot and maintaining tighter regulation at the setpoint:

image
Here, you can see the initial half cycle dropout in the output just as the input crosses above the setpoint. Then, the output gradually, but not completely, enters a half cycle oscillatory action. The hardware used was an SSR controlling a 140W, 220℃ PTC heater with temperature setpoint at only 80℃. I had the outputSpan set to 1000 and was plotting every 3rd sample, so each 100 on the scale is 300 sec (5 min). Tuning rule was set to ZN_PID.

2 Likes

I read both examples in QuickPID of interrupt timer (timerone.h) and software timer(ticker.h) and got the idea tho I got a bit lost when I read the functionalARM example in ticker library. I am going to try to work on multiple time settings with multiple degrees with timerone.h library and post it here. Do you think I should ditch the triac and use a SSR knowing what I am trying to do. I already thought about it. An SSR seems easier to work with and wire, no complications of zero crossing and multiple connections like a triac. If yes, which one should I use. I have a 250V ; 2 A SSR , can that withstand up to 500 degrees celsius ?

No. Mount it in a colder place.

1 Like

Do you mean a heat sinc, can you clarify more please?

Check your components for acceptable environment temperatures. Typical upper limit is 125*C, recommended 85°C.

1 Like

Hey guys a question, if I am using an SSR (with zero crossing) to control the heating element(AC load) of 50 Hz with PWM. How much do I need to set the frequency of the PWM in the code to keep full control of the cycle. using this command TCCR2B = TCCR2B & B11111000 | B00000111;.

An AC SSR is not compatible with Arduino hardware PWM. The frequency is far too high and can't be configured to have a workable low frequency. For this, PID control is done using use "time proportioning control" which is basically sofware PWM over a large window of time (1000 to 5000 ms). Most PID libraries use a "Relay" example to demonstrate this. The example I provided earlier used a 1000 ms window, but it can be configured to suit user preference.

1 Like

Got it. I worked on the ssr ramping up heat and then the pid intervenes when it reaches the set degree , so now I am going to use pid at the ramping up phase. Answer hits the spot as usual, thank you.

Just wondering what the actual AC voltage is for the heater, and what is the power rating for the heater (you may need a larger SSR). What is the heater and max6675 sensor connected to?

The heater is hand made, its a heater resistance (22 ohms) around cylindrical clay, thermocouple connected to max6675 is connected directly inside the heater with a long needle. The info is not provided to me since its hand made but I am pretty sure the voltage is 220 V since the outdated controller that they used supported a maximum voltage of 220V, It also uses a 16 A triac. So I am going to purchase this model, I checked and 40Da in china is 16A, they say its 40 A but actually there is a 16 A triac inside.
download

The 2,200W heater load will draw 10A at 220VAC. They specify heat-sinks in their datasheet ... looks like you'll need the HS-100H.

1 Like

Already planning on getting one, thank you for recommending a model.

Hello guys, I am faced with a problem. The timer one library stores the time in microseconds, so if I am going to use an unsigned long and in 32 bits it can only be at a maximum of 72 minutes. I want it to be able to store more minutes than that since I want the state to change every several hours. Is there a solution?

This part of your code doesn't have an overflow issue as long as its checked more frequently than 72 minutes ...

  if(currentMillis - previousMillis >= temp_read_Delay){
    previousMillis += temp_read_Delay;              //Increase the previous time for next loop

I want to set time to interrupt the set temperature with timerone, how can I have more than 72 min. I am talking about hour margins here (500 min per example).