micropython/ports/esp32/machine_pwm.c

460 lines
16 KiB
C

/*
* This file is part of the Micro Python project, http://micropython.org/
*
* The MIT License (MIT)
*
* 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "py/runtime.h"
#include "py/mphal.h"
#include "driver/ledc.h"
#include "esp_err.h"
#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 is default frequency
#define PWFREQ (5000)
// 10-bit resolution (compatible with esp8266 PWM)
#define PWRES (LEDC_TIMER_10_BIT)
// Config of timer upon which we run all PWM'ed GPIO pins
STATIC bool pwm_inited = false;
// 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 i = 0; i < PWM_CHANNEL_MAX; ++i) {
chans[i].pin = -1;
chans[i].timer_idx = -1;
}
// 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 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;
}
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->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);
}
}
}
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
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(pin=%u", self->pin);
if (self->active) {
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, ")");
}
STATIC void mp_machine_pwm_init_helper(machine_pwm_obj_t *self,
size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_freq, ARG_duty };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_freq, MP_ARG_INT, {.u_int = -1} },
{ MP_QSTR_duty, MP_ARG_INT, {.u_int = -1} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args,
MP_ARRAY_SIZE(allowed_args), allowed_args, args);
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
}
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 (freq < 0) {
freq = PWFREQ;
}
}
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);
}
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
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;
// Set timer frequency
set_freq(freq, &timers[timer_idx]);
// Set duty cycle?
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));
}
}
STATIC mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type,
size_t n_args, size_t n_kw, const mp_obj_t *args) {
mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true);
gpio_num_t pin_id = machine_pin_get_id(args[0]);
// create PWM object from the given pin
machine_pwm_obj_t *self = m_new_obj(machine_pwm_obj_t);
self->base.type = &machine_pwm_type;
self->pin = pin_id;
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) {
pwm_init();
pwm_inited = true;
}
// start the PWM running for this channel
mp_map_t kw_args;
mp_map_init_fixed_table(&kw_args, n_kw, args + n_args);
mp_machine_pwm_init_helper(self, n_args - 1, args + 1, &kw_args);
return MP_OBJ_FROM_PTR(self);
}
STATIC void mp_machine_pwm_deinit(machine_pwm_obj_t *self) {
int chan = CHANNEL_IDX(self->mode, self->channel);
// Valid channel?
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
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;
self->timer = -1;
}
}
STATIC mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) {
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 (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) {
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) {
set_duty(self, duty);
}