diff --git a/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp b/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp index 1438c306a..b5e8450e9 100644 --- a/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp +++ b/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp @@ -25,6 +25,21 @@ enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_D // ESP Stuff +// This is from Arduino code -- not sure why it is necessary +//Use XTAL clock if possible to avoid timer frequency error when setting APB clock < 80 Mhz +//Need to be fixed in ESP-IDF +#ifdef SOC_LEDC_SUPPORT_XTAL_CLOCK +#define LEDC_DEFAULT_CLK LEDC_USE_XTAL_CLK +#else +#define LEDC_DEFAULT_CLK LEDC_AUTO_CLK +#endif +#define LEDC_MAX_BIT_WIDTH SOC_LEDC_TIMER_BIT_WIDE_NUM + +// define our limits to ease any change from esp-idf +#define MAX_TIMERS LEDC_TIMER_MAX // 4 timers for all ESP32 variants +#define HAS_HIGHSPEED SOC_LEDC_SUPPORT_HS_MODE // are there 2 banks of timers/ledc + + // replicated from `tasmota.h` #if defined(CONFIG_IDF_TARGET_ESP32) const uint8_t MAX_PWMS = 16; // ESP32: 16 ledc PWM channels in total - TODO for now @@ -38,49 +53,98 @@ enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_D const uint8_t MAX_PWMS = 5; // Unknown - revert to 5 PWM max #endif +// current configuration of timers: frequency and resolution + +static uint32_t timer_freq_hz[MAX_TIMERS] = {0}; +static uint8_t timer_duty_resolution[MAX_TIMERS] = {0}; + // channel mapping -static uint8_t pwm_channel[MAX_PWMS]; -static uint32_t pwm_frequency = 977; // Default 977Hz -static uint8_t pwm_bit_num = 10; // Default 1023 +static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 }; // contains the channel assigned to each pin, 0 means unassigned, substract 1 +static uint8_t pwm_timer[MAX_PWMS] = {0}; // contains the timer assigned to each channel + +static const uint32_t pwm_def_frequency = 977; // Default 977Hz +static const ledc_timer_bit_t pwm_def_bit_num = LEDC_TIMER_10_BIT; // Default 1023 static bool pwm_impl_inited = false; // trigger initialization /*********************************************************************************************\ * ESP32 analogWrite emulation support \*********************************************************************************************/ +// apply the configuration of timer number `timer` to the actual timer +// it should be called whenever you change the configuration of a Timer +void _analog_applyTimerConfig(int32_t timer) { + esp_err_t ret; + if (timer < 0 || timer >= MAX_TIMERS) { return; } // avoid overflow or underflow + + // AddLog(LOG_LEVEL_INFO, "PWM: ledc_timer_config(res=%i timer=%i freq=%i)", timer_duty_resolution[timer], timer, timer_freq_hz[timer]); + // we apply configuration to timer + ledc_timer_config_t cfg = { + (ledc_mode_t) 0, // speed mode - first bank + (ledc_timer_bit_t) timer_duty_resolution[timer], // duty_resolution + (ledc_timer_t) timer, // timer_num + timer_freq_hz[timer], // freq_hz + LEDC_DEFAULT_CLK // clk_cfg + }; + ret = ledc_timer_config(&cfg); + if (ret != ESP_OK) { + AddLog(LOG_LEVEL_ERROR, "PWM: ledc_timer_config %i failed ret=%i", timer, ret); + } +#ifdef HAS_HIGHSPEED + // apply the same parameter to the low-speed timer as well + cfg.speed_mode = (ledc_mode_t) 1; // first bank + ret = ledc_timer_config(&cfg); + if (ret != ESP_OK) { + AddLog(LOG_LEVEL_ERROR, "PWM: ledc_timer_config %i failed ret=%i", timer + MAX_TIMERS, ret); + } +#endif +} + +// initialize all timers and memory structures void _analogInit(void) { - if (pwm_impl_inited) return; + if (pwm_impl_inited) { return; } // set all channels to unaffected (255) - for (uint32_t i = 0; i < MAX_PWMS; i++) { - pwm_channel[i] = 255; + + // On ESP32 there are 2 groups of timers. 0..LEDC_TIMER_MAX-1 and LEDC_TIMER_MAX..2*LEDC_TIMER_MAX-1 + for (uint32_t i = 0; i < MAX_TIMERS; i++) { + timer_freq_hz[i] = pwm_def_frequency; + timer_duty_resolution[i] = pwm_def_bit_num; + _analog_applyTimerConfig(i); // apply settings to Timer 0 } pwm_impl_inited = true; } -int _analog_pin2chan(uint32_t pin) { // returns -1 if uallocated - _analogInit(); // make sure the mapping array is initialized - for (uint32_t channel = 0; channel < MAX_PWMS; channel++) { - if ((pwm_channel[channel] < 255) && (pwm_channel[channel] == pin)) { - return channel; +// set the timer number for a GPIO, ignore if the GPIO is not set or Timer number is invalid +// Timer range is 0..3 +void ledcSetTimer(uint8_t chan, uint8_t timer) { + if (timer >= MAX_TIMERS || chan > MAX_PWMS) { return; } + uint8_t cur_timer = pwm_timer[chan]; + + if (timer != cur_timer) { // ignore if the timer number is the same + pwm_timer[chan] = timer; // change the timer value + // apply to hardware + uint8_t group=(chan/8); + uint8_t channel=(chan%8); + esp_err_t ret = ledc_bind_channel_timer((ledc_mode_t) group, (ledc_channel_t) channel, (ledc_timer_t) timer); + if (ret != ESP_OK) { + AddLog(LOG_LEVEL_ERROR, "PWM: ledc_bind_channel_timer %i failed ret=%i", timer, ret); } } - return -1; } -void _analogWriteFreqRange(uint32_t pin) { +// return the channel number for a GPIO, -1 if none +int32_t analogGetChannel2(uint32_t pin) { // returns -1 if uallocated + if (pin >= SOC_GPIO_PIN_COUNT) { return -1; } + return pin_to_channel[pin] - 1; +} + +/* Convert a GPIO number to the pointer of the Timer number */ +int32_t _analog_pin2timer(uint32_t pin) { // returns -1 if uallocated _analogInit(); // make sure the mapping array is initialized - if (255 == pin) { - for (uint32_t channel = 0; channel < MAX_PWMS; channel++) { - if (pwm_channel[channel] < 255) { - ledcSetup(channel, pwm_frequency, pwm_bit_num); - } - } - } else { - int channel = _analog_pin2chan(pin); - if (channel >= 0) { - ledcSetup(channel, pwm_frequency, pwm_bit_num); - } - } + int chan = analogGetChannel2(pin); + if (chan < 0) { return -1; } + int32_t timer = pwm_timer[chan]; + if (timer > MAX_TIMERS) { timer = 0; } + return timer; } // input range is in full range, ledc needs bits @@ -93,52 +157,163 @@ uint32_t _analogGetResolution(uint32_t x) { return bits; } -void analogWriteRange(uint32_t range, uint32_t pin) { - pwm_bit_num = _analogGetResolution(range); - _analogWriteFreqRange(pin); -} - -void analogWriteFreq(uint32_t freq, uint32_t pin) { - pwm_frequency = freq; - _analogWriteFreqRange(pin); -} - -int analogAttach(uint32_t pin, bool output_invert) { // returns ledc channel used, or -1 if failed +void analogWriteRange(uint32_t range, int32_t pin) { + // AddLog(LOG_LEVEL_INFO, "PWM: analogWriteRange range=%i pin=%i", range, pin); _analogInit(); // make sure the mapping array is initialized - // Find if pin is already attached - int chan = _analog_pin2chan(pin); - if (chan >= 0) { return chan; } - // Find an empty channel - for (chan = 0; chan < MAX_PWMS; chan++) { - if (255 == pwm_channel[chan]) { - pwm_channel[chan] = pin; + int32_t timer = (pin < 0) ? 0 : _analog_pin2timer(pin); + if (timer < 0) { return; } - // ledcAttachPin(pin, channel); -- replicating here because we want the default duty - uint8_t group=(chan/8), channel=(chan%8), timer=((chan/2)%4); + uint32_t pwm_bit_num = _analogGetResolution(range); + if (pwm_bit_num > LEDC_MAX_BIT_WIDTH || pwm_bit_num == 0) { + AddLog(LOG_LEVEL_ERROR, "PWM: range is invalid: %i", range); + return; + } + timer_duty_resolution[timer] = (ledc_timer_bit_t) pwm_bit_num; + _analog_applyTimerConfig(timer); +} - // 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); +// change both freq and range +// `0`: set to global value +// `-1`: keep unchanged +// if pin < 0 then change global value for timer 0 +void analogWriteFreqRange(int32_t freq, int32_t range, int32_t pin) { + AddLog(LOG_LEVEL_INFO, "PWM: analogWriteFreqRange freq=%i range=%i pin=%i", freq, range, pin); + _analogInit(); // make sure the mapping array is initialized + uint32_t timer0_freq = timer_freq_hz[0]; // global values + uint8_t timer0_res = timer_duty_resolution[0]; + + int32_t timer = 0; + int32_t res = timer0_res; + if (pin < 0) { + if (freq <= 0) { freq = timer0_freq; } + if (range > 0) { + res = _analogGetResolution(range); + if (res >= LEDC_TIMER_BIT_MAX) { return; } + } + } else { + int32_t chan = analogGetChannel2(pin); + if (chan < 0) { return; } + timer = pwm_timer[chan]; + if (freq < 0) { freq = timer_freq_hz[timer]; } + if (freq == 0) { freq = timer0_freq; } - ledcSetup(channel, pwm_frequency, pwm_bit_num); - // Serial.printf("PWM: New attach pin %d to channel %d\n", pin, channel); - return channel; + res = timer0_res; + if (range < 0) { res = timer_duty_resolution[timer]; } + if (range != 0) { res = _analogGetResolution(range); } + if (res >= LEDC_TIMER_BIT_MAX) { return; } + + if (freq == timer0_freq && res == timer0_res) { + // settings match with the global value + if (timer != 0) { + ledcSetTimer(chan, 0); + timer = 0; + } + // else nothing to change + } else { + // specific (non-global) values, require a specific timer + if (timer == 0) { // currently using the global timer, need to change + // we need to allocate a new timer to this pin + int32_t next_timer = analogNextFreeTimer(); + if (next_timer < 0) { + AddLog(LOG_LEVEL_ERROR, "PWM: failed to assign a timer to GPIO %i", pin); + } else { + ledcSetTimer(chan, next_timer); + timer = next_timer; + } + } + } + pwm_timer[chan] = timer; + } + + // AddLog(LOG_LEVEL_INFO, "PWM: analogWriteFreq actual freq=%i res=%i pin=%i timer=%i", freq, res, pin, timer); + if (timer_freq_hz[timer] != freq || timer_duty_resolution[timer] != res) { + timer_freq_hz[timer] = freq; + timer_duty_resolution[timer] = res; + _analog_applyTimerConfig(timer); + } +} + +// set the frequency, in pin == -1 then change the global value of timer 0 +void analogWriteFreq(uint32_t freq, int32_t pin) { + analogWriteFreqRange(freq, 0, pin); +} + +// find next unassigned channel, or -1 if none available +static int32_t findEmptyChannel() { + bool chan_used[MAX_PWMS] = {0}; + for (uint32_t pin = 0; pin < SOC_GPIO_PIN_COUNT; pin++) { + if (pin_to_channel[pin] > 0) { + chan_used[pin_to_channel[pin] - 1] = true; + } + } + + // find empty slot + for (uint32_t chan = 0; chan < MAX_PWMS; chan++) { + if (!chan_used[chan]) { + return chan; } } - // No more channels available - AddLog(LOG_LEVEL_INFO, "PWM: no more PWM (ledc) channel for GPIO %i", pin); return -1; } +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 chan = analogGetChannel2(pin); + if (chan >= 0) { return chan; } + // Find an empty channel + chan = findEmptyChannel(); + if (chan < 0) { + AddLog(LOG_LEVEL_INFO, "PWM: no more PWM (ledc) channel for GPIO %i", pin); + return -1; + } + + // new channel attached to pin + pin_to_channel[pin] = chan + 1; + + // ledcAttachPin(pin, channel); -- replicating here because we want the default duty + // timer0 used by default + uint8_t group=(chan/8); + uint8_t channel=(chan%8); + uint8_t timer=0; + + // 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); + + // AddLog(LOG_LEVEL_INFO, "PWM: New attach pin %d to channel %d", pin, channel); + return chan; +} + +uint32_t ledcReadFreq2(uint8_t chan) { +// _Z13ledcReadFreqh +// extern "C" uint32_t _Z13ledcReadFreqh(uint8_t chan) { + if (chan > MAX_PWMS) { + return 0; // wrong channel + } + int32_t timer = pwm_timer[chan]; + int32_t freq = timer_freq_hz[timer]; + return freq; +} + +uint8_t ledcReadResolution(uint8_t chan) { + if (chan > MAX_PWMS) { + return 0; // wrong channel + } + int32_t timer = pwm_timer[chan]; + int32_t res = timer_duty_resolution[timer]; + return res; +} + // void analogWrite(uint8_t pin, int val); extern "C" void __wrap__Z11analogWritehi(uint8_t pin, int val) { analogWritePhase(pin, val, 0); // if unspecified, use phase = 0 @@ -163,17 +338,24 @@ extern "C" void __wrap__Z11analogWritehi(uint8_t pin, int val) { implementation changes. */ -// exported from Arduno Core -extern uint8_t channels_resolution[MAX_PWMS]; - void analogWritePhase(uint8_t pin, uint32_t duty, uint32_t phase) { - int chan = _analog_pin2chan(pin); + int32_t chan = analogGetChannel2(pin); if (chan < 0) { // not yet allocated, try to allocate chan = analogAttach(pin); - if (chan < 0) { return; } // failed + if (chan < 0) { + AddLog(LOG_LEVEL_INFO, "PWM: analogWritePhase invalid chan=%i", chan); + return; + } // failed } - // AddLog(LOG_LEVEL_INFO, "PWM: analogWritePhase pin=%i chan=%i duty=%03X phase=%03X", pin, chan, duty, phase); + int32_t timer = _analog_pin2timer(pin); + if (timer < 0) { + AddLog(LOG_LEVEL_INFO, "PWM: analogWritePhase invalid timer=%i", timer); + return; + } + + int32_t pwm_bit_num = timer_duty_resolution[timer]; + // AddLog(LOG_LEVEL_INFO, "PWM: analogWritePhase pin=%i chan=%i duty=%03X phase=%03X pwm_bit_num=%i", pin, chan, duty, phase, pwm_bit_num); 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; @@ -181,7 +363,7 @@ void analogWritePhase(uint8_t pin, uint32_t duty, uint32_t phase) uint8_t group=(chan/8), channel=(chan%8); //Fixing if all bits in resolution is set = LEDC FULL ON - uint32_t max_duty = (1 << channels_resolution[chan]) - 1; + uint32_t max_duty = (1 << pwm_bit_num) - 1; phase = phase & max_duty; esp_err_t err1, err2; @@ -190,4 +372,57 @@ void analogWritePhase(uint8_t pin, uint32_t duty, uint32_t phase) // AddLog(LOG_LEVEL_INFO, "PWM: err1=%i err2=%i", err1, err2); } +// get the timer number for a GPIO, -1 if not found +int32_t analogGetTimer(uint8_t pin) { + return _analog_pin2timer(pin); +} + +int32_t analogGetTimerForChannel(uint8_t chan) { + _analogInit(); // make sure the mapping array is initialized + if (chan > MAX_PWMS) { return -1; } + int32_t timer = pwm_timer[chan]; + if (timer > MAX_TIMERS) { timer = 0; } + return timer; +} + + +// get the next unused timer, returns -1 if no free timer is available +// Keep in mind that Timer 0 is reserved, which leaves only 3 timers available +// +// This function does not reserve the timer, it is reserved only when you assign a GPIO to it +int32_t analogNextFreeTimer() { + _analogInit(); // make sure the mapping array is initialized + bool assigned[MAX_TIMERS] = {}; + assigned[0] = true; + + for (uint32_t chan = 0; chan < MAX_PWMS; chan++) { + assigned[pwm_timer[chan]] = true; + } + + // find first free + for (uint32_t j = 0; j < MAX_TIMERS; j++) { + if (!assigned[j]) { + // AddLog(LOG_LEVEL_INFO, "PWM: analogNextFreeTimer next_timer=%i", j); + return j; + } + } + // AddLog(LOG_LEVEL_INFO, "PWM: analogNextFreeTimer no free timer"); + return -1; // none available +} + +// Get timer resolution (in bits) - default 10 +uint8_t analogGetTimerResolution(uint8_t timer) { + _analogInit(); // make sure the mapping array is initialized + if (timer >= MAX_TIMERS) { timer = 0; } + return timer_duty_resolution[timer]; +} + +// Get timer frequency (in Hz) - default 977 +uint32_t analogGetTimerFrequency(uint8_t timer) { + _analogInit(); // make sure the mapping array is initialized + if (timer >= MAX_TIMERS) { timer = 0; } + return timer_freq_hz[timer]; // TODO check validity of value +} + + #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 c10f111fb..bc6e17c8b 100644 --- a/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.h +++ b/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.h @@ -23,18 +23,101 @@ // #include +/*******************************************************************************************\ + * ESP32/S2/S3/C3... PWM analog support + * + * The following supersedes Arduino framework and provides more granular control: + * - fine grained phase control (in addition to duty cycle) + * - fine control of which PWM uses which timers + * - fine control of frequency and resolution per timer + * + * By default, all PWM are using the same timer called Timer0. + * Changes in frequency of resolution apply to all PWM using Timer0. + * + * You can specify a different timer per GPIO (Timer1 to Timer3). + * Then any change of resolution or frequency to these GPIOs alter only + * the corresponding timer. + * + * Note: on ESP32-only, there are 2 groups of PWM and 2 groups of timers. + * - PWM 0..7 are using Timer 0..3 + * - PWM 8..15 are using Timer 4..7 +\*******************************************************************************************/ -// input range is in full range, ledc needs bits -//void analogWriteRange(uint32_t range); -void analogWriteRange(uint32_t range, uint32_t pin = 255); -//void analogWriteFreq(uint32_t freq); -void analogWriteFreq(uint32_t freq, uint32_t pin = 255); +uint32_t ledcReadFreq2(uint8_t chan); +uint8_t ledcReadResolution(uint8_t chan); +// +// analogAttach - attach a GPIO to a hardware PWM +// +// Calling explcitly analogAttach() allows to specify the `output_invert` flag +// However it is called implicitly if `analogWrite()` is called and the GPIO +// was not yet attached. +// +// Returns: hardware channel number, or -1 if it failed int 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 + +// +// analogWriteRange - change the range of PWM +// +// Internally the range is converted to a number of bits, so range must be a power of 2. +// By default, the resolution is 10 bits, i.e. a range of 1024. +// +// When changing this value, it changes the underlying Timer. This means +// that the range changes for all PWM using the same timer. +// +// If `pin == 255`, the value is changed for the default Timer(s) +// I.e. `Timer 0` (and `Timer 4` on ESP32) +void analogWriteRange(uint32_t range, int32_t pin = -1); + +// +// analogWriteFreq - change the frequency of PWM in Hz +// +// Default value is 977Hz. TODO: use the value in `my_user_config.h` +// +// When changing this value, it changes the underlying Timer. This means +// that the range changes for all PWM using the same timer. +// +// If `pin == 255`, the value is changed for the default Timer(s) +// I.e. `Timer 0` (and `Timer 4` on ESP32) +void analogWriteFreq(uint32_t freq, int32_t pin = -1); + +// +// analogWrite - change the value of PWM +// +// val must be in range. void analogWrite(uint8_t pin, int val); // Extended version that also allows to change phase extern void analogWritePhase(uint8_t pin, uint32_t duty, uint32_t phase = 0); +// return the channel assigned to a GPIO, or -1 if none +extern int32_t analogGetChannel2(uint32_t pin); + +/*******************************************************************************************\ + * Low-level Timer management +\*******************************************************************************************/ +// get the timer number for a GPIO, -1 if not found +int32_t analogGetTimer(uint8_t pin); +int32_t analogGetTimerForChannel(uint8_t chan); + +// set the timer number for a GPIO, ignore if the GPIO is not set or Timer number is invalid +// Timer range is 0..3 +void analogSetTimer(uint8_t pin, uint8_t timer); + +// get the next unused timer, returns -1 if no free timer is available +// Keep in mind that Timer 0 is reserved, which leaves only 3 timers available +// +// This function does not reserve the timer, it is reserved only when you assign a GPIO to it +int32_t analogNextFreeTimer(); + +// Get timer resolution (in bits) - default 10 +uint8_t analogGetTimerResolution(uint8_t timer); +// void analogSetTimerResolution(uint8_t timer, uint8_t resolution); + +// Get timer frequency (in Hz) - default 977 +uint32_t analogGetTimerFrequency(uint8_t timer); +// void analogSetTimerFrequency(uint8_t timer, uint32_t freq); + +// void analogSetTimerResFreq(uint8_t timer, uint8_t resolution, uint32_t freq); /*********************************************************************************************/ diff --git a/tasmota/tasmota_support/support_pwm.ino b/tasmota/tasmota_support/support_pwm.ino index b5ec67d72..962d7e2bf 100644 --- a/tasmota/tasmota_support/support_pwm.ino +++ b/tasmota/tasmota_support/support_pwm.ino @@ -68,31 +68,49 @@ void PwmSaveToSettings(void) { void PwmApplyGPIO(bool force_update_all) { uint32_t pwm_phase_accumulator = 0; // dephase each PWM channel with the value of the previous + uint8_t timer0_resolution = analogGetTimerResolution(0); + uint32_t timer0_freq = analogGetTimerFrequency(0); + + // AddLog(LOG_LEVEL_INFO, "PWM: resol0=%i freq0=%i", timer0_resolution, timer0_freq); + for (uint32_t i = 0; i < MAX_PWMS; i++) { + // compute `pwm_val`, the virtual value of PWM (not taking into account inverted) uint32_t pwm_val = TasmotaGlobal.pwm_cur_value[i]; // logical value of PWM, 0..1023 - 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 - - // compute phase uint32_t pwm_phase = TasmotaGlobal.pwm_cur_phase[i]; // pwm_phase is the logical phase of the active pulse, ignoring inverted - if (TasmotaGlobal.pwm_phase[i] >= 0) { - pwm_phase = TasmotaGlobal.pwm_phase[i]; // if explicit set explicitly, - } else if (Settings->flag5.pwm_force_same_phase) { - pwm_phase = 0; // if auto-phase is off - } else { - // compute auto-phase - pwm_phase = pwm_phase_accumulator; - // 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)) { + int32_t pin = Pin(GPIO_PWM1, i); + int32_t chan = analogGetChannel2(pin); + // int32_t timer = analogGetTimer(pin); + uint32_t res = ledcReadResolution(chan); + uint32_t range = (1 << res) - 1; + uint32_t freq = ledcReadFreq2(chan); + + // AddLog(LOG_LEVEL_INFO, "PWM: res0=%i freq0=%i pin=%i chan=%i res=%i timer=%i range=%i freq=%i", timer0_resolution, timer0_freq, pin, chan, res, analogGetTimerForChannel(chan), range, freq); + + if (TasmotaGlobal.pwm_value[i] >= 0) { pwm_val = TasmotaGlobal.pwm_value[i]; } // new value explicitly specified + if (pwm_val > range) { pwm_val = range; } // prevent overflow + + // compute phase + if (TasmotaGlobal.pwm_phase[i] >= 0) { + pwm_phase = TasmotaGlobal.pwm_phase[i]; // if explicit set explicitly, + } else if (Settings->flag5.pwm_force_same_phase) { + pwm_phase = 0; // if auto-phase is off + } else { + if (freq == timer0_freq && res == timer0_resolution) { // only apply if the frequency is equl to global one + // compute auto-phase only if default frequency + pwm_phase = pwm_phase_accumulator; + // accumulate phase for next GPIO + pwm_phase_accumulator = (pwm_phase + pwm_val) & range; + } + } + // 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 (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), pwm_val, pwm_phase); + analogWritePhase(pin, pwm_val, pwm_phase); // AddLog(LOG_LEVEL_INFO, "PWM: analogWritePhase i=%i val=%03X phase=%03X", i, pwm_val, pwm_phase); } } @@ -131,6 +149,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), bitRead(TasmotaGlobal.pwm_inverted, i)); + if (i < TasmotaGlobal.light_type) { // force PWM GPIOs to black TasmotaGlobal.pwm_value[i] = 0; @@ -157,6 +176,40 @@ void ResetPwm(void) PwmApplyGPIO(true); } +void CmndPwmfrequency(void) +{ + int32_t pwm_frequency = Settings->pwm_frequency; + int32_t pwm = -1; // PWM being targeted, or -1 for global value applied to Timer 0 + + // check if index if above 100, meaning we target only a specific PWM channel + uint32_t parm[2] = { 0, 0 }; + ParseParameters(2, parm); + + if (parm[1]) { // we have a second parameter + pwm = parm[1] - 1; + if (pwm < 0 || pwm >= MAX_PWMS) { pwm = -1; } // if invalid, revert to global value + } + + // AddLog(LOG_LEVEL_INFO, "PWM: payload=%i index=%i pwm=%i pwm_freqency=%i", XdrvMailbox.payload, XdrvMailbox.index , pwm, pwm_frequency); + if ((1 == XdrvMailbox.payload) || ((XdrvMailbox.payload >= PWM_MIN) && (XdrvMailbox.payload <= PWM_MAX))) { + pwm_frequency = (1 == XdrvMailbox.payload) ? PWM_FREQ : XdrvMailbox.payload; + + if (pwm >= 0 && PinUsed(GPIO_PWM1, pwm)) { + analogWriteFreq(pwm_frequency, Pin(GPIO_PWM1, pwm)); + } else { + // apply to all default PWM + // AddLog(LOG_LEVEL_INFO, "PWM: apply global freq=%i", pwm_frequency); + Settings->pwm_frequency = pwm_frequency; + analogWriteFreq(pwm_frequency); // Default is 977 + } +#ifdef USE_LIGHT + LightReapplyColor(); + LightAnimate(); +#endif // USE_LIGHT + } + ResponseCmndNumber(pwm_frequency); +} + #else // now for ESP8266 void PwmRearmChanges(void) {} @@ -210,8 +263,6 @@ void ResetPwm(void) } } -#endif // ESP8266 - void CmndPwmfrequency(void) { if ((1 == XdrvMailbox.payload) || ((XdrvMailbox.payload >= PWM_MIN) && (XdrvMailbox.payload <= PWM_MAX))) { @@ -225,6 +276,8 @@ void CmndPwmfrequency(void) ResponseCmndNumber(Settings->pwm_frequency); } +#endif // ESP8266 + void CmndPwmrange(void) { // Support only 8 (=255), 9 (=511) and 10 (=1023) bits resolution if ((1 == XdrvMailbox.payload) || ((XdrvMailbox.payload > 254) && (XdrvMailbox.payload < 1024))) { diff --git a/tasmota/tasmota_xdrv_driver/xdrv_27_shutter.ino b/tasmota/tasmota_xdrv_driver/xdrv_27_shutter.ino index 39e9c8d68..8a85c0c2d 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_27_shutter.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_27_shutter.ino @@ -156,6 +156,9 @@ void ShutterUpdateVelocity(uint8_t i) void ShutterRtc50mS(void) { +#ifdef ESP32 + bool pwm_apply = false; // ESP32 only, do we need to apply PWM changes +#endif // No Logging allowed. RTC Timer for (uint8_t i = 0; i < TasmotaGlobal.shutters_present; i++) { if (Shutter[i].direction) { @@ -184,13 +187,16 @@ void ShutterRtc50mS(void) #ifdef ESP32 analogWriteFreq(Shutter[i].pwm_velocity,Pin(GPIO_PWM1, i)); TasmotaGlobal.pwm_value[i] = 512; - PwmApplyGPIO(false); + pwm_apply = true; #endif // ESP32 } break; } } // if (Shutter[i].direction) } +#ifdef ESP32 + if (pwm_apply) { PwmApplyGPIO(false); } +#endif } int32_t ShutterPercentToRealPosition(int16_t percent, uint32_t index) @@ -458,6 +464,9 @@ void ShutterCalculateAccelerator(uint8_t i) void ShutterDecellerateForStop(uint8_t i) { +#ifdef ESP32 + bool pwm_apply = false; // ESP32 only, do we need to apply PWM changes +#endif switch (ShutterGlobal.position_mode) { case SHT_PWM_VALUE: case SHT_COUNTER: @@ -482,7 +491,7 @@ void ShutterDecellerateForStop(uint8_t i) #endif #ifdef ESP32 TasmotaGlobal.pwm_value[i] = 0; - PwmApplyGPIO(false); + pwm_apply = true; #endif // ESP32 Shutter[i].real_position = ShutterCalculatePosition(i); //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Remain steps %d"), missing_steps); @@ -492,6 +501,9 @@ void ShutterDecellerateForStop(uint8_t i) Shutter[i].pwm_velocity = 0; break; } +#ifdef ESP32 + if (pwm_apply) { PwmApplyGPIO(false); } +#endif } void ShutterPowerOff(uint8_t i)