mirror of https://github.com/arendst/Tasmota.git
Merge pull request #16131 from s-hadinger/pwm_fix_freq
Fix PWM to allow 4 different frequencies
This commit is contained in:
commit
63296e4cd4
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
/*********************************************************************************************/
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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))) {
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue