Arduino Nano zero crossing detection

Irving

Joined Jan 30, 2016
3,843
Thats the min time between reads, hence my 250mS, but you have to send it 16 clock pulses at min 250nS/clock to read the data in...

Looked at the max6675 library and a read takes ~320uS at 20uS/bit, but that could be sped up if need be.

Basically this should work much better. The max6675 library doesn't disable interrupts but thats not a problem if read done inside the ISR as nothing can interrupt it there.

So an even better ISR is:

Code:
int tempError = false;  // global error flag

void zero() {
  counter++;
  if (counter == 24) {
    digitalWrite(triac, LOW);
   }
  else if (counter >= 25) {
     counter = 0;
     pottemperature = analogRead(potentiometer);
     pottemperature = map(pottemperature, 0, 1023, 150, 400);

    realTemperature = thermocouple.readCelsius();
    temperature = 0.779828 * realTemperature - 10.3427;
    if (tempError || isnan(temperature) || temperature >= 432) { // on error kill power & set global error flag
       digitalWrite(relay, LOW); // turn off power to iron
       tempError = true;  //set error flag. can only be unset outside ISR. Once set no further action taken till unset in main loop.
    }
    else {  //reading valid
       if (temperature <= pottemperature)    { // not sure about = here,
           digitalWrite(triac, HIGH);
        }
     }//if(isnan...
  }//if(counter == 25...
} //zero()
The ISR is the only place the MAX6675 is read.

The main loop is now:

Code:
void loop() {
   if (tempError) {
     // do error stuff, reset tempError and turn on relay as appropriate
   }
   else {
      updateDisplay();
   }
   delay(200); delay between screen updates
} //loop
 
Last edited:

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
132
The code give some errors (a function-definition is not allowed here before ' ' token).
I tried to correct:

C:
void zero() {
  counter++;
  if (counter == 24) {
    digitalWrite(triac, LOW);
  }
  else if (counter == 25) {
    pottemperature = analogRead(potentiometer);
    pottemperature = map(pottemperature, 0, 1023, 150, 400);

    realTemperature = thermocouple.readCelsius();
    temperature = 0.779828 * realTemperature - 10.3427;
  }
  if (isnan(temperature) || temperature >= 432) {
    while (true) {
      digitalWrite(relay, LOW); //
    }
  }
  counter = 0;
  if (temperature <= pottemperature)    {
    digitalWrite(triac, HIGH);
  }
}
Also, should I write a new function called displayErorrs() which will pe called inside the while(true) loop to display on the LCD the error message ?
 
Last edited:

Irving

Joined Jan 30, 2016
3,843
I accidentally posted #21 before finishing it! Go back and review.

You don't need a while( true ) inside the ISR. ISR's should process and exit ASAP. Your error handling is now in the main loop. No display processing is done in the ISR, only readings and turning stuff on/off.
 

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
132
There is a problem... The 0V to 5V transition of the D7 output (blue trace) is not on the rising edge of the D2 input (yellow trace)...

Code:
#include <LiquidCrystal.h>
#include <SPI.h>
#include <Wire.h>
#include <max6675.h>

#define thermoDO 12
#define thermoCS 10
#define thermoCLK 13
#define potentiometer A0
#define zerocrossing 2
#define triac 7
#define relay A1
float temperature, realTemperature;
int pottemperature;
int counter;
int tempError = false;  // global error flag

byte thermometer[8] = //icon for termometer
{
  B00100,
  B01010,
  B01010,
  B01110,
  B01110,
  B11111,
  B11111,
  B01110
};

byte arrow[8] = //icon for arrow
{
  B11000,
  B01100,
  B00110,
  B00011,
  B00011,
  B00110,
  B01100,
  B11000
};

MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);

/*  The circuit:
   LCD RS pin to digital pin 12
   LCD Enable pin to digital pin 11
   LCD D4 pin to digital pin 5
   LCD D5 pin to digital pin 4
   LCD D6 pin to digital pin 3
   LCD D7 pin to digital pin 2
   LCD R/W pin to ground
   LCD VSS pin to ground
   LCD VCC pin to 5V
   10K resistor:
   ends to +5V and ground
   wiper to LCD VO pin (pin 3)
*/

LiquidCrystal lcd(3, 4, 5, 6, 8, 9);

void setup() {
  lcd.createChar(0, thermometer);
  lcd.createChar(1, arrow);
  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("STATIE DE LIPIT");
  delay(1200);
  lcd.clear();
  pinMode(relay, OUTPUT);
  pinMode(potentiometer, INPUT);
  pinMode(zerocrossing, INPUT_PULLUP);
  pinMode(triac, OUTPUT);
  digitalWrite(triac, LOW);
  digitalWrite(relay, HIGH);
  counter = 0;
  realTemperature = thermocouple.readCelsius();
  temperature = 0.779828 * realTemperature - 10.3427;
  updateDisplay();
  attachInterrupt(digitalPinToInterrupt(2), zero, RISING);
}

void loop() {
   if (tempError) {
     displayErrors();
     tempError = false;
   }
   else {
      updateDisplay();
   }
   delay(200); //delay between screen updates
} //loop

void zero() {
  counter++;
  if (counter == 24) {
    digitalWrite(triac, LOW);
   }
  else if (counter >= 25) {
     counter = 0;
     pottemperature = analogRead(potentiometer);
     pottemperature = map(pottemperature, 0, 1023, 150, 400);

    realTemperature = thermocouple.readCelsius();
    temperature = 0.779828 * realTemperature - 10.3427;
    if (tempError || isnan(temperature) || temperature >= 432) { // on error kill power & set global error flag
       digitalWrite(relay, LOW); // turn off power to iron
       tempError = true;  //set error flag. can only be unset outside ISR. Once set no further action taken till unset in main loop.
    }
    else {  //reading valid
       if (temperature <= pottemperature)    { // not sure about = here,
           digitalWrite(triac, HIGH);
        }
     }//if(isnan...
  }//if(counter == 25...
} //zero()

void updateDisplay() {
  pottemperature = analogRead(potentiometer);
  pottemperature = map(pottemperature, 0, 1023, 150, 400);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.write((byte)0);
  lcd.setCursor(2, 0);
  lcd.print((int)pottemperature);
  lcd.setCursor(6, 0);
  lcd.print((char)223); //degree sign
  lcd.setCursor(7, 0);
  lcd.print("C");
  lcd.setCursor(0, 1);
  lcd.write((byte)1);
  if (temperature <= 45) {
    lcd.setCursor(2, 1);
    lcd.print("Lo");
  } else {
    lcd.setCursor(2, 1);
    lcd.print((int)temperature);
  }
  lcd.setCursor(6, 1);
  lcd.print("[");
  lcd.setCursor(7, 1);
  lcd.print((int)realTemperature);
  lcd.setCursor(10, 1);
  lcd.print("]");
  lcd.setCursor(12, 1);
  lcd.print((char)223);
  lcd.setCursor(13, 1);
  lcd.print("C");
}

void displayErrors() {
  while (true) { // here I need an infinite loop. I need to turn off the entire circuit to reset the error
    digitalWrite(relay, LOW); // the relay will disconnect the power to the soldering iron heating element
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.write((byte)0);
    lcd.setCursor(1, 0);
    lcd.write((byte)0);
    lcd.setCursor(5, 0);
    lcd.print("ERROR!");
    lcd.setCursor(14, 0);
    lcd.write((byte)0);
    lcd.setCursor(15, 0);
    lcd.write((byte)0);
  }
}
 

Attachments

Last edited:

Irving

Joined Jan 30, 2016
3,843
I thought that might happen. The delay is in the reading of the temperature as it takes 350uS approx. Can you measure the delay? I reckon its sub-400uS. The trigger point of D2 is about 2.5v

I'm not going to worry about it, the actual AC volts at that point is only 1.1v, its near enough the zero crossing. Remember the rising edge of D2 is still the falling AC Volts, so the zero point is further along. See simulation #1 below.

It would be worth adding a 10k pullup to D2. The internal pullup on the Nano isn't giving the opto enough current and edge is very 'soft'. See simulation #2 below. It will also tighten the timing.

1595164097246.png

1595164490787.png
 

Irving

Joined Jan 30, 2016
3,843
That's so close to the simulation & a much better interrupt signal, pleased with that. How's the big picture looking?
 

Irving

Joined Jan 30, 2016
3,843
That looks good to me. Its reading every 250mS, Heating at about a 50% duty cycle. How does the temperature control look? Basic on/off control is not going to be great compared to a commercial unit with PID control, but that could be added in software later.

This is the code for the MAX6675 library. The lines 71 and 78 can be changed to reduce the # of microseconds, though on a Nano I think the minimum usable is 4. Even so, that will reduce read time to from 330uS to 140uS. you can experiment if you fancy.



