Dimming AC using Arduino Mega and interrupts

Aim:
To design a circuit that can control (dim/brighten) AC using Arduino. For this purpose, I am using a 6V transformer. The circuit diagram is available here (please ignore the attached circuit diagram).

The program:

/**
 * Digital pin that will detect the interrupt via H11AA1.
 */
const int PIN_INTERRUPT = 3;

/**
 * Digital pin that leads to MOC 3052 triac driver.
 */
const int PIN_TRIAC = 7;

int dimValue;

void setup() {

    pinMode(PIN_INTERRUPT, INPUT);
    pinMode(PIN_TRIAC, OUTPUT);
    pinMode(LED_BUILTIN, OUTPUT);

    digitalWrite(PIN_TRIAC, LOW);
    digitalWrite(LED_BUILTIN, LOW);

    dimValue = 50;

    attachInterrupt(digitalPinToInterrupt(PIN_INTERRUPT), zeroCrossing, FALLING);
}

void loop() {
}

/**
 * AC frequency = 50 Hz.
 * So, time period one full wave = 20 ms.
 * Therefore, time period of half the wave (time period from one zero crossing to another) = 10 ms = 10,000 μs.
 * 
 * Number of steps (taken arbitrarily) = 100.
 * => Each step corresponds to 10,000 μs/100 = 100 μs.
 * ∴ Time for dimming = (100 * dimValue) μs.
 */
void zeroCrossing(){

    int delayMicros = 100 * dimValue;
    delayMicroseconds(delayMicros);
    digitalWrite(PIN_TRIAC, HIGH);
    delayMicroseconds(20);
    digitalWrite(PIN_TRIAC, LOW);
    digitalWrite(LED_BUILTIN, HIGH);
}

What the circuit is supposed to do:
The external LED connected to AC will glow, and its brightness will depend on dimValue in the code above. Initially, the built-in LED is LOW, but it will glow as soon as interrupts are detected. This will indicate that the Arduino is detecting the interrupts.

The problem:
The inbuilt LED glows, but the external blue LED does not.

What I have already tried:

  • If I connect the "A" terminal of the MOC3052 to 5V, the LED glows, which means the TRIAC circuit is working fine.
  • If I read the voltage on "E" terminal of the H11AA1 using Arduino analogRead(), I get a nice graph (attached). The falling lines should be detected by the Arduino.
  • If I start the Arduino when the AC side is off, then the inbuilt LED does not glow. As soon as I switch on the AC, the inbuilt LED glows, which means that the Arduino is detecting the interrupts properly.
  • If I remove the digitalWrite(PIN_TRIAC, LOW) line from the interrupt service routine (ISR), the external LED glows.

This pin-points the problem to the digitalWrite(PIN_TRIAC, LOW) line in the ISR. The logic for controlling the LED is flawed somewhere.

I took the code from this website (and modified it a bit).

Any help is appreciated regarding this.

Your schematic needs more contrast.

Have you tried turning the TRIAC on & off with software but without any "dimming"?

And you might try temporarily adding a Serial.print() statement in your ISR, and maybe print-out the millis() or micros() time to confirm it's getting triggered regularly. (Those serial print statements take some time so it might mess-up the timing when you actually start dimming.)

DVDdoug:
Your schematic needs more contrast.

Sorry, sorry. That problem occurred when I edited it. The proper picture can be seen here.

DVDdoug:
Have you tried turning the TRIAC on & off with software but without any “dimming”?

You mean outside the ISR, in loop()? I modified the code as below:

void loop() {
    digitalWrite(PIN_TRIAC, HIGH);
    delay(1500);
    digitalWrite(PIN_TRIAC, LOW);
    delay(1500);
}

The LED blinks properly.

DVDdoug:
And you might try temporarily adding a Serial.print() statement in your ISR, and maybe print-out the millis() or micros() time to confirm it’s getting triggered regularly. (Those serial print statements take some time so it might mess-up the timing when you actually start dimming.)

I did that, and I am getting a straight increasing graph from the Serial plotter, which means that the ISR is being called regularly.

An LED without a diode in series with it that is connected to an AC source will likely fail due to the reverse voltage. Get a new LED and put a 1N4148 or 1N400x in series with it along with the resistor. Connect it to the transformer output first to test.

I wonder if the ~20uS on pulse on your ISR is too short; perhaps the current flow in the LED is not instantaneous and by the time the 20uS period is finished there's not enough current flowing to sustain the triac in the on-state.

I feel like you might want to change the drive so that the gate drive lasts from your turn on point to a short period of time before the next zero-crossing. Can you try changing that 20uS delay to, say, 3mS (you start at 5mS and should have 5mS before the next zero-cross so it should go off in time for the next zero cross event...)

I don’t think the pulse you’re getting is based on a true zero crossing. The H11AA1 is connected to create a normally high signal but I can’t see any pulldown resistor for the emitter. Are you sure you don’t want to do something like this where you would detect the rising signal (this is more common on the web) …

WattsThat:
An LED without a diode in series with it that is connected to an AC source will likely fail due to the reverse voltage. Get a new LED and put a 1N4148 or 1N400x in series with it along with the resistor. Connect it to the transformer output first to test.

As I have said before, the LED glows fine if I connect the "A" terminal of the MOC3052 directly to 5V, so the LED is probably working fine.

Blackfin:
Can you try changing that 20uS delay to, say, 3mS (you start at 5mS and should have 5mS before the next zero-cross so it should go off in time for the next zero cross event...)

Tried it, but no success.

delayMIcroseconds() is derived from micros() which is based on millis() which is based on a timer interrupt so this function may simply not work inside your ISR (the function zeroCrossion() is an ISR), as interrupts are off as long as you are in that ISR. You have to keep that function as quick and short as possible, so other interrupts can be serviced. It also helps keeping your overall sketch responsive.

You best use timer interrupts for this. So upon ZC your zeroCrossing() function set a timer interrupt to the moment you want to switch on the TRIAC (on an Uno use timer1 or timer2, this as timer0 is taken by the millis() function and rolls over every ~1 ms), then in that function set a new timer interrupt for when you want to switch off the signal again.

See also this simple example on the unfortunately discontinued Arduino playground.

wvmarle:
You best use timer interrupts for this. So upon ZC your zeroCrossing() function set a timer interrupt to the moment you want to switch on the TRIAC (on an Uno use timer1 or timer2, this as timer0 is taken by the millis() function and rolls over every ~1 ms), then in that function set a new timer interrupt for when you want to switch off the signal again.

See also this simple example on the unfortunately discontinued Arduino playground.

I read about timer interrupts some time back, and they seemed to be a bit perplexing. I will check out the example you linked.

I replaced the 6V transformer with a 9V one, and now the LED is glowing very faintly at some specific values of the two delays. So, it could be an issue of the series resistance — maybe it is too high and the LED is not glowing because of that. Also, at some values, the LED is glowing brightly, but blinking continuously.

wvmarle:
delayMIcroseconds() is derived from micros()…

Are you sure? From wiring.cpp, delayMicroseconds just appears to burn machine cycles, at least for AVR processors. The source (condensed down to the 16MHz F_CPU case):

void delayMicroseconds(unsigned int us)
{
	// for the 16 MHz clock on most Arduino boards

	// for a one-microsecond delay, simply return.  the overhead
	// of the function call takes 14 (16) cycles, which is 1us
	if (us <= 1) return; //  = 3 cycles, (4 when true)

	// the following loop takes 1/4 of a microsecond (4 cycles)
	// per iteration, so execute it four times for each microsecond of
	// delay requested.
	us <<= 2; // x4 us, = 4 cycles

	// account for the time taken in the preceeding commands.
	// we just burned 19 (21) cycles above, remove 5, (5*4=20)
	// us is at least 8 so we can substract 5
	us -= 5; // = 2 cycles,


	// busy wait
	__asm__ __volatile__ (
		"1: sbiw %0,1" "\n\t" // 2 cycles
		"brne 1b" : "=w" (us) : "0" (us) // 2 cycles
	);
	// return = 4 cycles
}

which is roughly equivalent to:

void delayMicroseconds( unsigned int us )
{
    if( us <= 1 )
        return;

    us = (us * 4) - 5;

    while( us )
        us--;

    return;
}

I agree that the timing for the dimming should be done using timer interrupts rather than any delay inside a pin interrupt ISR.

@OP, did you use delayMicroseconds( 3000 ) or another method of delay (e.g. delay(3) which would not work…)?

Blackfin:
@OP, did you use delayMicroseconds( 3000 ) or another method of delay (e.g. delay(3) which would not work...)?

Definitely delayMicroseconds(), not delay(), because I know that the latter won't work inside an ISR.

With the 6V transformer replaced with a 9V one, the following two code snippets work partially:

void zeroCrossing(){

    delayMicroseconds(100);
    digitalWrite(PIN_TRIAC, HIGH);
    delayMicroseconds(500);
    digitalWrite(PIN_TRIAC, LOW);

}

The above gives very faint steady glow from the LED.

void zeroCrossing(){

    delayMicroseconds(10);
    digitalWrite(PIN_TRIAC, HIGH);
    delayMicroseconds(7000);
    digitalWrite(PIN_TRIAC, LOW);

}

The above gives brightly glowing LED, but continuously blinking erratically.

Maybe you can try this:

/**
 * Digital pin that will detect the interrupt via H11AA1.
 */
const float kPeriod = 36000.0;

const uint8_t PIN_INTERRUPT = 3;
const uint8_t PIN_TRIAC = 7;

const uint8_t pinTest = 5;          //generates ~50Hz test signal for when AC input not available

uint8_t 
    dimValue,
    lastDimValue;
    
volatile uint16_t
    tickDelay,
    tickRemainder;

void setup() 
{
    //set up timer 1 to run at 2MHz
    TCCR1A = 0;
    TCCR1B = _BV(CS11);
    
    pinMode( pinTest, OUTPUT );
    
    pinMode(PIN_INTERRUPT, INPUT);
    pinMode(PIN_TRIAC, OUTPUT);
    pinMode(LED_BUILTIN, OUTPUT);

    digitalWrite(PIN_TRIAC, LOW);
    digitalWrite(LED_BUILTIN, LOW);

    dimValue = 50;
    lastDimValue = 255;

    attachInterrupt(digitalPinToInterrupt(PIN_INTERRUPT), zeroCrossing, FALLING);
}

void loop() 
{
    static bool
        bState = false;
    static uint32_t
        timeSig;
    uint32_t timeNow = micros();

    //only update the dimmer math if the desired % has changed
    if( dimValue != lastDimValue )
    {
        //compute new delay ticks
        float fTicks = kPeriod * (float)(constrain(dimValue, 1, 99))/100.0;
        
        noInterrupts();
        tickDelay = (uint16_t)(kPeriod - fTicks);
        tickRemainder = (uint16_t)(kPeriod - tickDelay);
        interrupts();
        
        lastDimValue = dimValue;
        
    }//if

    //generates 50Hz test waveform for bench testing
    //connect pin 5 to pin 3 if no AC ZC source is available
    if( (timeNow - timeSig) >= 10000ul )
    {
        timeSig = timeNow;
        bState ^= true;
        digitalWrite( pinTest, bState );
        
    }//if
    
}//loop

void zeroCrossing()
{
    //each TCNT1 tick is 500nS
    // delay 12mS       
    // hold for 6mS
    OCR1A = TCNT1 + tickDelay;
    TIFR1 |= _BV(OCF1A);
    TIMSK1 |= _BV(OCIE1A);

}

ISR( TIMER1_COMPA_vect )
{
    static bool
        bState = false;

    switch( bState )
    {
        case    false:
            digitalWrite(PIN_TRIAC, HIGH);
            OCR1A = OCR1A + tickRemainder;
            bState = true;
            
        break;

        case    true:
            digitalWrite(PIN_TRIAC, LOW);
            TIMSK1 &= ~_BV(OCIE1A);
            bState = false;
            
        break;
    }
    
}//

With the 6V transformer replaced with a 9V one, the following two code snippets work partially:

Yeah, the higher the AC Voltage, the closer the pulse can be to the true zero-cross. Probably the code was developed with a 120VAC or 220VAC input circuit. Anyways, using a transformer is safer, but if the voltage is too low, the Vf of the IRLED (probably 1.4V) takes up a huge portion of the 6VAC signal. Additionally, it takes a certain current through the IRLED to generate the pulse ... maybe this would be at at 4V (depends on the series resistor value). Sure would be nice to actually see your schematic. Lowering the series resistor value (which I can't see) to the IRLED would probably help, or increasing the voltage (as you have found).

Blackfin:
Maybe you can try this:

#include <TimerOne.h>

const byte INTERRUPT_PIN = 2;
const byte TRIAC_PIN = 4;
const byte TRIAC_PULSE_MICROS = 30;

const int FADE_MAX = 9800;
const int FADE_MIN = 2000;

volatile bool triacOn;
volatile int period = FADE_MIN; // microseconds cut out from AC pulse

int fadeAmount = 10;

void zeroCrossing() {
  triacOn = false; // triac tuns off self at zero crossing
  Timer1.setPeriod(period); // to call triacPulse() after off period
}

void triacPulse() {
  if (triacOn) { // stop pulse
    digitalWrite(TRIAC_PIN, LOW);
    Timer1.stop();
  } else { // start pulse
    digitalWrite(TRIAC_PIN, HIGH);
    triacOn = true;
    Timer1.setPeriod(TRIAC_PULSE_MICROS);
  }
}

void setup() {
  pinMode(TRIAC_PIN, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), zeroCrossing, RISING);
  Timer1.initialize();
  Timer1.attachInterrupt(triacPulse);
}

void loop() {
  period = period + fadeAmount;
  if (period <= FADE_MIN || period >= FADE_MAX) {
    fadeAmount = -fadeAmount;
  }
  delay(25);
}

It starts to sound like the TRIAC switches off the moment the input pulse is switched off, the current drawn by a single LED may not be enough to latch it on.

@Blackfin The code you gave in post #14 generates a steady, brightly glowing LED. No dimming effect, even when I change dimValue.

dlloyd:
Probably the code was developed with a 120VAC or 220VAC input circuit.

Yes, you are right. But directly interfacing mains is not something I want to do.

dlloyd:
Sure would be nice to actually see your schematic.

I have posted a link to the circuit in post #2. I have also edited the OP to include the correct link.