Arduino Nano zero crossing detection

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
132
Hello, I built the circuit from this instructable: https://www.instructables.com/id/DIY-Arduino-Soldering-Station/ . I modified the schematic a bit, and it looks like this:

schematic_ss.png


In the schematic, I connected a MAX6675 module on the SV1 connector and a 1602 LCD on the SV2 connector.

The code that I used is the following:
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;

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);

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(0, zero, RISING);
}

void loop() {
}

void zero() {
  counter++;
  if (counter < 40) {
    if (temperature <= pottemperature) {
      digitalWrite(triac, HIGH);
    }
    else {
      digitalWrite(triac, LOW);
    }
  }

  if (counter == 40) {
    digitalWrite(triac, LOW);
    detachInterrupt(0);
    realTemperature = thermocouple.readCelsius();
    temperature = 0.779828 * realTemperature - 10.3427;
    checkForErrors(temperature);
    updatedisplay();
    counter = 0;
    attachInterrupt(0, zero, RISING);
  }
}

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");
  delay(200);
}

void checkForErrors(float Temp) {
  if (isnan(Temp) || Temp >= 432) {
    while (true) {
      digitalWrite(relay, LOW); // the relay will disconnect the power to the soldering iron heating element
      // digitalWrite(triac, LOW);
      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);
      delay(200);
    }
  }
}
Currently I am trying to better understand the code and the working principle of this circuit.
I used the oscilloscope to check the signals coming from the PC817 (yellow trace) and from D7 (blue trace) pin of Arduino Nano.
DS0000.jpgDS0001.jpg




I have some problems in understanding some things and I want to ask:
1. Why the signal coming from the D7 pin is going from 5V to 0V when the signal coming from PC817 is on the rising edge ? It does not mean that it should go from 0V to 5V, on the rising edge of the PC817 signal ? It does not mean that when the signal is 0V the triac goes off and when the signal is 5V the triac goes on ?
2. Why the signal coming from D7 pin is going from 0V to 5V when there is no transition on the signal coming from PC817 ?
 
Last edited:

Irving

Joined Jan 30, 2016
3,884
D7 is the output to the MOC3020 optocoupler. A LOW on D7 turns the LED inside the optocoupler ON, so turning the triac ON. A HIGH turns it off. Since the triac won't turn off until the AC volts drops to near zero, it doesn't matter when its control input is turned off. So D7 LOW to HIGH can be any time.

The reverse is not true, the output from PC817 on D2 is LOW when the AC Volts is >about 10. When it transits from LOW to HIGH that is the zero voltage point and that is when D7 goes LOW turning the triac on.

All of the above happens in the zero() procedure, triggered by a rising edge on D2.

This 'upside-down logic' is common in microcontroller-based systems where negative logic (LOW = active) is driven by pulling voltages to ground by or to active components.

Hope that helps.
 

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
132
I am sorry for asking again: can you please help me with some more explanation about this 'upside down logic', and maybe with a schematic ?
I checked the circuit with a multi meter and oscilloscope in the same time, and when the multi meter shows about 4.5V on the D7 pin, then the sine wave appears on the oscilloscope screen. The oscilloscope is connected across the soldering iron heater.
But when the multi meter shows 0Vdc then on the scope also are 0Vdc and no pulses, which means that the triac is off...
 

Irving

Joined Jan 30, 2016
3,884
Apologies, I didn't look closely enough at the way that MOC3020 is wired. It is indeed connected so a HIGH turns the LED on and therefore the triac ON. What confused me was your second graph which shows a LOW level turning the AC on - that blue trace therefore cannot be D7.
 

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
132
I checked again, and I confirm that the blue trace is D7 output.
The transition from 0 to 5V of the D7 output should be on the rising edge of the input coming from pin D2 ?
 

Attachments

Last edited:

Irving

Joined Jan 30, 2016
3,884
Yes I believe so and that's what the code says too. Hmm, can you show me a 'scope trace with D2 on ChA and D7 on ChB
 

Irving

Joined Jan 30, 2016
3,884
Well D2 is definitely right. So we're not seeing something. Let's analyse it more closely...

The LED in the PC817 is ON when AC volts is > ~2v ie most of the time, so the output is LOW. The LED only goes OFF allowing the output, connected to D2, to be pulled HIGH (by internal pull_up) as the AC volts go to zero.

That rising edge triggers the interrupt and runs the "zero()" procedure. Most of the time (if counter < 40) if the temperature is too low it turns the heater on by putting D7 HIGH:
Code:
if (counter < 40) {
  if (temperature <= pottemperature) {
       digitalWrite(triac, HIGH)
  }
or if the temperature was too high, D7 is turned off:

Code:
  else {
      digitalWrite(triac, LOW);
    }
  }
So the transition of D7, LOW -> HIGH or HIGH -> LOW should always occur on a zero crossing.

Even when counter = 40. Then D7 is set LOW immediately after the interrupt:

Code:
  if (counter == 40) {
    digitalWrite(triac, LOW);
    detachInterrupt(0);
And turns interrupts off while the display is updated, which incurs a 200mS delay (20 zero crossings).

Turning interrupts off for such a long time like this, inside the interrupt service routine, isn't good coding practice but is acceptable given the simplicity of the system. I don't think there are any issues when they get re-attached 200mS later.

On the face of it there's no situation in the code where D7 is put HIGH 3mS after a zero crossing and only 13mS after it was put LOW.

So something else is not right (hardware fault, short between pins, etc.), or the 'scope is not showing us what we think it is showing...

I am a bit concerned about the switching spikes on D7; is that probe earth connected and where are you measuring D7, on the Nano pin?
 
Last edited:

Irving

Joined Jan 30, 2016
3,884
A further thought. That last set of scope shots shows the trigger on Ch2. D7, to be either <2 or ~2.5Hz.

Can you wind the time-base speed down to >=50mS/div so we can see the whole of your D7 waveform.
 

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
132
The D7 wave form was measured on the pin D7 side of the 330R resistor and also directly on the D7 pin, giving the same wave form that you can find in the previous screenshots.
You can find attached 2 screenshots with entire D7 (blue trace) output, measured directly on D7 pin.
 

Attachments

Last edited:

Irving

Joined Jan 30, 2016
3,884
Now that's much more what I expected and is informative. Now we can see D7 being activated, and high for 400mS before the counter=40 test kicks in and D7 goes low. But what happens next isn't right. According to the code the display gets updated, then there a wait for 200mS and the cycle should restart... instead we see this 13mS blip and D7 goes high again, and 400mS later it happens again. For 3 or 4 cycles.

How often does that burst of D7 high repeat?

What is shown on the display?

I see you added the 200mS delay to updatedisplay(), why was that?
 
Last edited:

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
132
The repeat of the D7 high (blue trace):

DS0009.jpg

The LCD display shows (the first line is the read temp from the pot and the second is the approximated temp and [] read temp from the thermocouple):

20200719_110751.jpg


The 200 mS delay to updatedisplay() was added because the display was updating very fast and sometimes, some characters were shown as one over another.
 
Last edited:

Irving

Joined Jan 30, 2016
3,884
Compared to GreatScott's original code I see you added the 200mS delay to updatedisplay(), why was that? Is it still there?

So, other than the waveforms don't tie up with the code as posted does it appear to work? Seems the temperature overshoots quite a bit but that's to be expected without more complex code because of the thermal mass of the iron.
 

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
132
I added the 200 mS delay to updatedisplay() was added because the display was updating very fast. Yes, it is still there.

Yes, it appears to work.
 

Irving

Joined Jan 30, 2016
3,884
Interesting. Can you show the wide view again.

Well at the moment I'm at a loss to explain why D7 gets set high away from a zero-crossing point and so quickly when it should be low for 200mS.

I found a comment in the Arduino reference that suggests delay() doesn't work inside an interrupt routine, and I wonder if detach and attach work correctly while the interrupt is being serviced.

I'm away from my computer at the moment for a couple of hours. I'll post some code which I think will throw some light on the problem, if you're OK with that?

Ideally the display update routine should be outside the interrupt routine and in the main loop. The only bit that should be in the interrupt routine is the reading of the temperature when the power to the iron is off in case that affects the measurement.
 

Thread Starter

mike_the_begginer

Joined Dec 7, 2019
132
I modified the code by moving the display update and check for errors from the interrupt routine to the main loop(), and I got the attached screenshots.

The code (I will probably make some modifications in the checkforerrors and updatedisplay code):
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;

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.begin(16, 2);
  lcd.setCursor(0,0);
  lcd.print("S S");
  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() {
  checkForErrors(temperature);
  updatedisplay();
}

void zero() {
  counter++;
  if (counter < 40) {
    if (temperature <= pottemperature) {
      digitalWrite(triac, HIGH);
    }
    else {
      digitalWrite(triac, LOW);
    }
  }

  if (counter == 40) {
    digitalWrite(triac, LOW);
    detachInterrupt(0);
    realTemperature = thermocouple.readCelsius();
    temperature = 0.779828 * realTemperature - 10.3427;
    counter = 0;
    attachInterrupt(digitalPinToInterrupt(2), zero, RISING);
  }
}

void updatedisplay() {
  pottemperature = analogRead(potentiometer);
  pottemperature = map(pottemperature, 0, 1023, 150, 400);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.write("SET= ");
  lcd.setCursor(5, 0);
  lcd.print((int)pottemperature);
  lcd.setCursor(0, 1);
  lcd.print("READ= ");
  if (temperature <= 45) {
    lcd.setCursor(5, 1);
    lcd.print("Lo");
  } else {
    lcd.setCursor(5, 1);
    lcd.print((int)temperature);
  }
  lcd.setCursor(9, 1);
  lcd.print("[");
  lcd.setCursor(10, 1);
  lcd.print((int)realTemperature);
  lcd.setCursor(13, 1);
  lcd.print("]");
  delay(200);
}

void checkForErrors(float Temp) {
  if (isnan(Temp) || Temp >= 432) {
    while (true) {
      digitalWrite(relay, LOW); // the relay will disconnect the power to the soldering iron heating element
      // digitalWrite(triac, LOW);
      lcd.clear();
      lcd.setCursor(5, 0);
      lcd.print("ERROR!");
      //delay(200);
    }
  }
}
 

Attachments

Irving

Joined Jan 30, 2016
3,884
That's exactly what expected!

What I think is happening is that delay(200) doesn't work in the interrupt routine. It actually gives 1mS of delay. That's why removing it reduced the D7 off time from 13mS to 12mS. Detach/attach similarly don't work inside an ISR. So updating the display takes about 12mS, but in the meantime another zero crossing has arrived. When the code returns from the ISR it immediately interrupts again and responds by setting D7 high. As counter is now 0 the process carries on.

The max6675 can only be read ~ every 250mS so a better interrupt routine is:

Code:
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;
    counter = 0;
    if (temperature <= pottemperature)    {
      digitalWrite(triac, HIGH);
    }
  }
}
So basically reading every 250mS but turning the triac off at 240mS and waiting 1 zero crossing to ensure power is off before taking readings.

Need to check how long a max6675 read takes, as long as less than say 5mS this'll be fine.
 
Top