Code:
// this library is public domain. enjoy!
// https://learn.adafruit.com/thermocouple/

#include "max6675.h"

/**************************************************************************/
/*!
    @brief  Initialize a MAX6675 sensor
    @param   SCLK The Arduino pin connected to Clock
    @param   CS The Arduino pin connected to Chip Select
    @param   MISO The Arduino pin connected to Data Out
*/
/**************************************************************************/
MAX6675::MAX6675(int8_t SCLK, int8_t CS, int8_t MISO) {
  sclk = SCLK;
  cs = CS;
  miso = MISO;

  // define pin modes
  pinMode(cs, OUTPUT);
  pinMode(sclk, OUTPUT);
  pinMode(miso, INPUT);

  digitalWrite(cs, HIGH);
}

/**************************************************************************/
/*!
    @brief  Read the Celsius temperature
    @returns Temperature in C or NAN on failure!
*/
/**************************************************************************/
float MAX6675::readCelsius(void) {

  uint16_t v;

  digitalWrite(cs, LOW);
  delayMicroseconds(10);

  v = spiread();
  v <<= 8;
  v |= spiread();

  digitalWrite(cs, HIGH);

  if (v & 0x4) {
    // uh oh, no thermocouple attached!
    return NAN;
    // return -100;
  }

  v >>= 3;

  return v * 0.25;
}

/**************************************************************************/
/*!
    @brief  Read the Fahenheit temperature
    @returns Temperature in F or NAN on failure!
*/
/**************************************************************************/
float MAX6675::readFahrenheit(void) { return readCelsius() * 9.0 / 5.0 + 32; }

byte MAX6675::spiread(void) {
  int i;
  byte d = 0;

  for (i = 7; i >= 0; i--) {
    digitalWrite(sclk, LOW);
    delayMicroseconds(10);
    if (digitalRead(miso)) {
      // set the bit to 0 no matter what
      d |= (1 << i);
    }

    digitalWrite(sclk, HIGH);
    delayMicroseconds(10);
  }

  return d;
}
 

Irving

Joined Jan 30, 2016
3,843
I'd say that was pretty good. There's always going to be overshoot on the way up, but 3degC is pretty close. At least it will hold up when in use.
 

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
132
I checked the error part of the soldering station and it seems that when it display a error, then the code enter into the displayErrors() function and remains there, in the while(true) loop, and on the display it print as shown in the attached photo.
To correct this problem, I think that I should add a delay(500) into the while(true) loop, which will not affect the rest of functionality of the code. Am I right ?
 

Attachments

Irving

Joined Jan 30, 2016
3,843
Why do you need a while(true) loop? You already have a loop... you just dont do anything in it...

something like:

Code:
void loop() {
    if(!tempError) {   // if no error
        updatedisplay();
    } else // do something on error
    {
      // eg show the word error on the display
         displayErrors();
    }
   delay(250); //controls loop timing
} //loop
You don't need an individual delay in each display routine, the single one at the bottom of the loop suffices.

Alternatively, just set a flag to say you've shown the error

Code:
int shownError = false; //flag to say error shown

void loop() {
    if(!tempError) {   // if no error
        updatedisplay();
    } else // do something on error
    {
      // eg show the word error on the display
       if(!shownError) { // we've not shown error yet, so show it
         displayErrors();
         shownError = true; //set flag so don't show it again
        }
    }
   delay(250); //controls loop timing
} //loop
 

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
132
Basic on/off control is not going to be great compared to a commercial unit with PID control, but that could be added in software later.
How hard it is to add in software the PID control ?
Should I use the PID library ?
The part of the code that I don't manage to write is how to make the connection between the zero cross and the PID algorithm ...
 
Last edited:

Irving

Joined Jan 30, 2016
3,843
PID control in software isn't hard, the most difficult bit is tuning it, but there are way to automate that. There is a PID library, though I've never used it, I've always rolled my own as the equations are pretty simple to code, even with integer math.

The PID algorithm runs on a timed loop, sampling the temperature at a fixed rate and determining the duty cycle (on/off ratio). Since you can't read faster than 4 read/sec that determines your cycle time. More correctly the system will be PI, you probably won't use D.

