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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy