Arduino PID soldering station with zero crossing detector

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
36
I build on bread board the attached schematic and I uploaded the attached code to an Arduino Uno.
I tested each part of the schematic (the pot, the OpAmp, the zero crossing detector, the optotriac) and they are working.
The problem is that when I start the circuit I got the attached screenshot from the oscilloscope, and if I rotate the pot, then nothing happens. Also, the heating of the soldering iron does not stop.
What should I do ? I tried different types of modifications in code, for example making some variables from int to long or from long to int, reading in the zero crossing function the value of the pot.

C:
#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 pot_pin = A1;

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 propagation 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(pot_pin);
  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);
}
 

Attachments

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
36
Hello, I have also checked with the attached code, but as my soldering iron is heating up, the controller is not reducing the power. It is increasing it. Where am I wrong ?
Code:
#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()
{
  noInterrupts();
  test_PID.Compute();
  interrupts();
//  Serial.print("Input Value: ");
//  Serial.println(input);
//  Serial.print("Output Value: ");
//  Serial.println(output);
  //delay(1000);
}
 
Last edited:

trebla

Joined Jun 29, 2019
14
Your interrupt source seems to set from D0 pin.
Recommended usage by Arduino reference :
attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)
 

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
36
I verified again, using this code (I re-wrote my code), and I've got the attached screenshot. If I rotate the pot, then nothing happens.

Code:
#include <PID_v1.h>

int AC_LOAD = 1;// Output to Opto Triac pin
double set_value = 0;
double input = 0;
double output = 0;

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

void setup()
{
  aaa_PID.SetOutputLimits(0, 128);
  aaa_PID.SetMode(AUTOMATIC);
  pinMode(AC_LOAD, OUTPUT);// Set AC Load pin as output
  attachInterrupt(digitalPinToInterrupt(2), zero_crosss_int, RISING);  // Choose the zero cross interrupt # from the table above
}

//the interrupt function must take no parameters and return nothing
void zero_crosss_int()  //function to be fired at the zero crossing to dim the light
{
  // Firing angle calculation : 1 full 50Hz wave =1/50=20ms
  // Every zerocrossing thus: (50Hz)-> 10ms (1/2 Cycle)
  // For 60Hz => 8.33ms (10.000/120)
  // 10ms=10000us
  // (10000us - 10us) / 128 = 75 (Approx) For 60Hz =>65
  int dimtime = (75*output);    // For 60Hz =>65  
  delayMicroseconds(dimtime);    // Wait till firing the TRIAC
  digitalWrite(AC_LOAD, HIGH);   // Fire the TRIAC
  delayMicroseconds(10);         // triac On propogation delay (for 60Hz use 8.33)
  digitalWrite(AC_LOAD, LOW);    // No longer trigger the TRIAC (the next zero crossing will swith it off) TRIAC
}

void loop()  {
  int set_point = analogRead(A1);
  set_point = map(set_point, 0, 1023, 150, 400);
  int in = analogRead(A0);
  input = map(in, 0, 550, 25, 400);
  set_value = set_point;
  aaa_PID.Compute();
       
}
 

Attachments

trebla

Joined Jun 29, 2019
14
I do not have this PID libray but you can test your software block- by -block. For example try temporarily eliminate PID calculation and by giving some fixed output variable value, track with scope what happens in the D1 pin. Or with PID routine and Serial enabled, send output variable value to terminal. With dual input scope is good idea track D1 output and D2 input simultaneously to see what happens in HW.
 

djsfantasi

Joined Apr 11, 2010
6,427
I verified again, using this code (I re-wrote my code), and I've got the attached screenshot. If I rotate the pot, then nothing happens.

Code:
#include <PID_v1.h>

int AC_LOAD = 1;// Output to Opto Triac pin
double set_value = 0;
double input = 0;
double output = 0;

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

void setup()
{
  aaa_PID.SetOutputLimits(0, 128);
  aaa_PID.SetMode(AUTOMATIC);
  pinMode(AC_LOAD, OUTPUT);// Set AC Load pin as output
  attachInterrupt(digitalPinToInterrupt(2), zero_crosss_int, RISING);  // Choose the zero cross interrupt # from the table above
}

