Arduino Nano zero crossing detection

Thread Starter


Joined Dec 7, 2019
Probably I was not sufficiently clear. I apologize if I was importunate when I started talking about the PID_v1 library.
I tried to use this library, but I have some questions, if you can, please have a look at them:
1. It is correct to call myPID.Compute() and myPID.SetTunings() in the ISR (zero() function) ?
2. I see that when the output signal (pin 7) is going from 0V to 5V, appears a delay of about (1mS) measured on the scope. Is this delay considered normal ?
3. Is the code implemented correctly ? Please have a look at information on reply #100.


Joined Jan 30, 2016
Hi Mike,
sorry, was tied up all day with a tutorial... got to train the next generation lol.

I've not used that PID library, I'll go have a browse. generally you shouldn't do anything complex inside an ISR. What we wee doing was marginal, but getting close to changing it...

Without knowing what those routines do, I'd probably not put them inside the ISR, but in the main loop. I'd run the ISR as now, but move the 'read temperature' & 'read pot' to the main loop too. All the ISR will do is count up zero-crossings and switch the iron on & off according to the demands of the PID controller. Whether that uses proportional control (duty cycle) or just On/Off is still an open question.

The 1 mS delay is because of all the floating point calculations. The Arduino has no FP coprocessor so is really unsuited to this. You can implement PID with integer arithmetic. 1mS from the zero crossing interrupt puts switch on at ~10% volts, or around 3v, not significant since this is only a 50W resistive load.

I'll have a look tomorrow.

Thread Starter


Joined Dec 7, 2019
I'll have a look tomorrow.

Meanwhile, I modified again the code, by introducing the PID calculations in loop() instead of zero().
Also I made 2 screenshots with the pin 7 signal (blue) and pin 2 signal (yellow).
#include <LiquidCrystal.h>
#include <SPI.h>
#include <Wire.h>
#include <max6675.h>
#include <PID_v1.h>

#define thermoDO 12
#define thermoCS 10
#define thermoCLK 13
#define potentiometer A0
#define zerocrossing 2
#define triac 7
#define relay A1

#define test A2
#define test1 A3

int lowError = 0; //guessing for now.  The bigger these values the smaller the deadband either side of 0 error that constitues a 50% duty cycle
int highError = 220;

volatile float temperature, realTemperature;  // I have declared those 2 variables as VOLATILE, because of using them inside and outside ISR
int pottemperature;
int counter;
int tempError = false;  // global error flag
int shownError = false; //flag to say error shown
int duty = 0; // variable for duty cycle

//PID constants
//double Kp = 5;
//double Ki = 0;
//double Kd = 0;

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

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

PID myPID(&input, &output, &setPoint, consKp, consKi, consKd, DIRECT);

byte thermometer[8] = //icon for termometer

byte arrow[8] = //icon for arrow

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)

#define PRINTRATE 100
#define DISPLAYRATE 250

char textbuf[100]; //buffer for data to send
unsigned long serialTime = millis(); //sending interval for data
unsigned long displayTime = serialTime;  //display interval for LCD
int pt; //local store for pot and iron temperatures;
int tmp;
double err, cErr, rErr, op;
int dty;

volatile int pidOut = 0; //this also is volatile because it is used in both loop() and zero()

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

void setup() {
  myPID.SetOutputLimits(0, 220);
  Serial.begin(115200); // or faster if your Arduino/PC can handle it...
  pinMode(test, OUTPUT);
  pinMode(test1, OUTPUT);
  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;
  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;
  attachInterrupt(digitalPinToInterrupt(2), zero, RISING);

// added stuff to log temperatures on serial monitor
// change loop time management from simple delay
void loop() {  //moved the PID computations outside the ISR
  pottemperature = analogRead(potentiometer);
  pottemperature = map(pottemperature, 0, 1023, 150, 400); //pottemperature is volatile
  setPoint = pottemperature;
  realTemperature = thermocouple.readCelsius();
  temperature = int(0.779828 * realTemperature - 10.3427); // make temperature an integer  //temperature is volatile
  input = temperature;
  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);
    //we're far from setpoint, use aggressive tuning parameters
    myPID.SetTunings(aggKp, aggKi, aggKd);
  pidOut = int(output);  //pidOut is volatile
  //  if (millis() > serialTime + PRINTRATE) { //send serial data every PRINTRATE mS
  //    noInterrupts(); // make sure our local copies are not corrupted while copying them over from ISR
  //    tmp = temperature;
  //    pt = pottemperature;
  //    err = error;
  //    cErr = cumError;
  //    rErr = rateError;
  //    op = output;
  //    dty = duty;
  //    interrupts();
  //    sprintf(textbuf, "Time: %lu, Set: %4u, Temp: %4u", millis() / 100, pt, tmp); //format the print string
  //    Serial.print(textbuf);
  //    sprintf(textbuf, ", error: %8.2f, cumErr: %8.2f, rateErr: %8.2f, output: %8.2f, duty: %3u", err, cErr, rErr, op, dty);
  //    Serial.println(textbuf); //send to serial monitor, about 3mS @ 115200
  //    serialTime += PRINTRATE;
  //  }
  //  if (millis() > displayTime + DISPLAYRATE) { //update display every DISPLAYRATE mS
  if (!tempError) {  // if no error
  } else // do something on error
    // eg show the word error on the display
    if (!shownError) { // we've not shown error yet, so show it
      shownError = true; //set flag so don't show it again
    //  }
    //    displayTime += DISPLAYRATE;

void zero() {
  //*** change this line below
  if (counter > duty) { //reach duty cycle limit, unless duty was 25 in which case leave on until next duty calculated later
    digitalWrite(triac, LOW);
  if (counter >= 25) {
    counter = 0;
    if (tempError || isnan(realTemperature) || 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
      duty = map(pidOut, lowError, highError, 0, 25); // *** lowError is const for fully off, highError is const for fully on. zero error maps to 50%
      duty = constrain(duty, 0, 25); // ***keep duty between 0 and 25 (25 = 100%)
      if (duty > 0) {
        digitalWrite(triac, HIGH);
      } else {
        digitalWrite(triac, LOW);
      //lastError = error;
    }//if (tempError
  } //if(counter >= 25
}// zero()

void updateDisplay() {
  pottemperature = analogRead(potentiometer);
  pottemperature = map(pottemperature, 0, 1023, 150, 400);
  lcd.setCursor(0, 0);
  lcd.setCursor(2, 0);
  lcd.setCursor(6, 0);
  lcd.print((char)223); //degree sign
  lcd.setCursor(7, 0);
  lcd.setCursor(0, 1);
  if (temperature <= 45) {
    lcd.setCursor(2, 1);
  } else {
    lcd.setCursor(2, 1);
  lcd.setCursor(6, 1);
  lcd.setCursor(7, 1);
  lcd.setCursor(10, 1);
  lcd.setCursor(12, 1);
  lcd.setCursor(13, 1);

void displayErrors() {
  digitalWrite(relay, LOW); // the relay will disconnect the power to the soldering iron heating element
  lcd.setCursor(0, 0);
  lcd.setCursor(1, 0);
  lcd.setCursor(5, 0);
  lcd.setCursor(14, 0);
  lcd.setCursor(15, 0);


Last edited:


Joined Jan 30, 2016
Hi Mike, had a day of crashing desktop, not sure what changed but pulled a backup from a couple of days back onto it and i'm back up I think; anyway not had a chance to review this in detail, but will do so more fully once I've got today's backlog out of the way...

Ignore that previous post, which I've now deleted - the java code behind this website and/or Firefox has a habit of stalling and posting stuff before i'm ready

From what I read, and understand from the PID library code there are a few problems using it - for a start it does the timing loop delay internally, so you cannot have any external delay otherwise it messes up the Ki and Kd calculations - so you can't do PID and display delays in the same loop... but there may be a way round it...

I'm tidying everything up and removing anything no longer relevant, and putting the logging back in. Will probably won't be til tomorrow now though.

I wouldn't try to get that going...