ESP-32s Dimmer using Triac

hy guyz need some project guidance i'm just completed an ir remote base dimmer for Arduino nano.
also timerOne.h not supported in espboard and i don't know how use ticker.h
i'm just started with esp-32S so i'm triying to making an wifi sever dimmer for esp32 but i don't have that experience for PWM Control with triac please help me out with it

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Insert your own wifi network SSID and Password
const char *ssid = "SQUIRREL IT DEVELOPERS";
const char *password = "squirrelit2912";

const int ledPin = 4;

String pwmSliderValue = "0";

// Set Desired PWM Settings
const int frequencyHz = 5000;
const int pwmChannel = 0;
const int resolution = 8;

const char *INPUT_PARAMETER = "value";

// Instatiate the AsyncWebServer object on port 80
AsyncWebServer webServer(80);

// Declare the webpage
// HTML comments look like this <! comment in between here >
const char htmlCode[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <!  define meta data >
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <! define the style CSS of your page >
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h1 {font-size: 2.9rem;}
    h2 {font-size: 2.1rem;}
    p {font-size: 1.9rem;}
    body {max-width: 400px; margin:0px auto; padding-bottom: 30px;}
    .slider { -webkit-appearance: none; margin: 14px; width: 400px; height: 15px; border-radius: 5px; background: #39a6de; outline: none; -webkit-transition: .2s; transition: opacity .2s;}
    .slider::-webkit-slider-thumb {-webkit-appearance: none; appearance: none; width: 25px; height: 25px; border-radius: 12px; background: #f74d4d; cursor: pointer;}
    .slider::-moz-range-thumb { width: 25px; height: 25px; border-radius: 12px; background: #F74D4D; cursor: pointer; } 
  </style>
</head>

<body>
  <! Edit the pages your heading 1 >
  <h1>Mish Mash Labs</h1>

  <! Adding the logo here >
  <img src="https://yt3.ggpht.com/a/AATXAJxkbzn1yJWmRJRNOJ5uTRH4KDZxt761H4ADQIjz=s288-c-k-c0xffffffff-no-rj-mo">
  
  <! Edit the pages your heading 2 >
  <h2>ESP32 PWM Slider</h2>
  <h2>LED Brightness</h2>
  
  <! Displays the value of the slider >
  <p><span id="textSliderValue">%SLIDERVALUE%</span> &#37</p>
  <! displays the range of the slider 0 - 100 in steps of 1 >
  <p><input type="range" onchange="updateSliderPWM(this)" id="pwmSlider" min="0" max="100" value="%SLIDERVALUE%" step="1" class="slider"></p>
<script>
function updateSliderPWM(element) {
  var pwmSliderValue = document.getElementById("pwmSlider").value;
  document.getElementById("textSliderValue").innerHTML = pwmSliderValue;
  console.log(pwmSliderValue);
  var httpRequest = new XMLHttpRequest();
  httpRequest.open("GET", "/slider?value="+pwmSliderValue, true);
  httpRequest.send();
}
</script>
</body>
</html>
)rawliteral";

// Replaces the placeholder with the button in your web page
String updateButton(const String &var)
{
  if (var == "SLIDERVALUE")
  {
    return pwmSliderValue;
  }
  return String();
}

void setup()
{
  // Begine Serial Communications over USB
  Serial.begin(115200);
  //  TIM_DIV1 = 80 ticks/uS  80000UL = 1mS
  // configure LED PWM functionalitites
  // use ledcSetup(channel, frequency, resolution) to configure the PWM signal
  // ledcSetup(ESP PWM channel not GPIO pin, frequency in Hz, resolution (1-16 bit))
  ledcSetup(pwmChannel, frequencyHz, resolution);

  // attach the channel to the GPIO to be controlled
  /*
   * The PWM channel is not the same as a physical pin. 
   * The ESP32 contains 16 independent channels. 
   * Each of those can be assigned to any PWM-capable pin. 
   * This means that you can use any 16 GPIOs to generate PWM output.
   */
  // use ledcAttachPin(GPIO pin, PWM channel) to bind the GPIO number and the PWM channel number that you want to use
  ledcAttachPin(ledPin, pwmChannel);

  ledcWrite(pwmChannel, pwmSliderValue.toInt());

  // Connect to your Wi-Fi network
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Print the IP Address of your device
  Serial.println(WiFi.localIP());

  // Detail the route for root / web page
  webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
               { request->send_P(200, "text/html", htmlCode, updateButton); });

  // Send a GET request to <ESP_IP>/slider?value=<inputMessage>
  webServer.on("/slider", HTTP_GET, [](AsyncWebServerRequest *request)
               {
                 String inputMessage;
                 // GET input1 value on <ESP_IP>/slider?value=<inputMessage>
                 if (request->hasParam(INPUT_PARAMETER))
                 {
                   inputMessage = request->getParam(INPUT_PARAMETER)->value();
                   pwmSliderValue = inputMessage;
                   ledcWrite(pwmChannel, pwmSliderValue.toInt());
                 }
                 else
                 {
                   inputMessage = "No message sent";
                 }
                 Serial.println(inputMessage);
                 request->send(200, "text/plain", "OK");
               });

  // Start server (remembering its on port 80)
  webServer.begin();
}

void loop()
{
}

here i can dim my built in led in pin 2 but i know it's not works with triac for ac dimmer circuit

PWM is not used for AC circuit control but is controlled by phase angular control. Only work with an AC main if you really understand what you are doing because it is potentially dangerous and / or deadly.

1 Like

i already complete same thing on ir remote for ac dimmer in arduino nano but thanks buddy
here i try this one but my ac lamp is blinking at 100 and at 0 it's looks like turned of

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

#define triacPulse 4 //D2
#define ZVC 12 //D6

int dimming;
int x = 0;
const char *INPUT_PARAMETER = "value";
String pwmSliderValue = "0";


const char *ssid = "";
const char *password = "";


AsyncWebServer webServer(80);

const char htmlCode[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <!  define meta data >
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <! define the style CSS of your page >
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h1 {font-size: 2.9rem;}
    h2 {font-size: 2.1rem;}
    p {font-size: 1.9rem;}
    body {max-width: 400px; margin:0px auto; padding-bottom: 30px;}
    .slider { -webkit-appearance: none; margin: 14px; width: 400px; height: 15px; border-radius: 5px; background: #39a6de; outline: none; -webkit-transition: .2s; transition: opacity .2s;}
    .slider::-webkit-slider-thumb {-webkit-appearance: none; appearance: none; width: 25px; height: 25px; border-radius: 12px; background: #f74d4d; cursor: pointer;}
    .slider::-moz-range-thumb { width: 25px; height: 25px; border-radius: 12px; background: #F74D4D; cursor: pointer; } 
  </style>
</head>

<body>
  <! Edit the pages your heading 1 >
  <h1>Mish Mash Labs</h1>

  <! Adding the logo here >
  <img src="https://yt3.ggpht.com/a/AATXAJxkbzn1yJWmRJRNOJ5uTRH4KDZxt761H4ADQIjz=s288-c-k-c0xffffffff-no-rj-mo">
  
  <! Edit the pages your heading 2 >
  <h2>ESP32 PWM Slider</h2>
  <h2>LED Brightness</h2>
  
  <! Displays the value of the slider >
  <p><span id="textSliderValue">%SLIDERVALUE%</span> &#37</p>
  <! displays the range of the slider 0 - 100 in steps of 1 >
  <p><input type="range" onchange="updateSliderPWM(this)" id="pwmSlider" min="0" max="100" value="%SLIDERVALUE%" step="1" class="slider"></p>
<script>
function updateSliderPWM(element) {
  var pwmSliderValue = document.getElementById("pwmSlider").value;
  document.getElementById("textSliderValue").innerHTML = pwmSliderValue;
  console.log(pwmSliderValue);
  var httpRequest = new XMLHttpRequest();
  httpRequest.open("GET", "/slider?value="+pwmSliderValue, true);
  httpRequest.send();
}
</script>
</body>
</html>
)rawliteral";

void acon();
void setup();
void loop();

String updateButton(const String &var)
{
  if (var == "SLIDERVALUE")
  {
    return pwmSliderValue;
  }
  return String();
}

void acon()
{
  // Serial.println("REad");

  delayMicroseconds(dimming); // read AD0
  digitalWrite(triacPulse, HIGH);

  delayMicroseconds(50); //delay 50 uSec on output pulse to turn on triac
  digitalWrite(triacPulse, LOW);

  // Serial.println(digitalRead(triacPulse));
}

  void setup()
{

  pinMode(ZVC, INPUT_PULLUP);
  pinMode(triacPulse, OUTPUT);
  Serial.begin(9600);

  attachInterrupt(digitalPinToInterrupt(ZVC), acon, FALLING); // attach Interrupt at PIN2

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Print the IP Address of your device
  Serial.println(WiFi.localIP());

  // Detail the route for root / web page
  webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
               { request->send_P(200, "text/html", htmlCode, updateButton); });

  // Send a GET request to <ESP_IP>/slider?value=<inputMessage>
  webServer.on("/slider", HTTP_GET, [](AsyncWebServerRequest *request)
               {
                 String inputMessage;
                 // GET input1 value on <ESP_IP>/slider?value=<inputMessage>
                 if (request->hasParam(INPUT_PARAMETER))
                 {
                   inputMessage = request->getParam(INPUT_PARAMETER)->value();
                   pwmSliderValue = inputMessage;
                   //ledcWrite(pwmChannel, pwmSliderValue.toInt());
                   dimming = map(pwmSliderValue.toInt(), 0, 100, 7200, 200);
                 }
                 else
                 {
                   inputMessage = "No message sent";
                 }
                 Serial.println(inputMessage);
                 request->send(200, "text/plain", "OK");
               });

  // Start server (remembering its on port 80)
  webServer.begin();
}



void loop()
{
}

Looks like a timing issue. How did you work out the range of the dimming variable? Have you put an oscilloscope on the AC line and the dimming pin to see how your triac pulses align with the AC cycle? That should pretty much tell the full story.

Quoted in bold for emphasis. Very true, this.

Also, I'd always think twice before TRIAC dimming a load. It creates nasty junk on your AC lines/grid. Since dimming is used in many cases for lighting purposes and we tend to opt for LED lighting these days, PWM-ing a DC power source makes much more sense at this day and age.

1 Like

This is your calculation of the delay :

dimming = map(pwmSliderValue.toInt(), 0, 100, 7200, 200);

but within the ISR you do

void acon()
{
  // Serial.println("REad");

  delayMicroseconds(dimming); // read AD0
  digitalWrite(triacPulse, HIGH);

  delayMicroseconds(50); //delay 50 uSec on output pulse to turn on triac
  digitalWrite(triacPulse, LOW);

  // Serial.println(digitalRead(triacPulse));
}

Now you delay holds up the processor, and has a maximum of 7.2ms
It is anyway a bad idea to use delay in an ISR, that is why you use a timer ISR to trigger a counter to determine when to flick the switch.

If you want to make something that you can just 'drop-in' on an existing switch, triac dimming is a better option, but you have to really know what you are doing. Some AC-lamps are not really suitable for triac dimming, and filament LEDS have capacitors in them that can influence the holding current to the triac, causing it to turn of pre-maturely.

1 Like

well i don't have oscilloscope and also i found a code from you tube which i modify a little for web server

here is original code from youtube

#define BLYNK_PRINT Serial
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>

#define triacPulse 4 //D2
#define ZVC 12 //D6

int Slider_Value;
int dimming;
int x = 0;

char auth[] = "AUTH TOKEN";        // You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon) in the Blynk app or check your email for auth token.

char ssid[] = "SSID";         // Your WiFi credentials.
char pass[] = "PASS";    // Set password to "" for open networks.


BLYNK_WRITE(V1)   // function to assign value to variable Slider_Value whenever slider changes position
{
  Slider_Value = param.asInt(); // assigning incoming value from pin V1 to a variable
}


void setup()
{

  pinMode(ZVC, INPUT_PULLUP);
  //digitalWrite(2, INPUT_PULLUP); // pull up
  pinMode(triacPulse, OUTPUT);
  Serial.begin(9600);
  Blynk.begin(auth, ssid, pass);
  attachInterrupt(digitalPinToInterrupt(ZVC), acon, FALLING); // attach Interrupt at PIN2
}



void loop()
{
  Blynk.run();
  // When the switch is closed
  dimming = map(Slider_Value, 0, 100, 7200, 200); //0.2ms 7.2 ms

}

void acon()
{
  // Serial.println("REad");

  delayMicroseconds(dimming); // read AD0
  digitalWrite(triacPulse, HIGH);

  delayMicroseconds(50);  //delay 50 uSec on output pulse to turn on triac
  digitalWrite(triacPulse, LOW);

  // Serial.println(digitalRead(triacPulse));
}

The diagram surely has to be clearer that the AC source is not the same in both cases. The 4N25 opto coupler would not last very long with rectified mains voltage passing through it's led with only a 165 ohm series resistor:

1 Like

ok i remove delay in isr and i do try again but no response even flickering is stop completely maybe i do make some mistake in code

instead of 4n25 i use 4n35 but this one works fine with arduino nano and uno but i'm triying to make same thing with esp32 web server

This looks like a circuit for a 5 volt arduino to control a mains circuit using an infra red remote control. Can you post the code which corresponds to that circuit.

If you are to use that as the basis for a Blynk controlled system, you'll have to make a number of changes and then build it in stages which you can test separately.

Probably, it would be best to get the circuit to perform with the ESP32 exactly as it does with the 5 volt arduino, adapting it as necessary.

Stage 2 would then be to integrate Blynk to replace the remote control.

1 Like

yes it's for ir remote control circuit 5v for nano as you describe
here is code
well arduino have TimerOne.h library and esp32 boar are not support that library and i found on internet there is ticker.h library for that but i don't have experience with it

//squirrel IT for quary mail us on squirrelit2912@gmail.com
#include "IRremote.h"

int receiver = 3; 
IRrecv irrecv(receiver);           
decode_results results;            



#include <TimerOne.h>           
volatile int i=0;               // Variable to use as a counter
volatile boolean zero_cross=0;  // Boolean to store a "switch" to tell us if we have crossed zero
int AC_pin = 5;                 // Output to Opto Triac

int dim2 = 0;                   // led control
int dim = 128;                  // Dimming level (0-128)  0 = on, 128 = 0ff                  
int freqStep = 75;    // This is the delay-per-brightness step in microseconds.

void setup() {   
  irrecv.enableIRIn(); // Start the IR receiver (classic remote)
  pinMode(AC_pin, OUTPUT);                          // Set the Triac pin as output
  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);      
}

void zero_cross_detect() 
{    
  zero_cross = true;               // set the boolean to true to tell our dimming function that a zero cross has occured
  i=0;
  digitalWrite(AC_pin, LOW);
}                                 
// Turn on the TRIAC at the appropriate time

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
    } 
    else {
      i++;  // increment time step counter                     
}}}                                      


void translateIR() // takes action based on IR code received
{
  switch(results.value)
  {
  case 33464415:  
    {
   
    if (dim<127)  
   {
    dim = dim + 8;
    if (dim>127) 
    {
      dim=128; // in vechiul sketch era 127
    }
    }}
    
    break;

  case 33448095:  
    {
    
      {
  if (dim>5)  
  {
     dim = dim - 8;
  if (dim<0) 
    {
      dim=0;  // in vechiul sketch era 1
    } }}}
    break;
  }}
  
void loop() {  
 if (irrecv.decode(&results)) // have we received an IR signal?
  {
    translateIR(); 
    irrecv.resume(); // receive the next value
  }  

}

It looks like you could simply replace the TimerOne part with the following code:

#include <Ticker.h>  //Ticker Library

Ticker dim_checker ;

int freqStep = 75; 

void setup() {
  . . . 
  dim_checker.attach_ms( freqStep, dim_check ) ;
  . . .
}


void loop() {
  . . .
}


void ICACHE_RAM_ATTR dim_check() {
  . . . 
  // your dim_check() code here
  . . .
}

Based on ESP8266 Timer and Ticker Example | Circuits4you.com but not tested.

1 Like

Normally speaking for 230 AC i would use in series one on either side of the 4n25 2x 33K 1W (Or 2x in series 3x 100K in parallel 1/4W) Putting a coil to convert the voltage would defy the purpose of trying to recognize the zero-crossing, due to the lag of the coil.

This does look more correct. You can probably reduce the freqStep for higher resolution

1 Like

is it right

//#define BLYNK_PRINT Serial
#include <WiFi.h>
#include <AsyncTCP.h>
//#include "BlynkSimpleEsp32.h"
#include <ESPAsyncWebServer.h>
#include "Ticker.h"
Ticker dim_checker;

int freqStep = 75;

#define triacPulse 4 //D2
#define ZVC 12 //D6

//int Slider_Value;
int dimming;
int x = 0;
const char *INPUT_PARAMETER = "value";
String pwmSliderValue = "0";

//char auth[] = "AUTH TOKEN";        // You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon) in the Blynk app or check your email for auth token.

const char *ssid = "";
const char *password = "";

//BLYNK_WRITE(V1)   // function to assign value to variable Slider_Value whenever slider changes position
//{
// Slider_Value = param.asInt(); // assigning incoming value from pin V1 to a variable
//}
AsyncWebServer webServer(80);

const char htmlCode[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <!  define meta data >
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <! define the style CSS of your page >
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h1 {font-size: 2.9rem;}
    h2 {font-size: 2.1rem;}
    p {font-size: 1.9rem;}
    body {max-width: 400px; margin:0px auto; padding-bottom: 30px;}
    .slider { -webkit-appearance: none; margin: 14px; width: 400px; height: 15px; border-radius: 5px; background: #39a6de; outline: none; -webkit-transition: .2s; transition: opacity .2s;}
    .slider::-webkit-slider-thumb {-webkit-appearance: none; appearance: none; width: 25px; height: 25px; border-radius: 12px; background: #f74d4d; cursor: pointer;}
    .slider::-moz-range-thumb { width: 25px; height: 25px; border-radius: 12px; background: #F74D4D; cursor: pointer; } 
  </style>
</head>

<body>
  <! Edit the pages your heading 1 >
  <h1>Mish Mash Labs</h1>

  <! Adding the logo here >
  <img src="https://yt3.ggpht.com/a/AATXAJxkbzn1yJWmRJRNOJ5uTRH4KDZxt761H4ADQIjz=s288-c-k-c0xffffffff-no-rj-mo">
  
  <! Edit the pages your heading 2 >
  <h2>ESP32 PWM Slider</h2>
  <h2>LED Brightness</h2>
  
  <! Displays the value of the slider >
  <p><span id="textSliderValue">%SLIDERVALUE%</span> &#37</p>
  <! displays the range of the slider 0 - 100 in steps of 1 >
  <p><input type="range" onchange="updateSliderPWM(this)" id="pwmSlider" min="0" max="100" value="%SLIDERVALUE%" step="1" class="slider"></p>
<script>
function updateSliderPWM(element) {
  var pwmSliderValue = document.getElementById("pwmSlider").value;
  document.getElementById("textSliderValue").innerHTML = pwmSliderValue;
  console.log(pwmSliderValue);
  var httpRequest = new XMLHttpRequest();
  httpRequest.open("GET", "/slider?value="+pwmSliderValue, true);
  httpRequest.send();
}
</script>
</body>
</html>
)rawliteral";


String updateButton(const String &var)
{
  if (var == "SLIDERVALUE")
  {
    return pwmSliderValue;
  }
  return String();
}

void ICACHE_RAM_ATTR dim_check()
{
  // Serial.println("REad");

  delayMicroseconds(dimming); // read AD0
  digitalWrite(triacPulse, HIGH);

  //delayMicroseconds(50); //delay 50 uSec on output pulse to turn on triac
  digitalWrite(triacPulse, LOW);

  // Serial.println(digitalRead(triacPulse));
}

  void setup()
{

  pinMode(ZVC, INPUT_PULLUP);
  //digitalWrite(2, INPUT_PULLUP); // pull up
  pinMode(triacPulse, OUTPUT);
  Serial.begin(9600);
  dim_checker.attach_ms(freqStep, dim_check);
  //Blynk.begin(auth, ssid, pass);

  attachInterrupt(digitalPinToInterrupt(ZVC), dim_check, FALLING); // attach Interrupt at PIN2

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Print the IP Address of your device
  Serial.println(WiFi.localIP());

  // Detail the route for root / web page
  webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
               { request->send_P(200, "text/html", htmlCode, updateButton); });

  // Send a GET request to <ESP_IP>/slider?value=<inputMessage>
  webServer.on("/slider", HTTP_GET, [](AsyncWebServerRequest *request)
               {
                 String inputMessage;
                 // GET input1 value on <ESP_IP>/slider?value=<inputMessage>
                 if (request->hasParam(INPUT_PARAMETER))
                 {
                   inputMessage = request->getParam(INPUT_PARAMETER)->value();
                   pwmSliderValue = inputMessage;
                   //ledcWrite(pwmChannel, pwmSliderValue.toInt());
                   dimming = map(pwmSliderValue.toInt(), 0, 100, 7200, 200);
                 }
                 else
                 {
                   inputMessage = "No message sent";
                 }
                 Serial.println(inputMessage);
                 request->send(200, "text/plain", "OK");
               });

  // Start server (remembering its on port 80)
  webServer.begin();
}



void loop()
{

}

Th this point I'd walk away, to be honest. Its stumbling blindly in the dark without one, especially for this kind of circuit.
I'd consider getting a decent USB scope; they're not insanely expensive anymore.

I don't know. The code sample with ticker was only to show you how to adapt your Uno/Nano IR (infra red) controlled dimmer sketch to use ticker as part of a conversion to ESP32.

You have not shown a schematic which has been adapted to the ESP32. The schematic which you have shown for the IR based dimmer needs some work before it could be used with an ESP32.