The zero crossing drives the duty cycle. At each sample point (count=25) the current temp is compared to the wanted temp and the error drives two calculations effectively managing the power needed to counteract loss in the tip and power input ramp gradient to ensure it doesn't overshoot (or does in a controlled damped way), this then sets the duty cycle ie "on-time" for the next measurement period. The temperature measurement isn't used to control the on/off directly as it is now, except in the system error condition of temp >= max absolute temp (your figure of 432). So instead of the block of 3 or 4 250mS "ON" followed by a 1/2 - 1 second gap of "OFF" you see now, you'll have a continuous stream of on/off in each 250mS cycle where it is "ON" for Ton counts (where Ton is 0 -> 24), and "OFF" the remainder.
 

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
132
I managed to study a little and wrote and took from internet a few lines of code in the Arduino IDE.
But I still don't know how to translate the output of the PID calculations to the output pin of the Arduino ?
C:
#include <LiquidCrystal.h>
#include <SPI.h>
#include <Wire.h>
#include <max6675.h>

#define thermoDO 12
#define thermoCS 10
#define thermoCLK 13
#define potentiometer A0
#define zerocrossing 2
#define triac 7
#define relay A1
float temperature, realTemperature;
int pottemperature;
int counter;
int tempError = false;  // global error flag
int shownError = false; //flag to say error shown

//PID constants
double Kp = 2;
double Ki = 5;
double Kd = 1;

//PID variables
unsigned long currentTime, previousTime;
double elapsedTime;
double error;
double lastError;
double input, output, setPoint;
double cumError, rateError;

byte thermometer[8] = //icon for termometer
{
  B00100,
  B01010,
  B01010,
  B01110,
  B01110,
  B11111,
  B11111,
  B01110
};

byte arrow[8] = //icon for arrow
{
  B11000,
  B01100,
  B00110,
  B00011,
  B00011,
  B00110,
  B01100,
  B11000
};

MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);

/*  The circuit:
   LCD RS pin to digital pin 12
   LCD Enable pin to digital pin 11
   LCD D4 pin to digital pin 5
   LCD D5 pin to digital pin 4
   LCD D6 pin to digital pin 3
   LCD D7 pin to digital pin 2
   LCD R/W pin to ground
   LCD VSS pin to ground
   LCD VCC pin to 5V
   10K resistor:
   ends to +5V and ground
   wiper to LCD VO pin (pin 3)
*/

LiquidCrystal lcd(3, 4, 5, 6, 8, 9);

void setup() {
  lcd.createChar(0, thermometer);
  lcd.createChar(1, arrow);
  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("STATIE DE LIPIT");
  output = 0;
  setPoint = 0;
  delay(1200);
  lcd.clear();
  pinMode(relay, OUTPUT);
  pinMode(potentiometer, INPUT);
  pinMode(zerocrossing, INPUT_PULLUP);
  pinMode(triac, OUTPUT);
  digitalWrite(triac, LOW);
  digitalWrite(relay, HIGH);
  realTemperature = thermocouple.readCelsius();
  temperature = 0.779828 * realTemperature - 10.3427;
  updateDisplay();
  attachInterrupt(digitalPinToInterrupt(2), zero, CHANGE);
}

void loop() {
  if (!tempError) {  // if no error
    updateDisplay();
  } else // do something on error
  {
    // eg show the word error on the display
    if (!shownError) { // we've not shown error yet, so show it
      displayErrors();
      shownError = true; //set flag so don't show it again
    }
  }
  delay(250); //controls loop timing
}

void zero() {
  counter++;
  if (counter == 24) {
    digitalWrite(triac, LOW);
  }
  else if (counter >= 25) {
    counter = 0;
    pottemperature = analogRead(potentiometer);
    pottemperature = map(pottemperature, 0, 1023, 150, 400);
    realTemperature = thermocouple.readCelsius();
    temperature = 0.779828 * realTemperature - 10.3427;
    if (tempError || isnan(temperature) || temperature >= 432) { // on error kill power & set global error flag
      digitalWrite(relay, LOW); // turn off power to iron
      tempError = true;  //set error flag. can only be unset outside ISR. Once set no further action taken till unset in main loop.
    }
    else {  //reading valid
      if (temperature <= pottemperature)    { // not sure about = here,
        currentTime = millis();
        elapsedTime = currentTime - previousTime;
        error = abs(pottemperature - temperature);
        cumError += error * elapsedTime;
        rateError = (error - lastError) / elapsedTime;
        output = Kp * error + Ki * cumError + Kd * rateError;
        lastError = error;
        previousTime = currentTime;
      }
    }//if(isnan...
  }//if(counter == 25...
}

void updateDisplay() {
  pottemperature = analogRead(potentiometer);
  pottemperature = map(pottemperature, 0, 1023, 150, 400);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.write((byte)0);
  lcd.setCursor(2, 0);
  lcd.print((int)pottemperature);
  lcd.setCursor(6, 0);
  lcd.print((char)223); //degree sign
  lcd.setCursor(7, 0);
  lcd.print("C");
  lcd.setCursor(0, 1);
  lcd.write((byte)1);
  if (temperature <= 45) {
    lcd.setCursor(2, 1);
    lcd.print("Lo");
  } else {
    lcd.setCursor(2, 1);
    lcd.print((int)temperature);
  }
  lcd.setCursor(6, 1);
  lcd.print("[");
  lcd.setCursor(7, 1);
  lcd.print((int)realTemperature);
  lcd.setCursor(10, 1);
  lcd.print("]");
  lcd.setCursor(12, 1);
  lcd.print((char)223);
  lcd.setCursor(13, 1);
  lcd.print("C");
}

void displayErrors() {
  digitalWrite(relay, LOW); // the relay will disconnect the power to the soldering iron heating element
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.write((byte)0);
  lcd.setCursor(1, 0);
  lcd.write((byte)0);
  lcd.setCursor(5, 0);
  lcd.print("ERROR!");
  lcd.setCursor(14, 0);
  lcd.write((byte)0);
  lcd.setCursor(15, 0);
  lcd.write((byte)0);
}
 

Irving

Joined Jan 30, 2016
3,843
But I still don't know how to translate the output of the PID calculations to the output pin of the Arduino ?
It's a good start!

Maybe something like...

C:
int duty = 0; // variable for duty cycle

void zero() {
  counter++;
  if ((counter == 24) || (counter>duty)) {  //reach max count or duty cycle limit.
    digitalWrite(triac, LOW);
  }
  else if (counter >= 25) {
    counter = 0;
    pottemperature = analogRead(potentiometer);
    pottemperature = map(pottemperature, 0, 1023, 150, 400);
    realTemperature = thermocouple.readCelsius();
    temperature = Int(0.779828 * realTemperature - 10.3427);  // make temperature an integer
    if (tempError || isnan(temperature) || temperature >= 432) { // on error kill power & set global error flag
      digitalWrite(relay, LOW); // turn off power to iron
      tempError = true;  //set error flag. can only be unset outside ISR. Once set no further action taken till unset in main loop.
    }
    else {  //reading valid
      if (temperature <= pottemperature)    { // not sure about = here, well if == then error = 0 so only rateError will have a value[S][/S]
        error = pottemperature - temperature;  // don't think you need abs() here as t cannot be > pt and get here...
        cumError += error * elapsedTime;
        rateError = (error - lastError) / elapsedTime;
        output = Kp * error + Ki * cumError + Kd * rateError;  //output error needs to be mapped to a number between 0 and 24 so...
        duty = map(output, lowError, highError, 0, 24); // lowError is const >= 0, highError is const for fully on.
        if(duty) { digitalWrite(triac, HIGH);  } // if duty > 0, turn on power to iron
        lastError = error;
        previousTime = currentTime;
      }//if (temperature
    }//if (tempError
  }//if(counter == 25...
}//zero
 

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
132
I uploaded the code to the Arduino Nano, but it does not work. The signal on pin 2 is present as usual, but on pin 7 there is no signal. Using the previous code there is signal on pin 7.
What should I do ?
Is there any solution ?
 

Irving

Joined Jan 30, 2016
3,843
Ah, sorry I forgot them!

lowError and highError are constants, should be declared as:

#define lowError 0
#define highError ???

but what value for ???

Basically its the error value that's mapped to full output. Lets say temp = 350 and pot = 360 then error = 10
Set Kp = 2, Ki = Kd = 0;
then output = 10 * 2 = 20, so lets try highError = 20

Need to add a four lines and remove one:
C:
        duty = map(output, lowError, highError, 0, 24); // lowError is const >= 0, highError is const for fully on.
        duty = constrain(duty, 0, 24); // keep duty between 0 and 24 (24 = 96%)
        lastError = error;
        // previousTime = currentTime; can remove this one
      }
      else {
        duty = 0;
      }//if (temperature
 
Top