Skip to content

Commit

Permalink
Allow continuous tone frequency changes (#186)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
earlephilhower authored Jun 4, 2021
1 parent 6431b81 commit c65c4bf
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 88 deletions.
68 changes: 45 additions & 23 deletions cores/rp2040/Tone.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) {
Expand All @@ -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;
Expand Down
52 changes: 0 additions & 52 deletions cores/rp2040/tone.pio.h

This file was deleted.

29 changes: 16 additions & 13 deletions cores/rp2040/tone.pio → cores/rp2040/tone2.pio
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
;
Expand All @@ -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 changes: 53 additions & 0 deletions cores/rp2040/tone2.pio.h
Original file line number Diff line number Diff line change
@@ -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 changes: 13 additions & 0 deletions libraries/rp2040/examples/Siren/Siren.ino
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit c65c4bf

Please sign in to comment.