Se rendre au contenu

LED Flickering with AC Dimmer: TRIAC or MOSFET — Causes and Fixes

LED lights flickering when dimmed? Three root causes explained — LED driver quality, TRIAC holding current, ESP32 timing — with specific fixes for each, including MOSFET AC dimmer as the hardware solution.

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:

  • ☐ LED lamps of any brand (including cheap no-name)
  • ☐ Light loads — single lamp 5–20 W
  • ☐ Need a wide dimming range (5–95%)
  • ☐ Want to eliminate flicker without replacing lamps
  • 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:

    1. Philips WarmGlow, SceneSwitch, Master LED
    2. Osram CLASSIC A, SUPERSTAR
    3. 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:

  • ☐ Raspberry Pi / single-board computer (no realtime OS for ISR)
  • ☐ Single-core ESP32 (S2, C3, H2) — software libraries not supported
  • ☐ WiFi conflicts with interrupts on ESP32
  • ☐ Need stability without debugging timing
  • cpp
    // 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.

    cpp
    // 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:

    cpp
    // rbdimmerESP32: ISR runs on Core 0 (system core)
    // loop() and WiFi run on Core 1 — no CPU competition
    #include "rbdimmerESP32.h"
    // ... rest of code unchanged

    Key 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

    cpp
    // 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 rbdimmerESP32 on 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)

    cpp
    // 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:

  • ☐ Have you considered a MOSFET AC dimmer as the hardware solution?
  • ☐ Is the lamp labeled «dimmable» and from a quality brand (Philips, Osram)?
  • ☐ Is the total load above 15 W (or ballast resistor connected)?
  • ☐ Is the minimum brightness in your code set to 20% or higher?
  • ☐ Are you using rbdimmerESP32 for ESP32 (not the legacy RBDdimmer)?
  • ☐ Did you test by simply swapping the lamp without changing code?
  • ☐ ESP32 + WiFi: does flicker disappear at `WiFi.disconnect()`?
  • → 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



    Still Have Questions?

    Post on rbdimmer.com forum or open a GitHub Issue.

    Partager cet article
    Se connecter pour laisser un commentaire.