Skip to content

esp32\machine_pwm.c: HOTFIX PWM duty. #8075

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions docs/esp32/quickref.rst
Original file line number Diff line number Diff line change
Expand Up @@ -224,14 +224,18 @@ Use the :ref:`machine.PWM <machine.PWM>` class::
from machine import Pin, PWM

pwm0 = PWM(Pin(0)) # create PWM object from a pin
pwm0.freq() # get current frequency (default 5kHz)
freq = pwm0.freq() # get current frequency (default 5kHz)
pwm0.freq(1000) # set PWM frequency from 1Hz to 40MHz
pwm0.duty() # get current duty cycle, range 0-1023 (default 512, 50%)

duty = pwm0.duty() # get current duty cycle, range 0-1023 (default 512, 50%)
pwm0.duty(256) # set duty cycle from 0 to 1023 as a ratio duty/1023, (now 25%)

duty_u16 = pwm0.duty_u16() # get current duty cycle, range 0-65535
pwm0.duty_u16(2**16*3//4) # set duty cycle from 0 to 65535 as a ratio duty_u16/65535, (now 75%)
pwm0.duty_u16() # get current duty cycle, range 0-65535

duty_ns = pwm0.duty_ns() # get current pulse width in ns
pwm0.duty_ns(250_000) # set pulse width in nanoseconds from 0 to 1_000_000_000/freq, (now 25%)
pwm0.duty_ns() # get current pulse width in ns

pwm0.deinit() # turn off PWM on the pin

pwm2 = PWM(Pin(2), freq=20000, duty=512) # create and configure in one go
Expand All @@ -246,7 +250,7 @@ Number of groups (speed modes) 2 1
Number of timers per group 4 4 4
Number of channels per group 8 8 6
----------------------------------------------------- -------- -------- --------
Different of PWM frequencies (groups * timers) 8 4 4
Different PWM frequencies (groups * timers) 8 4 4
Total PWM channels (Pins, duties) (groups * channels) 16 8 6
===================================================== ======== ======== ========

Expand Down Expand Up @@ -415,14 +419,14 @@ I2S bus
See :ref:`machine.I2S <machine.I2S>`. ::

from machine import I2S, Pin

i2s = I2S(0, sck=Pin(13), ws=Pin(14), sd=Pin(34), mode=I2S.TX, bits=16, format=I2S.STEREO, rate=44100, ibuf=40000) # create I2S object
i2s.write(buf) # write buffer of audio samples to I2S device

i2s = I2S(1, sck=Pin(33), ws=Pin(25), sd=Pin(32), mode=I2S.RX, bits=16, format=I2S.MONO, rate=22050, ibuf=40000) # create I2S object
i2s.readinto(buf) # fill buffer with audio samples from I2S device
The I2S class is currently available as a Technical Preview. During the preview period, feedback from

The I2S class is currently available as a Technical Preview. During the preview period, feedback from
users is encouraged. Based on this feedback, the I2S class API and implementation may be changed.

ESP32 has two I2S buses with id=0 and id=1
Expand Down
62 changes: 31 additions & 31 deletions ports/esp32/machine_pwm.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,17 @@ STATIC ledc_timer_config_t timers[PWM_TIMER_MAX];
// duty_u16() and duty_ns() use 16-bit resolution or less

// Possible highest resolution in device
#if CONFIG_IDF_TARGET_ESP32
#define HIGHEST_PWM_RES (LEDC_TIMER_16_BIT) // 20 bit in fact, but 16 bit is used
#if (LEDC_TIMER_BIT_MAX - 1) < LEDC_TIMER_16_BIT
#define HIGHEST_PWM_RES (LEDC_TIMER_BIT_MAX - 1)
#else
#define HIGHEST_PWM_RES (LEDC_TIMER_14_BIT)
#define HIGHEST_PWM_RES (LEDC_TIMER_16_BIT) // 20 bit for ESP32, but 16 bit is used
#endif
// Duty resolution of user interface in `duty_u16()` and `duty_u16` parameter in constructor/initializer
#define UI_RES_16_BIT (16)
// Maximum duty value on highest user interface resolution
#define UI_MAX_DUTY ((1 << UI_RES_16_BIT) - 1)
// How much to shift from the HIGHEST_PWM_RES duty resolution to the user interface duty resolution UI_RES_16_BIT
#define UI_RES_SHIFT (16 - HIGHEST_PWM_RES) // 0 for ESP32, 2 for S2, S3, C3
#define UI_RES_SHIFT (UI_RES_16_BIT - HIGHEST_PWM_RES) // 0 for ESP32, 2 for S2, S3, C3

// If the PWM frequency is less than EMPIRIC_FREQ, then LEDC_REF_CLK_HZ(1 MHz) source is used, else LEDC_APB_CLK_HZ(80 MHz) source is used
#define EMPIRIC_FREQ (10) // Hz
Expand Down Expand Up @@ -205,24 +205,20 @@ STATIC void configure_channel(machine_pwm_obj_t *self) {
}

STATIC void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_config_t *timer) {
// Even if the timer frequency is already set,
// the set_duty_x() is required to reconfigure the channel duty anyway
if (freq != timer->freq_hz) {
PWM_DBG("set_freq(%d)", freq)

// Find the highest bit resolution for the requested frequency
unsigned int i = LEDC_APB_CLK_HZ; // 80 MHz
if (freq < EMPIRIC_FREQ) {
i = LEDC_REF_CLK_HZ; // 1 MHz
}

#if 1
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
// original code
i /= freq;
#else
// See https://github.com/espressif/esp-idf/issues/7722
unsigned int divider = i / freq; // truncated
// int divider = (i + freq / 2) / freq; // rounded
int divider = (i + freq / 2) / freq; // rounded
if (divider == 0) {
divider = 1;
}
Expand All @@ -245,6 +241,7 @@ STATIC void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_conf
}

// Configure the new resolution and frequency
unsigned int save_duty_resolution = timer->duty_resolution;
timer->duty_resolution = res;
timer->freq_hz = freq;
timer->clk_cfg = LEDC_USE_APB_CLK;
Expand All @@ -256,7 +253,6 @@ STATIC void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_conf
esp_err_t err = ledc_timer_config(timer);
if (err != ESP_OK) {
if (err == ESP_FAIL) {
PWM_DBG(" (timer timer->speed_mode %d, timer->timer_num %d, timer->clk_cfg %d, timer->freq_hz %d, timer->duty_resolution %d) ", timer->speed_mode, timer->timer_num, timer->clk_cfg, timer->freq_hz, timer->duty_resolution);
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unreachable frequency %d"), freq);
} else {
check_esp_err(err);
Expand All @@ -266,15 +262,17 @@ STATIC void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_conf
if (self->mode == LEDC_LOW_SPEED_MODE) {
check_esp_err(ledc_timer_rst(self->mode, self->timer));
}
}

// Save the same duty cycle when frequency or channel are changed
if (self->duty_x == HIGHEST_PWM_RES) {
set_duty_u16(self, self->duty_u16);
} else if (self->duty_x == PWRES) {
set_duty_u10(self, self->duty_u10);
} else if (self->duty_x == -HIGHEST_PWM_RES) {
set_duty_ns(self, self->duty_ns);
// Save the same duty cycle when frequency is changed
if (save_duty_resolution != timer->duty_resolution) {
if (self->duty_x == HIGHEST_PWM_RES) {
set_duty_u16(self, self->duty_u16);
} else if (self->duty_x == PWRES) {
set_duty_u10(self, self->duty_u10);
} else if (self->duty_x == -HIGHEST_PWM_RES) {
set_duty_ns(self, self->duty_ns);
}
}
}
}

Expand All @@ -287,14 +285,12 @@ STATIC int ns_to_duty(machine_pwm_obj_t *self, int ns) {
} else if (duty > UI_MAX_DUTY) {
duty = UI_MAX_DUTY;
}
// PWM_DBG(" ns_to_duty(UI_MAX_DUTY=%d freq_hz=%d duty=%d=%f <- ns=%d) ", UI_MAX_DUTY, timer.freq_hz, duty, (float)ns * UI_MAX_DUTY * timer.freq_hz / 1000000000.0, ns);
return duty;
}

