Picture of Arduino controlled light dimmer

WARNING: Some people try to build this with an optocoupler with zerocrossing coz 'that is better' right? Some are even told in electronics shops it is better to use such an optocoupler. WRONG. This will only work with a random fire optocoupler: NOT igniting at zerocrossing is the principle of this dimmer.

Switching an AC load with an Arduino is rather simpel: either a mechanical relay or a solid state relay with an optically isolated Triac. (I say Arduino, but if you use an 8051 or PIC16F877A microcontroller, there is stuff for you too here.)

It becomes a bit more tricky if one wants to dim a mains AC lamp with an arduino: just limiting the current through e.g. a transistor is not really possible due to the large power the transistor then will need to dissipate, resulting in much heat and it is also not efficient from an energy use point of view.

Phase cutting
One way of doing it is through phase control with a Triac: the Triac then is fully opened, but only during a part of the sinus AC wave. This is called leading edge cutting.
One could let an Arduino just open the Triac for a number of microseconds, but that has the problem that it is unpredictable during what part of the sinus wave the triac opens and therefore the dimming level is unpredictable. One needs a reference point in the sinus wave.
For that a zero crossing detector is necessary. This is a circuit that tells the Arduino (or another micro controller) when the sinus-wave goes through zero and therefore gives a defined point on that sinus wave.
Opening the Triac after a number of microseconds delay starting from the zero crossing therefore gives a predictable level of dimming.

Pulse Skip Modulation
Another way of doing this is by Pulse Skip Modulation. With PSM, one or more full cycles (sinuswaves) are transferred to the load and then one or more cycles are not. Though effective, it is not a good way to dim lights as there is a chance for flickering. Though it might be tempting, in PSM one should always allow a full sinuswave to be passed to the load, not a half sinus as in that case the load will be fed factually from DC which is not a good thing for most AC loads. The difference between leading edge cutting and PSM is mainly in the software: in both cases one will need a circuit that detects the zero crossing and that can control a triac.

A circuit that can do this is easy to build: The zero crossing is directly derived from the rectified mains AC lines – via an optocoupler of course- and gives a signal every time the wave goes through zero. Because the sine wave first goes through double phased rectification, the zero-crossing signal is given regardless whether the sinus wave goes up through zero or down through zero. This signal then can be used to trigger an interrupt in the Arduino.

It goes without saying that there needs to be a galvanic separation between the Arduino side of things and anything connected to the mains. For those who do not understand 'galvanic separation' it means 'no metal connections' thus ---> opto-couplers. BUT, if you do not understand 'galvanic separation', maybe you should not build this.

The circuit pictured here does just that. The mains 220Volt voltage is led through two 30k resistors to a bridge rectifier that gives a double phased rectified signal to a 4N25 opto-coupler. The LED in this opto-coupler thus goes low with a frequency of 100Hz and the signal on the collector is going high with a frequency of 100Hz, in line with the sinusoid wave on the mains net. The signal of the 4N25 is fed to an interrupt pin in the Arduino (or other microprocessor). The interrupt routine feeds a signal of a specific length to one of the I/O pins. The I/O pin signal goes back to our circuit and opens the LED and a MOC3021, that triggers the Opto-Thyristor briefly. The LED in series with the MOC3021 indicates if there is any current going through the MOC3021. Mind you though that in dimming operation that light will not be very visible because it is very short lasting. Should you chose to use the triac switch for continuous use, the LED will light up clearly.

Mind you that only regular incandescent lamps are truly suitable for dimming. It will work with a halogen lamp as well, but it will shorten the life span of the halogen lamp. It will not work with any cfl lamps, unless they are specifically stated to be suited for a dimmer. The same goes for LED lamps

If you are interested in an AC dimmer such as this but you do not want to try building it yourself, there is a somewhat similar dimmer available at, however, that is a 110 Volt 60Hz version (but adaptable for 220 50Hz), that has been out of stock for a while. You will also find a schedule here.

NOTE! It is possible that depending on the LED that is used, the steering signal just does not cut it and you may end up with a lamp that just flickers rather than being smoothly regulated. Replacing the LED with a wire bridge will cure that. The LED is not really necessary. increase the 220 ohm resistor to 470 then

