Merge pull request #16131 from s-hadinger/pwm_fix_freq

Fix PWM to allow 4 different frequencies
This commit is contained in:
s-hadinger 2022-08-03 12:26:12 +02:00 committed by GitHub
commit 63296e4cd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 473 additions and 93 deletions

View File

@ -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,122 @@ 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;
}
// 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
static 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
}
// input range is in full range, ledc needs bits
@ -93,52 +181,162 @@ 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;
}
extern "C" uint32_t ledcReadFreq2(uint8_t chan) {
// extern "C" uint32_t __wrap_ledcReadFreq(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 +361,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 +386,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 +395,36 @@ 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
//
// 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

View File

@ -23,18 +23,96 @@
//
#include <Esp.h>
/*******************************************************************************************\
* 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 PWM frequency and resolution per GPIO
*
* By default, all PWM are using the same timer called Timer 0.
* Changes in frequency of resolution apply to all PWM using Timer 0.
*
* You can specify a different a different resolution/frequency for
* specific GPIOs, this will internally assign a new timer to the GPIO.
* The limit is 3 specific values in addition to the global value.
*
* Note: on ESP32-only, there are 2 groups of PWM and 2 groups of timers.
* Although there are internally 8 timers, to simplifiy management,
* Timer 4..7 are mirrored from Timer 0..3.
* So it all happens like if there were only 4 timers and a single group of PWM channels.
\*******************************************************************************************/
// 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);
extern "C" 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
// change both freq and range
// `0`: set to global value
// `-1`: keep unchanged
// if pin < 0 then change global value for timer 0
//
// analogWriteFreqRange - change the range and/or frequency of a GPIO
//
// `void analogWriteFreqRange(int32_t freq, int32_t range, int32_t pin)`
//
// The range is converted to a number of bits, so range must be a power of 2 minus 1.
// By default, the resolution is 10 bits, i.e. a range of 1023.
//
// Special cases:
// - if `pin < 0`, changes the global value for Timer 0 and all PWM using default
// - if `range == 0` or `freq == 0`, revert to using Timer 0 (i.e. reassign to global values)
// - if `range < 0` or `freq < 0`, keep the previous value unchanged
// - if `pin` is unassigned, silently ignore
void analogWriteFreqRange(int32_t freq, int32_t range, int32_t pin = -1);
//
// analogWriteRange - change the range of PWM
//
// short-cut for:
// `analogWriteFreqRange(-1, range, pin)`
void analogWriteRange(uint32_t range, int32_t pin = -1);
//
// analogWriteFreq - change the frequency of PWM in Hz
//
// short-cut for:
// `analogWriteFreqRange(-1, range, pin)`
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);
// Get timer resolution (in bits) - default 10
uint8_t analogGetTimerResolution(uint8_t timer);
// Get timer frequency (in Hz) - default 977
uint32_t analogGetTimerFrequency(uint8_t timer);
/*********************************************************************************************/

View File

@ -33,6 +33,7 @@ build_flags = ${esp_defaults.build_flags}
; wrappers for the crash-recorder
-Wl,--wrap=panicHandler -Wl,--wrap=xt_unhandled_exception
-Wl,--wrap=_Z11analogWritehi ; `analogWrite(unsigned char, int)` use the Tasmota version of analogWrite for deeper integration and phase control
-Wl,--wrap=ledcReadFreq ; `uint32_t ledcReadFreq(uint8_t chan)`
extra_scripts = pre:pio-tools/add_c_flags.py
post:pio-tools/post_esp32.py
${esp_defaults.extra_scripts}

View File

@ -68,31 +68,48 @@ 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);
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 +148,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 +175,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 +262,6 @@ void ResetPwm(void)
}
}
#endif // ESP8266
void CmndPwmfrequency(void)
{
if ((1 == XdrvMailbox.payload) || ((XdrvMailbox.payload >= PWM_MIN) && (XdrvMailbox.payload <= PWM_MAX))) {
@ -225,6 +275,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))) {

View File

@ -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)