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

+9-5
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

+31-32
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)