esp32/machine_pwm: Add support for all PWM timers and channels.

This commit allows using all the available PWM timers (up to 8) and
channels (up to 16), without affecting the PWM API.

If a new frequency is set, first it checks if another timer is using the
same frequency.  If yes, then it uses this timer, otherwise, it creates a
new one.  If all timers are used, the user should set an already used
frequency, or de-init a channel.

This work is based on #6276 and #3608.
This commit is contained in:
IhorNehrutsa 2021-09-19 00:38:37 +03:00 committed by Damien George
parent 0d9429f44c
commit 52636fa692
1 changed files with 315 additions and 106 deletions

View File

@ -3,7 +3,10 @@
*
* The MIT License (MIT)
*
* Copyright (c) 2016 Damien P. George
* Copyright (c) 2016-2021 Damien P. George
* Copyright (c) 2018 Alan Dragomirecky
* Copyright (c) 2020 Antoine Aubert
* Copyright (c) 2021 Ihor Nehrutsa
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -30,54 +33,105 @@
#include "driver/ledc.h"
#include "esp_err.h"
// Which channel has which GPIO pin assigned?
// (-1 if not assigned)
STATIC int chan_gpio[LEDC_CHANNEL_MAX];
#define PWM_DBG(...)
// #define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__)
// Total number of channels
#define PWM_CHANNEL_MAX (LEDC_SPEED_MODE_MAX * LEDC_CHANNEL_MAX)
typedef struct _chan_t {
// Which channel has which GPIO pin assigned?
// (-1 if not assigned)
gpio_num_t pin;
// Which channel has which timer assigned?
// (-1 if not assigned)
int timer_idx;
} chan_t;
// List of PWM channels
STATIC chan_t chans[PWM_CHANNEL_MAX];
// channel_idx is an index (end-to-end sequential numbering) for all channels
// available on the chip and described in chans[]
#define CHANNEL_IDX(mode, channel) (mode * LEDC_CHANNEL_MAX + channel)
#define CHANNEL_IDX_TO_MODE(channel_idx) (channel_idx / LEDC_CHANNEL_MAX)
#define CHANNEL_IDX_TO_CHANNEL(channel_idx) (channel_idx % LEDC_CHANNEL_MAX)
// Total number of timers
#define PWM_TIMER_MAX (LEDC_SPEED_MODE_MAX * LEDC_TIMER_MAX)
// List of timer configs
STATIC ledc_timer_config_t timers[PWM_TIMER_MAX];
// timer_idx is an index (end-to-end sequential numbering) for all timers
// available on the chip and configured in timers[]
#define TIMER_IDX(mode, timer) (mode * LEDC_TIMER_MAX + timer)
#define TIMER_IDX_TO_MODE(timer_idx) (timer_idx / LEDC_TIMER_MAX)
#define TIMER_IDX_TO_TIMER(timer_idx) (timer_idx % LEDC_TIMER_MAX)
// Params for PW operation
// 5khz
// 5khz is default frequency
#define PWFREQ (5000)
// High speed mode
#if CONFIG_IDF_TARGET_ESP32
#define PWMODE (LEDC_HIGH_SPEED_MODE)
#else
#define PWMODE (LEDC_LOW_SPEED_MODE)
#endif
// 10-bit resolution (compatible with esp8266 PWM)
#define PWRES (LEDC_TIMER_10_BIT)
// Timer 1
#define PWTIMER (LEDC_TIMER_1)
// Config of timer upon which we run all PWM'ed GPIO pins
STATIC bool pwm_inited = false;
STATIC ledc_timer_config_t timer_cfg = {
.duty_resolution = PWRES,
.freq_hz = PWFREQ,
.speed_mode = PWMODE,
.timer_num = PWTIMER
};
// MicroPython PWM object struct
typedef struct _machine_pwm_obj_t {
mp_obj_base_t base;
gpio_num_t pin;
bool active;
int mode;
int channel;
int timer;
} machine_pwm_obj_t;
STATIC void pwm_init(void) {
// Initial condition: no channels assigned
for (int x = 0; x < LEDC_CHANNEL_MAX; ++x) {
chan_gpio[x] = -1;
for (int i = 0; i < PWM_CHANNEL_MAX; ++i) {
chans[i].pin = -1;
chans[i].timer_idx = -1;
}
// Init with default timer params
ledc_timer_config(&timer_cfg);
// Prepare all timers config
// Initial condition: no timers assigned
for (int i = 0; i < PWM_TIMER_MAX; ++i) {
timers[i].duty_resolution = PWRES;
// unset timer is -1
timers[i].freq_hz = -1;
timers[i].speed_mode = TIMER_IDX_TO_MODE(i);
timers[i].timer_num = TIMER_IDX_TO_TIMER(i);
timers[i].clk_cfg = LEDC_AUTO_CLK;
}
}
STATIC int set_freq(int newval) {
int ores = timer_cfg.duty_resolution;
int oval = timer_cfg.freq_hz;
STATIC void configure_channel(machine_pwm_obj_t *self) {
ledc_channel_config_t cfg = {
.channel = self->channel,
.duty = (1 << (timers[TIMER_IDX(self->mode, self->timer)].duty_resolution)) / 2,
.gpio_num = self->pin,
.intr_type = LEDC_INTR_DISABLE,
.speed_mode = self->mode,
.timer_sel = self->timer,
};
if (ledc_channel_config(&cfg) != ESP_OK) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM not supported on Pin(%d)"), self->pin);
}
}
STATIC void set_freq(int newval, ledc_timer_config_t *timer) {
// If already set, do nothing
if (newval == timer->freq_hz) {
return;
}
// 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) {
for (unsigned int i = LEDC_APB_CLK_HZ / newval; i > 1; i >>= 1) {
++res;
}
if (res == 0) {
res = 1;
@ -87,32 +141,113 @@ STATIC int set_freq(int newval) {
}
// 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;
timer->duty_resolution = res;
timer->freq_hz = newval;
// set freq
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("bad frequency %d"), newval);
} else {
check_esp_err(err);
}
}
return 1;
}
STATIC int get_duty(machine_pwm_obj_t *self) {
uint32_t duty = ledc_get_duty(self->mode, self->channel);
duty <<= PWRES - timers[TIMER_IDX(self->mode, self->timer)].duty_resolution;
return duty;
}
STATIC void set_duty(machine_pwm_obj_t *self, int duty) {
if ((duty < 0) || (duty > (1 << PWRES) - 1)) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be between 0 and %u"), (1 << PWRES) - 1);
}
duty &= (1 << PWRES) - 1;
duty >>= PWRES - timers[TIMER_IDX(self->mode, self->timer)].duty_resolution;
check_esp_err(ledc_set_duty(self->mode, self->channel, duty));
check_esp_err(ledc_update_duty(self->mode, self->channel));
// check_esp_err(ledc_set_duty_and_update(self->mode, self->channel, duty, (1 << PWRES) - 1)); // thread safe function ???
// Bug: Sometimes duty is not set right now.
// See https://github.com/espressif/esp-idf/issues/7288
/*
if (duty != get_duty(self)) {
PWM_DBG("\n duty_set %u %u %d %d \n", duty, get_duty(self), PWRES, timers[TIMER_IDX(self->mode, self->timer)].duty_resolution);
}
*/
}
/******************************************************************************/
#define SAME_FREQ_ONLY (true)
#define SAME_FREQ_OR_FREE (false)
#define ANY_MODE (-1)
// Return timer_idx. Use TIMER_IDX_TO_MODE(timer_idx) and TIMER_IDX_TO_TIMER(timer_idx) to get mode and timer
STATIC int find_timer(int freq, bool same_freq_only, int mode) {
int free_timer_idx_found = -1;
// Find a free PWM Timer using the same freq
for (int timer_idx = 0; timer_idx < PWM_TIMER_MAX; ++timer_idx) {
if ((mode == ANY_MODE) || (mode == TIMER_IDX_TO_MODE(timer_idx))) {
if (timers[timer_idx].freq_hz == freq) {
// A timer already uses the same freq. Use it now.
return timer_idx;
}
if (!same_freq_only && (free_timer_idx_found == -1) && (timers[timer_idx].freq_hz == -1)) {
free_timer_idx_found = timer_idx;
// Continue to check if a channel with the same freq is in use.
}
}
}
return free_timer_idx_found;
}
// Return true if the timer is in use in addition to current channel
STATIC bool is_timer_in_use(int current_channel_idx, int timer_idx) {
for (int i = 0; i < PWM_CHANNEL_MAX; ++i) {
if ((i != current_channel_idx) && (chans[i].timer_idx == timer_idx)) {
return true;
}
}
return false;
}
// Find a free PWM channel, also spot if our pin is already mentioned.
// Return channel_idx. Use CHANNEL_IDX_TO_MODE(channel_idx) and CHANNEL_IDX_TO_CHANNEL(channel_idx) to get mode and channel
STATIC int find_channel(int pin, int mode) {
int avail_idx = -1;
int channel_idx;
for (channel_idx = 0; channel_idx < PWM_CHANNEL_MAX; ++channel_idx) {
if ((mode == ANY_MODE) || (mode == CHANNEL_IDX_TO_MODE(channel_idx))) {
if (chans[channel_idx].pin == pin) {
break;
}
if ((avail_idx == -1) && (chans[channel_idx].pin == -1)) {
avail_idx = channel_idx;
}
}
}
if (channel_idx >= PWM_CHANNEL_MAX) {
channel_idx = avail_idx;
}
return channel_idx;
}
/******************************************************************************/
// MicroPython bindings for PWM
typedef struct _machine_pwm_obj_t {
mp_obj_base_t base;
gpio_num_t pin;
uint8_t active;
uint8_t channel;
} machine_pwm_obj_t;
STATIC void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in);
mp_printf(print, "PWM(%u", self->pin);
mp_printf(print, "PWM(pin=%u", self->pin);
if (self->active) {
mp_printf(print, ", freq=%u, duty=%u", timer_cfg.freq_hz,
ledc_get_duty(PWMODE, self->channel));
int duty = get_duty(self);
mp_printf(print, ", freq=%u, duty=%u", ledc_get_freq(self->mode, self->timer), duty);
mp_printf(print, ", resolution=%u", timers[TIMER_IDX(self->mode, self->timer)].duty_resolution);
mp_printf(print, ", mode=%d, channel=%d, timer=%d", self->mode, self->channel, self->timer);
}
mp_printf(print, ")");
}
@ -128,61 +263,72 @@ STATIC void mp_machine_pwm_init_helper(machine_pwm_obj_t *self,
mp_arg_parse_all(n_args, pos_args, kw_args,
MP_ARRAY_SIZE(allowed_args), allowed_args, args);
int channel;
int avail = -1;
int channel_idx = find_channel(self->pin, ANY_MODE);
if (channel_idx == -1) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), PWM_CHANNEL_MAX); // in all modes
}
// Find a free PWM channel, also spot if our pin is
// already mentioned.
for (channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) {
if (chan_gpio[channel] == self->pin) {
break;
int freq = args[ARG_freq].u_int;
if ((freq < -1) || (freq > 40000000)) {
mp_raise_ValueError(MP_ERROR_TEXT("freqency must be between 1Hz and 40MHz"));
}
// Check if freq wasn't passed as an argument
if (freq == -1) {
// Check if already set, otherwise use the default freq.
// Possible case:
// pwm = PWM(pin, freq=1000, duty=256)
// pwm = PWM(pin, duty=128)
if (chans[channel_idx].timer_idx != -1) {
freq = timers[chans[channel_idx].timer_idx].freq_hz;
}
if ((avail == -1) && (chan_gpio[channel] == -1)) {
avail = channel;
if (freq < 0) {
freq = PWFREQ;
}
}
if (channel >= LEDC_CHANNEL_MAX) {
if (avail == -1) {
mp_raise_ValueError(MP_ERROR_TEXT("out of PWM channels"));
}
channel = avail;
int timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, CHANNEL_IDX_TO_MODE(channel_idx));
if (timer_idx == -1) {
timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, ANY_MODE);
}
self->channel = channel;
if (timer_idx == -1) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM timers:%d"), PWM_TIMER_MAX); // in all modes
}
int mode = TIMER_IDX_TO_MODE(timer_idx);
if (CHANNEL_IDX_TO_MODE(channel_idx) != mode) {
// unregister old channel
chans[channel_idx].pin = -1;
chans[channel_idx].timer_idx = -1;
// find new channel
channel_idx = find_channel(self->pin, mode);
if (CHANNEL_IDX_TO_MODE(channel_idx) != mode) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), PWM_CHANNEL_MAX); // in current mode
}
}
self->mode = mode;
self->timer = TIMER_IDX_TO_TIMER(timer_idx);
self->channel = CHANNEL_IDX_TO_CHANNEL(channel_idx);
// New PWM assignment
self->active = 1;
if (chan_gpio[channel] == -1) {
ledc_channel_config_t cfg = {
.channel = channel,
.duty = (1 << timer_cfg.duty_resolution) / 2,
.gpio_num = self->pin,
.intr_type = LEDC_INTR_DISABLE,
.speed_mode = PWMODE,
.timer_sel = PWTIMER,
};
if (ledc_channel_config(&cfg) != ESP_OK) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM not supported on pin %d"), self->pin);
}
chan_gpio[channel] = self->pin;
if ((chans[channel_idx].pin == -1) || (chans[channel_idx].timer_idx != timer_idx)) {
configure_channel(self);
chans[channel_idx].pin = self->pin;
}
chans[channel_idx].timer_idx = timer_idx;
self->active = true;
// Maybe change PWM timer
int tval = args[ARG_freq].u_int;
if (tval != -1) {
if (tval != timer_cfg.freq_hz) {
if (!set_freq(tval)) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("bad frequency %d"), tval);
}
}
}
// Set timer frequency
set_freq(freq, &timers[timer_idx]);
// Set duty cycle?
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);
int duty = args[ARG_duty].u_int;
if (duty != -1) {
set_duty(self, duty);
}
// Reset the timer if low speed
if (self->mode == LEDC_LOW_SPEED_MODE) {
check_esp_err(ledc_timer_rst(self->mode, self->timer));
}
}
@ -195,8 +341,10 @@ STATIC mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type,
machine_pwm_obj_t *self = m_new_obj(machine_pwm_obj_t);
self->base.type = &machine_pwm_type;
self->pin = pin_id;
self->active = 0;
self->active = false;
self->mode = -1;
self->channel = -1;
self->timer = -1;
// start the PWM subsystem if it's not already running
if (!pwm_inited) {
@ -213,38 +361,99 @@ STATIC mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type,
}
STATIC void mp_machine_pwm_deinit(machine_pwm_obj_t *self) {
int chan = self->channel;
int chan = CHANNEL_IDX(self->mode, self->channel);
// Valid channel?
if ((chan >= 0) && (chan < LEDC_CHANNEL_MAX)) {
if ((chan >= 0) && (chan < PWM_CHANNEL_MAX)) {
// Clean up timer if necessary
if (!is_timer_in_use(chan, chans[chan].timer_idx)) {
check_esp_err(ledc_timer_rst(self->mode, self->timer));
// Flag it unused
timers[chans[chan].timer_idx].freq_hz = -1;
}
// Mark it unused, and tell the hardware to stop routing
chan_gpio[chan] = -1;
ledc_stop(PWMODE, chan, 0);
self->active = 0;
check_esp_err(ledc_stop(self->mode, chan, 0));
// Disable ledc signal for the pin
// gpio_matrix_out(self->pin, SIG_GPIO_OUT_IDX, false, false);
if (self->mode == LEDC_LOW_SPEED_MODE) {
gpio_matrix_out(self->pin, LEDC_LS_SIG_OUT0_IDX + self->channel, false, true);
} else {
#if LEDC_SPEED_MODE_MAX > 1
#if CONFIG_IDF_TARGET_ESP32
gpio_matrix_out(self->pin, LEDC_HS_SIG_OUT0_IDX + self->channel, false, true);
#else
#error Add supported CONFIG_IDF_TARGET_ESP32_xxx
#endif
#endif
}
chans[chan].pin = -1;
chans[chan].timer_idx = -1;
self->active = false;
self->mode = -1;
self->channel = -1;
gpio_matrix_out(self->pin, SIG_GPIO_OUT_IDX, false, false);
self->timer = -1;
}
}
STATIC mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) {
return MP_OBJ_NEW_SMALL_INT(timer_cfg.freq_hz);
return MP_OBJ_NEW_SMALL_INT(ledc_get_freq(self->mode, self->timer));
}
STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) {
if (!set_freq(freq)) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("bad frequency %d"), freq);
if (freq == timers[TIMER_IDX(self->mode, self->timer)].freq_hz) {
return;
}
int current_timer_idx = chans[CHANNEL_IDX(self->mode, self->channel)].timer_idx;
bool current_in_use = is_timer_in_use(CHANNEL_IDX(self->mode, self->channel), current_timer_idx);
// Check if an already running timer with the same freq is running
int new_timer_idx = find_timer(freq, SAME_FREQ_ONLY, self->mode);
// If no existing timer was found, and the current one is in use, then find a new one
if ((new_timer_idx == -1) && current_in_use) {
// Have to find a new timer
new_timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, self->mode);
if (new_timer_idx == -1) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM timers:%d"), PWM_TIMER_MAX); // in current mode
}
}
if ((new_timer_idx != -1) && (new_timer_idx != current_timer_idx)) {
// Bind the channel to the new timer
chans[self->channel].timer_idx = new_timer_idx;
if (ledc_bind_channel_timer(self->mode, self->channel, TIMER_IDX_TO_TIMER(new_timer_idx)) != ESP_OK) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("failed to bind timer to channel"));
}
if (!current_in_use) {
// Free the old timer
check_esp_err(ledc_timer_rst(self->mode, self->timer));
// Flag it unused
timers[current_timer_idx].freq_hz = -1;
}
current_timer_idx = new_timer_idx;
}
self->mode = TIMER_IDX_TO_MODE(current_timer_idx);
self->timer = TIMER_IDX_TO_TIMER(current_timer_idx);
// Set the freq
set_freq(freq, &timers[current_timer_idx]);
// Reset the timer if low speed
if (self->mode == LEDC_LOW_SPEED_MODE) {
check_esp_err(ledc_timer_rst(self->mode, self->timer));
}
}
STATIC mp_obj_t mp_machine_pwm_duty_get(machine_pwm_obj_t *self) {
int duty = ledc_get_duty(PWMODE, self->channel);
duty <<= PWRES - timer_cfg.duty_resolution;
return MP_OBJ_NEW_SMALL_INT(duty);
return MP_OBJ_NEW_SMALL_INT(get_duty(self));
}
STATIC void mp_machine_pwm_duty_set(machine_pwm_obj_t *self, mp_int_t duty) {
duty &= ((1 << PWRES) - 1);
duty >>= PWRES - timer_cfg.duty_resolution;
ledc_set_duty(PWMODE, self->channel, duty);
ledc_update_duty(PWMODE, self->channel);
set_duty(self, duty);
}