From ed1f42cb493cf70c326e9a986d2c03ba5da52641 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 8 Mar 2023 11:55:24 +0100 Subject: [PATCH] nrf/modules/machine/pwm: Support using all 4 channels of a PWM module. These have the same frequency, but can have different duty cycle and polarity. pwm.deinit() stops all channels of a module, but does not release the module. pwm.init() without arguments restarts all outputs. --- ports/nrf/modules/machine/pwm.c | 203 ++++++++++++++++++-------------- 1 file changed, 116 insertions(+), 87 deletions(-) diff --git a/ports/nrf/modules/machine/pwm.c b/ports/nrf/modules/machine/pwm.c index 525c3aba6b..8e435bcf3a 100644 --- a/ports/nrf/modules/machine/pwm.c +++ b/ports/nrf/modules/machine/pwm.c @@ -4,6 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2016-2018 Glenn Ruben Bakke + * Copyright (c) 2023 Robert Hammelrath * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -47,31 +48,40 @@ #define PWM_MAX_PERIOD (32768) typedef enum { - MODE_HIGH_LOW, + MODE_HIGH_LOW = 0, MODE_LOW_HIGH } pwm_mode_t; typedef enum { - DUTY_NOT_SET, + DUTY_NOT_SET = 0, DUTY_PERCENT, DUTY_U16, DUTY_NS } pwm_duty_t; +typedef enum { + FREE = 0, + STOPPED, + RUNNING +} pwm_run_t; + typedef struct { - uint8_t pwm_pin; - uint8_t duty_mode; - int8_t freq_div; + uint8_t pwm_pin[NRF_PWM_CHANNEL_COUNT]; + pwm_mode_t mode[NRF_PWM_CHANNEL_COUNT]; + pwm_duty_t duty_mode[NRF_PWM_CHANNEL_COUNT]; + uint32_t duty[NRF_PWM_CHANNEL_COUNT]; + pwm_run_t active; bool defer_start; - uint32_t duty; + int8_t freq_div; uint32_t freq; - bool mode; } machine_pwm_config_t; typedef struct _machine_pwm_obj_t { mp_obj_base_t base; const nrfx_pwm_t *p_pwm; machine_pwm_config_t *p_config; + uint8_t id; + uint8_t channel; } machine_pwm_obj_t; STATIC const nrfx_pwm_t machine_hard_pwm_instances[] = { @@ -86,46 +96,57 @@ STATIC const nrfx_pwm_t machine_hard_pwm_instances[] = { }; STATIC machine_pwm_config_t hard_configs[MP_ARRAY_SIZE(machine_hard_pwm_instances)]; -STATIC uint8_t pwm_used[MP_ARRAY_SIZE(machine_hard_pwm_instances)]; STATIC const machine_pwm_obj_t machine_hard_pwm_obj[] = { #if defined(NRF52_SERIES) - {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[0], .p_config = &hard_configs[0]}, - {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[1], .p_config = &hard_configs[1]}, - {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[2], .p_config = &hard_configs[2]}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[0], .p_config = &hard_configs[0], 0, 0}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[0], .p_config = &hard_configs[0], 0, 1}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[0], .p_config = &hard_configs[0], 0, 2}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[0], .p_config = &hard_configs[0], 0, 3}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[1], .p_config = &hard_configs[1], 1, 0}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[1], .p_config = &hard_configs[1], 1, 1}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[1], .p_config = &hard_configs[1], 1, 2}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[1], .p_config = &hard_configs[1], 1, 3}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[2], .p_config = &hard_configs[2], 2, 0}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[2], .p_config = &hard_configs[2], 2, 1}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[2], .p_config = &hard_configs[2], 2, 2}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[2], .p_config = &hard_configs[2], 2, 3}, #if NRF52840 - {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[3], .p_config = &hard_configs[3]}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[3], .p_config = &hard_configs[3], 3, 0}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[3], .p_config = &hard_configs[3], 3, 1}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[3], .p_config = &hard_configs[3], 3, 2}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[3], .p_config = &hard_configs[3], 3, 3}, #endif #endif }; void pwm_init0(void) { + for (int i = 0; i < MP_ARRAY_SIZE(hard_configs); i++) { + hard_configs[i].active = FREE; + hard_configs[i].freq_div = -1; + hard_configs[i].freq = 0; + memset(hard_configs[i].duty_mode, DUTY_NOT_SET, NRF_PWM_CHANNEL_COUNT); + } } -// Find a free PWM -STATIC int hard_pwm_find(int pin) { - // check, if a PWM object can be reused. - for (int i = 0; i < MP_ARRAY_SIZE(machine_hard_pwm_obj); i++) { - if (machine_hard_pwm_obj[i].p_config->pwm_pin == pin) { - return i; +// Find a free PWM object +STATIC int hard_pwm_find() { + // look for a free module. + for (int j = 0; j < MP_ARRAY_SIZE(hard_configs); j++) { + if (hard_configs[j].active == FREE) { + return j * NRF_PWM_CHANNEL_COUNT; } } - // if not, look for a free object. - for (int i = 0; i < MP_ARRAY_SIZE(machine_hard_pwm_obj); i++) { - if (pwm_used[i] == 0) { - return i; - } - } - mp_raise_ValueError(MP_ERROR_TEXT("no free PWM id")); + mp_raise_ValueError(MP_ERROR_TEXT("no free PWM object")); } 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 = self_in; static char *duty_suffix[] = { "", "", "_u16", "_ns" }; - mp_printf(print, "", - self->p_config->pwm_pin, self->p_config->freq, - duty_suffix[self->p_config->duty_mode], self->p_config->duty, - self->p_config->mode, self->p_pwm->drv_inst_idx); + mp_printf(print, "", + self->p_config->pwm_pin[self->channel], self->p_config->freq, + duty_suffix[self->p_config->duty_mode[self->channel]], self->p_config->duty[self->channel], + self->p_config->mode[self->channel], self->id, self->channel); } /******************************************************************************/ @@ -146,10 +167,11 @@ static const mp_arg_t allowed_args[] = { { MP_QSTR_duty_ns, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_id, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_channel, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, }; STATIC void mp_machine_pwm_init_helper(const machine_pwm_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_pin, ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns, ARG_invert, ARG_id }; + enum { ARG_pin, ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns, ARG_invert, ARG_id, ARG_channel }; 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); @@ -168,7 +190,7 @@ STATIC void mp_machine_pwm_init_helper(const machine_pwm_obj_t *self, size_t n_a mp_machine_pwm_duty_set_ns(self, args[ARG_duty_ns].u_int); } if (args[ARG_invert].u_int != -1) { - self->p_config->mode = args[ARG_invert].u_int ? MODE_LOW_HIGH : MODE_HIGH_LOW; + self->p_config->mode[self->channel] = args[ARG_invert].u_int ? MODE_LOW_HIGH : MODE_HIGH_LOW; } self->p_config->defer_start = false; @@ -177,7 +199,7 @@ STATIC void mp_machine_pwm_init_helper(const machine_pwm_obj_t *self, size_t n_a 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 *all_args) { - enum { ARG_pin, ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns, ARG_invert, ARG_id }; + enum { ARG_pin, ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns, ARG_invert, ARG_id, ARG_channel }; // parse args mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -191,26 +213,33 @@ STATIC mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args mp_raise_ValueError(MP_ERROR_TEXT("Pin missing")); } + // Get the PWM object number + // If just the ID is given, use channel 0 + // If none is given, attempt to find an unused object. int pwm_id = -1; if (args[ARG_id].u_int != -1) { - // get static peripheral object - if (args[ARG_id].u_int >= 0 && args[ARG_id].u_int < MP_ARRAY_SIZE(machine_hard_pwm_obj)) { - pwm_id = args[ARG_id].u_int; + if (args[ARG_id].u_int >= 0 && args[ARG_id].u_int < MP_ARRAY_SIZE(machine_hard_pwm_instances)) { + pwm_id = args[ARG_id].u_int * NRF_PWM_CHANNEL_COUNT; + if (args[ARG_channel].u_int != -1) { + if (args[ARG_channel].u_int >= 0 && args[ARG_channel].u_int < NRF_PWM_CHANNEL_COUNT) { + pwm_id += args[ARG_channel].u_int; + } + } } } else { - pwm_id = hard_pwm_find(pwm_pin); + // no ID given, search for a free ID. + pwm_id = hard_pwm_find(); } if (pwm_id < 0) { mp_raise_ValueError(MP_ERROR_TEXT("invalid PWM id")); } const machine_pwm_obj_t *self = &machine_hard_pwm_obj[pwm_id]; - self->p_config->pwm_pin = pwm_pin; + int pwm_channel = pwm_id % NRF_PWM_CHANNEL_COUNT; + self->p_config->pwm_pin[pwm_channel] = pwm_pin; + self->p_config->duty_mode[pwm_channel] = DUTY_NOT_SET; + self->p_config->duty[pwm_channel] = 0; + self->p_config->mode[pwm_channel] = MODE_HIGH_LOW; self->p_config->defer_start = false; - self->p_config->duty_mode = DUTY_NOT_SET; - self->p_config->duty = 0; - self->p_config->freq = 0; - self->p_config->freq_div = -1; - self->p_config->mode = MODE_HIGH_LOW; // start the PWM running for this channel mp_map_t kw_args; @@ -220,14 +249,17 @@ STATIC mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args return MP_OBJ_FROM_PTR(self); } +// Stop all PWM modules and release them void pwm_deinit_all(void) { - for (int i = 0; i < MP_ARRAY_SIZE(machine_hard_pwm_obj); i++) { - mp_machine_pwm_deinit(&machine_hard_pwm_obj[i]); + for (int i = 0; i < MP_ARRAY_SIZE(machine_hard_pwm_instances); i++) { + mp_machine_pwm_deinit(&machine_hard_pwm_obj[i * NRF_PWM_CHANNEL_COUNT]); } + pwm_init0(); } +// Stop the PWM module, but do not release it. STATIC void mp_machine_pwm_deinit(const machine_pwm_obj_t *self) { - pwm_used[self->p_pwm->drv_inst_idx] = 0; + self->p_config->active = STOPPED; nrfx_pwm_stop(self->p_pwm, true); nrfx_pwm_uninit(self->p_pwm); } @@ -253,48 +285,48 @@ STATIC void mp_machine_pwm_freq_set(const machine_pwm_obj_t *self, mp_int_t freq } STATIC mp_obj_t mp_machine_pwm_duty_get(const machine_pwm_obj_t *self) { - if (self->p_config->duty_mode == DUTY_PERCENT) { - return MP_OBJ_NEW_SMALL_INT(self->p_config->duty); - } else if (self->p_config->duty_mode == DUTY_U16) { - return MP_OBJ_NEW_SMALL_INT(self->p_config->duty * 100 / 65536); + if (self->p_config->duty_mode[self->channel] == DUTY_PERCENT) { + return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel]); + } else if (self->p_config->duty_mode[self->channel] == DUTY_U16) { + return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel] * 100 / 65536); } else { return MP_OBJ_NEW_SMALL_INT(-1); } } STATIC void mp_machine_pwm_duty_set(const machine_pwm_obj_t *self, mp_int_t duty) { - self->p_config->duty = duty; - self->p_config->duty_mode = DUTY_PERCENT; + self->p_config->duty[self->channel] = duty; + self->p_config->duty_mode[self->channel] = DUTY_PERCENT; machine_hard_pwm_start(self); } STATIC mp_obj_t mp_machine_pwm_duty_get_u16(const machine_pwm_obj_t *self) { - if (self->p_config->duty_mode == DUTY_U16) { - return MP_OBJ_NEW_SMALL_INT(self->p_config->duty); - } else if (self->p_config->duty_mode == DUTY_PERCENT) { - return MP_OBJ_NEW_SMALL_INT(self->p_config->duty * 65536 / 100); + if (self->p_config->duty_mode[self->channel] == DUTY_U16) { + return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel]); + } else if (self->p_config->duty_mode[self->channel] == DUTY_PERCENT) { + return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel] * 65536 / 100); } else { return MP_OBJ_NEW_SMALL_INT(-1); } } STATIC void mp_machine_pwm_duty_set_u16(const machine_pwm_obj_t *self, mp_int_t duty) { - self->p_config->duty = duty; - self->p_config->duty_mode = DUTY_U16; + self->p_config->duty[self->channel] = duty; + self->p_config->duty_mode[self->channel] = DUTY_U16; machine_hard_pwm_start(self); } STATIC mp_obj_t mp_machine_pwm_duty_get_ns(const machine_pwm_obj_t *self) { - if (self->p_config->duty_mode == DUTY_NS) { - return MP_OBJ_NEW_SMALL_INT(self->p_config->duty); + if (self->p_config->duty_mode[self->channel] == DUTY_NS) { + return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel]); } else { return MP_OBJ_NEW_SMALL_INT(-1); } } STATIC void mp_machine_pwm_duty_set_ns(const machine_pwm_obj_t *self, mp_int_t duty) { - self->p_config->duty = duty; - self->p_config->duty_mode = DUTY_NS; + self->p_config->duty[self->channel] = duty; + self->p_config->duty_mode[self->channel] = DUTY_NS; machine_hard_pwm_start(self); } @@ -305,15 +337,16 @@ STATIC void machine_hard_pwm_start(const machine_pwm_obj_t *self) { nrfx_pwm_config_t config; // check if ready to go - if (self->p_config->defer_start == true || self->p_config->freq_div < 0 || self->p_config->duty_mode == DUTY_NOT_SET) { + if (self->p_config->defer_start == true || self->p_config->freq_div < 0 || self->p_config->duty_mode[self->channel] == DUTY_NOT_SET) { return; // Not ready yet. } - pwm_used[self->p_pwm->drv_inst_idx] = 1; - config.output_pins[0] = self->p_config->pwm_pin; - config.output_pins[1] = NRFX_PWM_PIN_NOT_USED; - config.output_pins[2] = NRFX_PWM_PIN_NOT_USED; - config.output_pins[3] = NRFX_PWM_PIN_NOT_USED; + self->p_config->active = RUNNING; + + config.output_pins[0] = self->p_config->duty_mode[0] != DUTY_NOT_SET ? self->p_config->pwm_pin[0] : NRFX_PWM_PIN_NOT_USED; + config.output_pins[1] = self->p_config->duty_mode[1] != DUTY_NOT_SET ? self->p_config->pwm_pin[1] : NRFX_PWM_PIN_NOT_USED; + config.output_pins[2] = self->p_config->duty_mode[2] != DUTY_NOT_SET ? self->p_config->pwm_pin[2] : NRFX_PWM_PIN_NOT_USED; + config.output_pins[3] = self->p_config->duty_mode[3] != DUTY_NOT_SET ? self->p_config->pwm_pin[3] : NRFX_PWM_PIN_NOT_USED; uint32_t tick_freq = PWM_MAX_BASE_FREQ / (1 << self->p_config->freq_div); uint32_t period = tick_freq / self->p_config->freq; @@ -330,29 +363,25 @@ STATIC void machine_hard_pwm_start(const machine_pwm_obj_t *self) { nrfx_pwm_init(self->p_pwm, &config, NULL, NULL); - uint16_t pulse_width; - if (self->p_config->duty_mode == DUTY_PERCENT) { - pulse_width = ((period * self->p_config->duty) / 100); - } else if (self->p_config->duty_mode == DUTY_U16) { - pulse_width = ((period * self->p_config->duty) / 65536); - } - if (self->p_config->duty_mode == DUTY_NS) { - pulse_width = (uint64_t)self->p_config->duty * tick_freq / 1000000000ULL; - } - - // TODO: Move DMA buffer to global memory. volatile static uint16_t pwm_seq[4]; - if (self->p_config->mode == MODE_HIGH_LOW) { - pwm_seq[0] = 0x8000 | pulse_width; - } else { - pwm_seq[0] = pulse_width; - } + for (int i = 0; i < NRF_PWM_CHANNEL_COUNT; i++) { + uint16_t pulse_width = 0; + if (self->p_config->duty_mode[i] == DUTY_PERCENT) { + pulse_width = ((period * self->p_config->duty[i]) / 100); + } else if (self->p_config->duty_mode[i] == DUTY_U16) { + pulse_width = ((period * self->p_config->duty[i]) / 65536); + } + if (self->p_config->duty_mode[i] == DUTY_NS) { + pulse_width = (uint64_t)self->p_config->duty[i] * tick_freq / 1000000000ULL; + } - // Outputs 1..3 are not used for now - // pwm_seq[1] = 0x8000 | pulse_width; - // pwm_seq[2] = 0x8000 | pulse_width; - // pwm_seq[3] = 0x8000 | pulse_width; + if (self->p_config->mode[i] == MODE_HIGH_LOW) { + pwm_seq[i] = 0x8000 | pulse_width; + } else { + pwm_seq[i] = pulse_width; + } + } const nrf_pwm_sequence_t pwm_sequence = { .values.p_raw = (const uint16_t *)&pwm_seq,