220V AC Lamp dimmer with PIC16F877A and TRIAC

This post shows how to control 220V AC lamp brightness using PIC16F877A microcontroller and one TRIAC. The TRIAC is a three terminal component (terminal 1, terminal 2 and gate) that is used to control the current following to the load. The TRIAC is fired by applying a pulse to the gate.

Hardware Required:

  • PIC16F877A microcontroller
  • BT136 TRIAC  —  datasheet
  • 220V AC lamp
  • LM393 (or LM339) comparator
  • Optocoupler (MOC3020, MOC3021, MOC3022, MOC3023 or equivalent)  —  datasheet
  • 2 x 1N4007 diode (or 1N4001)
  • 2 x 220k ohm resistor
  • 10k ohm potentiometer
  • 2 x 10k ohm resistor
  • 470 ohm resistor
  • 180 ohm resistor
  • 100 ohm resistor
  • 0.01µF capacitor
  • 5V source
  • Breadboard
  • Jumper wires

PIC16F877A dimmer with triac AC lamp dimmer

AC Lamp dimmer with PIC16F877A and TRIAC circuit:
The following image shows project circuit schematic diagram.

PIC16F877A light dimmer BT136 TRIAC 220V AC lamp

All grounded terminals are connected together.

In this project I used the LM393 (dual comparator IC) for zero crossing detection, the LM339 quad comparator IC also can be used. An optocoupler can be used for the same purpose( zero crossing detection) but I think the comparator is much better. The two diodes which are connected between the non-inverting input (+) and the inverting input (-) of the comparator are used to limit the voltage across the 2 pins. The output of the LM393 (or LM339) is an open collector, so I added a 10k ohm resistor between +5V and comparator output (pull-up resistor).
The output of the analog comparator is connected to RB0 pin of the PI16F877A which is also external interrupt pin of the microcontroller (INT).

The optocoupler (MOC302x) is used for firing the BT136 triac, its LED is connected to PIC16F877A RD2 pin through 180 ohm resistor. I chose the value 180 ohm in order to get a current of about 20mA (current passes through the optocoupler LED).

The 10k potentiometer is used to control the firing angle and therefore the brightness of the lamp.

In this project the PIC16F877A runs with crystal oscillator of 20MHz.

AC Lamp dimmer with PIC16F877A and TRIAC C code:
The C code below is for CCS C compiler, it was tested with version 5.051.

The frequency of my AC source is 50Hz which means the period is 20ms, so the half wave period is 10ms = 10000µs.
The resolution of PIC16F877A microcontroller ADC module is 10 bits which means the digital value can vary between 0 and 1023.

I used PIC16F877A external interrupt (INT) to detect the zero-crossing events, so when there is a zero crossing event (falling or rising), the PIC16F877A interrupted and after some delay (angle alpha) it fires the triac.

AC Lamp dimmer with PIC16F877A and TRIAC video:

21 thoughts on “220V AC Lamp dimmer with PIC16F877A and TRIAC”



  2. I get errors [SPICE] TRAN: Timestep too small; timestep = 1.25e-019: trouble with node #00040.
    i set Frequency on AC is 50Hz, is it right ?

        1. Then you’ll get a delay between zero crossing events and falling of the opto-isolator terminal, you can test it using an oscilloscope!
          A comparator (such as LM393) gives better results and with the two 220k ohm resistors there’s no dangerous, also the circuit should be well grounded.

  3. please can you convert the program to assembly language or in the syntax mplab compiler?b/se i need the program to compile by mplab,bcoz i have only mplab compiler.

  4. Spiros Vardaramatos

    I am a bit nervous about “All grounded terminals are connected together.” It is something i dont like so i desided to put a rectifier bridge with 4n25 optocoupler and a pull up resistor on the collector.
    if i do that i have to change the
    if(INTEDG) INTEDG = 0;
    else INTEDG = 1;
    with the follow -> clear_interrupt(int_ext)
    and also do i have to add into the main routine -> ext_int_edge(H_to_L)
    or it is not necessary?

    1. Yes you can do that but I don’t know if it will give you a good result!
      Try with the following:
      Remove the 2 lines below:
      if(INTEDG) INTEDG = 0;
      else INTEDG = 1;

      In the main function replace: enable_interrupts(INT_EXT); by: enable_interrupts(INT_EXT_L2H);

      1. Spiros Vardaramatos

        Also, doesnt it need in the interrupt service routine the follow clear_interrupt(int_ext)

        void ext_isr(void)
        ZC = 1;

        1. You can remove clear_interrupt(int_ext); because CCS C compiler already do that(clears the interrupt flag) unless you add NOCLEAR after #INT_EXT.

  5. Spiros Vardaramatos

    Thanks for the reply Simple Projects. I have a couple of questions. I am new to ccs c compiler so
    excuse me if my questions are too simple. I have to say that i do have looked at the manual first but i have been confused. Why do we use the below two commands?
    #bit INTEDG = OPTION_REG.6
    Are there for this command? -> if(INTEDG)
    if yes, why is that? I mean when we define the chip “#include ” doesnt the compiler already know that?

    What exactly does this command? #use fast_io(d)


    1. The directive #use fast_io causes the compiler to perform I/O operations without programming of the direction register (see CCS C manual).
      INTEDG bit (or OPTION_REG bit number 6) decides the external interrupt edge (more details in PIC16F877A datasheet).

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top