Skip to content

Commit 09fe80d

Browse files
IhorNehrutsadpgeorge
authored andcommitted
esp32/machine_pwm: Keep duty constant when changing frequency.
Save and restore the same duty cycle when the frequency (or frequency resolution) is changed. This allows a smooth frequency change. Also update the esp32 PWM quickref to be clearer.
1 parent 4189c64 commit 09fe80d

File tree

2 files changed

+40
-37
lines changed

2 files changed

+40
-37
lines changed

docs/esp32/quickref.rst

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,14 +224,18 @@ Use the :ref:`machine.PWM <machine.PWM>` class::
224224
from machine import Pin, PWM
225225

226226
pwm0 = PWM(Pin(0)) # create PWM object from a pin
227-
pwm0.freq() # get current frequency (default 5kHz)
227+
freq = pwm0.freq() # get current frequency (default 5kHz)
228228
pwm0.freq(1000) # set PWM frequency from 1Hz to 40MHz
229-
pwm0.duty() # get current duty cycle, range 0-1023 (default 512, 50%)
229+
230+
duty = pwm0.duty() # get current duty cycle, range 0-1023 (default 512, 50%)
230231
pwm0.duty(256) # set duty cycle from 0 to 1023 as a ratio duty/1023, (now 25%)
232+
233+
duty_u16 = pwm0.duty_u16() # get current duty cycle, range 0-65535
231234
pwm0.duty_u16(2**16*3//4) # set duty cycle from 0 to 65535 as a ratio duty_u16/65535, (now 75%)
232-
pwm0.duty_u16() # get current duty cycle, range 0-65535
235+
236+
duty_ns = pwm0.duty_ns() # get current pulse width in ns
233237
pwm0.duty_ns(250_000) # set pulse width in nanoseconds from 0 to 1_000_000_000/freq, (now 25%)
234-
pwm0.duty_ns() # get current pulse width in ns
238+
235239
pwm0.deinit() # turn off PWM on the pin
236240

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

ports/esp32/machine_pwm.c

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,17 @@ STATIC ledc_timer_config_t timers[PWM_TIMER_MAX];
8585
// duty_u16() and duty_ns() use 16-bit resolution or less
8686

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

100100
// 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
101101
#define EMPIRIC_FREQ (10) // Hz
@@ -205,24 +205,19 @@ STATIC void configure_channel(machine_pwm_obj_t *self) {
205205
}
206206

207207
STATIC void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_config_t *timer) {
208-
// Even if the timer frequency is already set,
209-
// the set_duty_x() is required to reconfigure the channel duty anyway
210208
if (freq != timer->freq_hz) {
211-
PWM_DBG("set_freq(%d)", freq)
212-
213209
// Find the highest bit resolution for the requested frequency
214210
unsigned int i = LEDC_APB_CLK_HZ; // 80 MHz
215211
if (freq < EMPIRIC_FREQ) {
216212
i = LEDC_REF_CLK_HZ; // 1 MHz
217213
}
218214

219-
#if 1
215+
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
220216
// original code
221217
i /= freq;
222218
#else
223219
// See https://github.com/espressif/esp-idf/issues/7722
224-
unsigned int divider = i / freq; // truncated
225-
// int divider = (i + freq / 2) / freq; // rounded
220+
int divider = (i + freq / 2) / freq; // rounded
226221
if (divider == 0) {
227222
divider = 1;
228223
}
@@ -245,6 +240,7 @@ STATIC void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_conf
245240
}
246241

247242
// Configure the new resolution and frequency
243+
unsigned int save_duty_resolution = timer->duty_resolution;
248244
timer->duty_resolution = res;
249245
timer->freq_hz = freq;
250246
timer->clk_cfg = LEDC_USE_APB_CLK;
@@ -256,7 +252,6 @@ STATIC void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_conf
256252
esp_err_t err = ledc_timer_config(timer);
257253
if (err != ESP_OK) {
258254
if (err == ESP_FAIL) {
259-
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);
260255
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unreachable frequency %d"), freq);
261256
} else {
262257
check_esp_err(err);
@@ -266,15 +261,17 @@ STATIC void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_conf
266261
if (self->mode == LEDC_LOW_SPEED_MODE) {
267262
check_esp_err(ledc_timer_rst(self->mode, self->timer));
268263
}
269-
}
270264

271-
// Save the same duty cycle when frequency or channel are changed
272-
if (self->duty_x == HIGHEST_PWM_RES) {
273-
set_duty_u16(self, self->duty_u16);
274-
} else if (self->duty_x == PWRES) {
275-
set_duty_u10(self, self->duty_u10);
276-
} else if (self->duty_x == -HIGHEST_PWM_RES) {
277-
set_duty_ns(self, self->duty_ns);
265+
// Save the same duty cycle when frequency is changed
266+
if (save_duty_resolution != timer->duty_resolution) {
267+
if (self->duty_x == HIGHEST_PWM_RES) {
268+
set_duty_u16(self, self->duty_u16);
269+
} else if (self->duty_x == PWRES) {
270+
set_duty_u10(self, self->duty_u10);
271+
} else if (self->duty_x == -HIGHEST_PWM_RES) {
272+
set_duty_ns(self, self->duty_ns);
273+
}
274+
}
278275
}
279276
}
280277

@@ -287,14 +284,12 @@ STATIC int ns_to_duty(machine_pwm_obj_t *self, int ns) {
287284
} else if (duty > UI_MAX_DUTY) {
288285
duty = UI_MAX_DUTY;
289286
}
290-
// 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);
291287
return duty;
292288
}
293289

294290
STATIC int duty_to_ns(machine_pwm_obj_t *self, int duty) {
295291
ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)];
296292
int64_t ns = ((int64_t)duty * 1000000000LL + (int64_t)timer.freq_hz * UI_MAX_DUTY / 2) / ((int64_t)timer.freq_hz * UI_MAX_DUTY);
297-
// 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);
298293
return ns;
299294
}
300295

@@ -316,23 +311,27 @@ STATIC void set_duty_u16(machine_pwm_obj_t *self, int duty) {
316311
if ((duty < 0) || (duty > UI_MAX_DUTY)) {
317312
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), UI_MAX_DUTY);
318313
}
319-
duty >>= HIGHEST_PWM_RES + UI_RES_SHIFT - timers[TIMER_IDX(self->mode, self->timer)].duty_resolution;
320-
int max_duty = (1 << timers[TIMER_IDX(self->mode, self->timer)].duty_resolution) - 1;
321-
if (duty < 0) {
322-
duty = 0;
323-
} else if (duty > max_duty) {
324-
duty = max_duty;
325-
}
326-
check_esp_err(ledc_set_duty(self->mode, self->channel, duty));
314+
ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)];
315+
int channel_duty = duty >> (HIGHEST_PWM_RES + UI_RES_SHIFT - timer.duty_resolution);
316+
int max_duty = (1 << timer.duty_resolution) - 1;
317+
if (channel_duty < 0) {
318+
channel_duty = 0;
319+
} else if (channel_duty > max_duty) {
320+
channel_duty = max_duty;
321+
}
322+
check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty));
327323
check_esp_err(ledc_update_duty(self->mode, self->channel));
328324

329325
/*
330326
// Bug: Sometimes duty is not set right now.
327+
// Not a bug. It's a feature. The duty is applied at the beginning of the next signal period.
328+
// Bug: It has been experimentally established that the duty is setted during 2 signal periods, but 1 period is expected.
331329
// See https://github.com/espressif/esp-idf/issues/7288
332330
if (duty != get_duty_u16(self)) {
333-
ets_delay_us(100);
331+
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);
332+
ets_delay_us(2 * 1000000 / timer.freq_hz);
334333
if (duty != get_duty_u16(self)) {
335-
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);
334+
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);
336335
}
337336
}
338337
*/

0 commit comments

Comments
 (0)
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