//the interrupt function must take no parameters and return nothing
void zero_crosss_int()  //function to be fired at the zero crossing to dim the light
{
  // Firing angle calculation : 1 full 50Hz wave =1/50=20ms
  // Every zerocrossing thus: (50Hz)-> 10ms (1/2 Cycle)
  // For 60Hz => 8.33ms (10.000/120)
  // 10ms=10000us
  // (10000us - 10us) / 128 = 75 (Approx) For 60Hz =>65
  int dimtime = (75*output);    // For 60Hz =>65
  delayMicroseconds(dimtime);    // Wait till firing the TRIAC
  digitalWrite(AC_LOAD, HIGH);   // Fire the TRIAC
  delayMicroseconds(10);         // triac On propogation delay (for 60Hz use 8.33)
  digitalWrite(AC_LOAD, LOW);    // No longer trigger the TRIAC (the next zero crossing will swith it off) TRIAC
}

void loop()  {
  int set_point = analogRead(A1);
  set_point = map(set_point, 0, 1023, 150, 400);
  int in = analogRead(A0);
  input = map(in, 0, 550, 25, 400);
  set_value = set_point;
  aaa_PID.Compute();
     
}
When in an interrupt routine, the delay() and delayMicroseconds() functions do not work. Look up the attachInterrupt() documentation for an explanation.

Imagine what your ISR does without any delays. Likely what you’re seeing.

In this case, define a global boolean flag (Arduino) variable in the ISR. That’s it. Then in your main loop, if the flag is set, execute your code with delays.
 

trebla

Joined Jun 29, 2019
14
If you turn the pot, you must see triac control pulse moving compared to ZC pulse. If not, try to eliminate pot reading routine from code and try set_value variable with different values.

EDIT: Of course the ISR routine with delays is not good
 

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
36
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 instead of pin D8 I used pin D2 as zero cross input and for optotriac output I used pin D5.

C:
//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
 

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
36
No, I have the thermocouple amplifier connected between the thermocouple and the A0 pin.
Please find attached the updated schematic. I have also tried to read directly from pin 1 of LM358, but the problem persists.
I checked individually each part of the schematic, and it seems that they are working correctly.
 

Attachments

trebla

Joined Jun 29, 2019
14
In your code is triac control output set to 5 but in schematic i see it at D4.
If you measure temperature sensor voltage at A0, what is the readings range?
And you can test variables current values on your terminal with Serial.print(variable_name). It's common debugging practice for Arduino IDE.
 

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
36
There is a mistake in the schematic. In real circuit, the output is connected to pin D5.
The measure range of A0 is from about 0V to about 2V when the soldering iron is hot.
I found that the event when the temperature starts rising happens when pid error is equal to set point, which gives 0 delay time.
delayMicroseconds(maximum_firing_delay - PID_value);
 

trebla

Joined Jun 29, 2019
14
PID algorihtm works that way, if error is big, it accelerates change and if error decreases, it breaks changes. Big P can make constant oscillations, big I can make big overshoots and big D can break down too much.
 

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
36
Yes, but when it needs to decrease the temperature, it increases it. And this increase of temperature starts only when pid error is equal to pid set point and this enters into a infinite cycle, which does not stop, and the soldering iron temperature is rising without ending.
 

MrChips

Joined Oct 2, 2009
21,126
Your code is too complex for me to follow.
Here is how I would approach the problem.

1) Begin with a simple ON-OFF temperature controller. Measure its performance.
2) Add PWM to control the ON power. Apply P parameter to modify the power based on the temperature error.
3) Accumulate the integral of the error and apply I parameter.
 

trebla

Joined Jun 29, 2019
14
In your ISR routine you check D1 interrupt instead of D2:
if(PIND & B00000010)

If you want use Atmega 328 registers directly in your code then, please, check out the External interrupts and Alternate Functions of PORTD or PORTB from Atmega 328 MCU datasheet. On Arduino board, D2 pin corresponds to PD2 pin of Atmega 328 which is in binary b00000100.

Imho, for beginners is easier to use attachInterrupt() function, but if you want learn more about MCU-s, the registers approach is better :)
 

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
36
@trebla Same thing happens... now the maximum voltage on the TC is 16mV, and when I decrease the temp from the pot, to minimum, when the voltage on the TC reach 14mV, on the scope appears suddenly the full sine wave, instead of the Chopped sine wave.
I have also modified the interrupt, but the problem is still present in the circuit and on the oscilloscope screen.
And I already said, the sudden heating up occurs only when PID_ERROR is equal to SET_POINT and when PID_OUTPUT is equal to 7400.
 

Attachments

Last edited:

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
36
This is what I have on the heater of the soldering iron when it suddenly starts to heat up, instead of cool down: DS0169.jpg
I think I should have a chopped sine wave because the soldering iron should cool down ...
 
Top