From 60855bf367f7ee62cf22196e700f606a1dd72160 Mon Sep 17 00:00:00 2001 From: stefanbode Date: Thu, 27 Apr 2023 08:03:00 +0200 Subject: [PATCH] Enabled ZCDimmerSet for ESP8266 (#18518) * Enabled ZCDimmerSet for ESP8266 - Increased interrupts time to get stablity. Savedata issue is gone. - Increased accuracy to trigger an on sinus curve. - Enabled high resolution trigger ZCDimmerSet for ESP8266 * Update xdrv_68_zerocrossDimmer.ino --- .../xdrv_68_zerocrossDimmer.ino | 101 ++++++++++-------- 1 file changed, 59 insertions(+), 42 deletions(-) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_68_zerocrossDimmer.ino b/tasmota/tasmota_xdrv_driver/xdrv_68_zerocrossDimmer.ino index 307fabc00..890b3bb3b 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_68_zerocrossDimmer.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_68_zerocrossDimmer.ino @@ -24,23 +24,25 @@ #define XDRV_68 68 -static const uint32_t GATE_ENABLE_TIME = 100; +static const uint8_t GATE_ENABLE_TIME = 100; +static const uint8_t MIN_PERCENT = 5; +static const uint8_t MAX_PERCENT = 99; +static const uint8_t TRIGGER_PERIOD = 75; struct AC_ZERO_CROSS_DIMMER { - uint32_t cycle_time_us; - /// Time (in micros()) of last ZC signal - uint32_t crossed_zero_at; - /// Time since last ZC pulse to enable gate pin. 0 means not set. - bool timer_iterrupt_started = false; - bool dimmer_in_use = false; - // Check if 50µs timer is running. - uint32_t enable_time_us[MAX_PWMS]; - /// Time since last ZC pulse to disable gate pin. 0 means no disable. - uint32_t disable_time_us[MAX_PWMS]; - uint8_t current_state_in_phase[MAX_PWMS]; // 0=before fire HIGH, 1=HIGH, 2=after setting LOW, 3=before HIGH without setting LOW (POWER ON) - uint32_t lastlight[MAX_PWMS]; - uint16_t detailpower[MAX_PWMS]; // replaces dimmer and light controll 0..10000. required savedata 0. - uint32_t intr_counter = 0; + uint32_t cycle_time_us; // Time (in micros()) of last ZC signal + uint32_t crossed_zero_at; // Time (in micros()) of last ZC signal + bool timer_iterrupt_started = false; // verification of the interrupt running + bool dimmer_in_use = false; // Check if interrupt has to be run. Is stopped if all lights off + uint32_t enable_time_us[MAX_PWMS]; // Time since last ZC pulse to enable gate pin. 0 means no disable. + uint32_t disable_time_us[MAX_PWMS]; // Time since last ZC pulse to disable gate pin. 0 means no disable. + uint8_t current_state_in_phase[MAX_PWMS]; // 0=before fire HIGH, 1=HIGH, 2=after setting LOW, 3=before HIGH without setting LOW (POWER ON) + uint32_t lastlight[MAX_PWMS]; // Store the light value. Set 1 if controlled through ZCDimmerSet + uint16_t detailpower[MAX_PWMS]; // replaces dimmer and light controll 0..10000. required savedata 0. + uint32_t accurracy[MAX_PWMS]; // offset of the time to fire the triac and the real time when it fired + uint8_t triggertime = GATE_ENABLE_TIME; // copy of the Time for the gate keep open to start TRIAC + uint32_t intr_counter = 0; // counter internally on interrerupt calls + uint32_t missed_zero_cross; // count up all missed Zero-cross events. } ac_zero_cross_dimmer; @@ -49,12 +51,13 @@ struct AC_ZERO_CROSS_DIMMER { #endif #define D_PRFX_ZCDIMMER "ZCDimmer" -#define D_CMND_DIMMERSET "Set" +#define D_CMND_DIMMERSET "Set" // Set percent as float value for dimmer +#define D_CMND_GATEENABLE "GateTime" // Override the GATE ON time DEBUG PURPOSE -const char kZCDimmerCommands[] PROGMEM = D_PRFX_ZCDIMMER "|" D_CMND_DIMMERSET; +const char kZCDimmerCommands[] PROGMEM = D_PRFX_ZCDIMMER "|" D_CMND_DIMMERSET "|" D_CMND_GATEENABLE; void (* const ZCDimmerCommand[])(void) PROGMEM = { - &CmndZCDimmerSet + &CmndZCDimmerSet//,&CmndZCGateEnableTime }; void IRAM_ATTR ACDimmerZeroCross(uint32_t time) { @@ -66,12 +69,12 @@ void IRAM_ATTR ACDimmerZeroCross(uint32_t time) { ac_zero_cross_dimmer.dimmer_in_use |= ac_zero_cross_dimmer.lastlight[i] > 0; // Dimmer is physically off. Skip swich on ac_zero_cross_dimmer.current_state_in_phase[i] = 0; - if (100 * ac_zero_cross_dimmer.enable_time_us[i] > 95 * ac_zero_cross_dimmer.cycle_time_us ) { + if (100 * ac_zero_cross_dimmer.enable_time_us[i] > MAX_PERCENT * ac_zero_cross_dimmer.cycle_time_us ) { ac_zero_cross_dimmer.current_state_in_phase[i] = 1; ac_zero_cross_dimmer.disable_time_us[i] = ac_zero_cross_dimmer.cycle_time_us / 2; } // If full cycle is required keep pin HIGH, skip LOW by skipping phase - if (100 * ac_zero_cross_dimmer.enable_time_us[i] < 15 * ac_zero_cross_dimmer.cycle_time_us) { + if (100 * ac_zero_cross_dimmer.enable_time_us[i] < MIN_PERCENT * ac_zero_cross_dimmer.cycle_time_us) { ac_zero_cross_dimmer.current_state_in_phase[i] = 3; } } @@ -112,13 +115,7 @@ void ACDimmerInterruptDisable(bool disable) // For ESP32, we can't use dynamic interval calculation because the timerX functions // are not callable from ISR (placed in flash storage). // Here we just use an interrupt firing every 75 µs. - if (Settings->save_data == 0) { - AddLog(LOG_LEVEL_INFO, PSTR("ZCD: Save disabled. High frequency scan enabled. DO NOT USE SAVEDATA if channel is on")); - timerAlarmWrite(dimmer_timer, 30, true); - } else { - AddLog(LOG_LEVEL_INFO, PSTR("ZCD: Save on. 75µs scan enabled")); - timerAlarmWrite(dimmer_timer, 75, true); - } + timerAlarmWrite(dimmer_timer, TRIGGER_PERIOD , true); } timerAlarmEnable(dimmer_timer); #endif @@ -139,9 +136,12 @@ void IRAM_ATTR ACDimmerTimer_intr() { return; uint32_t time_since_zc = now - ac_zero_cross_dimmer.crossed_zero_at; + + // Check for missed Zero-Cross event. Single failure will correct if (time_since_zc > 10100) { memset(&ac_zero_cross_dimmer.current_state_in_phase, 0x00, sizeof(ac_zero_cross_dimmer.current_state_in_phase)); ac_zero_cross_dimmer.crossed_zero_at += ac_zero_cross_dimmer.cycle_time_us; + ac_zero_cross_dimmer.missed_zero_cross++; time_since_zc = now - ac_zero_cross_dimmer.crossed_zero_at; } @@ -149,6 +149,7 @@ void IRAM_ATTR ACDimmerTimer_intr() { if (Pin(GPIO_PWM1, i) == -1) continue; switch (ac_zero_cross_dimmer.current_state_in_phase[i]) { case 1: + // Switch off does not need high accuracy. Happens at the next 75µs trigger if (time_since_zc >= ac_zero_cross_dimmer.disable_time_us[i]) { digitalWrite(Pin(GPIO_PWM1, i), LOW); ac_zero_cross_dimmer.current_state_in_phase[i]++; @@ -156,9 +157,17 @@ void IRAM_ATTR ACDimmerTimer_intr() { break; case 0: case 3: + if (time_since_zc + TRIGGER_PERIOD >= ac_zero_cross_dimmer.enable_time_us[i]){ + // Very close to the fire event. Loop the last µseconds to wait. + while (time_since_zc < ac_zero_cross_dimmer.enable_time_us[i]) { + now = micros(); + time_since_zc = now - ac_zero_cross_dimmer.crossed_zero_at; + } + } if (time_since_zc >= ac_zero_cross_dimmer.enable_time_us[i]) { digitalWrite(Pin(GPIO_PWM1, i), HIGH); ac_zero_cross_dimmer.current_state_in_phase[i]++; + ac_zero_cross_dimmer.accurracy[i] = time_since_zc-ac_zero_cross_dimmer.enable_time_us[i]; } break; } @@ -173,21 +182,25 @@ void ACDimmerControllTrigger(void) { } for (uint8_t i = 0; i < MAX_PWMS; i++){ if (Pin(GPIO_PWM1, i) == -1) continue; - ac_zero_cross_dimmer.lastlight[i] = Light.fade_running ? Light.fade_cur_10[i] : Light.fade_start_10[i]; + + if (ac_zero_cross_dimmer.detailpower[i]){ + ac_zero_cross_dimmer.lastlight[i] = changeUIntScale(ac_zero_cross_dimmer.detailpower[i]/10, 0, 1000, 0, 1023); + } else { + ac_zero_cross_dimmer.lastlight[i] = Light.fade_running ? Light.fade_cur_10[i] : Light.fade_start_10[i]; + } ac_zero_cross_dimmer.enable_time_us[i] = (ac_zero_cross_dimmer.cycle_time_us * (1023 - ac_zero_cross_power(ac_zero_cross_dimmer.lastlight[i]))) / 1023; #ifdef ESP32 if (ac_zero_cross_dimmer.detailpower[i]){ float state = (float)(1 - (ac_zero_cross_dimmer.detailpower[i]/10000.0)); //state = std::acos(1 - (2 * state)) / 3.14159 * ac_zero_cross_dimmer.cycle_time_us; - state = std::acos(1 - (2 * state)) / 3.14159 * 10000; + state = std::acos(1 - (2 * state)) / 3.14159 * ac_zero_cross_dimmer.cycle_time_us; //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("ZCD: Float2: %*_f"),0,&state); ac_zero_cross_dimmer.enable_time_us[i] = (uint32_t)state; - ac_zero_cross_dimmer.lastlight[i] = 1; } #endif - ac_zero_cross_dimmer.disable_time_us[i] = ac_zero_cross_dimmer.enable_time_us[i] + GATE_ENABLE_TIME; + ac_zero_cross_dimmer.disable_time_us[i] = ac_zero_cross_dimmer.enable_time_us[i] + ac_zero_cross_dimmer.triggertime; } } @@ -204,17 +217,18 @@ void ACDimmerLogging(void) } #endif - AddLog(LOG_LEVEL_DEBUG, PSTR("ZCD: ZeroEnable %d -> %d, Alarm %d, intr: %ld, cycle time: %ld µs"), - ac_zero_cross_dimmer.dimmer_in_use, ac_zero_cross_dimmer.timer_iterrupt_started, alarmEnabled, timercounter, ac_zero_cross_dimmer.cycle_time_us + AddLog(LOG_LEVEL_DEBUG, PSTR("ZCD: ZeroEnable %d -> %d, Alarm %d, intr: %ld, cycle time: %ld µs, missed zc %ld"), + ac_zero_cross_dimmer.dimmer_in_use, ac_zero_cross_dimmer.timer_iterrupt_started, alarmEnabled, timercounter, + ac_zero_cross_dimmer.cycle_time_us, ac_zero_cross_dimmer.missed_zero_cross ); for (uint8_t i = 0; i < MAX_PWMS; i++){ if (Pin(GPIO_PWM1, i) == -1) continue; - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("ZCD: PWM[%d] en: %ld µs, dis: %ld µs, state %d, fade: %d, cur: %d, end: %d, lastlight: %d"), + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("ZCD: PWM[%d] en: %ld µs, dis: %ld µs, state %d, fade: %d, cur: %d, end: %d, lastlight: %d, acc: %ld"), i+1, ac_zero_cross_dimmer.enable_time_us[i], ac_zero_cross_dimmer.disable_time_us[i], - ac_zero_cross_dimmer.current_state_in_phase[i], Light.fade_cur_10[i], Light.fade_start_10[i], Light.fade_end_10[i], ac_zero_cross_dimmer.lastlight[i] + ac_zero_cross_dimmer.current_state_in_phase[i], Light.fade_cur_10[i], Light.fade_start_10[i], Light.fade_end_10[i], ac_zero_cross_dimmer.lastlight[i], + ac_zero_cross_dimmer.accurracy[i] ); } - } @@ -232,6 +246,14 @@ void CmndZCDimmerSet(void) } } +/* void CmndZCGateEnableTime(void) +{ + if (XdrvMailbox.payload > 0) { + ac_zero_cross_dimmer.triggertime = XdrvMailbox.payload; + } + ResponseCmndNumber(ac_zero_cross_dimmer.triggertime); +} */ + /*********************************************************************************************\ * Interface \*********************************************************************************************/ @@ -256,21 +278,16 @@ bool Xdrv68(uint32_t function) ACDimmerControllTrigger(); break; case FUNC_INTERRUPT_STOP: - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("ZCD: FUNC_INTERRUPT_STOP")); ACDimmerInterruptDisable(true); break; case FUNC_INTERRUPT_START: - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("ZCD: FUNC_INTERRUPT_START")); ACDimmerInterruptDisable(false); break; case FUNC_COMMAND: result = DecodeCommand(kZCDimmerCommands, ZCDimmerCommand); - //result = DecodeCommand(kShutterCommands, ShutterCommand); - break; } - } + } return result; } - #endif // USE_AC_ZERO_CROSS_DIMMER