Ir al contenido

ESP32-S2/C3/H2: AC Dimmer Doesn't Work on Single-Core ESP32

ESP32-C3/S2/H2 are single-core — rbdimmerESP32 cannot isolate the dimmer task from WiFi. WiFi DMA interrupts run at NMI level and disrupt TRIAC timing at the hardware level. DimmerLink over I2C is the only reliable solution.

TL;DR: rbdimmerESP32 is designed for dual-core ESP32 (original and S3). ESP32-S2, C3, C6, and H2 are single-core. The library tries to isolate the dimmer task on a dedicated core — which is impossible on single-core chips. The ISR competes with the WiFi stack, timing is disrupted, and the result is flickering, random glitches, and unstable brightness. The solution is DimmerLink (hardware ZC + I2C) or switching to a dual-core chip.



Problem Description

You bought an ESP32-C3 or ESP32-S2 — compact, WiFi/BLE capable, affordable. You connect an AC dimmer directly, use rbdimmerESP32 — and see:

  • The load flickers at any brightness level
  • Timing is unstablesetPower(50) gives 30% one moment, 70% the next
  • Glitches with active WiFi — works better without WiFi, chaos with it
  • Code compiles without errors, instability only at runtime

Or the compiler warns about xTaskCreatePinnedToCore with a non-existent core.

Typical forum messages:

  • "ESP32-C3 + dimmer — flickering at any power level"
  • "rbdimmerESP32 works on ESP32 but not on ESP32-S2"
  • "setPower(50) gives random brightness on C3"
  • "Works fine without WiFi, breaks as soon as WiFi connects"



Root Cause


Which ESP32 chips are dual-core and which are not

Chip Cores Architecture Direct ISR dimming
ESP32 (original) 2 Xtensa LX6 rbdimmerESP32
ESP32-S3 2 Xtensa LX7 rbdimmerESP32
ESP32-S2 1 Xtensa LX7 ⚠️ DimmerLink only
ESP32-C3 1 RISC-V ⚠️ DimmerLink only
ESP32-C6 1 RISC-V ⚠️ DimmerLink only
ESP32-H2 1 RISC-V ⚠️ DimmerLink only

⚠️ ESP32-C6 note: C6 has an additional LP core (Low Power core), but it's not suitable for ISR dimming — it runs at a reduced clock speed without full FreeRTOS support. The main core is single.

⚠️ Common confusion: ESP32-S3 is dual-core (works). ESP32-S2 is single-core (doesn't work with direct ISR). These are different chips.


Why phase-cut dimming requires core isolation

TRIAC gate timing: from zero-cross to the gate pulse — a delay of 100–9000 µs, accuracy ±10–20 µs. Any delay means wrong brightness.

rbdimmerESP32 achieves this precision on dual-core like this:

  • Core 0 — dimmer task (timing loop, ZC handling, TRIAC firing)
  • Core 1 — WiFi stack, TCP/IP, user code

They run in parallel and don't block each other.

On single-core (C3, S2, H2) — both threads share one core. The WiFi stack regularly blocks the CPU for 1–5 ms to process packets. During that time the dimmer ISR misses its timing window → the lamp flickers.

WiFi DMA interrupts have a fixed high hardware priority in ESP-IDF — they are not preempted by user-level IRAM_ATTR ISR on single-core. portDISABLE_INTERRUPTS() doesn't help either: WiFi DMA runs at NMI level. This cannot be solved in software.



Solutions



🟢 For everyone: DimmerLink — hardware solution

The only reliable solution for single-core ESP32 with WiFi. DimmerLink detects zero-cross and controls the TRIAC itself — your MCU only sends the brightness level over I2C.

DimmerLink has its own controller. Your ESP32-C3 only sets the brightness — no ISR, no timing-critical code on the MCU side.

Wiring for ESP32-C3:

  • VCC → 3.3V
  • GND → GND
  • SDA → GPIO 8 (default SDA on ESP32-C3)
  • SCL → GPIO 9 (default SCL on ESP32-C3)

⚠️ On some ESP32-C3 modules GPIO 8/9 are used by the external flash. If Wire.begin() doesn't work — specify pins explicitly: Wire.begin(SDA_PIN, SCL_PIN) with free GPIOs (e.g., 1, 10).

Wiring for ESP32-S2:

  • SDA → GPIO 3 (verify your board's pinout)
  • SCL → GPIO 4

Code (same for all platforms):

cpp
// DimmerLink on ESP32-C3 / S2 / H2 — no ISR on the MCU
// Stable operation with active WiFi
#include <Wire.h>
#define DIMMER_ADDR 0x50
#define REG_LEVEL   0x10
void setLevel(uint8_t level) {  // level: 0–100%
    Wire.beginTransmission(DIMMER_ADDR);
    Wire.write(REG_LEVEL);
    Wire.write(level);
    Wire.endTransmission();
}
void setup() {
    Wire.begin();  // default SDA/SCL for your board
    setLevel(50);  // 50% brightness
}
void loop() {
    // WiFi can be used freely — the dimmer is unaffected
}

Result: Stable operation with WiFi/BLE on any single-core ESP32.



🔵 Want direct ISR — switch to a dual-core ESP32

Need the direct ISR architecture? Use the original ESP32 or S3.

The original ESP32 and ESP32-S3 are dual-core, rbdimmerESP32 works normally:

cpp
// ESP32 original / ESP32-S3 — dual-core
// rbdimmerESP32 works as intended
#include "rbdimmerESP32.h"
#define ZC_PIN  18
#define DIM_PIN 19
rbdimmer dimmer;
void setup() {
    Serial.begin(115200);
    dimmer.begin(ZC_PIN, DIM_PIN, 50);  // ZC_PIN, DIM_PIN, 50 Hz
    dimmer.setPower(50);
}
void loop() {}

When to choose switching the chip:

  • ☐ Already have code with direct ZC/DIM connection
  • ☐ Need to control multiple channels simultaneously
  • ☐ The price difference between ESP32 and ESP32-C3 is not significant for
  • the project



    ⚠️ Common pitfalls

    • "ESP32-C3 is cheaper — I'll use it for AC dimming": ESP32-C3 is a great chip for most IoT tasks, but direct TRIAC dimmer control with WiFi is its weak spot. DimmerLink solves this.

    • "Works without WiFi on C3, flickers with WiFi": Classic symptom of single-core ISR conflict. Well-documented behavior. Solution: DimmerLink.

    • "rbdimmerESP32 compiled on C3 — should work": It compiles because Core 0 exists even on single-core chips. xTaskCreatePinnedToCore(..., 0) will create the task, but without isolation from WiFi. Timing is unstable — that's not "working".

    • "Using ESPHome / Tasmota on ESP32-C3 — dimmer doesn't work": ESPHome and Tasmota on single-core ESP32 have the same limitations as custom code. The WiFi stack competes with the dimmer ISR. Use DimmerLink with ESPHome via I2C (i2c: + sensor: or a custom custom_component).

    • "ESP32-S3 also has S — is it single-core too?": No. ESP32-S3 is dual-core (like the original ESP32, just LX7). People confuse it with ESP32-S2. Check the chip marking on your board.

    • "I'm using FreeRTOS manually and can pin tasks myself": On single-core xTaskCreatePinnedToCore(..., 0) creates the task, but the WiFi stack is also on Core 0. Any vTaskDelay(1) in the dimmer task yields the CPU to WiFi — timing breaks. And WiFi DMA interrupts have a hardware priority above user ISR. This can't be solved in software. DimmerLink is the only reliable solution.




    Quick Check

  • ☐ Which exact ESP32? Check the chip marking (not the board label!)
  • ☐ ESP32-C3/S2/H2/C6 — single-core → DimmerLink
  • ☐ ESP32 (no suffix) / S3 — dual-core → `rbdimmerESP32` works
  • ☐ Works without WiFi, breaks with WiFi? → Single-core ISR conflict
  • ☐ DimmerLink: correct SDA/SCL pins for your chip?


  • Related Issues

    • Wrong library for ESP32troubleshooting/wrong-library-esp32.md
    • ESP32 + TRIAC: Guru Meditation Errortroubleshooting/esp32-iram-attr.md
    • Zero-cross not detectedtroubleshooting/zero-cross-detection-errors.md



    Still have questions?

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

    Compartir esta publicación
    Iniciar sesión para dejar un comentario