STOP: This circuit is attached to a 110-220 Voltage. Do not build this if you are not confident about what you are doing. Unplug it before coming even close to the PCB. The cooling plate of the Triac is attached to the mains. Do not touch it while in operation. Put it in a proper enclosure/container.

WAIT: Let me just add a stronger warning here: This circuit is safe if it is built and implemented only by people who know what they are doing. If you have no clue or if you are doubting about what you do, chances are you are going to be DEAD!

4N25 €0.25 or H11AA1 or IL250, IL251, IL252, LTV814 (see text in the next step)
Resistor 10k €0.10
bridge rectifier 400 Volt €0.30
2x 30 k resistor 1/2 Watt (resistors will probably dissipate 400mW max each €0.30
1 connector €0.20
5.1 Volt zenerdiode (optional)

Lamp driver
LED (Note: you can replace the LED with a wire bridge as the LED may sometimes cause the lamp to flicker rather than to regulate smoothly)
MOC3021 If you chose another type, make sure it has NO zero-crossing detection, I can't stress this enough DO NOT use e.g. a MOC3042
Resistor 220 Ohm €0.10 (I actually used a 330 Ohm and that worked fine)
Resistor 470 Ohm-1k (I ended up using a 560 Ohm and that worked well)
TRIAC TIC206 €1.20 or BR136 €0.50
1 connector €0.20

Piece of PCB 6x3cm
electric wiring

That is about €3 in parts

Remove these adsRemove these ads by Signing Up
1-40 of 481Next »
quocanh8321 hours ago

Can i change DB104 = DB107, 4N25 to 4N35?

diy_bloke (author)  quocanh833 hours ago

yes you can. the DB107 can take 1000 Volt. that is more than sufficient

nodoubtman7 days ago

AC Light Control
Uses up and down buttons to set levels
makes use of a timer interrupt to set the level of dimming

#include <IRremote.h>
#include <TimerOne.h> // Avaiable from

const int RECV_PIN = 6;

IRrecv irrecv(RECV_PIN);
decode_results results;
unsigned long last = millis();

// Hex code for remote control buttons
#define POWER 0x10EFD827
#define A 0x10EFF807
#define B 0x10EF7887
#define C 0x10EF58A7
#define UP 0x10EFA05F
#define DOWN 0x10EF00FF
#define LEFT 0x10EF10EF
#define RIGHT 0x10EF807F
#define SELECT 0x10EF20DF

volatile int i=0; // Variable to use as a counter of dimming steps. It is volatile since it is passed between interrupts
volatile boolean zero_cross=0; // Flag to indicate we have crossed zero
int AC_pin = 3; // Output to Opto Triac

int dim2 = 0; // led control
int dim = 128; // Dimming level (0-128) 0 = on, 128 = 0ff
int pas = 8; // step for count;
int freqStep = 65; // This is the delay-per-brightness step in microseconds. It allows for 128 steps
// If using 60 Hz grid frequency set this to 65

void setup() { // Begin setup
pinMode(AC_pin, OUTPUT); // Set the Triac pin as output

irrecv.enableIRIn(); // Start the IR receiver

attachInterrupt(0, zero_cross_detect, RISING); // Attach an Interupt to Pin 2 (interupt 0) for Zero Cross Detection
Timer1.initialize(freqStep); // Initialize TimerOne library for the freq we need
Timer1.attachInterrupt(dim_check, freqStep); // Go to dim_check procedure every 75 uS (50Hz) or 65 uS (60Hz)
// Use the TimerOne Library to attach an interrupt


void zero_cross_detect() {
zero_cross = true; // set flag for dim_check function that a zero cross has occured
i=0; // stepcounter to 0.... as we start a new cycle
digitalWrite(AC_pin, LOW);

// Turn on the TRIAC at the appropriate time
// We arrive here every 75 (65) uS
// First check if a flag has been set
// Then check if the counter 'i' has reached the dimming level
// if so.... switch on the TRIAC and reset the counter
void dim_check() {
if(zero_cross == true) {
if(i>=dim) {
digitalWrite(AC_pin, HIGH); // turn on light
i=0; // reset time step counter
zero_cross=false; // reset zero cross detection flag
else {
i++; // increment time step counter

void test_dimmer(){
if((dim_ac>=128) || (dim_ac<=0))

void loop() {

if (irrecv.decode(&results))

if (results.value == UP){
// If it's been at least 1/4 second since the last
if (millis() - last > 250) {
Serial.print("Dimming up --> ");
if (dim<127)
dim = dim + pas;
if (dim>127){

if (results.value == DOWN ){
// If it's been at least 1/4 second since the last
if (millis() - last > 250) {
Serial.print("Dimming down --> ");

if (dim>5)
dim = dim - pas;
if (dim<0){
} // loop

there's not dimming at all, IR receiver doesnt work with this code..

Do you think you can help me?

thank you sooo much!


diy_bloke (author)  nodoubtman7 days ago

you may have a timer conflict. I am not sure anymore what timer the receive function uses, I thought it was timer2, but not sure.
But I do know the IRremote library to have many problems when timers are used. Check your library and if necessary change the timer

nodoubtman9 days ago

Hi! Nice project. I tried to use IR remote control library to use it. But when i start the program, the lamp is turning on, and i cannot do anything with the remote .

can you help?

thank you!

diy_bloke (author)  nodoubtman9 days ago

Hi Marc.
kinda hard to say whjat is wrong as I have no idea what your program looks like. can you dim the lamp without an IR remote, like with the program i provided, so at least it is clear the circuit is OK?
As far as the remote goes, start with printing the dim values to your serial port so at least you know the commands are received.

Thank you for answering diy_bloke, i can send you the program with the ir code, just let me know what is wrong?

Where can i send you my program? at what email address?

thank you so much! :)


diy_bloke (author)  nodoubtman8 days ago

just send it in a private message in instructables

yes your program works very well :)

I just want to create and IR dimmer with your program , is it possible?

thank you!


Also how would the values be effected for a 120V 60Hz application? Like the 220 Vin resistors and anything else?

diy_bloke (author)  Ian.McElhenny8 days ago

yes, I describe that in the article

What is the purpose of the LED in series with the 220 ohm resistor for the dimmer signal? Is that just for a reference to see if the arduino is sending a signal?

diy_bloke (author)  Ian.McElhenny8 days ago

indeed it is. Ofcourse not much will be visible if you are sending a short signal, but one can use the circuit also to just switch on or off and then the LED has an indicator function. Should be red or green, with blue and white the voltage drop may be too big

Murphi28 days ago

Hi, after reading several times the post I decided to do it, but I have two questions..

-For make the pcb, we must print at paper something like this:

But I don´t see in fritzing this option..I select the PCB tab in the soft and I see this:

If I print, it prints as the second capture, no? What I have to do to see tracks in black ?? Sorry, but it´s first time that I use Fritzing...

-The triac that I have is BTA12-600 BW (12a and up to 800v), and I guess it works fine, right??

Thanks and regards

diy_bloke (author)  Murphi28 days ago

Go to 'File-Export-As Image-pdf'
Then just print that pdf on some glossy paper.
depending on your settings you may get more than 1 pdf: for the coppertracks, for the silkscreen etc. Pick the one that is right. as the ink layer is put on the copper, make sure it is the one that is mirrorred. THAT IS USUALLY NOT THE ONE THAT IS CALLED 'MIRRORRED'
Th epicture you showed is just the component lay-out
BTA12-600 is fine

Ok, thanks for your advice..In fritzing I select "maximum" for width..

I will make the circuit tomorrow and the print monday or tuesday (I don´t have laser printer and the correct paper..)

Thank you for your support and regards.

diy_bloke (author)  Murphi28 days ago

good luck

diy_bloke (author)  Murphi28 days ago

just a final piece of advice: the standard coppertracks in Fritzing are a bit narrow. Make them wider by selecting a larger track width

Where do you connect the zero-crossing pin in arduino
diy_bloke (author)  Tanishq Jaiswal1 month ago

that depends on the interrupt you want to use, but the example program I provided it should be attached to D2

muhammad861 month ago

Hi DIY bloke.Thanks becoz it works and furthermore it works with a LED lamp(the dimmable one).However i realised that the i value should be from i=10 to i=120 for incandescent light. It should not go to i=128 becoz it will ficker at i=128.Somewhere from i=120 to i=125 should be a good point to end. Please correct me if i am wrong.This is what i observed.

diy_bloke (author)  muhammad861 month ago

That is good to hear. You are lucky on the LED lamp as some work and some do no.
With regard to the lowest and higehst values, yes, because the zerocross detection is not an ideal narrow pulse and the period length of the grid may vary, choosing extreme values like <5 or >125 may give some flickering.
If you have time, add a picture in the 'I made it' section. I always like to see how peoples makes turn out

MuratE11 month ago

can you show me links to Arduino?

diy_bloke (author)  MuratE11 month ago

u asked this also in a pm. i replied there

choucsou1 month ago


Thanks for this tutorial.

You have two AC wires on input and two "AC" wires on output.

What if we try to deal with one only on each side ? This mean the light will be in serial with the dimmer. And that the zero crossing signal will be read on the "corrected" AC (by the triac).

So the triac and the lamp are in serial and the zero crossing detection is on the triac in parallel.

Do you think it could work ?

Please have a look at

You install it in serial with the lamp. The third wire is for command


diy_bloke (author)  choucsou1 month ago

sure that works. after all it is just a switch.

but.............. the zerocross signal needs to get input from both terminals onthe grid

MuratE11 month ago

can you show me links to Arduino?

MuratE11 month ago

can you show me links to Arduino?

andre66351 month ago


What is the maximum load power(W) for the bulb? Would it work with 2 or 3 incandescent bulbs connected in parallel in which all the bulbs would dim at the same time?

diy_bloke (author)  andre66351 month ago

The maximumload depends on the TRIAC. The TIC 206 can handle 4 amps RMS, so if you are using it 2ith 220 Volt, the max load is 880 Watt.
You can put as many incandescent bulbs in parallel as you like, as long as the total load does not exceed 880 Watts.
So 2 or 3 lamps of say 100 Watt is no problem at all

Thanks for the info. I will also add a radiator for the triac just to keep it safe.

Today I started to make holes in the PCB. I was using a 1 mm bit, and it seems large to me. Should I use an 0.8 mm bit? I also have an 0.6 mm one but I think it is too small for some holes. What drill bit sizes have you used? I tried to see in Fritzing the hole size, but I didn't found this option.

diy_bloke (author)  andre66351 month ago

I think I used 0.8

diy_bloke (author)  diy_bloke1 month ago


muhammad861 month ago

hi the ground is it common ground for the neutral wire and arduino ground?

diy_bloke (author)  muhammad861 month ago

No please never connect those. See my earlier reply

Hi thanks just needed to confirm.I kind of realised it when i saw the neutral wire not being connected to common ground on your diagram.Anyway i found out my MOC3052 is spoilt.I don know why maybe becoz i connected to common ground.

Also i realised that the optotriac and triac is not able to take huge voltages(240V).Is it ok to increase the resistance of the resistor connected to the optotriac(right now u only put 1k)? and is it ok to connect a resistor to the MT2 terminal of the triac, T1(CONNECTED directly to mains supply) .

diy_bloke (author)  muhammad861 month ago

Muhammed, I do not have much experience with the 3052 but it should be able to take 600 volts on its output leads.
The TIC206 should be able to work with a 1k gate resistor. In fact I am using a 560 Ohm resistor.
I am not sure why or where you exactly want to connect the resistor to M2 to. Yes, M2, but leading where?
The circuit as I supplied is proven to work many times so I am not sure why you would want to change it.

Hi thanks just needed to confirm.I kind of realised it when i saw the neutral wire not being connected to common ground on your diagram.Anyway i found out my MOC3052 is spoilt.I don know why maybe becoz i connected to common ground.

Also i realised that the optotriac and triac is not able to take huge voltages(240V).Is it ok to increase the resistance of the resistor connected to the optotriac(right now u only put 1k)? and is it ok to connect a resistor to the MT2 terminal of the triac, T1(CONNECTED directly to mains supply) .

muhammad861 month ago

Oh man my comment also went off.

Hi DIY_bloke,Is the ground from arduino and the neutral wire connected as a common ground or do you SEPARATE them?

diy_bloke (author)  muhammad861 month ago

indeed sometimes I have to press 'make comment' twice. If I dont pay attention, the comment gets lost.


the optocoplers are there to completely isolate the arduino from the mains voltage.
Please reread everything I wrote in bold as well as what I write about galvanic isolation.

At best you will blow up yr Arduino but at worst you will be toast!!

1-40 of 481Next »