-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
ports/esp32/machine_pwm: Add support for all PWM timers and modes. #3608
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,56 +35,52 @@ | |
// Forward dec'l | ||
extern const mp_obj_type_t machine_pwm_type; | ||
|
||
|
||
typedef struct _esp32_pwm_obj_t { | ||
mp_obj_base_t base; | ||
gpio_num_t pin; | ||
uint8_t active; | ||
uint8_t channel; | ||
uint8_t timer; | ||
uint8_t mode; | ||
} esp32_pwm_obj_t; | ||
|
||
// Number of available timers | ||
#define LEDC_TIMER_MAX 4 | ||
|
||
// Which channel has which GPIO pin assigned? | ||
// (-1 if not assigned) | ||
STATIC int chan_gpio[LEDC_CHANNEL_MAX]; | ||
|
||
// Params for PW operation | ||
// 5khz | ||
#define PWFREQ (5000) | ||
// High speed mode | ||
#define PWMODE (LEDC_HIGH_SPEED_MODE) | ||
// 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 = { | ||
.bit_num = PWRES, | ||
.freq_hz = PWFREQ, | ||
.speed_mode = PWMODE, | ||
.timer_num = PWTIMER | ||
}; | ||
|
||
STATIC void pwm_init(void) { | ||
|
||
// Initial condition: no channels assigned | ||
for (int x = 0; x < LEDC_CHANNEL_MAX; ++x) { | ||
chan_gpio[x] = -1; | ||
} | ||
|
||
// Init with default timer params | ||
ledc_timer_config(&timer_cfg); | ||
} | ||
|
||
STATIC int set_freq(int newval) { | ||
int oval = timer_cfg.freq_hz; | ||
|
||
timer_cfg.freq_hz = newval; | ||
if (ledc_timer_config(&timer_cfg) != ESP_OK) { | ||
timer_cfg.freq_hz = oval; | ||
return 0; | ||
// prepare all timers | ||
ledc_timer_config_t timer_cfg = { | ||
.bit_num = PWRES, | ||
.freq_hz = PWFREQ, | ||
.speed_mode = LEDC_LOW_SPEED_MODE, | ||
.timer_num = 0, | ||
}; | ||
for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { | ||
for (int idx = 0; idx < LEDC_TIMER_MAX; ++idx) { | ||
timer_cfg.timer_num = idx; | ||
timer_cfg.speed_mode = mode; | ||
ledc_timer_config(&timer_cfg); | ||
} | ||
} | ||
return 1; | ||
} | ||
|
||
/******************************************************************************/ | ||
|
@@ -93,20 +89,23 @@ STATIC int set_freq(int newval) { | |
|
||
STATIC void esp32_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { | ||
esp32_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); | ||
mp_printf(print, "PWM(%u", self->pin); | ||
mp_printf(print, "PWM(%u, timer=%u, speed_mode=%u", self->pin, self->timer, self->mode); | ||
if (self->active) { | ||
mp_printf(print, ", freq=%u, duty=%u", timer_cfg.freq_hz, | ||
ledc_get_duty(PWMODE, self->channel)); | ||
mp_printf(print, ", freq=%u, duty=%u", | ||
ledc_get_freq(self->mode, self->timer), | ||
ledc_get_duty(self->mode, self->channel)); | ||
} | ||
mp_printf(print, ")"); | ||
} | ||
|
||
STATIC void esp32_pwm_init_helper(esp32_pwm_obj_t *self, | ||
size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { | ||
enum { ARG_freq, ARG_duty }; | ||
enum { ARG_freq, ARG_duty, ARG_timer, ARG_mode }; | ||
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_QSTR_timer, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, | ||
{ MP_QSTR_speed_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = LEDC_HIGH_SPEED_MODE} }, | ||
}; | ||
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; | ||
mp_arg_parse_all(n_args, pos_args, kw_args, | ||
|
@@ -133,16 +132,31 @@ STATIC void esp32_pwm_init_helper(esp32_pwm_obj_t *self, | |
} | ||
self->channel = channel; | ||
|
||
// set timer | ||
if (0 <= args[ARG_timer].u_int && args[ARG_timer].u_int <= 3) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't the upper bound be |
||
self->timer = args[ARG_timer].u_int; | ||
} else { | ||
mp_raise_ValueError("invalid timer"); | ||
} | ||
|
||
// set speed mode | ||
if (0 <= args[ARG_mode].u_int && args[ARG_mode].u_int <= LEDC_SPEED_MODE_MAX) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the upper bound here should be |
||
self->mode = args[ARG_mode].u_int; | ||
} else { | ||
mp_raise_ValueError("invalid speed_mode"); | ||
} | ||
|
||
// New PWM assignment | ||
self->active = 1; | ||
if (chan_gpio[channel] == -1) { | ||
// configure channel | ||
ledc_channel_config_t cfg = { | ||
.channel = channel, | ||
.duty = (1 << PWRES) / 2, | ||
.gpio_num = self->pin, | ||
.intr_type = LEDC_INTR_DISABLE, | ||
.speed_mode = PWMODE, | ||
.timer_sel = PWTIMER, | ||
.speed_mode = self->mode, | ||
.timer_sel = self->timer, | ||
}; | ||
if (ledc_channel_config(&cfg) != ESP_OK) { | ||
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, | ||
|
@@ -152,22 +166,23 @@ STATIC void esp32_pwm_init_helper(esp32_pwm_obj_t *self, | |
} | ||
|
||
// Maybe change PWM timer | ||
int tval = args[ARG_freq].u_int; | ||
if (tval != -1) { | ||
if (tval != timer_cfg.freq_hz) { | ||
if (!set_freq(tval)) { | ||
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, | ||
"Bad frequency %d", tval)); | ||
} | ||
} | ||
int fval = args[ARG_freq].u_int; | ||
if (fval != -1 && ledc_set_freq(self->mode, self->timer, fval) != ESP_OK) { | ||
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, | ||
"Bad frequency %d", fval)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use lower-case at the start of error messages, for consistency with the rest of the code. |
||
} | ||
|
||
// Set duty cycle? | ||
int dval = args[ARG_duty].u_int; | ||
if (dval != -1) { | ||
dval &= ((1 << PWRES)-1); | ||
ledc_set_duty(PWMODE, channel, dval); | ||
ledc_update_duty(PWMODE, channel); | ||
ledc_set_duty(self->mode, channel, dval); | ||
ledc_update_duty(self->mode, channel); | ||
} | ||
|
||
// Reset the timer if low speed | ||
if (self->mode == LEDC_LOW_SPEED_MODE) { | ||
ledc_timer_rst(self->mode, self->timer); | ||
} | ||
} | ||
|
||
|
@@ -210,28 +225,42 @@ STATIC mp_obj_t esp32_pwm_deinit(mp_obj_t self_in) { | |
|
||
// Valid channel? | ||
if ((chan >= 0) && (chan < LEDC_CHANNEL_MAX)) { | ||
// Mark it unused, and tell the hardware to stop routing | ||
// Mark it unused, and tell the hardware to stop | ||
chan_gpio[chan] = -1; | ||
ledc_stop(PWMODE, chan, 0); | ||
ledc_stop(self->mode, chan, 0); | ||
self->active = 0; | ||
self->channel = -1; | ||
// Disable ledc signal for the pin | ||
if (self->mode == LEDC_LOW_SPEED_MODE) { | ||
gpio_matrix_out(self->pin, LEDC_LS_SIG_OUT0_IDX + self->channel, false, true); | ||
} else { | ||
gpio_matrix_out(self->pin, LEDC_HS_SIG_OUT0_IDX + self->channel, false, true); | ||
} | ||
} | ||
return mp_const_none; | ||
} | ||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_pwm_deinit_obj, esp32_pwm_deinit); | ||
|
||
STATIC mp_obj_t esp32_pwm_freq(size_t n_args, const mp_obj_t *args) { | ||
esp32_pwm_obj_t *self = MP_OBJ_TO_PTR(args[0]); | ||
|
||
if (n_args == 1) { | ||
// get | ||
return MP_OBJ_NEW_SMALL_INT(timer_cfg.freq_hz); | ||
return MP_OBJ_NEW_SMALL_INT(ledc_get_freq(self->mode, self->timer)); | ||
} | ||
|
||
// set | ||
int tval = mp_obj_get_int(args[1]); | ||
if (!set_freq(tval)) { | ||
if (tval <= 0 || ledc_set_freq(self->mode, self->timer, tval) != ESP_OK) { | ||
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, | ||
"Bad frequency %d", tval)); | ||
"bad frequency %d", tval)); | ||
} | ||
|
||
// Reset the timer if low speed | ||
if (self->mode == LEDC_LOW_SPEED_MODE) { | ||
ledc_timer_rst(self->mode, self->timer); | ||
} | ||
|
||
return mp_const_none; | ||
} | ||
|
||
|
@@ -243,15 +272,15 @@ 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 = ledc_get_duty(self->mode, self->channel); | ||
return MP_OBJ_NEW_SMALL_INT(duty); | ||
} | ||
|
||
// set | ||
duty = mp_obj_get_int(args[1]); | ||
duty &= ((1 << PWRES)-1); | ||
ledc_set_duty(PWMODE, self->channel, duty); | ||
ledc_update_duty(PWMODE, self->channel); | ||
ledc_set_duty(self->mode, self->channel, duty); | ||
ledc_update_duty(self->mode, self->channel); | ||
|
||
return mp_const_none; | ||
} | ||
|
@@ -263,6 +292,8 @@ STATIC const mp_rom_map_elem_t esp32_pwm_locals_dict_table[] = { | |
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_pwm_deinit_obj) }, | ||
{ MP_ROM_QSTR(MP_QSTR_freq), MP_ROM_PTR(&esp32_pwm_freq_obj) }, | ||
{ MP_ROM_QSTR(MP_QSTR_duty), MP_ROM_PTR(&esp32_pwm_duty_obj) }, | ||
{ MP_ROM_QSTR(MP_QSTR_HIGH_SPEED_MODE), MP_ROM_INT(LEDC_HIGH_SPEED_MODE) }, | ||
{ MP_ROM_QSTR(MP_QSTR_LOW_SPEED_MODE), MP_ROM_INT(LEDC_LOW_SPEED_MODE) }, | ||
}; | ||
|
||
STATIC MP_DEFINE_CONST_DICT(esp32_pwm_locals_dict, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't add blank lines when not necessary.