esp32/machine_pwm: Support higher PWM freq by auto-scaling timer res.
@@ -59,7 +59,7 @@ STATIC int chan_gpio[LEDC_CHANNEL_MAX];
// Config of timer upon which we run all PWM'ed GPIO pins
STATIC bool pwm_inited = false;
STATIC ledc_timer_config_t timer_cfg = {
- .bit_num = PWRES,
+ .duty_resolution = PWRES,
.freq_hz = PWFREQ,
.speed_mode = PWMODE,
.timer_num = PWTIMER
@@ -77,10 +77,28 @@ STATIC void pwm_init(void) {
}
STATIC int set_freq(int newval) {
+ int ores = timer_cfg.duty_resolution;
int oval = timer_cfg.freq_hz;
+ // Find the highest bit resolution for the requested frequency
+ if (newval <= 0) {
+ newval = 1;
+ }
+ unsigned int res = 0;
+ for (unsigned int i = LEDC_APB_CLK_HZ / newval; i > 1; i >>= 1, ++res) {
+ }
+ if (res == 0) {
+ res = 1;
+ } else if (res > PWRES) {
+ // Limit resolution to PWRES to match units of our duty
+ res = PWRES;
+ }
+
+ // Configure the new resolution and frequency
+ timer_cfg.duty_resolution = res;
timer_cfg.freq_hz = newval;
if (ledc_timer_config(&timer_cfg) != ESP_OK) {
+ timer_cfg.duty_resolution = ores;
timer_cfg.freq_hz = oval;
return 0;
}
@@ -138,7 +156,7 @@ STATIC void esp32_pwm_init_helper(esp32_pwm_obj_t *self,
if (chan_gpio[channel] == -1) {
ledc_channel_config_t cfg = {
.channel = channel,
- .duty = (1 << PWRES) / 2,
+ .duty = (1 << timer_cfg.duty_resolution) / 2,
.gpio_num = self->pin,
.intr_type = LEDC_INTR_DISABLE,
.speed_mode = PWMODE,
@@ -166,6 +184,7 @@ STATIC void esp32_pwm_init_helper(esp32_pwm_obj_t *self,
int dval = args[ARG_duty].u_int;
if (dval != -1) {
dval &= ((1 << PWRES)-1);
+ dval >>= PWRES - timer_cfg.duty_resolution;
ledc_set_duty(PWMODE, channel, dval);
ledc_update_duty(PWMODE, channel);
}
@@ -244,12 +263,14 @@ STATIC mp_obj_t esp32_pwm_duty(size_t n_args, const mp_obj_t *args) {
if (n_args == 1) {
// get
duty = ledc_get_duty(PWMODE, self->channel);
+ duty <<= PWRES - timer_cfg.duty_resolution;
return MP_OBJ_NEW_SMALL_INT(duty);
}
// set
duty = mp_obj_get_int(args[1]);
duty &= ((1 << PWRES)-1);
+ duty >>= PWRES - timer_cfg.duty_resolution;
ledc_set_duty(PWMODE, self->channel, duty);
ledc_update_duty(PWMODE, self->channel);
