Arduino PID Controller for Triac

Hello. I drew the attached schematic. The schematic represents a soldering iron controller. The soldering iron will be powered from a 24Vac/100va toroidal transformer.
I am trying to write the code for this project, using the PID_v1.h library.
The code is not finished, it gives an error in the loop() function and I am wondering what I am doing wrong ?

#include <PID_v1.h>

int firing_triac = 1;    // OUTPUT TO CONTROL TRIAC
int zero_in = 0; // INPUT FROM ZERO DETECTION   
int pot = A1; // Potentiometer input
int adc0 = A0; // Termocouple input

unsigned long updaterate = 1200; //Change how fast the display updates. No lower than 500
unsigned long lastupdate;

int temperature = 0;

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Define the aggressive and conservative Tuning Parameters
double aggKp = 4, aggKi = 0.2, aggKd = 1;
double consKp = 1, consKi = 0.05, consKd = 0.25;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, consKp, consKi, consKd, DIRECT);

void setup()
{
  myPID.SetOutputLimits(0, 220);
  myPID.SetMode(AUTOMATIC);
  lastupdate = millis();
  Setpoint = 0;
 
  pinMode(firing_triac, OUTPUT);        // Set the AC Load as output
  attachInterrupt(1, zero_crosss_int, RISING);  // Choose the zero cross interrupt # from the table above
}

void zero_crosss_int()  // function to be fired at the zero crossing to dim the light
{
  Input = 0;
  for(int i=0;i<50;i++)
  Input += analogRead(A0);
  Input /= 50;
  // Firing angle calculation :: 50Hz-> 10ms (1/2 Cycle)
  // (10000us - 10us) / 255 = 40 (Approx)
  int dimtime = (40*Output);     
  delayMicroseconds(dimtime);    // Off cycle
  digitalWrite(firing_triac, HIGH);   // triac firing
  delayMicroseconds(10);         // triac On propogation delay
  digitalWrite(firing_triac, LOW);    // triac Off
}

void loop()
{

  test_PID.Compute();
 
  
  //delay(1000);
}

Hi,
OPs diagram.


Tom... :slight_smile:

Hi,
What is the error.
If it is a compiling error, if you scroll through your code after you get the error, you may find a line highlighted, this is where the error has been detected, it is possibly on that line or previous lines.

Tom... :slight_smile:

Hi,
You start with this;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, consKp, consKi, consKd, DIRECT);

But use this. (This is the highlighted line)

 test_PID.Compute();

This is the error shown.

'test_PID' was not declared in this scope

Tom... :slight_smile:

Now it is compiling the code. Thank you.
The code needs to implement 2 types of PID controller: one with aggressive constants and one with conservative constants. How can I make this ? Or it is already done ?
Also, I saw that in the setup() function it is called the function zero_crosss_int(). Where is the connection between the output of the PID and the output of the Arduino "Firing Pulse" output ?

I need to read the signal from the Thermocouple and from the Potentiometer and also from the zero crossing detector and then to apply the output signal to the optotriac.
Could you please help me ?

#include <PID_v1.h>

int firing_triac = 1;    // OUTPUT TO CONTROL TRIAC
int zero_in = 2; // INPUT FROM ZERO DETECTION   
int pot = A1;
int adc0 = A0;


int temperature = 0;

//Define Variables we'll be connecting to
double Setpoint, Input, Output;
double lastupdate;

//Define the aggressive and conservative Tuning Parameters
double aggKp = 4, aggKi = 0.2, aggKd = 1;
double consKp = 1, consKi = 0.05, consKd = 0.25;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, consKp, consKi, consKd, DIRECT);

void setup()
{
  myPID.SetOutputLimits(0, 220);
  myPID.SetMode(AUTOMATIC);
  lastupdate = millis();
  Setpoint = 0;
  pinMode(firing_triac, OUTPUT);        // Set the AC Load as output
  attachInterrupt(1, zero_crosss_int, RISING);
}

void zero_crosss_int()  // function to be fired at the zero crossing to dim the light
{
  double newSetpoint = analogRead(A1);
  newSetpoint = map(newSetpoint, 0, 1023, 150, 400);
  // Firing angle calculation :: 50Hz-> 10ms (1/2 Cycle)
  // (10000us - 10us) / 255 = 40 (Approx)
  int dimtime = (40*Output);     
  delayMicroseconds(dimtime);    // Off cycle
  digitalWrite(firing_triac, HIGH);   // triac firing
  delayMicroseconds(10);         // triac On propogation delay
  digitalWrite(firing_triac, LOW);    // triac Off
}

void loop()
{
  Input = 0;
  for(int i=0;i<50;i++)
  Input += analogRead(A0);
  Input /= 50;
  double newSetpoint = analogRead(A1);
  newSetpoint = map(newSetpoint, 0, 1023, 150, 400);
  //Display setpoint
  if (abs(newSetpoint - Setpoint) > 3) {
    Setpoint = newSetpoint;
    temperature = newSetpoint;
    lastupdate = millis();
  }
  double gap = abs(Setpoint - Input); //distance away from setpoint

  if (gap < 10)
  { //we're close to setpoint, use conservative tuning parameters
    myPID.SetTunings(consKp, consKi, consKd);
  }
  else
  {
    //we're far from setpoint, use aggressive tuning parameters
    myPID.SetTunings(aggKp, aggKi, aggKd);
  }
  noInterrupts();
  myPID.Compute();
  interrupts();
  //delay(1000);
}

//later I will write the code for displaying the temperature

Hi,
Have you developed this code in stages or just written it all at once and are trying to debug it?

Have you any code that JUST reads the thermocouple signal?
Have you any code that JUST reads the potentiometer signal?
Have you any code that JUST reads the zero crossing signal?

What is your thermocouple, does the twin opamp circuit work?

Can you tell us your electronics, programming, arduino, hardware experience?

Thanks.. Tom... :slight_smile:

Hi, I developed the code by using the old code from a soldering station that uses mosfet instead of triac.
I wrote it all at once, using already wrote instructions from the internet sites.

Until now I managed to check the output of the zero crossing detector and I have some pulses on the oscilloscope (please have a look at the attached image).

I don't have any code that reads only parts of the circuit, but I can write the necessary code.

The thermocouple circuit works, it was tested before in another circuit. It reads a K-Type thermocouple.

I don't have much experience, I am a hobbyist, but I am not very very new to programming and electronics. I like to work electronics in my free time.

DS0149.jpg

Hi,
The MOSFET version would have not needed any reference to zero crossing, it probably used PWM output control, now you have to use a timing function for Triac control.

Google
arduino phase angle control

There are plenty of examples that will help.

Tom... :slight_smile:

Hi Tom,
I searched the internet and I manage to write my program.
I successfully tested each part of the schematic.
The problem is that I see on the scope the firing signal for the optocoupler, but when I rotate the potentiometer nothing happens.
Please find attached my program.

#include <PID_v1.h>

int firing_triac = 1;    // OUTPUT TO CONTROL TRIAC
int zero_in = 2; // INPUT FROM ZERO DETECTION   
int pot = A1;   // POTENTIOMETER INPUT
int adc0 = A0; // TERMOCOUPLE INPUT


int temperature = 0;

//Define Variables we'll be connecting to
double Setpoint, Input, Output;
double lastupdate;

//AGGRESSIVE AND CONSERVATIVE VARIABLES FOR PID CONTROLLER
double aggKp = 4, aggKi = 0.2, aggKd = 1;
double consKp = 1, consKi = 0.05, consKd = 0.25;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, consKp, consKi, consKd, DIRECT);

void setup()
{
 
  myPID.SetOutputLimits(0, 220);
  myPID.SetMode(AUTOMATIC);
  lastupdate = millis();
  Setpoint = 0;
  pinMode(firing_triac, OUTPUT); // Set the AC Load as output
  pinMode(zero_in, INPUT);
  digitalWrite(zero_in, HIGH); // pull up
  attachInterrupt(0, zero_crosss_int, RISING);
}

void zero_crosss_int()  // function to be fired at the zero crossing to dim the light
{
  double newSetpoint = analogRead(A1);
  newSetpoint = map(newSetpoint, 0, 1023, 150, 400);
  // Firing angle calculation :: 50Hz-> 10ms (1/2 Cycle)
  // (10000us - 10us) / 255 = 40 (Approx)
  int dimtime = (40*Output);     
  delayMicroseconds(dimtime);    // Off cycle
  digitalWrite(firing_triac, HIGH);   // triac firing
  delayMicroseconds(10);         // triac On propogation delay
  digitalWrite(firing_triac, LOW);    // triac Off
}

