Unverified Commit c65c4bfb authored by Earle F. Philhower, III's avatar Earle F. Philhower, III Committed by GitHub

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.
parent 6431b815
......@@ -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);
}
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 *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;
}
}
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;
......
; 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);
}
%}
......@@ -6,44 +6,45 @@
#include "hardware/pio.h"
#endif
// ---- //
// tone //
// ---- //
// ----- //
// tone2 //
// ----- //
#define tone_wrap_target 0
#define tone_wrap 7
#define tone2_wrap_target 0
#define tone2_wrap 8
static const uint16_t tone_program_instructions[] = {
static const uint16_t tone2_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
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 tone_program = {
.instructions = tone_program_instructions,
.length = 8,
static const struct pio_program tone2_program = {
.instructions = tone2_program_instructions,
.length = 9,
.origin = -1,
};
static inline pio_sm_config tone_program_get_default_config(uint offset) {
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 + tone_wrap_target, offset + tone_wrap);
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 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);
}
......
/* 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);
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment