From a7577cfefcfcc1d6ce2931097a2e0fcf249bb89a Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Tue, 8 Mar 2022 22:49:12 +0100 Subject: [PATCH] Esp32 improve PWM inverted --- .../src/esp8266toEsp32.cpp | 38 ++++++++++++++----- .../src/esp8266toEsp32.h | 2 +- tasmota/support_pwm.ino | 36 +++++++++--------- tasmota/xdrv_04_light.ino | 2 +- 4 files changed, 48 insertions(+), 30 deletions(-) diff --git a/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp b/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp index 372d03a7a..563095a36 100644 --- a/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp +++ b/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp @@ -97,16 +97,33 @@ void analogWriteFreq(uint32_t freq) { _analogWriteFreqRange(); } -int32_t analogAttach(uint32_t pin) { // returns ledc channel used, or -1 if failed +int32_t analogAttach(uint32_t pin, bool output_invert) { // returns ledc channel used, or -1 if failed _analogInit(); // make sure the mapping array is initialized // Find if pin is already attached - int32_t channel = _analog_pin2chan(pin); - if (channel >= 0) { return channel; } + int32_t chan = _analog_pin2chan(pin); + if (chan >= 0) { return chan; } // Find an empty channel - for (channel = 0; channel < MAX_PWMS; channel++) { - if (255 == pwm_channel[channel]) { - pwm_channel[channel] = pin; - ledcAttachPin(pin, channel); + for (chan = 0; chan < MAX_PWMS; chan++) { + if (255 == pwm_channel[chan]) { + pwm_channel[chan] = pin; + + // ledcAttachPin(pin, channel); -- replicating here because we want the default duty + uint8_t group=(chan/8), channel=(chan%8), timer=((chan/2)%4); + + // AddLog(LOG_LEVEL_INFO, "PWM: ledc_channel pin=%i out_invert=%i", pin, output_invert); + ledc_channel_config_t ledc_channel = { + (int)pin, // gpio + (ledc_mode_t)group, // speed-mode + (ledc_channel_t)channel, // channel + (ledc_intr_type_t)LEDC_INTR_DISABLE, // intr_type + (ledc_timer_t)timer, // timer_sel + 0, // duty + 0, // hpoint + { output_invert ? 1u : 0u },// output_invert + }; + ledc_channel_config(&ledc_channel); + + ledcSetup(channel, pwm_frequency, pwm_bit_num); // Serial.printf("PWM: New attach pin %d to channel %d\n", pin, channel); return channel; @@ -153,6 +170,7 @@ void analogWritePhase(uint8_t pin, uint32_t duty, uint32_t phase) chan = analogAttach(pin); if (chan < 0) { return; } // failed } + // AddLog(LOG_LEVEL_INFO, "PWM: analogWritePhase pin=%i chan=%i duty=%03X phase=%03X", pin, chan, duty, phase); if (duty >> (pwm_bit_num-1) ) ++duty; // input is 0..1023 but PWM takes 0..1024 - so we skip at mid-range. It creates a small non-linearity if (phase >> (pwm_bit_num-1) ) ++phase; @@ -163,8 +181,10 @@ void analogWritePhase(uint8_t pin, uint32_t duty, uint32_t phase) uint32_t max_duty = (1 << channels_resolution[chan]) - 1; phase = phase & max_duty; - ledc_set_duty_with_hpoint((ledc_mode_t)group, (ledc_channel_t)channel, duty, phase); - ledc_update_duty((ledc_mode_t)group, (ledc_channel_t)channel); + esp_err_t err1, err2; + err1 = ledc_set_duty_with_hpoint((ledc_mode_t)group, (ledc_channel_t)channel, duty, phase); + err2 = ledc_update_duty((ledc_mode_t)group, (ledc_channel_t)channel); + // AddLog(LOG_LEVEL_INFO, "PWM: err1=%i err2=%i", err1, err2); } #endif // ESP32 diff --git a/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.h b/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.h index b32e16bcf..59c63801a 100644 --- a/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.h +++ b/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.h @@ -27,7 +27,7 @@ // input range is in full range, ledc needs bits void analogWriteRange(uint32_t range); void analogWriteFreq(uint32_t freq); -int32_t analogAttach(uint32_t pin); // returns the ledc channel, or -1 if failed. This is implicitly called by analogWrite if the channel was not already allocated +int32_t analogAttach(uint32_t pin, bool output_invert = false); // returns the ledc channel, or -1 if failed. This is implicitly called by analogWrite if the channel was not already allocated void analogWrite(uint8_t pin, int val); // Extended version that also allows to change phase diff --git a/tasmota/support_pwm.ino b/tasmota/support_pwm.ino index 82e4bdd7e..b71dd575a 100644 --- a/tasmota/support_pwm.ino +++ b/tasmota/support_pwm.ino @@ -64,7 +64,8 @@ void PwmSaveToSettings(void) { // or `-1` if no change. // Auto-phasing is recomputed, and changes are applied to GPIO if there is a physical GPIO configured and an actual change needed // -void PwmApplyGPIO(void) { +// force_update_all: force applying the PWM values even if the value didn't change (necessary at initialization) +void PwmApplyGPIO(bool force_update_all) { uint32_t pwm_phase_accumulator = 0; // dephase each PWM channel with the value of the previous for (uint32_t i = 0; i < MAX_PWMS; i++) { @@ -73,9 +74,6 @@ void PwmApplyGPIO(void) { if (TasmotaGlobal.pwm_value[i] >= 0) { pwm_val = TasmotaGlobal.pwm_value[i]; } // new value explicitly specified if (pwm_val > Settings->pwm_range) { pwm_val = Settings->pwm_range; } // prevent overflow - // gpio_val : actual value of GPIO, taking into account inversion - uint32_t gpio_val = bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range - pwm_val : pwm_val; - // compute phase uint32_t pwm_phase = TasmotaGlobal.pwm_cur_phase[i]; // pwm_phase is the logical phase of the active pulse, ignoring inverted uint32_t gpio_phase = pwm_phase; // gpio is the physical phase taking into account inverted @@ -86,17 +84,17 @@ void PwmApplyGPIO(void) { } else { // compute auto-phase pwm_phase = pwm_phase_accumulator; - uint32_t pwm_phase_invert = bitRead(TasmotaGlobal.pwm_inverted, i) ? pwm_val : 0; // move phase if inverted - gpio_phase = (pwm_phase + pwm_phase_invert) & Settings->pwm_range; // accumulate phase for next GPIO pwm_phase_accumulator = (pwm_phase + pwm_val) & Settings->pwm_range; } // apply new values to GPIO if GPIO is set + // AddLog(LOG_LEVEL_INFO, "PWM: i=%i used=%i pwm_val=%03X vs %03X pwm_phase=%03X vs %03X", i, PinUsed(GPIO_PWM1, i), pwm_val, TasmotaGlobal.pwm_cur_value[i], pwm_phase, TasmotaGlobal.pwm_cur_phase[i]); if (PinUsed(GPIO_PWM1, i)) { - if ((pwm_val != TasmotaGlobal.pwm_cur_value[i]) || (pwm_phase != TasmotaGlobal.pwm_cur_phase[i])) { + if (force_update_all || (pwm_val != TasmotaGlobal.pwm_cur_value[i]) || (pwm_phase != TasmotaGlobal.pwm_cur_phase[i])) { // GPIO has PWM and there is a chnage to apply, apply it - analogWritePhase(Pin(GPIO_PWM1, i), gpio_val, gpio_phase); + analogWritePhase(Pin(GPIO_PWM1, i), pwm_val, pwm_phase); + // AddLog(LOG_LEVEL_INFO, "PWM: analogWritePhase i=%i val=%03X phase=%03X", i, pwm_val, pwm_phase); } } @@ -104,13 +102,13 @@ void PwmApplyGPIO(void) { TasmotaGlobal.pwm_cur_value[i] = pwm_val; TasmotaGlobal.pwm_cur_phase[i] = pwm_phase; } - // AddLog(LOG_LEVEL_INFO, "PWM: Val=%03X-%03X-%03X-%03X-%03X Phase=%03X-%03X-%03X-%03X-%03X Range=%03X", - // TasmotaGlobal.pwm_cur_value[0], TasmotaGlobal.pwm_cur_value[1], TasmotaGlobal.pwm_cur_value[2], TasmotaGlobal.pwm_cur_value[3], - // TasmotaGlobal.pwm_cur_value[4], - // TasmotaGlobal.pwm_cur_phase[0], TasmotaGlobal.pwm_cur_phase[1], TasmotaGlobal.pwm_cur_phase[2], TasmotaGlobal.pwm_cur_phase[3], - // TasmotaGlobal.pwm_cur_phase[4], - // Settings->pwm_range - // ); + AddLog(LOG_LEVEL_INFO, "PWM: Val=%03X-%03X-%03X-%03X-%03X Phase=%03X-%03X-%03X-%03X-%03X Range=%03X", + TasmotaGlobal.pwm_cur_value[0], TasmotaGlobal.pwm_cur_value[1], TasmotaGlobal.pwm_cur_value[2], TasmotaGlobal.pwm_cur_value[3], + TasmotaGlobal.pwm_cur_value[4], + TasmotaGlobal.pwm_cur_phase[0], TasmotaGlobal.pwm_cur_phase[1], TasmotaGlobal.pwm_cur_phase[2], TasmotaGlobal.pwm_cur_phase[3], + TasmotaGlobal.pwm_cur_phase[4], + Settings->pwm_range + ); PwmSaveToSettings(); // copy to Settings PwmRearmChanges(); // reset expected changes } @@ -120,7 +118,7 @@ void CmndPwm(void) if (TasmotaGlobal.pwm_present && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_PWMS)) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= Settings->pwm_range) && PinUsed(GPIO_PWM1, XdrvMailbox.index -1)) { TasmotaGlobal.pwm_value[XdrvMailbox.index - 1] = XdrvMailbox.payload; - PwmApplyGPIO(); + PwmApplyGPIO(false); } Response_P(PSTR("{")); MqttShowPWMState(); // Render the PWM status to MQTT @@ -133,7 +131,7 @@ void GpioInitPwm(void) { for (uint32_t i = 0; i < MAX_PWMS; i++) { // Basic PWM control only if (PinUsed(GPIO_PWM1, i)) { - analogAttach(Pin(GPIO_PWM1, i)); + analogAttach(Pin(GPIO_PWM1, i), bitRead(TasmotaGlobal.pwm_inverted, i)); if (i < TasmotaGlobal.light_type) { // force PWM GPIOs to black TasmotaGlobal.pwm_value[i] = 0; @@ -147,7 +145,7 @@ void GpioInitPwm(void) { } } } - PwmApplyGPIO(); // apply all changes + PwmApplyGPIO(true); // apply all changes } /********************************************************************************************/ @@ -157,7 +155,7 @@ void ResetPwm(void) for (uint32_t i = 0; i < MAX_PWMS; i++) { // Basic PWM control only TasmotaGlobal.pwm_value[i] = 0; } - PwmApplyGPIO(); + PwmApplyGPIO(true); } #else // now for ESP8266 diff --git a/tasmota/xdrv_04_light.ino b/tasmota/xdrv_04_light.ino index edce4b707..7c0b8344d 100644 --- a/tasmota/xdrv_04_light.ino +++ b/tasmota/xdrv_04_light.ino @@ -2163,7 +2163,7 @@ void LightSetOutputs(const uint16_t *cur_col_10) { } } #ifdef ESP32 - PwmApplyGPIO(); + PwmApplyGPIO(false); #endif // ESP32 #ifdef USE_PWM_DIMMER