Ir al contenido

Cruce por cero no detectado: el dimmer AC no responde a los comandos

¿Dimmer AC atascado en brillo máximo o sin respuesta? La falla en la detección de cruce por cero es la causa más común — pin de interrupción incorrecto, sin alimentación en el circuito ZC o ruido. Diagnóstico en 5 pasos con código de prueba y solución para Arduino y ESP32.

Resumen: Un dimmer AC no puede regular la carga si el microcontrolador no está recibiendo la señal de cruce por cero (el momento en que la sinusoide cruza por cero). Sin esta señal es imposible calcular el momento de disparo del TRIAC. Causas: pin incorrecto (no soporta interrupciones), cableado ZC incorrecto, ruido en la línea de señal o error de software (modo de interrupción incorrecto). Siga la lista de verificación paso a paso.



Descripción del problema

Conectaste un dimmer AC, subiste código de ejemplo — pero la carga no responde o siempre está a brillo máximo. El cableado parece correcto, sin errores de compilación.

La causa más común en estos casos es que el cruce por cero no está siendo detectado por el microcontrolador. La atenuación por corte de fase funciona con este principio: detectar el cruce por cero de la sinusoide → esperar el retardo calculado → disparar el TRIAC. Sin cruce por cero no hay temporización, y el dimmer o no enciende la carga o la mantiene al 100% sin regulación.

Síntomas típicos:

  • Carga siempre a brillo máximo (TRIAC siempre abierto)
  • Carga no se enciende en absoluto
  • setPower(50) no tiene efecto — carga completamente encendida o apagada
  • El contador de cruce por cero no se incrementa en el monitor serial (con depuración)
  • El código compila sin errores pero el dimmer «no funciona»

Mensajes típicos en foros:

  • «El dimmer no atenúa — lámpara siempre a brillo máximo»
  • «setPower no tiene efecto»
  • «La interrupción de cruce por cero no dispara»
  • «El dimmer funciona a veces, aleatoriamente»



Causa raíz

La señal de cruce por cero es generada por un circuito detector de cruce por cero integrado en el dimmer AC o conectado externamente. Típicamente es un optoacoplador + divisor resistivo que produce un pulso corto en cada cruce por cero de la sinusoide.

Frecuencia de pulsos: 100 Hz (red 50 Hz) / 120 Hz (red 60 Hz). Un pulso por cada semiciclo.

Para recibir estos pulsos el microcontrolador usa una interrupción externa (attachInterrupt()). Si la interrupción no está configurada correctamente o la señal no llega al pin correcto — el manejador ISR nunca se llama, la temporización no se puede calcular, y el dimmer no funciona.

Causas principales de fallo:

  1. Pin incorrecto (no soporta interrupciones) — error más común en Arduino
  2. Cableado ZC incorrecto — la señal no llega al pin
  3. Modo de interrupción incorrecto (FALLING en lugar de RISING o viceversa — depende del circuito)
  4. Ruido — múltiples disparos falsos en un semiciclo
  5. Problema de alimentación del circuito ZC — sin VCC en el detector de cruce por cero



Soluciones



🟢 Para principiantes: DimmerLink — cruce por cero integrado

¿No quieres lidiar con pines de interrupción, RISING/FALLING y ruido eléctrico? DimmerLink tiene su propio detector de cruce por cero y controla el TRIAC de forma autónoma.

Cualquier ESP32 detects zero-cross in hardware and handles TRIAC firing timing internally. Your microcontroller only sets the brightness level — no ISR, no interrupts, no pin headaches.

Cuándo elegir esta solución:

  • ☐ No puedes entender los pines de interrupción en Arduino
  • ☐ Usas Raspberry Pi (sin tiempo real para interrupciones)
  • ☐ Usas ESP32-S2/C3/H2 (núcleo único, ISR no funciona)
  • ☐ Quieres una solución fiable sin depurar el circuito ZC
  • DimmerLink → Arduino/ESP32 cableado:

    • VCC → 3.3V (ESP32) o 5V (Arduino)
    • GND → GND
    • SDA → SDA (GPIO 21 en ESP32, A4 en Arduino Uno)
    • SCL → SCL (GPIO 22 en ESP32, A5 en Arduino Uno)

    Código:

    cpp
    // DimmerLink — detector de cruce por cero y control TRIAC dentro del módulo
    // El microcontrolador solo establece el nivel de brillo
    // Documentación: https://www.rbdimmer.com/docs/dimmerlink-I2CCommunication
    #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();        // SDA/SCL por defecto para tu placa
        setLevel(50);        // brillo 50%
    }
    void loop() {
        // Sin ISR, sin interrupciones, sin cruce por cero en el MCU
    }

    Resultado: El dimmer funciona sin depender de pines de interrupción ni de la señal de cruce por cero en el microcontrolador.



    🔵 Para usuarios avanzados: diagnosticar y corregir el ZC

    ¿Quieres mantener la conexión ISR directa? Sigue los pasos de diagnóstico.


    Diagnóstico: pasos 1–5

    Paso 1: Verificar que el pin soporta interrupciones

    Esta es la causa n.º 1 en Arduino.

    cpp
    // Arduino Uno / Nano / Mini:
    // ✅ Soportan interrupciones: SOLO pines 2 y 3
    // ❌ Todos los demás pines (4, 5, 6...) — interrupciones no soportadas
    // Correcto:
    attachInterrupt(digitalPinToInterrupt(2), zeroCrossISR, RISING);  // pin 2 ✅
    attachInterrupt(digitalPinToInterrupt(3), zeroCrossISR, RISING);  // pin 3 ✅
    // Incorrecto:
    attachInterrupt(4, zeroCrossISR, RISING);  // ❌ pin 4 — no soporta interrupciones
    // En Uno, attachInterrupt(4) se refiere a INT4, que no existe

    En ESP32 — cualquier GPIO soporta interrupciones:

    cpp
    // ESP32: cualquier GPIO — todos funcionan
    attachInterrupt(digitalPinToInterrupt(18), zeroCrossISR, RISING);  // ✅
    attachInterrupt(digitalPinToInterrupt(34), zeroCrossISR, RISING);  // ✅

    En ESP8266 — todos los GPIO excepto GPIO 16 soportan interrupciones; se recomiendan 4, 5, 12, 13, 14 (sin dependencias de modo de arranque).


    Paso 2: Agregar un contador para verificar

    La prueba más simple — contar pulsos durante 1 segundo:

    cpp
    // Diagnóstico de cruce por cero: se esperan ~100 pulsos/seg (red 50 Hz)
    // o ~120 pulsos/seg (red 60 Hz)
    #define ZC_PIN 2  // ← asegúrate de que es un pin con soporte de interrupciones
    volatile uint32_t zcCount = 0;
    #ifdef ESP32
    void IRAM_ATTR zeroCrossISR() {  // IRAM_ATTR requerido en ESP32
    #else
    void zeroCrossISR() {             // Arduino/ESP8266: IRAM_ATTR no necesario
    #endif
        zcCount++;
    }
    void setup() {
        Serial.begin(115200);
        pinMode(ZC_PIN, INPUT);
        attachInterrupt(digitalPinToInterrupt(ZC_PIN), zeroCrossISR, RISING);
    }
    void loop() {
        delay(1000);
        Serial.print("Pulsos ZC por segundo: ");
        Serial.println(zcCount);
        zcCount = 0;
        // Resultados esperados:
        // ~100 — red 50 Hz, todo correcto
        // ~120 — red 60 Hz, todo correcto
        //   0  — ZC no detectado (problema de cableado o pin)
        //  >200 — ruido, disparos falsos (problema de filtro RC)
    }

    Paso 3: Verificar el cableado ZC

    Cableado típico RBDimmer → Arduino/ESP32:

    text
    RBDimmer  →  Arduino/ESP32
    -------      -------------
    VCC       →  5V (Arduino) / 3.3V (ESP32)
    GND       →  GND
    ZC        →  Pin con soporte de interrupciones (2 o 3 en Arduino Uno)
    DIM       →  Cualquier salida digital

    Lista de verificación:

  • ☐ Pin ZC conectado al GPIO correcto con soporte de interrupciones
  • ☐ VCC suministrado al módulo (sin alimentación el circuito ZC no funciona)
  • ☐ GND común — secciones lógica y de potencia
  • ☐ El cable ZC no va paralelo a los cables de 220V (induce ruido)
  • ☐ Conector ZC no intercambiado con DIM (están adyacentes en algunos módulos)

  • Paso 4: Verificar el modo de interrupción (RISING/FALLING/CHANGE)

    El modo depende del circuito detector ZC de tu módulo específico:

    cpp
    // La mayoría de los módulos RBDimmer: RISING (pulso en flanco ascendente)
    attachInterrupt(digitalPinToInterrupt(ZC_PIN), zeroCrossISR, RISING);
    // Algunos circuitos con optoacoplador inversor: FALLING
    attachInterrupt(digitalPinToInterrupt(ZC_PIN), zeroCrossISR, FALLING);
    // Si no estás seguro — prueba CHANGE (captura ambos flancos):
    // Nota: con CHANGE el contador mostrará ~200 Hz en lugar de 100 Hz
    attachInterrupt(digitalPinToInterrupt(ZC_PIN), zeroCrossISR, CHANGE);

    Usa un osciloscopio o analizador lógico para verificar la forma de la señal ZC — identifica si necesitas capturar el flanco ascendente o descendente.


    Paso 5: Filtrar ruido

    Si el contador muestra >200 (en red de 50 Hz) — disparos falsos por ruido. Esto es especialmente común con cables largos o proximidad a la carga:

    cpp
    // Antirrebote por software para ZC: ignorar pulsos demasiado cercanos al anterior
    // Período del semiciclo a 50 Hz = 10 000 µs → filtrar todo lo menor a 8 000 µs
    volatile uint32_t lastZC = 0;
    void IRAM_ATTR zeroCrossISR() {
        uint32_t now = micros();
        if (now - lastZC > 8000) {  // intervalo mínimo de 8 ms entre pulsos ZC
            lastZC = now;
            // tu lógica de temporización TRIAC aquí
        }
    }

    Filtro de hardware: un filtro RC en la línea ZC (resistencia de 1 kΩ + condensador de 100 nF entre ZC y GND) elimina el ruido de alta frecuencia.



    Corrección: elige tu opción

    Opción A: rbdimmerESP32 en ESP32 ✅

    Cuándo: ESP32 de doble núcleo con conexión directa al dimmer. La biblioteca maneja ZC y temporización automáticamente:

    cpp
    // Plataforma: ESP32 doble núcleo
    // Biblioteca: rbdimmerESP32
    #include "rbdimmerESP32.h"
    #define ZC_PIN  18  // cualquier GPIO ESP32
    #define DIM_PIN 19  // cualquier GPIO ESP32
    rbdimmer dimmer;
    void setup() {
        Serial.begin(115200);
        dimmer.begin(ZC_PIN, DIM_PIN, 50);  // red 50 Hz
        dimmer.setPower(50);
        Serial.println("Dimmer inicializado");
    }
    void loop() {}

    Errores comunes:

    • ZC_PIN y DIM_PIN intercambiados en dimmer.begin() — verifica cuál pin va a la carga y cuál al cruce por cero.
    • Frecuencia de red incorrecta (tercer parámetro) — 50 o 60 Hz.

    Opción B: Arduino Uno/Mega con RBDdimmer
    cpp
    // Plataforma: Arduino Uno / Mega / Nano (AVR)
    // Biblioteca: RBDdimmer — https://github.com/robotdyn/dimmer
    // ¡ADVERTENCIA: para ESP32 usa rbdimmerESP32, no esta biblioteca!
    #include <RBDdimmer.h>
    // ⚠️ Arduino Uno: ZC SOLO en pines 2 o 3
    #define ZC_PIN   2   // soporta interrupciones ✅
    #define DIM_PIN  11  // cualquier salida digital
    dimmerLamp dimmer(DIM_PIN, ZC_PIN);
    void setup() {
        Serial.begin(9600);
        dimmer.begin(NORMAL_MODE, ON);
        dimmer.setPower(50);  // 50%
        Serial.println("Dimmer listo");
    }
    void loop() {}


    ⚠️ Errores comunes

    • «Uso el pin 4 en Arduino Uno — no funciona»: Los pines 4, 5, 6, ... en el Uno no soportan interrupciones externas. Solo los pines 2 y 3. Este es el error más común de principiantes.

    • «attachInterrupt(4, ...) compila — debería funcionar»: Compila, pero no funcionará. attachInterrupt(4, ...) en el Uno se refiere a INT4 (número de interrupción por hardware), no a GPIO 4. Usa siempre digitalPinToInterrupt(pin).

    • «Agregué contador — siempre 0»: Tres posibles causas: 1. Pin incorrecto (no soporta interrupciones) — verifica arriba 2. Sin alimentación en el módulo ZC 3. Pin ZC no conectado físicamente a la placa

    • «Contador muestra 300–400 en lugar de 100»: Disparos falsos por ruido. Agrega antirrebote por software (ver arriba) o un filtro RC de hardware.

    • «Pin 34 en ESP32 no funciona para ZC»: GPIO 34–39 en ESP32 son solo entrada — soportan interrupciones. Pero no tienen pull-up/pull-down interno. Agrega una resistencia externa de 10 kΩ a 3.3V.

    • «Funciona sin carga, falla con carga conectada»: El ruido eléctrico de la carga (especialmente motores, transformadores) se acopla a la línea ZC. Separa físicamente los cables de potencia y señal.




    Verificación rápida

    Antes de publicar en el foro, verifica:

  • ☐ Arduino: pin ZC — solo 2 o 3 (Uno/Nano). Los demás no funcionan.
  • ☐ ¿Se suministra VCC al circuito ZC del dimmer?
  • ☐ ¿El pin ZC está físicamente conectado a la placa (no solo DIM)?
  • ☐ ¿Contador de cruce por cero en 1 segundo: ~100 (50 Hz) o ~120 (60 Hz)?
  • ☐ ¿Contador `>200`? Ruido — agrega antirrebote o filtro RC.
  • ☐ ¿Modo de interrupción: `RISING` o `FALLING` para tu circuito?
  • ☐ ESP32: ¿estás usando `rbdimmerESP32`, no la vieja `RBDdimmer`?


  • Tabla de compatibilidad de pines de interrupción

    Placa Pines con soporte de interrupciones Nota
    Arduino Uno 2, 3 Solo estos dos
    Arduino Nano 2, 3 Solo estos dos
    Arduino Mega 2, 3, 18, 19, 20, 21 Seis pines
    ❌ no Todos los GPIO (0–39) Excepto reservados
    ESP8266 Todos los GPIO excepto GPIO 16 Recomendados 4,5,12,13,14 (sin boot)
    — (sin ISR) Todos los GPIO (vía pigpio) Sin tiempo real — preferir DimmerLink



    Problemas relacionados

    • ESP32 + dimmer AC: Guru Meditation Errortroubleshooting/esp32-iram-attr.md
    • Trailing vs Leading Edgeload-types/trailing-vs-leading-edge.md
    • El dimmer no regula LEDload-types/led-lamp-compatibility-triac.md



    ¿Todavía tienes preguntas?

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

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