diff --git a/cores/rp2040/Tone.cpp b/cores/rp2040/Tone.cpp index 96d3e6f..c5b5901 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); + 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; diff --git a/cores/rp2040/tone.pio.h b/cores/rp2040/tone.pio.h deleted file mode 100644 index 5d427da..0000000 --- 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 8d11a07..9f28518 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 0000000..ac2ccec --- /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 0000000..d395a73 --- /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); + } +}