Short answer: LED flickering with an AC dimmer has three distinct root causes. Types 1 and 2 are caused by TRIAC architectural incompatibility with LED lamps — the hardware fix is a MOSFET AC dimmer (trailing edge, built for LED). If you use a TRIAC dimmer, switch to a Philips/Osram/Ikea lamp and keep brightness above 20%. Type 3 is ESP32 timing disruption from WiFi/BLE — fixed by the rbdimmerESP32 library or DimmerLink (applies to any dimmer type).
The Problem
You set up your dimmer, the code works, zero-cross detection is correct — but the LED lamp still flickers. It's most noticeable below 40–50% brightness. You suspect something is wrong with interrupts or timing, but code changes don't help.
The key insight: flickering has multiple root causes that need different fixes. Code changes only help with Type 3 (ESP32 timing). For Types 1 and 2, you need to change the dimmer type (to MOSFET) or the lamp — not the code.
Symptoms by flicker type:
Type 1 (LED driver / TRIAC architecture):
- «Cheap dimmable LEDs use the dimmed mains directly to PWM the LED which gives you noticeable flicker» (Arduino Forum, 2019)
- Flicker visible below 40–50% brightness; lamp is fine at 100%
- Code changes make no difference — same effect at any settings
- Even at 50%, cheap lamps flicker visibly
Type 2 (TRIAC holding current):
- Random flashes at low brightness or with a light load
- Buzzing/humming at brightness below 20%
- Single 5–7 W LED lamp without additional load
Type 3 (ESP32 timing):
- Irregular, unpredictable flicker — not synchronized with the mains
- Disappears when you disconnect WiFi (
WiFi.disconnect()) - Appears under active MQTT/HTTP load on ESP32
- Quality lamps also flicker — changes when you modify the code
TRIAC vs MOSFET: Why Dimmer Type Matters More Than Code
Types 1 and 2 are not bugs in the lamp or the code. They are the result of TRIAC AC dimmers being designed for incandescent bulbs — not for LED lamps.
| Parameter | TRIAC AC Dimmer | MOSFET AC Dimmer |
|---|---|---|
| Switching method | Leading edge (sharp spike at start of half-cycle) | Trailing edge (smooth cut at end) |
| Designed for | Incandescent, halogen, resistive heaters | LED lamps, low-voltage transformers |
| Holding current | Yes — minimum current to stay on | No — works with any load |
| LED flicker | Types 1 and 2 with cheap lamps | None — trailing edge is LED-compatible |
| Dimming range with LED | 20–95% (unstable below that) | 5–95% |
MOSFET AC dimmer eliminates Types 1 and 2 at the hardware level:
- Trailing edge opens the load at the start of each half-cycle: the LED driver sees full voltage immediately and charges its buffer capacitors — no 100/120 Hz pulsation.
- No holding current: works stably with loads as small as 5 W.
Type 3 (ESP32 timing) applies equally to both dimmer types — it is not related to the switching method, only to MCU interrupt accuracy.
Root Causes
Detailed theory of zero-cross detection and TRIAC operation: AC Dimmer: Zero-Cross and TRIAC — How It Works.
Type 1: LED Driver Flicker (TRIAC-Specific)
TRIAC cuts the AC sine wave from the leading edge: in each half-cycle, the first portion is cut off and current flows only in the second portion. At low brightness — only a brief current pulse at the end of each half-cycle.
A quality LED driver smooths these pulses through capacitors and runs its own high-frequency PWM (1–4 kHz). Result: steady, smooth light.
A cheap LED driver rectifies the current without buffering. The LED receives pulsating DC at 100 Hz (50 Hz mains) or 120 Hz (60 Hz mains). This frequency is visible to the human eye — flicker is obvious.
A MOSFET AC dimmer (trailing edge) opens the load at the start of each half-cycle: the LED driver gets full voltage immediately and charges its capacitors. The 100 Hz pulsation disappears — even with a cheap lamp.
Type 2: TRIAC Holding Current (TRIAC-Specific)
Every TRIAC has a holding current parameter — the minimum current needed to remain conducting. If load current drops below this value, the TRIAC turns off randomly — before the zero-crossing. Result: missed half-cycles, random flashes, and buzzing at low brightness.
MOSFET has no holding current — it is a transistor, not a thyristor. Stable operation even with loads as small as 5 W.
Type 3: ESP32 Timing Disruption (Dimmer-Independent)
TRIAC control requires ±50 µs accuracy: the MCU must fire the TRIAC exactly at the calculated delay after zero-cross. The ESP32 WiFi stack and RTOS tasks can delay ISR execution by 200–500 µs — flickering appears under active WiFi.
Sources of delay:
- WiFi / BLE stack temporarily blocks interrupts while processing packets
- Flash memory access (OTA, SPIFFS, Serial) — freeze for 10–100 ms
- ISR not in IRAM: ESP32 fetches it from Flash on every call — adds delay
loop()and WiFi running on the same core (Core 1) compete for CPU
Key difference from Types 1 and 2: the flicker is irregular, disappears
at WiFi.disconnect(), and changes when you modify the code — not when
you change the lamp.
Detailed breakdown: How to Fix ESP32 AC Dimmer Flickering.
Solutions
🔑 Best Solution: MOSFET AC Dimmer
Eliminates Types 1 and 2 at the hardware level — no lamp replacement, no code tuning required.
MOSFET AC dimmer (trailing edge) is purpose-built for LED lamps. Trailing edge switching gives the LED driver a clean start to each half-cycle — 100/120 Hz pulsation disappears. No holding current — stable operation with a single 5–7 W lamp.
When MOSFET AC dimmer is the right choice:
Type 3 (ESP32 timing) applies to MOSFET dimmers too — ISR accuracy is equally important. Use rbdimmerESP32 or DimmerLink to solve it.
🟢 Beginner: TRIAC + Quality Lamp or DimmerLink
Using a TRIAC dimmer? Two options: replace the lamp or use DimmerLink.
Option 1: Replace the lamp with a model that has a quality LED driver:
- Philips WarmGlow, SceneSwitch, Master LED
- Osram CLASSIC A, SUPERSTAR
- Ikea TRÅDFRI (dimmable range)
Look for on the box: «TRIAC dimmable», «phase-cut compatible», «leading edge compatible».
Option 2: Minimum load.
At least 15–20 W. For lighter loads, connect a ballast resistor in parallel: 100–150 Ω / 20 W.
Option 3: I2C control without ISR — DimmerLink.
If you use Raspberry Pi, ESP32-S2/C3/H2, or simply don't want to deal with interrupts — DimmerLink controls TRIAC via I2C without writing any ISR code.
When to choose DimmerLink:
// DimmerLink via I2C — no ISR, no zero-cross in your code
// Works with: Arduino, ESP32, Raspberry Pi
// Docs: https://www.rbdimmer.com/docs/dimmerlink-I2CCommunication
#include <Wire.h>
#define DIMMER_ADDR 0x50
#define REG_LEVEL 0x10
// Set brightness (20–95% for LED lamps with TRIAC)
void setLevel(uint8_t level) {
Wire.beginTransmission(DIMMER_ADDR);
Wire.write(REG_LEVEL);
Wire.write(level);
Wire.endTransmission();
}
void setup() {
Wire.begin();
setLevel(50); // 50% brightness
}
void loop() {}Result: stable brightness without flicker, provided you use a quality lamp.
🔵 Advanced: Fix ESP32 Timing (Type 3)
Type 3 applies to any AC dimmer — TRIAC or MOSFET.
Identify the flicker type first — otherwise the fix won't help:
| Symptom | Type | Fix |
|---|---|---|
| Same flicker with any code | Type 1 — LED driver | MOSFET AC dimmer or replace lamp |
| Random flashes, buzz below 20% | Type 2 — TRIAC holding | MOSFET AC dimmer or min 20% |
| Changes with WiFi on/off | Type 3 — ESP32 timing | IRAM_ATTR / rbdimmerESP32 |
Fixing ESP32 Timing (Type 3)
If flicker disappears at WiFi.disconnect() — the problem is ISR timing.
// Problem: ISR not in IRAM — ESP32 reads it from Flash on each call
// Fix: IRAM_ATTR places ISR in RAM permanently
// ❌ Wrong (no IRAM_ATTR — flicker under WiFi load):
void zeroCrossISR() {
triggerTriac();
}
// ✅ Correct (IRAM_ATTR — ISR always in RAM, no Flash delays):
void IRAM_ATTR zeroCrossISR() {
triggerTriac();
}For rbdimmerESP32, IRAM_ATTR is applied automatically inside the library.
Core separation further improves stability:
// rbdimmerESP32: ISR runs on Core 0 (system core)
// loop() and WiFi run on Core 1 — no CPU competition
#include "rbdimmerESP32.h"
// ... rest of code unchangedKey rule for LED lamps: never set brightness below 20%. Most LED lamps behave unstably in the 0–15% range.
Option A: ESP32 with rbdimmerESP32 ✅ Recommended
When: dual-core ESP32 only (not S2/C3/H2) Library: rbdimmerESP32 — docs · GitHub
// Platform: dual-core ESP32
// Library: rbdimmerESP32
// Range: 20–95% for stable LED dimming with TRIAC
#include "rbdimmerESP32.h"
#define ZC_PIN 18 // zero-cross input
#define DIM_PIN 19 // TRIAC gate control
rbdimmer dimmer;
// Constrain brightness range for LED lamps
void setLedLevel(uint8_t percent) {
// Map 0–100% → 20–95% for LED stability with TRIAC
uint8_t mapped = 20 + (percent * 75) / 100;
if (mapped > 95) mapped = 95;
dimmer.setPower(mapped);
}
void setup() {
dimmer.begin(ZC_PIN, DIM_PIN, 50); // 50 Hz mains
setLedLevel(50);
}
void loop() {}Common mistakes:
- Using
rbdimmerESP32on single-core ESP32 (S2/C3/H2) — not supported - Setting brightness = 0 instead of calling off: TRIAC is unstable
at very small firing angles. Use
setPower(0)only to turn off fully.
Option B: Arduino AVR (Uno, Mega, Nano)
When: AVR boards only — do not use on ESP32! Library: RBDdimmer (legacy)
// Platform: Arduino Uno / Mega / Nano (AVR only)
// WARNING: for ESP32, use rbdimmerESP32 instead!
// Zero-cross: only pins 2 or 3 on Uno/Nano
#include <RBDdimmer.h>
#define ZC_PIN 2 // zero-cross — pins 2 or 3 only
#define DIM_PIN 11 // PWM-capable pin
dimmerLamp dimmer(DIM_PIN, ZC_PIN);
void setup() {
dimmer.begin(NORMAL_MODE, ON);
dimmer.setPower(20); // minimum 20% for LED lamps
}
void loop() {}⚠️ Common Mistakes from the Forums
Real errors from 3 forum threads (2019–2025).
-
«Tried different code — flicker won't go away»: The code is not the problem for Types 1 and 2. You need to change the dimmer (MOSFET AC dimmer) or the lamp (Philips/Osram).
-
«Below 30% brightness, random bright flashes»: TRIAC holding current (Type 2). Fix: MOSFET AC dimmer, or keep brightness above 20%, or add more load.
-
«Works fine at 100%, only flickers when dimmed»: Classic sign of a cheap LED driver (Type 1). At full power the sine wave is uncut. MOSFET AC dimmer or lamp replacement fixes this.
-
«Added delay() in loop() — got a bit better»: An illusion. Accidental synchronization with mains frequency. Different brightness settings will flicker again.
-
«ESP32 only flickers when WiFi is active»: Type 3 — ISR timing disruption. Fix:
rbdimmerESP32(IRAM_ATTR applied automatically) or DimmerLink (hardware isolation of dimmer control).
Quick Checklist
Before posting to the forum, verify:
→ Type 3 — use IRAM_ATTR or rbdimmerESP32
Compatibility Table
| Lamp | TRIAC (leading edge) | MOSFET (trailing edge) | Notes |
|---|---|---|---|
| Philips WarmGlow / Master LED | ✅ 10–95% | ✅ 5–95% | Recommended |
| Osram SUPERSTAR | ✅ 15–95% | ✅ 5–95% | Recommended |
| Ikea TRÅDFRI dimmable | ✅ 10–95% | ✅ 5–95% | Recommended |
| Cheap «dimmable» no-name | ⚠️ Flickers < 40% | ✅ 5–95% | MOSFET fixes it |
| Incandescent bulb | ✅ 10–95% | ✅ 5–95% | Ideal resistive load |
| Any «non-dimmable» LED | ❌ Constant flicker | ❌ Possible | Replace the lamp |
Related Topics
- LED Lamps with AC Dimmer: TRIAC vs MOSFET — Compatibility Guide
- Trailing Edge vs Leading Edge: Which Dimmer Method to Choose
Still Have Questions?
Post on rbdimmer.com forum or open a GitHub Issue.