void loop()
{
  
  Input = 0;
  for(int i=0;i<50;i++)
  Input += analogRead(A0);
  Input /= 50;
  Input = map(Input, 0, 550, 25, 400);
  temperature = Input;
  double newSetpoint = analogRead(A1);
  newSetpoint = map(newSetpoint, 0, 1023, 150, 400);
  //Display setpoint
  if (abs(newSetpoint - Setpoint) > 3) {
    Setpoint = newSetpoint;
    temperature = newSetpoint;
    lastupdate = millis();
  }
  double gap = abs(Setpoint - Input); //distance away from setpoint

  if (gap < 10)
  { //we're close to setpoint, use conservative tuning parameters
    myPID.SetTunings(consKp, consKi, consKd);
  }
  else
  {
    //we're far from setpoint, use aggressive tuning parameters
    myPID.SetTunings(aggKp, aggKi, aggKd);
  }
  noInterrupts();
  myPID.Compute();
  interrupts();
  //delay(1000);
  
}

DS0150.jpg

Hi,
Can you put the zero crossing signal on the second channel as a timing reference and sync off that?

Tom... :slight_smile:
PS, I'm off to bed, 12:53am here. :slight_smile:

Please find attached.
I also uploaded the updated schematic. The 5V is being supplied from USB. The LM317 will be on the final PCB only.

DS0151.jpg

I modified a little bit the connection of the probe and I got the attached screenshot on the heater of the iron.
But the pot is still not working... and the soldering station is still powering the iron, it is not stopping it.

DS0158.jpg

But the pot is still not working.

You have this code repeated twice. Once in loop() and once in the zero cross interrupt.

double newSetpoint = analogRead(A1);
newSetpoint = map(newSetpoint, 0, 1023, 150, 400);

I don't see why it belongs in the interrupt and it should be removed.

newSetpoint should be typed as an int, not a double.

I remember the PID library expects a double type for all its inputs.

The result of the map() function is a floating point number - so if used in PID that number should be typed double.

The result of the map() function is a floating point number

The Arduino map function returns a long

long map(long x, long in_min, long in_max, long out_min, long out_max) {
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

I modified the code, but the problem persist. I really don't know what is happening there. The soldering iron does not heat up. I am worked all day long, but without any good results. Please help if you can :slight_smile:

#include <PID_v1.h>

int firing_triac = 1;    // OUTPUT TO CONTROL TRIAC
int zero_in = 2; // INPUT FROM ZERO DETECTION   
double value_zero = 0;


int temperature = 0;

//Define Variables we'll be connecting to
double Setpoint, Input, Output;
double lastupdate;
double newSetpointPot;
double newSetpoint; 

double consKp = 1, consKi = 0.05, consKd = 0.25;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, consKp, consKi, consKd, DIRECT);

void setup()
{
 
  myPID.SetOutputLimits(0, 220);
  myPID.SetMode(AUTOMATIC);
  lastupdate = millis();
  Setpoint = 0;
  pinMode(firing_triac, OUTPUT); // Set the AC Load as output
  pinMode(zero_in, INPUT);
  digitalWrite(zero_in, HIGH); // pull up
  attachInterrupt(0, zero_crosss_int, RISING);
}

void zero_crosss_int()  // function to be fired at the zero crossing to dim the light
{
  int dimtime = (40*Output);     
  delayMicroseconds(dimtime);    // Off cycle
  digitalWrite(firing_triac, HIGH);   // triac firing
  delayMicroseconds(10);         // triac On propogation delay
  digitalWrite(firing_triac, LOW);    // triac Off
}

void loop()
{
  
  Input = 0;
  for(int i=0;i<50;i++)
  Input += analogRead(A0);
  Input /= 50;
  Input = map(Input, 0, 550, 25, 400);
  temperature = Input;
  newSetpointPot = analogRead(A1);
  newSetpoint = map(newSetpointPot, 0, 1023, 150, 400);
  //Display setpoint
  if (abs(newSetpoint - Setpoint) > 3) {
    Setpoint = newSetpoint;
    temperature = newSetpoint;
    lastupdate = millis();
  }
  myPID.SetTunings(consKp, consKi, consKd);
  noInterrupts();
  myPID.Compute();
  interrupts();
  //delay(1000);
  
}

//later I will write the code for displaying the temperature

DS0159.jpg

DS0159.jpg

The soldering iron does not heat up

The yellow wave form looks correct for the chopped AC output. If the soldering iron is not heating up, perhaps it is defective?

The soldering iron is OK. I checked it with the code below and it heats up, but the controller does not stop it from heating... I mean it is continuously heating up.

#include <PID_v1.h>

int AC_LOAD = 1;    // Output to Opto Triac pin
double High_Value = 220; // Lamp Fully Off
double Low_Value = 2;    // Lamp Fully On
double set_value = 100;
double input = 0;
double output = 0;

PID test_PID(&input, &output, &set_value, 1, 0.05, 0.25, DIRECT);

void setup()
{
//  Serial.begin(9600);
// 
  input = analogRead(A0);
  input = map(input, 0, 1023, 0, 255);
  test_PID.SetMode(AUTOMATIC);
  test_PID.SetOutputLimits(Low_Value, High_Value);
 
  pinMode(AC_LOAD, OUTPUT);        // Set the AC Load as output
  attachInterrupt(0, zero_crosss_int, RISING);  // Choose the zero cross interrupt # from the table above
}

void zero_crosss_int()  // function to be fired at the zero crossing to dim the light
{
  input = analogRead(A0);
  input = map(input, 0, 1023, 0, 255);
  // Firing angle calculation :: 50Hz-> 10ms (1/2 Cycle)
  // (10000us - 10us) / 255 = 40 (Approx)
  int dimtime = (40*output);     
  delayMicroseconds(dimtime);    // Off cycle
  digitalWrite(AC_LOAD, HIGH);   // triac firing
  delayMicroseconds(10);         // triac On propogation delay
  digitalWrite(AC_LOAD, LOW);    // triac Off
}

void loop()
{
  int set_point = analogRead(A1);
  set_point = map(set_point, 0, 1023, 0, 255);
  set_value = set_point; 
  noInterrupts(); 
  test_PID.Compute();
  interrupts();
//  Serial.print("Input Value: ");
//  Serial.println(input);
//  Serial.print("Output Value: ");
//  Serial.println(output);
  //delay(1000);
}

Hi, I have a new code which seems to work, but only partially.
The problem is, that when I reduce from the potentiometer the temperature starts to go down, for example, I measure 18.4mV on the TC (thermocouple) then I rotate the pot and the temperature starts to go down, and when the TC voltage is about 17mV, the temperature starts to rise fast, up to about 22-23mV, when I unplug the 24V/100VA transformer from the wall socket. When the temperature starts to rise, the wave form on the oscilloscope modifies from less than a half sine wave to a full sine wave.

Can you please tell me where is the problem in my code ?

All other components from the circuit diagram have been tested individually and they seems to work.
Note that I used pin D2 as input from the zero cross detector and pin D5 to output from the PID controller.

//Inputs and outputs
int firing_pin = 5;
//int increase_pin = 11;
//int decrease_pin = 12;
int zero_cross = 2;
//int thermoDO = 9;
//int thermoCS = 10;
//int thermoCLK = 13;

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

//////////////////////////////////////////////////////
int maximum_firing_delay = 7400;
/*Later in the code you will se that the maximum delay after the zero detection
 * is 7400. Why? Well, we know that the 220V AC voltage has a frequency of around 50-60HZ so
 * the period is between 20ms and 16ms, depending on the country. We control the firing
 * delay each half period so each 10ms or 8 ms. To amke sure we wont pass thsoe 10ms, I've made tests
 * and the 7400us or 7.4ms was a good value. Measure your frequency and chande that value later */
//////////////////////////////////////////////////////

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 = 20;   int ki= 5;   int kd = 10;
int PID_p = 0;    int PID_i = 0;    int PID_d = 0;


void setup() {
  //Define the pins
  pinMode (firing_pin,OUTPUT); 
  pinMode (zero_cross,INPUT); 
//  pinMode (increase_pin,INPUT); 
//  pinMode (decrease_pin,INPUT);   
  PCICR |= (1 << PCIE2);    //enable PCMSK0 scan                                                 
  PCMSK2 |= (1 << PCINT18);  //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.    
//  lcd.init();       //Start the LC communication
//  lcd.backlight();  //Turn on backlight for LCD
}


void loop() {    
  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 = analogRead(A0);  //get the real temperature in Celsius degrees
    real_temperature = map(real_temperature, 0, 550, 25, 400);
    setpoint = analogRead(A1);
    setpoint = map(setpoint, 0, 1023, 150, 400);
    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(PCINT2_vect){
  ///////////////////////////////////////Input from optocoupler
  if(PIND & B00000010){            //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 + 5;   //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 - 5;   //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;
//    }

//End of interruption vector for pins on port B: D8-D13