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.
This commit is contained in:
parent
6431b8157b
commit
c65c4bfb4e
5 changed files with 127 additions and 88 deletions
|
|
@ -28,16 +28,24 @@ typedef struct {
|
|||
pin_size_t pin;
|
||||
PIO pio;
|
||||
int sm;
|
||||
alarm_id_t alarm;
|
||||
} Tone;
|
||||
|
||||
// Keep std::map safe for multicore use
|
||||
auto_init_mutex(_toneMutex);
|
||||
|
||||
|
||||
#include "tone.pio.h"
|
||||
static PIOProgram _tonePgm(&tone_program);
|
||||
#include "tone2.pio.h"
|
||||
static PIOProgram _tone2Pgm(&tone2_program);
|
||||
static std::map<pin_size_t, Tone *> _toneMap;
|
||||
|
||||
int64_t _stopTonePIO(alarm_id_t id, void *user_data) {
|
||||
(void) id;
|
||||
Tone *tone = (Tone *)user_data;
|
||||
tone->alarm = 0;
|
||||
pio_sm_set_enabled(tone->pio, tone->sm, false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tone(uint8_t pin, unsigned int frequency, unsigned long duration) {
|
||||
if (pin > 29) {
|
||||
DEBUGCORE("ERROR: Illegal pin in tone (%d)\n", pin);
|
||||
|
|
@ -58,32 +66,42 @@ void tone(uint8_t pin, unsigned int frequency, unsigned long duration) {
|
|||
if (us < 5) {
|
||||
us = 5;
|
||||
}
|
||||
// Even phases run forever, odd phases end after count...so ensure its odd
|
||||
int phases = duration ? (duration * 1000 / us) | 1 : 2;
|
||||
auto entry = _toneMap.find(pin);
|
||||
if (entry != _toneMap.end()) {
|
||||
noTone(pin);
|
||||
Tone *newTone;
|
||||
if (entry == _toneMap.end()) {
|
||||
newTone = new Tone();
|
||||
newTone->pin = pin;
|
||||
pinMode(pin, OUTPUT);
|
||||
int off;
|
||||
if (!_tone2Pgm.prepare(&newTone->pio, &newTone->sm, &off)) {
|
||||
DEBUGCORE("ERROR: tone unable to start, out of PIO resources\n");
|
||||
// ERROR, no free slots
|
||||
delete newTone;
|
||||
return;
|
||||
}
|
||||
tone2_program_init(newTone->pio, newTone->sm, off, pin);
|
||||
newTone->alarm = 0;
|
||||
} else {
|
||||
newTone = entry->second;
|
||||
if (newTone->alarm) {
|
||||
cancel_alarm(newTone->alarm);
|
||||
newTone->alarm = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto newTone = new Tone();
|
||||
newTone->pin = pin;
|
||||
pinMode(pin, OUTPUT);
|
||||
int off;
|
||||
if (!_tonePgm.prepare(&newTone->pio, &newTone->sm, &off)) {
|
||||
DEBUGCORE("ERROR: tone unable to start, out of PIO resources\n");
|
||||
// ERROR, no free slots
|
||||
delete newTone;
|
||||
return;
|
||||
}
|
||||
tone_program_init(newTone->pio, newTone->sm, off, pin);
|
||||
pio_sm_set_enabled(newTone->pio, newTone->sm, false);
|
||||
pio_sm_put_blocking(newTone->pio, newTone->sm, RP2040::usToPIOCycles(us));
|
||||
pio_sm_exec(newTone->pio, newTone->sm, pio_encode_pull(false, false));
|
||||
pio_sm_exec(newTone->pio, newTone->sm, pio_encode_out(pio_isr, 32));
|
||||
pio_sm_set_enabled(newTone->pio, newTone->sm, true);
|
||||
pio_sm_put_blocking(newTone->pio, newTone->sm, phases);
|
||||
|
||||
_toneMap.insert({pin, newTone});
|
||||
|
||||
if (duration) {
|
||||
auto ret = add_alarm_in_ms(duration, _stopTonePIO, (void *)newTone, true);
|
||||
if (ret > 0) {
|
||||
newTone->alarm = ret;
|
||||
} else {
|
||||
DEBUGCORE("ERROR: Unable to allocate timer for tone(%d, %d, %d)\n",
|
||||
pin, frequency, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void noTone(uint8_t pin) {
|
||||
|
|
@ -95,6 +113,10 @@ void noTone(uint8_t pin) {
|
|||
}
|
||||
auto entry = _toneMap.find(pin);
|
||||
if (entry != _toneMap.end()) {
|
||||
if (entry->second->alarm) {
|
||||
cancel_alarm(entry->second->alarm);
|
||||
entry->second->alarm = 0;
|
||||
}
|
||||
pio_sm_set_enabled(entry->second->pio, entry->second->sm, false);
|
||||
pio_sm_unclaim(entry->second->pio, entry->second->sm);
|
||||
delete entry->second;
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
// -------------------------------------------------- //
|
||||
// This file is autogenerated by pioasm; do not edit! //
|
||||
// -------------------------------------------------- //
|
||||
|
||||
#if !PICO_NO_HARDWARE
|
||||
#include "hardware/pio.h"
|
||||
#endif
|
||||
|
||||
// ---- //
|
||||
// tone //
|
||||
// ---- //
|
||||
|
||||
#define tone_wrap_target 0
|
||||
#define tone_wrap 7
|
||||
|
||||
static const uint16_t tone_program_instructions[] = {
|
||||
// .wrap_target
|
||||
0x80a0, // 0: pull block
|
||||
0xa027, // 1: mov x, osr
|
||||
0xb846, // 2: mov y, isr side 1
|
||||
0x0083, // 3: jmp y--, 3
|
||||
0x0045, // 4: jmp x--, 5
|
||||
0xb046, // 5: mov y, isr side 0
|
||||
0x0086, // 6: jmp y--, 6
|
||||
0x0042, // 7: jmp x--, 2
|
||||
// .wrap
|
||||
};
|
||||
|
||||
#if !PICO_NO_HARDWARE
|
||||
static const struct pio_program tone_program = {
|
||||
.instructions = tone_program_instructions,
|
||||
.length = 8,
|
||||
.origin = -1,
|
||||
};
|
||||
|
||||
static inline pio_sm_config tone_program_get_default_config(uint offset) {
|
||||
pio_sm_config c = pio_get_default_sm_config();
|
||||
sm_config_set_wrap(&c, offset + tone_wrap_target, offset + tone_wrap);
|
||||
sm_config_set_sideset(&c, 2, true, false);
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline void tone_program_init(PIO pio, uint sm, uint offset, uint pin) {
|
||||
pio_gpio_init(pio, pin);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
|
||||
pio_sm_config c = tone_program_get_default_config(offset);
|
||||
sm_config_set_sideset_pins(&c, pin);
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
; Tone for the Raspberry Pi Pico RP2040
|
||||
; Tone2 for the Raspberry Pi Pico RP2040
|
||||
;
|
||||
; Copyright (c) 2021 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
;
|
||||
|
|
@ -18,32 +18,35 @@
|
|||
|
||||
; Side-set pin 0 is used for Tone output
|
||||
|
||||
.program tone
|
||||
; OSR == Halfcycle count
|
||||
|
||||
.program tone2
|
||||
.side_set 1 opt
|
||||
|
||||
pull
|
||||
mov x, osr
|
||||
pull ; TXFIFO -> OSR, or X -> OSR if no new period
|
||||
mov x, osr ; OSR -> X
|
||||
|
||||
high:
|
||||
mov y, isr side 1
|
||||
pull noblock ; Potentially grab new HALFCYCLECOUNT, OTW copy from backup in X
|
||||
mov x, osr ; OSR -> X
|
||||
mov y, osr side 1 ; HALFCYCLECOUNT -> Y
|
||||
highloop:
|
||||
jmp y-- highloop
|
||||
|
||||
jmp x-- low
|
||||
jmp y-- highloop ; while (y--) { /* noop delay */ }
|
||||
|
||||
low:
|
||||
mov y, isr side 0
|
||||
mov y, osr side 0 ; HALFCYCLECOUNT -> Y
|
||||
lowloop:
|
||||
jmp y-- lowloop
|
||||
jmp y-- lowloop ; while (y--) { /* noop delay */ }
|
||||
|
||||
jmp x-- high
|
||||
jmp high ; GOTO high
|
||||
|
||||
% c-sdk {
|
||||
static inline void tone_program_init(PIO pio, uint sm, uint offset, uint pin) {
|
||||
static inline void tone2_program_init(PIO pio, uint sm, uint offset, uint pin) {
|
||||
pio_gpio_init(pio, pin);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
|
||||
pio_sm_config c = tone_program_get_default_config(offset);
|
||||
pio_sm_config c = tone2_program_get_default_config(offset);
|
||||
sm_config_set_sideset_pins(&c, pin);
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
}
|
||||
%}
|
||||
|
||||
53
cores/rp2040/tone2.pio.h
Normal file
53
cores/rp2040/tone2.pio.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// -------------------------------------------------- //
|
||||
// This file is autogenerated by pioasm; do not edit! //
|
||||
// -------------------------------------------------- //
|
||||
|
||||
#if !PICO_NO_HARDWARE
|
||||
#include "hardware/pio.h"
|
||||
#endif
|
||||
|
||||
// ----- //
|
||||
// tone2 //
|
||||
// ----- //
|
||||
|
||||
#define tone2_wrap_target 0
|
||||
#define tone2_wrap 8
|
||||
|
||||
static const uint16_t tone2_program_instructions[] = {
|
||||
// .wrap_target
|
||||
0x80a0, // 0: pull block
|
||||
0xa027, // 1: mov x, osr
|
||||
0x8080, // 2: pull noblock
|
||||
0xa027, // 3: mov x, osr
|
||||
0xb847, // 4: mov y, osr side 1
|
||||
0x0085, // 5: jmp y--, 5
|
||||
0xb047, // 6: mov y, osr side 0
|
||||
0x0087, // 7: jmp y--, 7
|
||||
0x0002, // 8: jmp 2
|
||||
// .wrap
|
||||
};
|
||||
|
||||
#if !PICO_NO_HARDWARE
|
||||
static const struct pio_program tone2_program = {
|
||||
.instructions = tone2_program_instructions,
|
||||
.length = 9,
|
||||
.origin = -1,
|
||||
};
|
||||
|
||||
static inline pio_sm_config tone2_program_get_default_config(uint offset) {
|
||||
pio_sm_config c = pio_get_default_sm_config();
|
||||
sm_config_set_wrap(&c, offset + tone2_wrap_target, offset + tone2_wrap);
|
||||
sm_config_set_sideset(&c, 2, true, false);
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline void tone2_program_init(PIO pio, uint sm, uint offset, uint pin) {
|
||||
pio_gpio_init(pio, pin);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
|
||||
pio_sm_config c = tone2_program_get_default_config(offset);
|
||||
sm_config_set_sideset_pins(&c, pin);
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
13
libraries/rp2040/examples/Siren/Siren.ino
Normal file
13
libraries/rp2040/examples/Siren/Siren.ino
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/* Simple annoying siren example using tone() */
|
||||
/* Released to the public domain by Earle F. Philhower, III */
|
||||
|
||||
#define TONEPIN 7
|
||||
|
||||
void setup() {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
for (int i = 100; i < 10000; i += 5) {
|
||||
tone(TONEPIN, i);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue