Skip to content

Commit ed1f42c

Browse files
robert-hhdpgeorge
authored andcommitted
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.
1 parent e3b8778 commit ed1f42c

File tree

1 file changed

+116
-87
lines changed
  • ports/nrf/modules/machine

1 file changed

+116
-87
lines changed

ports/nrf/modules/machine/pwm.c

+116-87
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* The MIT License (MIT)
55
*
66
* Copyright (c) 2016-2018 Glenn Ruben Bakke
7+
* Copyright (c) 2023 Robert Hammelrath
78
*
89
* Permission is hereby granted, free of charge, to any person obtaining a copy
910
* of this software and associated documentation files (the "Software"), to deal
@@ -47,31 +48,40 @@
4748
#define PWM_MAX_PERIOD (32768)
4849

4950
typedef enum {
50-
MODE_HIGH_LOW,
51+
MODE_HIGH_LOW = 0,
5152
MODE_LOW_HIGH
5253
} pwm_mode_t;
5354

5455
typedef enum {
55-
DUTY_NOT_SET,
56+
DUTY_NOT_SET = 0,
5657
DUTY_PERCENT,
5758
DUTY_U16,
5859
DUTY_NS
5960
} pwm_duty_t;
6061

62+
typedef enum {
63+
FREE = 0,
64+
STOPPED,
65+
RUNNING
66+
} pwm_run_t;
67+
6168
typedef struct {
62-
uint8_t pwm_pin;
63-
uint8_t duty_mode;
64-
int8_t freq_div;
69+
uint8_t pwm_pin[NRF_PWM_CHANNEL_COUNT];
70+
pwm_mode_t mode[NRF_PWM_CHANNEL_COUNT];
71+
pwm_duty_t duty_mode[NRF_PWM_CHANNEL_COUNT];
72+
uint32_t duty[NRF_PWM_CHANNEL_COUNT];
73+
pwm_run_t active;
6574
bool defer_start;
66-
uint32_t duty;
75+
int8_t freq_div;
6776
uint32_t freq;
68-
bool mode;
6977
} machine_pwm_config_t;
7078

7179
typedef struct _machine_pwm_obj_t {
7280
mp_obj_base_t base;
7381
const nrfx_pwm_t *p_pwm;
7482
machine_pwm_config_t *p_config;
83+
uint8_t id;
84+
uint8_t channel;
7585
} machine_pwm_obj_t;
7686

7787
STATIC const nrfx_pwm_t machine_hard_pwm_instances[] = {
@@ -86,46 +96,57 @@ STATIC const nrfx_pwm_t machine_hard_pwm_instances[] = {
8696
};
8797

8898
STATIC machine_pwm_config_t hard_configs[MP_ARRAY_SIZE(machine_hard_pwm_instances)];
89-
STATIC uint8_t pwm_used[MP_ARRAY_SIZE(machine_hard_pwm_instances)];
9099

91100
STATIC const machine_pwm_obj_t machine_hard_pwm_obj[] = {
92101
#if defined(NRF52_SERIES)
93-
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[0], .p_config = &hard_configs[0]},
94-
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[1], .p_config = &hard_configs[1]},
95-
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[2], .p_config = &hard_configs[2]},
102+
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[0], .p_config = &hard_configs[0], 0, 0},
103+
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[0], .p_config = &hard_configs[0], 0, 1},
104+
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[0], .p_config = &hard_configs[0], 0, 2},
105+
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[0], .p_config = &hard_configs[0], 0, 3},
106+
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[1], .p_config = &hard_configs[1], 1, 0},
107+
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[1], .p_config = &hard_configs[1], 1, 1},
108+
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[1], .p_config = &hard_configs[1], 1, 2},
109+
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[1], .p_config = &hard_configs[1], 1, 3},
110+
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[2], .p_config = &hard_configs[2], 2, 0},
111+
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[2], .p_config = &hard_configs[2], 2, 1},
112+
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[2], .p_config = &hard_configs[2], 2, 2},
113+
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[2], .p_config = &hard_configs[2], 2, 3},
96114
#if NRF52840
97-
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[3], .p_config = &hard_configs[3]},
115+
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[3], .p_config = &hard_configs[3], 3, 0},
116+
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[3], .p_config = &hard_configs[3], 3, 1},
117+
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[3], .p_config = &hard_configs[3], 3, 2},
118+
{{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[3], .p_config = &hard_configs[3], 3, 3},
98119
#endif
99120
#endif
100121
};
101122

102123
void pwm_init0(void) {
124+
for (int i = 0; i < MP_ARRAY_SIZE(hard_configs); i++) {
125+
hard_configs[i].active = FREE;
126+
hard_configs[i].freq_div = -1;
127+
hard_configs[i].freq = 0;
128+
memset(hard_configs[i].duty_mode, DUTY_NOT_SET, NRF_PWM_CHANNEL_COUNT);
129+
}
103130
}
104131

105-
// Find a free PWM
106-
STATIC int hard_pwm_find(int pin) {
107-
// check, if a PWM object can be reused.
108-
for (int i = 0; i < MP_ARRAY_SIZE(machine_hard_pwm_obj); i++) {
109-
if (machine_hard_pwm_obj[i].p_config->pwm_pin == pin) {
110-
return i;
132+
// Find a free PWM object
133+
STATIC int hard_pwm_find() {
134+
// look for a free module.
135+
for (int j = 0; j < MP_ARRAY_SIZE(hard_configs); j++) {
136+
if (hard_configs[j].active == FREE) {
137+
return j * NRF_PWM_CHANNEL_COUNT;
111138
}
112139
}
113-
// if not, look for a free object.
114-
for (int i = 0; i < MP_ARRAY_SIZE(machine_hard_pwm_obj); i++) {
115-
if (pwm_used[i] == 0) {
116-
return i;
117-
}
118-
}
119-
mp_raise_ValueError(MP_ERROR_TEXT("no free PWM id"));
140+
mp_raise_ValueError(MP_ERROR_TEXT("no free PWM object"));
120141
}
121142

122143
STATIC void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
123144
machine_pwm_obj_t *self = self_in;
124145
static char *duty_suffix[] = { "", "", "_u16", "_ns" };
125-
mp_printf(print, "<PWM: Pin=%u freq=%dHz duty%s=%d invert=%d id=%u>",
126-
self->p_config->pwm_pin, self->p_config->freq,
127-
duty_suffix[self->p_config->duty_mode], self->p_config->duty,
128-
self->p_config->mode, self->p_pwm->drv_inst_idx);
146+
mp_printf(print, "<PWM: Pin=%u freq=%dHz duty%s=%d invert=%d id=%u channel=%u>",
147+
self->p_config->pwm_pin[self->channel], self->p_config->freq,
148+
duty_suffix[self->p_config->duty_mode[self->channel]], self->p_config->duty[self->channel],
149+
self->p_config->mode[self->channel], self->id, self->channel);
129150
}
130151

131152
/******************************************************************************/
@@ -146,10 +167,11 @@ static const mp_arg_t allowed_args[] = {
146167
{ MP_QSTR_duty_ns, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
147168
{ MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
148169
{ MP_QSTR_id, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
170+
{ MP_QSTR_channel, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
149171
};
150172

151173
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) {
152-
enum { ARG_pin, ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns, ARG_invert, ARG_id };
174+
enum { ARG_pin, ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns, ARG_invert, ARG_id, ARG_channel };
153175

154176
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
155177
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
168190
mp_machine_pwm_duty_set_ns(self, args[ARG_duty_ns].u_int);
169191
}
170192
if (args[ARG_invert].u_int != -1) {
171-
self->p_config->mode = args[ARG_invert].u_int ? MODE_LOW_HIGH : MODE_HIGH_LOW;
193+
self->p_config->mode[self->channel] = args[ARG_invert].u_int ? MODE_LOW_HIGH : MODE_HIGH_LOW;
172194
}
173195
self->p_config->defer_start = false;
174196

@@ -177,7 +199,7 @@ STATIC void mp_machine_pwm_init_helper(const machine_pwm_obj_t *self, size_t n_a
177199

178200

179201
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) {
180-
enum { ARG_pin, ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns, ARG_invert, ARG_id };
202+
enum { ARG_pin, ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns, ARG_invert, ARG_id, ARG_channel };
181203

182204
// parse args
183205
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
191213
mp_raise_ValueError(MP_ERROR_TEXT("Pin missing"));
192214
}
193215

216+
// Get the PWM object number
217+
// If just the ID is given, use channel 0
218+
// If none is given, attempt to find an unused object.
194219
int pwm_id = -1;
195220
if (args[ARG_id].u_int != -1) {
196-
// get static peripheral object
197-
if (args[ARG_id].u_int >= 0 && args[ARG_id].u_int < MP_ARRAY_SIZE(machine_hard_pwm_obj)) {
198-
pwm_id = args[ARG_id].u_int;
221+
if (args[ARG_id].u_int >= 0 && args[ARG_id].u_int < MP_ARRAY_SIZE(machine_hard_pwm_instances)) {
222+
pwm_id = args[ARG_id].u_int * NRF_PWM_CHANNEL_COUNT;
223+
if (args[ARG_channel].u_int != -1) {
224+
if (args[ARG_channel].u_int >= 0 && args[ARG_channel].u_int < NRF_PWM_CHANNEL_COUNT) {
225+
pwm_id += args[ARG_channel].u_int;
226+
}
227+
}
199228
}
200229
} else {
201-
pwm_id = hard_pwm_find(pwm_pin);
230+
// no ID given, search for a free ID.
231+
pwm_id = hard_pwm_find();
202232
}
203233
if (pwm_id < 0) {
204234
mp_raise_ValueError(MP_ERROR_TEXT("invalid PWM id"));
205235
}
206236
const machine_pwm_obj_t *self = &machine_hard_pwm_obj[pwm_id];
207-
self->p_config->pwm_pin = pwm_pin;
237+
int pwm_channel = pwm_id % NRF_PWM_CHANNEL_COUNT;
238+
self->p_config->pwm_pin[pwm_channel] = pwm_pin;
239+
self->p_config->duty_mode[pwm_channel] = DUTY_NOT_SET;
240+
self->p_config->duty[pwm_channel] = 0;
241+
self->p_config->mode[pwm_channel] = MODE_HIGH_LOW;
208242
self->p_config->defer_start = false;
209-
self->p_config->duty_mode = DUTY_NOT_SET;
210-
self->p_config->duty = 0;
211-
self->p_config->freq = 0;
212-
self->p_config->freq_div = -1;
213-
self->p_config->mode = MODE_HIGH_LOW;
214243

215244
// start the PWM running for this channel
216245
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
220249
return MP_OBJ_FROM_PTR(self);
221250
}
222251

252+
// Stop all PWM modules and release them
223253
void pwm_deinit_all(void) {
224-
for (int i = 0; i < MP_ARRAY_SIZE(machine_hard_pwm_obj); i++) {
225-
mp_machine_pwm_deinit(&machine_hard_pwm_obj[i]);
254+
for (int i = 0; i < MP_ARRAY_SIZE(machine_hard_pwm_instances); i++) {
255+
mp_machine_pwm_deinit(&machine_hard_pwm_obj[i * NRF_PWM_CHANNEL_COUNT]);
226256
}
257+
pwm_init0();
227258
}
228259

260+
// Stop the PWM module, but do not release it.
229261
STATIC void mp_machine_pwm_deinit(const machine_pwm_obj_t *self) {
230-
pwm_used[self->p_pwm->drv_inst_idx] = 0;
262+
self->p_config->active = STOPPED;
231263
nrfx_pwm_stop(self->p_pwm, true);
232264
nrfx_pwm_uninit(self->p_pwm);
233265
}
@@ -253,48 +285,48 @@ STATIC void mp_machine_pwm_freq_set(const machine_pwm_obj_t *self, mp_int_t freq
253285
}
254286

255287
STATIC mp_obj_t mp_machine_pwm_duty_get(const machine_pwm_obj_t *self) {
256-
if (self->p_config->duty_mode == DUTY_PERCENT) {
257-
return MP_OBJ_NEW_SMALL_INT(self->p_config->duty);
258-
} else if (self->p_config->duty_mode == DUTY_U16) {
259-
return MP_OBJ_NEW_SMALL_INT(self->p_config->duty * 100 / 65536);
288+
if (self->p_config->duty_mode[self->channel] == DUTY_PERCENT) {
289+
return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel]);
290+
} else if (self->p_config->duty_mode[self->channel] == DUTY_U16) {
291+
return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel] * 100 / 65536);
260292
} else {
261293
return MP_OBJ_NEW_SMALL_INT(-1);
262294
}
263295
}
264296

265297
STATIC void mp_machine_pwm_duty_set(const machine_pwm_obj_t *self, mp_int_t duty) {
266-
self->p_config->duty = duty;
267-
self->p_config->duty_mode = DUTY_PERCENT;
298+
self->p_config->duty[self->channel] = duty;
299+
self->p_config->duty_mode[self->channel] = DUTY_PERCENT;
268300
machine_hard_pwm_start(self);
269301
}
270302

271303
STATIC mp_obj_t mp_machine_pwm_duty_get_u16(const machine_pwm_obj_t *self) {
272-
if (self->p_config->duty_mode == DUTY_U16) {
273-
return MP_OBJ_NEW_SMALL_INT(self->p_config->duty);
274-
} else if (self->p_config->duty_mode == DUTY_PERCENT) {
275-
return MP_OBJ_NEW_SMALL_INT(self->p_config->duty * 65536 / 100);
304+
if (self->p_config->duty_mode[self->channel] == DUTY_U16) {
305+
return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel]);
306+
} else if (self->p_config->duty_mode[self->channel] == DUTY_PERCENT) {
307+
return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel] * 65536 / 100);
276308
} else {
277309
return MP_OBJ_NEW_SMALL_INT(-1);
278310
}
279311
}
280312

281313
STATIC void mp_machine_pwm_duty_set_u16(const machine_pwm_obj_t *self, mp_int_t duty) {
282-
self->p_config->duty = duty;
283-
self->p_config->duty_mode = DUTY_U16;
314+
self->p_config->duty[self->channel] = duty;
315+
self->p_config->duty_mode[self->channel] = DUTY_U16;
284316
machine_hard_pwm_start(self);
285317
}
286318

287319
STATIC mp_obj_t mp_machine_pwm_duty_get_ns(const machine_pwm_obj_t *self) {
288-
if (self->p_config->duty_mode == DUTY_NS) {
289-
return MP_OBJ_NEW_SMALL_INT(self->p_config->duty);
320+
if (self->p_config->duty_mode[self->channel] == DUTY_NS) {
321+
return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel]);
290322
} else {
291323
return MP_OBJ_NEW_SMALL_INT(-1);
292324
}
293325
}
294326

295327
STATIC void mp_machine_pwm_duty_set_ns(const machine_pwm_obj_t *self, mp_int_t duty) {
296-
self->p_config->duty = duty;
297-
self->p_config->duty_mode = DUTY_NS;
328+
self->p_config->duty[self->channel] = duty;
329+
self->p_config->duty_mode[self->channel] = DUTY_NS;
298330
machine_hard_pwm_start(self);
299331
}
300332

@@ -305,15 +337,16 @@ STATIC void machine_hard_pwm_start(const machine_pwm_obj_t *self) {
305337
nrfx_pwm_config_t config;
306338

307339
// check if ready to go
308-
if (self->p_config->defer_start == true || self->p_config->freq_div < 0 || self->p_config->duty_mode == DUTY_NOT_SET) {
340+
if (self->p_config->defer_start == true || self->p_config->freq_div < 0 || self->p_config->duty_mode[self->channel] == DUTY_NOT_SET) {
309341
return; // Not ready yet.
310342
}
311-
pwm_used[self->p_pwm->drv_inst_idx] = 1;
312343

313-
config.output_pins[0] = self->p_config->pwm_pin;
314-
config.output_pins[1] = NRFX_PWM_PIN_NOT_USED;
315-
config.output_pins[2] = NRFX_PWM_PIN_NOT_USED;
316-
config.output_pins[3] = NRFX_PWM_PIN_NOT_USED;
344+
self->p_config->active = RUNNING;
345+
346+
config.output_pins[0] = self->p_config->duty_mode[0] != DUTY_NOT_SET ? self->p_config->pwm_pin[0] : NRFX_PWM_PIN_NOT_USED;
347+
config.output_pins[1] = self->p_config->duty_mode[1] != DUTY_NOT_SET ? self->p_config->pwm_pin[1] : NRFX_PWM_PIN_NOT_USED;
348+
config.output_pins[2] = self->p_config->duty_mode[2] != DUTY_NOT_SET ? self->p_config->pwm_pin[2] : NRFX_PWM_PIN_NOT_USED;
349+
config.output_pins[3] = self->p_config->duty_mode[3] != DUTY_NOT_SET ? self->p_config->pwm_pin[3] : NRFX_PWM_PIN_NOT_USED;
317350

318351
uint32_t tick_freq = PWM_MAX_BASE_FREQ / (1 << self->p_config->freq_div);
319352
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) {
330363

331364
nrfx_pwm_init(self->p_pwm, &config, NULL, NULL);
332365

333-
uint16_t pulse_width;
334-
if (self->p_config->duty_mode == DUTY_PERCENT) {
335-
pulse_width = ((period * self->p_config->duty) / 100);
336-
} else if (self->p_config->duty_mode == DUTY_U16) {
337-
pulse_width = ((period * self->p_config->duty) / 65536);
338-
}
339-
if (self->p_config->duty_mode == DUTY_NS) {
340-
pulse_width = (uint64_t)self->p_config->duty * tick_freq / 1000000000ULL;
341-
}
342-
343-
// TODO: Move DMA buffer to global memory.
344366
volatile static uint16_t pwm_seq[4];
345367

346-
if (self->p_config->mode == MODE_HIGH_LOW) {
347-
pwm_seq[0] = 0x8000 | pulse_width;
348-
} else {
349-
pwm_seq[0] = pulse_width;
350-
}
368+
for (int i = 0; i < NRF_PWM_CHANNEL_COUNT; i++) {
369+
uint16_t pulse_width = 0;
370+
if (self->p_config->duty_mode[i] == DUTY_PERCENT) {
371+
pulse_width = ((period * self->p_config->duty[i]) / 100);
372+
} else if (self->p_config->duty_mode[i] == DUTY_U16) {
373+
pulse_width = ((period * self->p_config->duty[i]) / 65536);
374+
}
375+
if (self->p_config->duty_mode[i] == DUTY_NS) {
376+
pulse_width = (uint64_t)self->p_config->duty[i] * tick_freq / 1000000000ULL;
377+
}
351378

352-
// Outputs 1..3 are not used for now
353-
// pwm_seq[1] = 0x8000 | pulse_width;
354-
// pwm_seq[2] = 0x8000 | pulse_width;
355-
// pwm_seq[3] = 0x8000 | pulse_width;
379+
if (self->p_config->mode[i] == MODE_HIGH_LOW) {
380+
pwm_seq[i] = 0x8000 | pulse_width;
381+
} else {
382+
pwm_seq[i] = pulse_width;
383+
}
384+
}
356385

357386
const nrf_pwm_sequence_t pwm_sequence = {
358387
.values.p_raw = (const uint16_t *)&pwm_seq,

0 commit comments

Comments
 (0)