Skip to content

Commit e3b8778

Browse files
robert-hhdpgeorge
authored andcommitted
nrf/modules/machine/soft_pwm: Add PWM for nrf51x boards using soft PWM.
Using extmod/machine_pwm.c for the Python bindings and the existing softpwm.c driver, by just adding the interface. Properties: - Frequency range 1-3906 Hz. - All PWM outputs run at the same frequency but can have different duty cycles. - Limited to the P0.x pins. Since it uses the existing softpwm.c mechanism, it will be affected by playing music with the music class.
1 parent a1f838c commit e3b8778

File tree

8 files changed

+231
-2
lines changed

8 files changed

+231
-2
lines changed

ports/nrf/boards/microbit/mpconfigboard.h

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
#define MICROPY_PY_MACHINE_UART (1)
3232
#define MICROPY_PY_MUSIC (1)
33+
#define MICROPY_PY_MACHINE_PWM (1)
3334
#define MICROPY_PY_MACHINE_SOFT_PWM (1)
3435
#define MICROPY_PY_MACHINE_HW_SPI (1)
3536
#define MICROPY_PY_MACHINE_RTCOUNTER (1)

ports/nrf/boards/pca10000/mpconfigboard.h

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
#define MICROPY_PY_MACHINE_ADC (0)
3636
#define MICROPY_PY_MACHINE_TEMP (1)
3737

38+
#define MICROPY_PY_MACHINE_PWM (1)
39+
#define MICROPY_PY_MACHINE_SOFT_PWM (1)
40+
3841
#define MICROPY_HW_ENABLE_RNG (1)
3942

4043
#define MICROPY_HW_HAS_LED (1)

ports/nrf/boards/pca10001/mpconfigboard.h

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
#define MICROPY_PY_MACHINE_ADC (1)
3636
#define MICROPY_PY_MACHINE_TEMP (1)
3737

38+
#define MICROPY_PY_MACHINE_PWM (1)
39+
#define MICROPY_PY_MACHINE_SOFT_PWM (1)
40+
3841
#define MICROPY_HW_ENABLE_RNG (1)
3942

4043
#define MICROPY_HW_HAS_LED (1)

ports/nrf/boards/pca10028/mpconfigboard.h

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
#define MICROPY_PY_MACHINE_ADC (1)
3636
#define MICROPY_PY_MACHINE_TEMP (1)
3737

38+
#define MICROPY_PY_MACHINE_PWM (1)
39+
#define MICROPY_PY_MACHINE_SOFT_PWM (1)
40+
3841
#define MICROPY_HW_ENABLE_RNG (1)
3942

4043
#define MICROPY_HW_HAS_LED (1)

ports/nrf/boards/pca10031/mpconfigboard.h

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
#define MICROPY_PY_MACHINE_ADC (1)
3636
#define MICROPY_PY_MACHINE_TEMP (1)
3737

38+
#define MICROPY_PY_MACHINE_PWM (1)
39+
#define MICROPY_PY_MACHINE_SOFT_PWM (1)
40+
3841
#define MICROPY_HW_ENABLE_RNG (1)
3942

4043
#define MICROPY_HW_HAS_LED (1)

ports/nrf/modules/machine/modmachine.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
#include "spi.h"
4343
#include "i2c.h"
4444
#include "timer.h"
45-
#if MICROPY_PY_MACHINE_HW_PWM
45+
#if MICROPY_PY_MACHINE_HW_PWM || MICROPY_PY_MACHINE_SOFT_PWM
4646
#include "pwm.h"
4747
#endif
4848
#if MICROPY_PY_MACHINE_ADC
@@ -235,7 +235,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = {
235235
#if MICROPY_PY_MACHINE_TIMER_NRF
236236
{ MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) },
237237
#endif
238-
#if MICROPY_PY_MACHINE_HW_PWM
238+
#if MICROPY_PY_MACHINE_HW_PWM || MICROPY_PY_MACHINE_SOFT_PWM
239239
{ MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&machine_pwm_type) },
240240
#endif
241241
#if MICROPY_PY_MACHINE_TEMP

ports/nrf/modules/machine/soft_pwm.c

+214
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2023 Robert Hammelrath
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#include <stdio.h>
28+
#include "py/runtime.h"
29+
#include "py/mphal.h"
30+
31+
#if MICROPY_PY_MACHINE_SOFT_PWM
32+
33+
#include "softpwm.h"
34+
35+
typedef enum {
36+
DUTY_NOT_SET = 0,
37+
DUTY,
38+
DUTY_U16,
39+
DUTY_NS
40+
} pwm_duty_t;
41+
42+
typedef struct _machine_pwm_obj_t {
43+
mp_obj_base_t base;
44+
uint8_t pwm_pin;
45+
bool defer_start;
46+
uint8_t duty_mode;
47+
uint32_t duty;
48+
uint32_t freq;
49+
} machine_pwm_obj_t;
50+
51+
#define SOFT_PWM_BASE_FREQ (1000000)
52+
#define DUTY_FULL_SCALE (1024)
53+
54+
STATIC void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
55+
machine_pwm_obj_t *self = self_in;
56+
static char *duty_suffix[] = { "", "", "_u16", "_ns" };
57+
mp_printf(print, "<PWM: Pin=%u freq=%dHz duty%s=%d>",
58+
self->pwm_pin, self->freq,
59+
duty_suffix[self->duty_mode], self->duty);
60+
}
61+
62+
// MicroPython bindings for machine API
63+
64+
STATIC void machine_soft_pwm_start(machine_pwm_obj_t *self);
65+
STATIC void mp_machine_pwm_deinit(machine_pwm_obj_t *self);
66+
STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq);
67+
STATIC void mp_machine_pwm_duty_set(machine_pwm_obj_t *self, mp_int_t duty);
68+
STATIC void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, mp_int_t duty_u16);
69+
STATIC void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, mp_int_t duty_ns);
70+
71+
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) {
72+
enum { ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns };
73+
static const mp_arg_t allowed_args[] = {
74+
{ MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
75+
{ MP_QSTR_duty, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
76+
{ MP_QSTR_duty_u16, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
77+
{ MP_QSTR_duty_ns, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
78+
};
79+
80+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
81+
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
82+
83+
self->defer_start = true;
84+
if (args[ARG_freq].u_int != -1) {
85+
mp_machine_pwm_freq_set(self, args[ARG_freq].u_int);
86+
}
87+
if (args[ARG_duty].u_int != -1) {
88+
mp_machine_pwm_duty_set(self, args[ARG_duty].u_int);
89+
}
90+
if (args[ARG_duty_u16].u_int != -1) {
91+
mp_machine_pwm_duty_set_u16(self, args[ARG_duty_u16].u_int);
92+
}
93+
if (args[ARG_duty_ns].u_int != -1) {
94+
mp_machine_pwm_duty_set_ns(self, args[ARG_duty_ns].u_int);
95+
}
96+
self->defer_start = false;
97+
// (Re-)start the PWM.
98+
machine_soft_pwm_start(self);
99+
}
100+
101+
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) {
102+
enum { ARG_pin, ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns, ARG_id };
103+
104+
mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true);
105+
106+
// check if the PWM pin is valid.
107+
int pwm_pin = mp_hal_get_pin_obj(args[0])->pin;
108+
if (pwm_pin > 31) {
109+
mp_raise_ValueError(MP_ERROR_TEXT("Pin number >31"));
110+
}
111+
112+
machine_pwm_obj_t *self = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type);;
113+
self->defer_start = false;
114+
self->pwm_pin = pwm_pin;
115+
self->duty_mode = DUTY_NOT_SET;
116+
self->duty = 0;
117+
self->freq = 0;
118+
119+
// parse the remaining arguments and start the PWM
120+
mp_map_t kw_args;
121+
mp_map_init_fixed_table(&kw_args, n_kw, args + n_args);
122+
mp_machine_pwm_init_helper(self, n_args - 1, args + 1, &kw_args);
123+
124+
return MP_OBJ_FROM_PTR(self);
125+
}
126+
127+
STATIC void mp_machine_pwm_deinit(machine_pwm_obj_t *self) {
128+
pwm_release(self->pwm_pin);
129+
}
130+
131+
STATIC mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) {
132+
return MP_OBJ_NEW_SMALL_INT(self->freq);
133+
}
134+
135+
STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) {
136+
137+
if (freq > (SOFT_PWM_BASE_FREQ / 256)) {
138+
mp_raise_ValueError(MP_ERROR_TEXT("frequency out of range"));
139+
}
140+
self->freq = freq;
141+
machine_soft_pwm_start(self);
142+
}
143+
144+
STATIC mp_obj_t mp_machine_pwm_duty_get(machine_pwm_obj_t *self) {
145+
if (self->duty_mode) {
146+
return MP_OBJ_NEW_SMALL_INT(self->duty);
147+
} else if (self->duty_mode == DUTY_U16) {
148+
return MP_OBJ_NEW_SMALL_INT(self->duty * 100 / 65536);
149+
} else {
150+
return MP_OBJ_NEW_SMALL_INT(-1);
151+
}
152+
}
153+
154+
STATIC void mp_machine_pwm_duty_set(machine_pwm_obj_t *self, mp_int_t duty) {
155+
self->duty = duty;
156+
self->duty_mode = DUTY;
157+
machine_soft_pwm_start(self);
158+
}
159+
160+
STATIC mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) {
161+
if (self->duty_mode == DUTY_U16) {
162+
return MP_OBJ_NEW_SMALL_INT(self->duty);
163+
} else if (self->duty_mode == DUTY) {
164+
return MP_OBJ_NEW_SMALL_INT(self->duty * 65536 / 100);
165+
} else {
166+
return MP_OBJ_NEW_SMALL_INT(-1);
167+
}
168+
}
169+
170+
STATIC void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, mp_int_t duty) {
171+
self->duty = duty;
172+
self->duty_mode = DUTY_U16;
173+
machine_soft_pwm_start(self);
174+
}
175+
176+
STATIC mp_obj_t mp_machine_pwm_duty_get_ns(machine_pwm_obj_t *self) {
177+
if (self->duty_mode == DUTY_NS) {
178+
return MP_OBJ_NEW_SMALL_INT(self->duty);
179+
} else {
180+
return MP_OBJ_NEW_SMALL_INT(-1);
181+
}
182+
}
183+
184+
STATIC void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, mp_int_t duty) {
185+
self->duty = duty;
186+
self->duty_mode = DUTY_NS;
187+
machine_soft_pwm_start(self);
188+
}
189+
190+
/* Interface for the implementation */
191+
192+
STATIC void machine_soft_pwm_start(machine_pwm_obj_t *self) {
193+
194+
// check if ready to go
195+
if (self->defer_start == true || self->freq == 0 || self->duty_mode == DUTY_NOT_SET) {
196+
return; // Not ready yet.
197+
}
198+
199+
int ret = pwm_set_period_us(SOFT_PWM_BASE_FREQ / self->freq);
200+
201+
if (ret >= 0) {
202+
int duty_width;
203+
if (self->duty_mode == DUTY) {
204+
duty_width = self->duty * DUTY_FULL_SCALE / 100;
205+
} else if (self->duty_mode == DUTY_U16) {
206+
duty_width = self->duty * DUTY_FULL_SCALE / 65536;
207+
}if (self->duty_mode == DUTY_NS) {
208+
duty_width = (uint64_t)self->duty * self->freq * DUTY_FULL_SCALE / 1000000000ULL;
209+
}
210+
pwm_set_duty_cycle(self->pwm_pin, duty_width);
211+
}
212+
}
213+
214+
#endif // MICROPY_PY_MACHINE_HW_PWM

ports/nrf/mpconfigport.h

+2
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@
202202

203203
#if MICROPY_PY_MACHINE_HW_PWM
204204
#define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/nrf/modules/machine/pwm.c"
205+
#elif MICROPY_PY_MACHINE_SOFT_PWM
206+
#define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/nrf/modules/machine/soft_pwm.c"
205207
#endif
206208

207209
#ifndef MICROPY_PY_MACHINE_TIMER_NRF

0 commit comments

Comments
 (0)