Digital 12 socket light dimmer. For christmas!

I am making a digital 12 dimmer light control system for Christmas lights. I am using a ATmega328 on a custom board with external 16MHz crystal at 5V. I'm in the US so mains is 120VAC RMS at 60Hz. I have an AC input, transistor output optocoupler connected to a hardware interrupt to detect zero cross. The antiparallel leds in the optocoupler are driven using a series capacitor and resistor straight from the mains. My outputs are Arduino pins 6-17. These drive DC input, triac output optocouplers which drive triacs.

My theory is to detect a zero cross and then turn on any pin not marked for off. I then scan an array which contains the percentages for each outlet. This number is mapped to 0 to 8333 representing the time, in microseconds, after the zero cross to turn the pin back off. This array then needs to be scanned at least 100 times between each zero cross for 1% resolution.

I set the outlets in 10% increments to showcase the dimming. Right now I get what appears to fully on down to 30%. 20% flickers and surges badly and 10% strobes/flickers but is consistently dim. This was tested on a 40w incandescent light bulb. I believe it to be a software issue and that is why I am here but I have included a basic hardware explanation in case.

I originally had the interrupt function call "on" (which calls dim) but this did not work as the code either still thought it was in an interrupt (crippling timing functions) and/or, because the interrupt pulled the program out of dim, dug a continuously deeper loop until freezing.

#include <IRremote.h>
#include <IRremoteInt.h>

int level[] = {
  0, 100, 90, 80, 70, 60, 50, 40, 30, 20, 10,100,100};//percentage on
//    1   2   3   4   5   6   7   8   9   10  11  12  :sockets-zero is placeholder

//int program = 0;
int x;
int limit = 11;                      //sets the number of sockets used.  set to 12 to use all.
//unsigned long milli = 0;
unsigned long micro = 0;
volatile boolean interruptv = false;

void setup(){
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);
  pinMode(14, OUTPUT);
  pinMode(15, OUTPUT);
  pinMode(16, OUTPUT);
  pinMode(17, OUTPUT);
  pinMode(3, INPUT);
  digitalWrite(3, HIGH);
  attachInterrupt(1, interrupt, FALLING);

void loop(){
  if (interruptv)

void interrupt(){
  interruptv = true;

void on(){
  //delayMicroseconds();          //possibly accounts for time after trigger but
  interruptv = false;             //before zero cross (Vin is below voltage necessary to turn on ZC sensing leds.
  for (x = 1; x <= limit; x++){
    if (level[x] > 0)
      digitalWrite((x+5), HIGH);  //turns on all the plugs not set for off at the ZC

void dim(){
  micro = micros();                //sets time of ZC
  while(!interruptv){              //do until next ZC
    for (x = 1; x <= limit; x++){//continuously look through the set levels to see if any need to be turned off.  This should run at least 100 times every 8ms.
      if( ((map(level[x], 0, 100, 0, 8333)) <= (micros() - micro)) && (level[x] < 100)){ //this should run every (limit * 100) every 8ms
        digitalWrite((x+5), LOW);//turns lights off once their percentage of the 8ms cycle is complete.

I did some calculations and I think I had come up with 900 some clock cycles per percent of dimming. I would think this is enough but I could be wrong.

Any help would be appreciated.



I always wonder why people are so dog-gone-eager to use interrupts when it is not necessary. 60Hz, half cycle thereof, is 133thousand clock cycles on my calculator.

The interrupt is mercifully brief and uncomplicated. It simply sets the flag.

Loop() keps calling on() as long as the flag is high.
The first thing on() does is clear the flag, so it only executes on() and dim() once per mains halfcycle.

dim() on the other hand keeps looping until until the flag is set high.

In short with two routines and the interrupt you are doing something that would just as easy be in the loop() directly, in-line. (Yes, I noticed #include <IRremote.h> indicating you are going to do some more later). You could just look at the zero crossing input line on each pass in your inner loop, setting the flag to cause the outer loop to activate again.

You are constantly calling digitalWrite for the lights to turn off. That routine does take some time, so you may want to add an extra flag array to only set it low once. Complicates it, but will use less CPU overall.

Otherwise the software on inspection seems correct. Have you testet if you zero crossing detection is working? Just count, say, 5200, pulses, and print out the millis() delta on the Serial port, and check that that matches with 1 minute on the wall clock.

Is your output electronics correct. The simple test is just to do a square wave On/Off at close to 60Hz with millis() (or even delay()). It will beat with your mains frequency, giving some pulsating light. If it does not, something else is amiss.

If (and I doubt it) we are running out of clockcycles per mains cycle, then how well does it work with only 2 lights (int limit=2)?

Well, TRIAC's only turn off when there is a zero-crossing, so it might be handy to turn then off when the AC wave is crossing well the zero voltage, or they will be always on.

Once you turn a triac on it stays on for the rest of that half cycle. So to dim a light you need to detect the zero cross, delay for a while and then turn the triac on with a short pulse.

Yeah, my brainfart. :disappointed_relieved: TRIACS are OFF until we trigger them on. So Gtech needs to invert the program logic. There are though some more complicated MOSFET-TRIACS, SCS and similar which do allow on&off control.

Thanks guys for the responses. I've been delayed by a more time sensitive project but was briefly looking at the code again today. Some good advice on the set low once thing. I was thinking a triac had to be of the zero crossing type to shut off at the cross but now that I think of it, I believe ZC or no ZC only applies to the optocoupler and thinking of a single SCR it makes sense that a triac cannot be shut off mid cycle. I did test a few of the pins with simple high and low and they worked fine. After I complete my other project, I'll get back to this and invert the theory.

EDIT Just so I'm straight on this--it takes less time to check a value in a boolean array then to simply tell the pin to go low when it already it?

EDIT Just so I'm straight on this--it takes less time to check a value in a boolean array then to simply tell the pin to go low when it already it?

If you are setting the pin low using digitalWrite, yes. If you are doing direct port access, no.

After reversing the action of the software and turning on after a delay, things worked just as bad. I hooked up to a real oscope at school today and found what seems to be the problem. Now the question is how to fix it. My zero cross trigger is 90deg out of phase. It triggers on the peaks and valleys. I suppose a quick fix would be to delay roughly 4ms extra but I am very puzzled on why it is not performing how I expect it to. Below I have included a basic schematic of my ZC detector and of the observed waveform. The black led is part of the opto (blue)--its AC input. Input resistor is 1k and the cap is somewhere in the .1 to .3uF range (don't remember-its SMD and doesn't say) My only thought was that the led wasn't turning on until mains was nearing 170V but this would show the output peaking downward not upward (to 5V). Any ideas?

EDIT BAhhhh I got the same exact results in a simulation as well. What the heck am I missing?

Your zero cross detector is 90 deg out of phase because you are feeding it with a capacitor, and the current in a capacitor with sinusoidal AC across it is 90 deg out of phase with the voltage. If you replace the capacitor with a resistor chosen to pass the same current, it should be OK. Be sure that the resistor has sufficient voltage and power rating.

So how did this work out for you. I was planning on trying something like this for halloween...

Sorry I didn't get back to you. I used it for Halloween and had it flicker for a "loose wire" simulator. The digital control works well and I decided to forget the IR for now in place of bluetooth! I made a socket inside for a RN-42 module I move around to whatever current project I'm working on and I send it commands from my phone with a program designed in AppInventor. Also works well but I need some emergency help on the dimming. I absolutely cannot get solid code to work well for dimming. I have lots of snippets that sort of work under certain conditions (one light, one level, etc) but I'm incredibly frustrated and the Christmas tree is ready.

I really need to fix the phase thing and then dimming should be incredibly easy. What I would like to know from the community is how a LED (ac optoisolator) will respond to being driven off the secondary of the transformer. When resistor only driven will the response be in phase with primary mains voltage? I think it would also be 90 out out of phase so then I can maybe see about RC driving the LED but my voltage may be too low (the capacitor would need to be pretty big). What do you think? Your quick responses will be greatly appreciated.


EDIT: Basically I can only drive from mains with a RC combo (thus causing my problem) and may only be able to use a resistor from the secondary for the opposite reason. I think my secondary is only about 7vac thus needing only about 500 ohms of resistance/reactance. (a 5uF cap + R100 then)

Update: The forums seemed to be down earlier and I had some extra time so I connected to the secondary with a resistor only and voila! perfect zero crossing detector. I'm sure if there was some sort of inductive or capacitive load on the secondary this would mess things up but it only runs the uC. I'll try to post a video sometime. I'm off to write the new code, hope it is as simple as I think it will be.

This is awesome, great work with AppInventor! :smiley:

hi dude i am also doing the same sort of project
please send me the code of ur porject thanku :wink:

That would be real hard to do with my candles :smiley: