Banner publicitario de PCBWay

[Aporte] Control de fase (Dimmer) con Arduino

Scooter

Cascarrabias crónico
Acabo de realizar el control de fase de uun tirac por software con un arduino.
Por si a alguien le puede interesar, publico el código fuente y un video del funcionamiento, el esquema lo publicaré en cuanto lo haga.

Código:
int cuenta = 0;
int segundo = 0;
int tarda =1;
boolean cero = false;
int espera ;

#include <FlexiTimer2.h>

void interrupcion(){
  cero = true;
  FlexiTimer2::set(espera, 1.0/10000, timer); // call every 500 1ms "ticks"
  FlexiTimer2::start();

}

void timer(){
static boolean output = HIGH;
if (cero){
  digitalWrite(8,HIGH);
  cero = false;
  FlexiTimer2::set(1, 1.0/10000, timer); // call every 500 1ms "ticks"
  FlexiTimer2::start();
  }
else{
  digitalWrite(8,LOW);
  FlexiTimer2::stop();
}
}




void setup() {
  // put your setup code here, to run once:
pinMode (7,INPUT_PULLUP);
pinMode (8,OUTPUT);
attachInterrupt(digitalPinToInterrupt(7), interrupcion, RISING);
Serial.begin(9600);
digitalWrite(8,LOW);



}

void loop() {
  // put your main code here, to run repeatedly:
 espera = map(analogRead(0),0,1023,0,100);

}

 
Aclarando el funcionamiento usa una interrupción externa para detectar el paso por cero en el pin7.
Eso puede cambiar según que Arduino se use, ya que no todos disponen de los mismos pines para activar interrupciones externas.
Una vez recibida la interrupción se lanza el timer2 configurado a 0,1ms con una parte proporcional a la lectura del potenciómetro. Cuando el timer2 desborda se activa el pin 8 que es donde está conectado el optotriac y se lanza de nuevo el timer2 0,1ms más para desactivar el pin. En ese punto se para el timer2.

Como se puede ver el main loop está prácticamente vacío, allí se puede hacer lo que se crea conveniente, basta con dar un valor entre 0 y 100 a la variable tarda y la lámpara ya va sola con ese porcentaje. Va al revés, 100 es apagada y 0 encendida a tope.

Esto es para 50Hz, para 60Hz, estará totalmente apagada con tarda por encima de tarda = 83. Y no tengo claro que pasaría, creo que funcionará.
 
Como lo prometido es duda, aporto el esquema y el código editado algo mas limpio.

1559233991756.png
C:
/*
 *  Dimmer por interrupciones
 * 
 *  Programado para Arduino Leonardo, micro, promicro o semejante, pero adpatable a acasi cualquier otro
 * 
 *  (CC) con mención del autor Por Félix Díaz 2019
 * 
 *  Está pensado para sistemas de 50Hz en los que un semiperiodo dura 10ms y por ello se permiten cien fracciones a 100us cada una
 *  Para 60Hz al llegar a 83 el triac estará totalemte apagado, habrá que reconfigurar los timers si se quiere un ajuste de 0~100
 *  No lo he probado a 60Hz porque no dispongo de una red eléctrica para poder hacerlo
 * 
 */

// Definición de variables
boolean cero = false;   //Flag para saber en que paso estamos ya que se llama dos veces a la misma interrupción
int espera ;            // Tiempo de espera en 0,1ms desde el paso por cero hasta el disparo del triac

#define interrup 7    //Pin en el que entran las interrupciones del detector de paso por cero, dependerá del arduino que usemos
#define triac 8       //Pin en el que se conecta el triac, el que nosotros queramos

#include <FlexiTimer2.h>      //Librería para el uso del Timer2

// Interrupción por paso por cero en la red, es necesario un optoacoplador de alterna o uno de continua con un puente
void interrupcion(){
  cero = true;
  FlexiTimer2::set(espera, 1.0/10000, timer); // se programa el timer con el número de 100 us que se desee
  FlexiTimer2::start();
}

// Interrupción por desbordamiento del timer2
/*
 * Esta interrupción se llama dos veces, para disparar el triac con el retardo deseado y 100us después para cortar el pulso de disparo, por eso hay un flag,
 * para diferenciar en cual de esas dos ocasiones estamos.
 *
 */
void timer(){
static boolean output = HIGH;
if (cero){    // Es la primera vez, activamos el triac y lanzamos una nueva interrupción para dentro de 100us
  digitalWrite(triac,HIGH);
  cero = false;
  FlexiTimer2::set(1, 1.0/10000, timer); // Se espera 1 100us
  FlexiTimer2::start();
  }
else{       // es la segunda vez, cortamos el pulso de disparo y paramos el timer
  digitalWrite(triac,LOW);
  FlexiTimer2::stop();
}
}


void setup() {
  // Inicialización los pines etcput your setup code here, to run once:
pinMode (interrup,INPUT_PULLUP);      //Activamos internal pullup para ahorrar una resistencia
pinMode (triac,OUTPUT);               // Pin de control de triac como salida
attachInterrupt(digitalPinToInterrupt(interrup), interrupcion, RISING);  //interrupción, pin, función
digitalWrite(triac,LOW);              // Apagamos el triac por si acaso, debería de estar (apagado sin hacer nada)
}

void loop() {
  // Aquí iría el código correspondiente, en este caso se lee un potenciometro y ya está
 espera = map(analogRead(0),0,1023,0,100);

}
 
Versión revisada y afinada del código para ESP32C3
Supongo que irá con "otras" versiones de ESP32.
El otro no iba del todo fino y daba unos cuantos Fogonazos con ángulos elevados. Este va OK.
C:
/*
 *  Dimmer por interrupciones
 *
 *  Programado para Arduino XIAO ESP32D3, pero adpatable a acasi cualquier otro ESP
 *
 *  (CC) con mención del autor Por Félix Díaz 2024
 *
 *  Está pensado para sistemas de 50Hz en los que un semiperiodo dura 10ms y por ello se permiten cien fracciones a 100us cada una
 *  Para 60Hz al llegar a 83 el triac estará totalemte apagado, habrá que reconfigurar los timers si se quiere un ajuste de 0~100
 *  No lo he probado a 60Hz porque no dispongo de una red eléctrica para poder hacerlo
 *
 *  Conectado el detector de paso por 0 en D0 y la salida del triac en D1
 *
 *  Agradecimiento a www.forosdeelectronica.com y los que han colaborado con la problemática de las interrupciones
 *  Dr. Zoidberg, ilkidior, cosmefulanito04 y tiovik
 */

volatile unsigned long espera=5000;  //Por defecto al 50%
volatile bool esperar = false;
volatile bool timerSemaforo = false;  //Flag que indica si hay timer en marcha
volatile unsigned long hora=0;      //"hora" a la que ocurre un ainterrupción
#define triac D1
#define dpc D0

hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

volatile uint32_t isrCounter = 0;
volatile uint32_t lastIsrAt = 0;

// Rutina de atención a la interrupción
void ARDUINO_ISR_ATTR interr(){
  portENTER_CRITICAL_ISR(&timerMux);
    if(!timerSemaforo){
      //if(micros()-hora>10){   //Esta condición lo desestabiliza
        disableInterrupt(dpc);   //Apagamos la interrupción
        timerSemaforo=true;
        timerAlarm(timer, espera , true, 1);    //Lanzamos el timer 1 vez al valor indicado "espera" por defecto 5ms 5000us, que sería el 50%
        timerStart(timer);
        esperar=true;
      //}       
    }
  portEXIT_CRITICAL_ISR(&timerMux);
}

void ARDUINO_ISR_ATTR onTimer(){
  // Increment the counter and set the time of ISR
  portENTER_CRITICAL_ISR(&timerMux);
  if(esperar){
    digitalWrite(triac,HIGH);
    timerAlarm(timer, 300, true, 1); //Se lanza el timer 300us 1 vez para apagar el pulso
    esperar=false;
  }
  else{
    timerStop(timer);
    digitalWrite(triac,LOW);  //Acabamos el pulso de disparo del triac
    timerSemaforo=false;
    hora=micros();    //Guardamos cuando fue apagado el triac
    enableInterrupt(dpc);
  }
  portEXIT_CRITICAL_ISR(&timerMux);
}

void setup() {
  // Configuración de las cosas
  Serial.begin(115200);

  // Ajustar la frecuencia del temporizador a 1Mhz
  timer = timerBegin(1000000);

  // Asociar la función onTimer a nuestro temporizador.
  timerAttachInterrupt(timer, &onTimer);
  Serial.println("Dimmer por software con un ESP32C3");
  pinMode(dpc,INPUT_PULLUP);
  pinMode(triac,OUTPUT);
  attachInterrupt(dpc,interr,RISING);    // En el ESP32 hagas lo que hagas es CHANGE
}

void loop() {
  // Bucle principal, hacer algo para ir moviendo el dimmer
  delay (1000);
  for( unsigned long i=10; i<85 ; i++){
    espera = i*100;
    Serial.print ("Retardo: ");
    Serial.println(espera);
    delay (100);
  }
  delay(500);
  for( unsigned long i=85; i>=10 ; i--){
    espera = i*100;
    Serial.print ("Retardo: ");
    Serial.println(espera);
    delay(100);
  }
  delay(500);
}
 
Excelente aporte.

Me pregunto si valdría modificar aquello que intervenga con la frecuencia de red, y realizar un cálculo según algún "#define", algo como:

C++:
#define USAR_50

#ifdef USAR_50
// Cálculo para 50Hz
#else
// Cálculo para 60Hz
#endif

Con ésto, el compilador usaría sólo los cálculos para X frecuencia de red configurable...

...

Otra que se me ocurre es usar un control de corriente constante del lado del LED del PC814, para que llegue a 0 Vca. No sé qué tanto cambio haría...
 
Excelente aporte.

Me pregunto si valdría modificar aquello que intervenga con la frecuencia de red, y realizar un cálculo según algún "#define", algo como:

C++:
#define USAR_50

#ifdef USAR_50
// Cálculo para 50Hz
#else
// Cálculo para 60Hz
#endif

Con ésto, el compilador usaría sólo los cálculos para X frecuencia de red configurable...
En el caso del Arduino, puede ser, habría que mirarlo.
En realidad en el código no hay frecuencia de red por ningún lado, después del paso por cero temporiza...
El código Arduino permite ajuste de 0 a 100 para 10ms osea que cada salto son 100μs. A 60Hz en lugar de 0 a 10ms solo tienes de de 0 a 83 para ajustar, habria que cambiar el timer si quieres tener de 0 a 100.

En el caso del ESP como se ajusta de μs en μs tienes precisión de sobra para usar la frecuencia que quieras. Puedes ajustar de 0 a 10000μs mientras que a 60Hz puedes ajustar de 0 a 8333μs que es precisión para aburrir.


Usando map() puedes ajustar a 0~100 o lo que sea. Tanto a 50 como a 60Hz sin tocar nada del código fuente.
Otra que se me ocurre es usar un control de corriente constante del lado del LED del PC814, para que llegue a 0 Vca. No sé qué tanto cambio haría...
Si la gracia está en que se apague en el 0, y va bien con una mísera resistencia de 150k.
Hacer un generador de corriente constante en alterna... Se puede pero no se muy bien como.
En su día hice uno con un opto normal, un puente, un tener, un transistor, dos resistencias... Creo que no compensa.
Un divague.... implementar una detección de cruce por cero que cargue la variable ingresando por alguna entrada analógica? :unsure:
En el primer ejemplo se lee un potenciómetro para ajustar el dimmer, en el segundo hace un ciclo "día"~"noche"~"día"...
Cambia un loop por otro y ya está.
 
Última edición:
Parece poca corriente pero es suficiente, la relación de corriente es más o menos 1:1 y la resistencia interna de pullup anda por los 30k, así que la corriente de saturación está entorno los 100μA
Si miras los oscilogramas del otro post de paso por cero verás que el pulso es bastante estrecho.
En el circuito que tengo las pistas tienen como 5mm así que poco ruido le puede entrar por ahí.
Se puede poner una resistencia de menos valor pero será de más potencia y se calentará.
Poner un condensador, tendríamos el desfase que habría que corregir de algún modo.
El que quiera que mejore el circuito, yo es que pongo el 10% de los componentes que ponen los demás por alguna tara genética que tengo, siempre he sido minimalista en el hardware.
 
En realidad en el código no hay frecuencia de red por ningún lado, después del paso por cero temporiza...
Claro, por eso decía lo de "el cálculo", ya que manejas interrupciones por timer y quizas en algún punto puede, o quedar encendido tenue o no llegar al 100%.

Habría que intentar realizar un generador de 50 y 60 Hz para realizar pruebas, o quien disponga de la red eléctrica de 60Hz lo pruebe y comente.


yo es que pongo el 10% de los componentes que ponen los demás
Es totalmente entendible para algo tan simple, lo que sugerí era para agregar algo mas de "precisión", pero la mayoría de circuitos que vi utilizan tu método o similar. Aparte hay que tener en cuenta que son muchos voltios los que se manejan... Que-se-yo...

Quizas ese mejor agregar un mini-micro-transformador de 220/110Vca a 1 a 5Vca, pero ya es mas lío de conseguir y/o armar...
 
El código está hecho lo mas simple posible. Si te pasas de hacer larga la espera llegará el próximo paso por cero antes y disparará el timer antes de que llegue a cero, no tengo claro que pasa, supongo que simplemente no llega nunca.
Lo que suelo hacer es que cuando el ángulo es muy grande ya escribo 0 todo el rato y cuando es muy pequeño enciendo todo el rato.

Cuando tengo pulido el PID para control de velocidad el motor de lavadora lo publicaré. Con Arduino funciona bastante bien , con el ESP aún no lo he podido probar.
 
Atrás
Arriba