STATIC int duty_to_ns(machine_pwm_obj_t *self, int duty) {
ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)];
int64_t ns = ((int64_t)duty * 1000000000LL + (int64_t)timer.freq_hz * UI_MAX_DUTY / 2) / ((int64_t)timer.freq_hz * UI_MAX_DUTY);
// PWM_DBG(" duty_to_ns(UI_MAX_DUTY=%d freq_hz=%d duty=%d -> ns=%f=%d) ", UI_MAX_DUTY, timer.freq_hz, duty, (float)duty * 1000000000.0 / ((float)timer.freq_hz * UI_MAX_DUTY), ns);
return ns;
}

Expand All @@ -316,23 +312,27 @@ STATIC void set_duty_u16(machine_pwm_obj_t *self, int duty) {
if ((duty < 0) || (duty > UI_MAX_DUTY)) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), UI_MAX_DUTY);
}
duty >>= HIGHEST_PWM_RES + UI_RES_SHIFT - timers[TIMER_IDX(self->mode, self->timer)].duty_resolution;
int max_duty = (1 << timers[TIMER_IDX(self->mode, self->timer)].duty_resolution) - 1;
if (duty < 0) {
duty = 0;
} else if (duty > max_duty) {
duty = max_duty;
}
check_esp_err(ledc_set_duty(self->mode, self->channel, duty));
ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)];
int channel_duty = duty >> (HIGHEST_PWM_RES + UI_RES_SHIFT - timer.duty_resolution);
int max_duty = (1 << timer.duty_resolution) - 1;
if (channel_duty < 0) {
channel_duty = 0;
} else if (channel_duty > max_duty) {
channel_duty = max_duty;
}
check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty));
check_esp_err(ledc_update_duty(self->mode, self->channel));

/*
// Bug: Sometimes duty is not set right now.
// Not a bug. It's a feature. The duty is applied at the beginning of the next signal period.
// Bug: It has been experimentally established that the duty is setted during 2 signal periods, but 1 period is expected.
// See https://github.com/espressif/esp-idf/issues/7288
if (duty != get_duty_u16(self)) {
ets_delay_us(100);
PWM_DBG("set_duty_u16(%u), get_duty_u16():%u, channel_duty:%d, duty_resolution:%d, freq_hz:%d", duty, get_duty_u16(self), channel_duty, timer.duty_resolution, timer.freq_hz);
ets_delay_us(2 * 1000000 / timer.freq_hz);
if (duty != get_duty_u16(self)) {
PWM_DBG(" (set_duty_u16(%u) get_duty_u16()=%u duty_resolution=%d) ", duty, get_duty_u16(self), timers[TIMER_IDX(self->mode, self->timer)].duty_resolution);
PWM_DBG("set_duty_u16(%u), get_duty_u16():%u, channel_duty:%d, duty_resolution:%d, freq_hz:%d", duty, get_duty_u16(self), channel_duty, timer.duty_resolution, timer.freq_hz);
}
}
*/
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy