Skip to content

Commit d70f1ca

Browse files
committed
PWM based on new ticker and GPIO. Leaving GPIOTE and PPI free for sound.
1 parent d9e5380 commit d70f1ca

File tree

7 files changed

+223
-4
lines changed

7 files changed

+223
-4
lines changed

inc/genhdr/qstrdefs.generated.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ QDEF(MP_QSTR_read_analog, (const byte*)"\x62\x0b" "read_analog")
351351
QDEF(MP_QSTR_write_analog, (const byte*)"\x2d\x0c" "write_analog")
352352
QDEF(MP_QSTR_set_analog_period, (const byte*)"\x08\x11" "set_analog_period")
353353
QDEF(MP_QSTR_set_analog_period_microseconds, (const byte*)"\xee\x1e" "set_analog_period_microseconds")
354+
QDEF(MP_QSTR_get_analog_period_microseconds, (const byte*)"\x7a\x1e" "get_analog_period_microseconds")
354355
QDEF(MP_QSTR_is_touched, (const byte*)"\x04\x0a" "is_touched")
355356
QDEF(MP_QSTR_MicroBitIO, (const byte*)"\xe6\x0a" "MicroBitIO")
356357
QDEF(MP_QSTR_pin0, (const byte*)"\x02\x04" "pin0")

inc/lib/pwm.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#ifndef __MICROPY_INCLUDED_LIB_PWM_H__
2+
#define __MICROPY_INCLUDED_LIB_PWM_H__
3+
4+
void pwm_start(void);
5+
void pwm_stop(void);
6+
7+
int32_t pwm_set_period_us(int32_t us);
8+
int32_t pwm_get_period_us(void);
9+
int pwm_set_duty_cycle(int32_t pin, int32_t value);
10+
11+
#endif // __MICROPY_INCLUDED_LIB_PWM_H__

inc/microbit/mpconfigport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ extern const struct _mp_obj_module_t radio_module;
119119
void *async_data[2]; \
120120
void *async_music_data; \
121121
uint8_t *radio_buf; \
122+
void *pwm_next_event; \
122123

123124
// We need to provide a declaration/definition of alloca()
124125
#include <alloca.h>

inc/microbit/qstrdefsport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Q(read_analog)
2828
Q(write_analog)
2929
Q(set_analog_period)
3030
Q(set_analog_period_microseconds)
31+
Q(get_analog_period_microseconds)
3132
Q(is_touched)
3233

3334
Q(MicroBitIO)

source/lib/pwm.c

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
/*
2+
* This file is part of the Micro Python project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2016 Mark Shannon
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 "stddef.h"
28+
#include "lib/ticker.h"
29+
#include "nrf_gpio.h"
30+
#include "py/runtime.h"
31+
#include "py/gc.h"
32+
33+
#define PWM_TICKER_INDEX 2
34+
35+
typedef struct _pwm_event {
36+
uint32_t pins;
37+
struct _pwm_event *next;
38+
uint16_t timestamp;
39+
} pwm_event;
40+
41+
// Default period of 20ms
42+
#define DEFAULT_PERIOD ((20*1000)/MICROSECONDS_PER_TICK)
43+
44+
static pwm_event all_off = {
45+
.pins = 0,
46+
.next = &all_off,
47+
.timestamp = DEFAULT_PERIOD
48+
};
49+
50+
static inline int32_t pwm_get_period_ticks(void) {
51+
return all_off.timestamp;
52+
}
53+
54+
#if 0
55+
void pwm_dump_state(void) {
56+
pwm_event *event = &all_off;
57+
do {
58+
printf("Event. pins: %d, timestamp: %d\n", event->pins, event->timestamp);
59+
event = event->next;
60+
} while (event != &all_off);
61+
}
62+
#endif
63+
64+
int32_t pwm_callback(void) {
65+
pwm_event *event = (pwm_event *)MP_STATE_PORT(pwm_next_event);
66+
if (event == &all_off) {
67+
nrf_gpio_pins_clear(event->pins);
68+
} else {
69+
nrf_gpio_pins_set(event->pins);
70+
}
71+
int32_t tnow = event->timestamp;
72+
MP_STATE_PORT(pwm_next_event) = event->next;
73+
int32_t tnext = event->next->timestamp;
74+
int32_t tdiff = tnext - tnow;
75+
if (tdiff <= 0) {
76+
tdiff += pwm_get_period_ticks();
77+
}
78+
return tdiff;
79+
}
80+
81+
void pwm_start(void) {
82+
MP_STATE_PORT(pwm_next_event) = &all_off;
83+
set_ticker_callback(PWM_TICKER_INDEX, pwm_callback, 120);
84+
}
85+
86+
void pwm_stop(void) {
87+
clear_ticker_callback(PWM_TICKER_INDEX);
88+
}
89+
90+
/* Turn pin off and return relevant event if now unused */
91+
static pwm_event *pwm_turn_off_pin(int32_t pin) {
92+
pwm_event *previous = &all_off;
93+
pwm_event *event = all_off.next;
94+
while (event != &all_off) {
95+
if (event->pins & (1UL << pin)) {
96+
goto found;
97+
}
98+
previous = event;
99+
event = event->next;
100+
}
101+
return NULL;
102+
found:
103+
event->pins &= ~(1UL << pin);
104+
nrf_gpio_pin_clear(pin);
105+
all_off.pins &= ~(1UL << pin);
106+
if (event->pins == 0) {
107+
previous->next = event->next;
108+
return event;
109+
}
110+
return NULL;
111+
}
112+
113+
/* Turn pin on, using supplied event (if not NULL) */
114+
static void pwm_insert_pin_on_event(pwm_event *event, int8_t pin, uint16_t timestamp) {
115+
nrf_gpio_cfg_output(pin);
116+
pwm_event *previous = &all_off;
117+
pwm_event *next = all_off.next;
118+
all_off.pins |= (1UL<<pin);
119+
while (next != &all_off) {
120+
if (next->timestamp >= timestamp) {
121+
break;
122+
}
123+
previous = next;
124+
next = next->next;
125+
}
126+
if (next->timestamp == timestamp) {
127+
// GC will clean up event when it is no longer reachable
128+
next->pins |= (1UL<<pin);
129+
} else {
130+
if (event == NULL) {
131+
event = gc_alloc(sizeof(pwm_event), false);
132+
}
133+
event->pins = (1UL<<pin);
134+
event->next = previous->next;
135+
event->timestamp = timestamp;
136+
previous->next = event;
137+
}
138+
}
139+
140+
static int32_t pwm_set_period_ticks(int32_t ticks) {
141+
if (all_off.pins == 0) {
142+
all_off.timestamp = ticks;
143+
return 0;
144+
}
145+
pwm_stop();
146+
nrf_gpio_pins_clear(all_off.pins);
147+
int32_t previous = pwm_get_period_ticks();
148+
pwm_event *event = all_off.next;
149+
while (event != &all_off) {
150+
event->timestamp = event->timestamp * ticks / previous;
151+
event = event->next;
152+
}
153+
all_off.timestamp = ticks;
154+
pwm_start();
155+
return 0;
156+
}
157+
158+
int32_t pwm_set_period_us(int32_t us) {
159+
if ((us < 1000) ||
160+
(us > 1000000)) {
161+
return -1;
162+
}
163+
return pwm_set_period_ticks(us/MICROSECONDS_PER_TICK);
164+
}
165+
166+
int32_t pwm_get_period_us(void) {
167+
return pwm_get_period_ticks()*MICROSECONDS_PER_TICK;
168+
}
169+
170+
int pwm_set_duty_cycle(int32_t pin, int32_t value) {
171+
if (value < 0 || value >= (1<<10)) {
172+
return -1;
173+
}
174+
pwm_event *event = pwm_turn_off_pin(pin);
175+
if (value == 0)
176+
return 0;
177+
int32_t period = pwm_get_period_ticks();
178+
int32_t ticks = ((value*2+1) * period) >> 11;
179+
if (ticks == 0)
180+
ticks = 1;
181+
if (ticks == period)
182+
ticks = period-1;
183+
pwm_insert_pin_on_event(event, pin, period-ticks);
184+
return 0;
185+
}
186+

source/microbit/main.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
extern "C" {
77
#include "lib/ticker.h"
88
#include "filesystem.h"
9+
#include "lib/pwm.h"
910

1011
void mp_run(void);
1112

@@ -83,7 +84,7 @@ void microbit_init(void) {
8384
uBit.systemTicker.detach();
8485
ticker_init(microbit_ticker);
8586
ticker_start();
86-
87+
pwm_start();
8788
}
8889

8990
}

source/microbit/microbitpin.cpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ extern "C" {
3131

3232
#include "py/runtime.h"
3333
#include "modmicrobit.h"
34+
#include "lib/pwm.h"
3435

3536
typedef struct _microbit_pin_obj_t {
3637
mp_obj_base_t base;
@@ -68,7 +69,7 @@ mp_obj_t microbit_pin_write_analog(mp_obj_t self_in, mp_obj_t value_in) {
6869
if (set_value < 0 || set_value > MICROBIT_PIN_MAX_OUTPUT) {
6970
nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "value must be between 0 and 1023"));
7071
}
71-
self->pin->setAnalogValue(set_value);
72+
pwm_set_duty_cycle(self->pin->name, set_value);
7273
return mp_const_none;
7374
}
7475
MP_DEFINE_CONST_FUN_OBJ_2(microbit_pin_write_analog_obj, microbit_pin_write_analog);
@@ -83,18 +84,31 @@ MP_DEFINE_CONST_FUN_OBJ_1(microbit_pin_read_analog_obj, microbit_pin_read_analog
8384

8485
mp_obj_t microbit_pin_set_analog_period(mp_obj_t self_in, mp_obj_t period_in) {
8586
microbit_pin_obj_t *self = (microbit_pin_obj_t*)self_in;
86-
self->pin->setAnalogPeriod(mp_obj_get_int(period_in));
87+
int err = pwm_set_period_us(mp_obj_get_int(period_in)*1000);
88+
if (err) {
89+
nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "invalid period"));
90+
}
8791
return mp_const_none;
8892
}
8993
MP_DEFINE_CONST_FUN_OBJ_2(microbit_pin_set_analog_period_obj, microbit_pin_set_analog_period);
9094

9195
mp_obj_t microbit_pin_set_analog_period_microseconds(mp_obj_t self_in, mp_obj_t period_in) {
9296
microbit_pin_obj_t *self = (microbit_pin_obj_t*)self_in;
93-
self->pin->setAnalogPeriodUs(mp_obj_get_int(period_in));
97+
int err = pwm_set_period_us(mp_obj_get_int(period_in));
98+
if (err) {
99+
nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "invalid period"));
100+
}
94101
return mp_const_none;
95102
}
96103
MP_DEFINE_CONST_FUN_OBJ_2(microbit_pin_set_analog_period_microseconds_obj, microbit_pin_set_analog_period_microseconds);
97104

105+
mp_obj_t microbit_pin_get_analog_period_microseconds(mp_obj_t self_in) {
106+
microbit_pin_obj_t *self = (microbit_pin_obj_t*)self_in;
107+
int32_t period = pwm_get_period_us();
108+
return mp_obj_new_int(period);
109+
}
110+
MP_DEFINE_CONST_FUN_OBJ_1(microbit_pin_get_analog_period_microseconds_obj, microbit_pin_get_analog_period_microseconds);
111+
98112
mp_obj_t microbit_pin_is_touched(mp_obj_t self_in) {
99113
microbit_pin_obj_t *self = (microbit_pin_obj_t*)self_in;
100114
return mp_obj_new_bool(self->pin->isTouched());
@@ -104,6 +118,10 @@ MP_DEFINE_CONST_FUN_OBJ_1(microbit_pin_is_touched_obj, microbit_pin_is_touched);
104118
STATIC const mp_map_elem_t microbit_dig_pin_locals_dict_table[] = {
105119
{ MP_OBJ_NEW_QSTR(MP_QSTR_write_digital), (mp_obj_t)&microbit_pin_write_digital_obj },
106120
{ MP_OBJ_NEW_QSTR(MP_QSTR_read_digital), (mp_obj_t)&microbit_pin_read_digital_obj },
121+
{ MP_OBJ_NEW_QSTR(MP_QSTR_write_analog), (mp_obj_t)&microbit_pin_write_analog_obj },
122+
{ MP_OBJ_NEW_QSTR(MP_QSTR_set_analog_period), (mp_obj_t)&microbit_pin_set_analog_period_obj },
123+
{ MP_OBJ_NEW_QSTR(MP_QSTR_set_analog_period_microseconds), (mp_obj_t)&microbit_pin_set_analog_period_microseconds_obj },
124+
{ MP_OBJ_NEW_QSTR(MP_QSTR_get_analog_period_microseconds), (mp_obj_t)&microbit_pin_get_analog_period_microseconds_obj },
107125
};
108126

109127
STATIC const mp_map_elem_t microbit_ann_pin_locals_dict_table[] = {

0 commit comments

Comments
 (0)