Skip to content

Commit c65c4bf

Browse files
Allow continuous tone frequency changes (#186)
Fixes #121 Supersedes #185 Redo the PIO program to allow the tone generator on a pin to be updated without interruption, at waveform boundaries. This allows for things like sirens or slurs to be implemented simply. Use an alarm, not the PIO hardware, to manage time-limited tones(). Add a simple siren example.
1 parent 6431b81 commit c65c4bf

File tree

5 files changed

+127
-88
lines changed

5 files changed

+127
-88
lines changed

cores/rp2040/Tone.cpp

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,24 @@ typedef struct {
2828
pin_size_t pin;
2929
PIO pio;
3030
int sm;
31+
alarm_id_t alarm;
3132
} Tone;
3233

3334
// Keep std::map safe for multicore use
3435
auto_init_mutex(_toneMutex);
3536

36-
37-
#include "tone.pio.h"
38-
static PIOProgram _tonePgm(&tone_program);
37+
#include "tone2.pio.h"
38+
static PIOProgram _tone2Pgm(&tone2_program);
3939
static std::map<pin_size_t, Tone *> _toneMap;
4040

41+
int64_t _stopTonePIO(alarm_id_t id, void *user_data) {
42+
(void) id;
43+
Tone *tone = (Tone *)user_data;
44+
tone->alarm = 0;
45+
pio_sm_set_enabled(tone->pio, tone->sm, false);
46+
return 0;
47+
}
48+
4149
void tone(uint8_t pin, unsigned int frequency, unsigned long duration) {
4250
if (pin > 29) {
4351
DEBUGCORE("ERROR: Illegal pin in tone (%d)\n", pin);
@@ -58,32 +66,42 @@ void tone(uint8_t pin, unsigned int frequency, unsigned long duration) {
5866
if (us < 5) {
5967
us = 5;
6068
}
61-
// Even phases run forever, odd phases end after count...so ensure its odd
62-
int phases = duration ? (duration * 1000 / us) | 1 : 2;
6369
auto entry = _toneMap.find(pin);
64-
if (entry != _toneMap.end()) {
65-
noTone(pin);
66-
}
67-
68-
auto newTone = new Tone();
69-
newTone->pin = pin;
70-
pinMode(pin, OUTPUT);
71-
int off;
72-
if (!_tonePgm.prepare(&newTone->pio, &newTone->sm, &off)) {
73-
DEBUGCORE("ERROR: tone unable to start, out of PIO resources\n");
74-
// ERROR, no free slots
75-
delete newTone;
76-
return;
70+
Tone *newTone;
71+
if (entry == _toneMap.end()) {
72+
newTone = new Tone();
73+
newTone->pin = pin;
74+
pinMode(pin, OUTPUT);
75+
int off;
76+
if (!_tone2Pgm.prepare(&newTone->pio, &newTone->sm, &off)) {
77+
DEBUGCORE("ERROR: tone unable to start, out of PIO resources\n");
78+
// ERROR, no free slots
79+
delete newTone;
80+
return;
81+
}
82+
tone2_program_init(newTone->pio, newTone->sm, off, pin);
83+
newTone->alarm = 0;
84+
} else {
85+
newTone = entry->second;
86+
if (newTone->alarm) {
87+
cancel_alarm(newTone->alarm);
88+
newTone->alarm = 0;
89+
}
7790
}
78-
tone_program_init(newTone->pio, newTone->sm, off, pin);
79-
pio_sm_set_enabled(newTone->pio, newTone->sm, false);
8091
pio_sm_put_blocking(newTone->pio, newTone->sm, RP2040::usToPIOCycles(us));
81-
pio_sm_exec(newTone->pio, newTone->sm, pio_encode_pull(false, false));
82-
pio_sm_exec(newTone->pio, newTone->sm, pio_encode_out(pio_isr, 32));
8392
pio_sm_set_enabled(newTone->pio, newTone->sm, true);
84-
pio_sm_put_blocking(newTone->pio, newTone->sm, phases);
8593

8694
_toneMap.insert({pin, newTone});
95+
96+
if (duration) {
97+
auto ret = add_alarm_in_ms(duration, _stopTonePIO, (void *)newTone, true);
98+
if (ret > 0) {
99+
newTone->alarm = ret;
100+
} else {
101+
DEBUGCORE("ERROR: Unable to allocate timer for tone(%d, %d, %d)\n",
102+
pin, frequency, duration);
103+
}
104+
}
87105
}
88106

89107
void noTone(uint8_t pin) {
@@ -95,6 +113,10 @@ void noTone(uint8_t pin) {
95113
}
96114
auto entry = _toneMap.find(pin);
97115
if (entry != _toneMap.end()) {
116+
if (entry->second->alarm) {
117+
cancel_alarm(entry->second->alarm);
118+
entry->second->alarm = 0;
119+
}
98120
pio_sm_set_enabled(entry->second->pio, entry->second->sm, false);
99121
pio_sm_unclaim(entry->second->pio, entry->second->sm);
100122
delete entry->second;

cores/rp2040/tone.pio.h

Lines changed: 0 additions & 52 deletions
This file was deleted.
Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
; Tone for the Raspberry Pi Pico RP2040
1+
; Tone2 for the Raspberry Pi Pico RP2040
22
;
33
; Copyright (c) 2021 Earle F. Philhower, III <[email protected]>
44
;
@@ -18,32 +18,35 @@
1818

1919
; Side-set pin 0 is used for Tone output
2020

21-
.program tone
21+
; OSR == Halfcycle count
22+
23+
.program tone2
2224
.side_set 1 opt
2325

24-
pull
25-
mov x, osr
26+
pull ; TXFIFO -> OSR, or X -> OSR if no new period
27+
mov x, osr ; OSR -> X
2628

2729
high:
28-
mov y, isr side 1
30+
pull noblock ; Potentially grab new HALFCYCLECOUNT, OTW copy from backup in X
31+
mov x, osr ; OSR -> X
32+
mov y, osr side 1 ; HALFCYCLECOUNT -> Y
2933
highloop:
30-
jmp y-- highloop
31-
32-
jmp x-- low
34+
jmp y-- highloop ; while (y--) { /* noop delay */ }
3335

3436
low:
35-
mov y, isr side 0
37+
mov y, osr side 0 ; HALFCYCLECOUNT -> Y
3638
lowloop:
37-
jmp y-- lowloop
39+
jmp y-- lowloop ; while (y--) { /* noop delay */ }
3840

39-
jmp x-- high
41+
jmp high ; GOTO high
4042

4143
% c-sdk {
42-
static inline void tone_program_init(PIO pio, uint sm, uint offset, uint pin) {
44+
static inline void tone2_program_init(PIO pio, uint sm, uint offset, uint pin) {
4345
pio_gpio_init(pio, pin);
4446
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
45-
pio_sm_config c = tone_program_get_default_config(offset);
47+
pio_sm_config c = tone2_program_get_default_config(offset);
4648
sm_config_set_sideset_pins(&c, pin);
4749
pio_sm_init(pio, sm, offset, &c);
4850
}
4951
%}
52+

cores/rp2040/tone2.pio.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// -------------------------------------------------- //
2+
// This file is autogenerated by pioasm; do not edit! //
3+
// -------------------------------------------------- //
4+
5+
#if !PICO_NO_HARDWARE
6+
#include "hardware/pio.h"
7+
#endif
8+
9+
// ----- //
10+
// tone2 //
11+
// ----- //
12+
13+
#define tone2_wrap_target 0
14+
#define tone2_wrap 8
15+
16+
static const uint16_t tone2_program_instructions[] = {
17+
// .wrap_target
18+
0x80a0, // 0: pull block
19+
0xa027, // 1: mov x, osr
20+
0x8080, // 2: pull noblock
21+
0xa027, // 3: mov x, osr
22+
0xb847, // 4: mov y, osr side 1
23+
0x0085, // 5: jmp y--, 5
24+
0xb047, // 6: mov y, osr side 0
25+
0x0087, // 7: jmp y--, 7
26+
0x0002, // 8: jmp 2
27+
// .wrap
28+
};
29+
30+
#if !PICO_NO_HARDWARE
31+
static const struct pio_program tone2_program = {
32+
.instructions = tone2_program_instructions,
33+
.length = 9,
34+
.origin = -1,
35+
};
36+
37+
static inline pio_sm_config tone2_program_get_default_config(uint offset) {
38+
pio_sm_config c = pio_get_default_sm_config();
39+
sm_config_set_wrap(&c, offset + tone2_wrap_target, offset + tone2_wrap);
40+
sm_config_set_sideset(&c, 2, true, false);
41+
return c;
42+
}
43+
44+
static inline void tone2_program_init(PIO pio, uint sm, uint offset, uint pin) {
45+
pio_gpio_init(pio, pin);
46+
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
47+
pio_sm_config c = tone2_program_get_default_config(offset);
48+
sm_config_set_sideset_pins(&c, pin);
49+
pio_sm_init(pio, sm, offset, &c);
50+
}
51+
52+
#endif
53+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* Simple annoying siren example using tone() */
2+
/* Released to the public domain by Earle F. Philhower, III */
3+
4+
#define TONEPIN 7
5+
6+
void setup() {
7+
}
8+
9+
void loop() {
10+
for (int i = 100; i < 10000; i += 5) {
11+
tone(TONEPIN, i);
12+
}
13+
}

0 commit comments

Comments
 (0)