Zero-Crossing Detector Circuits: AC Power Control

by Lewis Loflin lloflin2@gmail.com

Zero-crossing pulse timing
Fig. 1: Zero-crossing pulse timing relative to the AC sine wave.

In AC power control applications like dimmers or motor speed controllers, "firing" a Triac at the wrong time—such as at the peak of the voltage cycle—can cause massive current surges and EMI (Electromagnetic Interference). A zero-crossing detector is essential to synchronize firing pulses to the precise moment the AC voltage crosses zero, ensuring smooth, efficient power regulation.

The H11AA1: A Bidirectional Solution

The H11AA1 is uniquely suited for this task. Unlike standard optocouplers that require a diode bridge to rectify AC, the H11AA1 contains back-to-back LEDs at the input. This allows it to respond to both the positive and negative half-cycles of the AC line directly, providing a clean 120 Hz pulse train (in a 60 Hz system) that marks every time the voltage passes through zero.

H11AA1 detector circuit
Fig. 2: H11AA1 zero-crossing detector.

Arduino Implementation & Safety

When interfacing with the AC line, isolation is paramount. The optocoupler provides the necessary galvanic isolation between the lethal mains voltage and your low-voltage microcontroller. By connecting the output of the detector to an Arduino interrupt pin (e.g., INT0), you can precisely time the delay required to fire the Triac, thereby controlling the RMS power delivered to the load.

Arduino Sketch: Triac Power Control

/* 
  Zero-crossing triac control
  INT0 (pin 2) = zero-crossing pulse
  Pin 5 = triac gate
  Pot on A0 sets power (phase delay)
*/

#define TRIAC_PIN 5

volatile bool fireTriac = false;

void setup() {
  pinMode(TRIAC_PIN, OUTPUT);
  // Interrupt triggers on every pulse
  attachInterrupt(digitalPinToInterrupt(2), zeroCross, FALLING);
}

void loop() {
  if (fireTriac) {
    // Phase delay logic (0 to 8.3ms for 60Hz)
    int delayTime = (analogRead(A0) * 8) + 200;
    delayMicroseconds(delayTime);
    digitalWrite(TRIAC_PIN, HIGH);
    delayMicroseconds(50); // Pulse duration
    digitalWrite(TRIAC_PIN, LOW);
    fireTriac = false;
  }
}

void zeroCross() { fireTriac = true; }

Related Tutorials