From c65c4bfb4e9df8786bebb7100e5e031df068c573 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Fri, 4 Jun 2021 02:45:13 -0700 Subject: [PATCH] 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. --- cores/rp2040/Tone.cpp | 68 +++++++++++++++-------- cores/rp2040/tone.pio.h | 52 ----------------- cores/rp2040/{tone.pio => tone2.pio} | 29 +++++----- cores/rp2040/tone2.pio.h | 53 ++++++++++++++++++ libraries/rp2040/examples/Siren/Siren.ino | 13 +++++ 5 files changed, 127 insertions(+), 88 deletions(-) delete mode 100644 cores/rp2040/tone.pio.h rename cores/rp2040/{tone.pio => tone2.pio} (58%) create mode 100644 cores/rp2040/tone2.pio.h create mode 100644 libraries/rp2040/examples/Siren/Siren.ino diff --git a/cores/rp2040/Tone.cpp b/cores/rp2040/Tone.cpp index 96d3e6f17..c5b590180 100644 --- a/cores/rp2040/Tone.cpp +++ b/cores/rp2040/Tone.cpp @@ -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 _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; diff --git a/cores/rp2040/tone.pio.h b/cores/rp2040/tone.pio.h deleted file mode 100644 index 5d427daf6..000000000 --- a/cores/rp2040/tone.pio.h +++ /dev/null @@ -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 - diff --git a/cores/rp2040/tone.pio b/cores/rp2040/tone2.pio similarity index 58% rename from cores/rp2040/tone.pio rename to cores/rp2040/tone2.pio index 8d11a0780..9f28518b5 100644 --- a/cores/rp2040/tone.pio +++ b/cores/rp2040/tone2.pio @@ -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 ; @@ -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); } %} + diff --git a/cores/rp2040/tone2.pio.h b/cores/rp2040/tone2.pio.h new file mode 100644 index 000000000..ac2ccec3d --- /dev/null +++ b/cores/rp2040/tone2.pio.h @@ -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 + diff --git a/libraries/rp2040/examples/Siren/Siren.ino b/libraries/rp2040/examples/Siren/Siren.ino new file mode 100644 index 000000000..d395a73d5 --- /dev/null +++ b/libraries/rp2040/examples/Siren/Siren.ino @@ -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); + } +}