Skip to content

Commit

Permalink
nrf/modules/machine/pwm: Support using all 4 channels of a PWM module.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
robert-hh authored and dpgeorge committed Mar 9, 2023
1 parent e3b8778 commit ed1f42c
Showing 1 changed file with 116 additions and 87 deletions.
203 changes: 116 additions & 87 deletions ports/nrf/modules/machine/pwm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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[] = {
Expand All @@ -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, "<PWM: Pin=%u freq=%dHz duty%s=%d invert=%d id=%u>",
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, "<PWM: Pin=%u freq=%dHz duty%s=%d invert=%d id=%u channel=%u>",
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);
}

/******************************************************************************/
Expand All @@ -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);
Expand All @@ -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;

Expand All @@ -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)];
Expand All @@ -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;
Expand All @@ -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);
}
Expand All @@ -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);
}

Expand All @@ -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;
Expand All @@ -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,
Expand Down

0 comments on commit ed1f42c

Please sign in to comment.