Compare commits

..
4 Commits
Author SHA1 Message Date
Earle F. Philhower, III c65c4bfb4e 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.
2021-06-04 02:45:13 -07:00
Ha Thach 6431b8157b Add tone music example (#185) 2021-06-04 02:39:04 -07:00
per1234 7d1e83b416 Use optimal bundled library names (#183)
When multiple libraries contain files matching an #include directive in the program, the Arduino build system must pick
one to use for compilation. Multiple factors are used in order to make an intelligent determination of which library is
best.

In order to enhance this determination, the closeness of match between the library.properties name value and the
filename in the #include directive is being added as one of those factors. This new factor is referred to as
"Library Name Priority".

Unfortunately, this change can result in platform bundled libraries which had previously been correctly correctly chosen
no longer being given priority over their equivalent standalone libraries, which may be incompatible or not optimized
for the platform's boards.

This priority inversion only occurs when all the following conditions are true:

- There is a standalone library installed which provides a header filename collision.
- The platform bundled library is architecture optimized (e.g., architectures=esp32).
- The standalone library is architecture compatible (architectures=*).
- The standalone library has equal "Folder Name Priority".
- The standalone library has better "Library Name Priority" (e.g., name=SD vs name=SD(ESP32) for a library with primary
  header file SD.h.

The fix is to simply give the platform bundled library a perfect "Library Name Priority".

Some platform bundled libraries were given a modified name as a workaround to a bug in the Arduino IDE's Library Manager
which caused Library Manager to always show the library as updatable under specific circumstances. That bug was fixed in
Arduino IDE 1.8.6, ~3 years ago.
2021-05-31 07:32:20 -07:00
Earle F. Philhower, III fd685aac82 PANIC on attempting an invalid setXXX pin (#182)
Fixes #169

Trying to change pinout while running, or to an illegal configuration,
will now immediately panic() with an error message.  Such an attempt
is a pretty big problem since pinouts are hardware related/static.

Prior code would fail silently and return false, but nobody checked
the setXXX return values, anyway.
2021-05-29 10:50:13 -07:00
10 changed files with 339 additions and 137 deletions
+20 -18
View File
@@ -31,36 +31,38 @@
extern void serialEvent1() __attribute__((weak));
extern void serialEvent2() __attribute__((weak));
bool SerialUART::setRX(pin_size_t rx) {
bool SerialUART::setRX(pin_size_t pin) {
constexpr uint32_t valid[2] = { __bitset({1, 13, 17, 29}) /* UART0 */,
__bitset({5, 9, 21, 25}) /* UART1 */
};
if (_running) {
DEBUGCORE("ERROR: SerialUART setRX while running\n");
return false;
} else if ((1 << rx) & valid[uart_get_index(_uart)]) {
_rx = rx;
if ((!_running) && ((1 << pin) & valid[uart_get_index(_uart)])) {
_rx = pin;
return true;
} else {
DEBUGCORE("ERROR: SerialUART setRX illegal pin (%d)\n", rx);
return false;
}
if (_running) {
panic("FATAL: Attempting to set Serial%d.RX while running", uart_get_index(_uart) + 1);
} else {
panic("FATAL: Attempting to set Serial%d.RX to illegal pin %d", uart_get_index(_uart) + 1, pin);
}
return false;
}
bool SerialUART::setTX(pin_size_t tx) {
bool SerialUART::setTX(pin_size_t pin) {
constexpr uint32_t valid[2] = { __bitset({0, 12, 16, 28}) /* UART0 */,
__bitset({4, 8, 20, 24}) /* UART1 */
};
if (_running) {
DEBUGCORE("ERROR: SerialUART setTX while running\n");
return false;
} else if ((1 << tx) & valid[uart_get_index(_uart)]) {
_tx = tx;
if ((!_running) && ((1 << pin) & valid[uart_get_index(_uart)])) {
_tx = pin;
return true;
} else {
DEBUGCORE("ERROR: SerialUART setTX illegal pin (%d)\n", tx);
return false;
}
if (_running) {
panic("FATAL: Attempting to set Serial%d.TX while running", uart_get_index(_uart) + 1);
} else {
panic("FATAL: Attempting to set Serial%d.TX to illegal pin %d", uart_get_index(_uart) + 1, pin);
}
return false;
}
SerialUART::SerialUART(uart_inst_t *uart, pin_size_t tx, pin_size_t rx) {
+45 -23
View File
@@ -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;
-52
View File
@@ -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
View 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
+1 -1
View File
@@ -1,4 +1,4 @@
name=SD(rp2040)
name=SD
version=2.0.0
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
+32 -20
View File
@@ -202,56 +202,68 @@ bool SPIClassRP2040::setRX(pin_size_t pin) {
constexpr uint32_t valid[2] = { __bitset({0, 4, 16, 20}) /* SPI0 */,
__bitset({8, 12, 24, 28}) /* SPI1 */
};
if (_running) {
return false;
} else if ((1 << pin) & valid[spi_get_index(_spi)]) {
if ((!_running) && ((1 << pin) & valid[spi_get_index(_spi)])) {
_RX = pin;
return true;
} else {
return false;
}
if (_running) {
panic("FATAL: Attempting to set SPI%s.RX while running", spi_get_index(_spi) ? "1" : "");
} else {
panic("FATAL: Attempting to set SPI%s.RX to illegal pin %d", spi_get_index(_spi) ? "1" : "", pin);
}
return false;
}
bool SPIClassRP2040::setCS(pin_size_t pin) {
constexpr uint32_t valid[2] = { __bitset({1, 5, 17, 21}) /* SPI0 */,
__bitset({9, 13, 25, 29}) /* SPI1 */
};
if (_running) {
return false;
} else if ((1 << pin) & valid[spi_get_index(_spi)]) {
if ((!_running) && ((1 << pin) & valid[spi_get_index(_spi)])) {
_CS = pin;
return true;
} else {
return false;
}
if (_running) {
panic("FATAL: Attempting to set SPI%s.CS while running", spi_get_index(_spi) ? "1" : "");
} else {
panic("FATAL: Attempting to set SPI%s.CS to illegal pin %d", spi_get_index(_spi) ? "1" : "", pin);
}
return false;
}
bool SPIClassRP2040::setSCK(pin_size_t pin) {
constexpr uint32_t valid[2] = { __bitset({2, 6, 18, 22}) /* SPI0 */,
__bitset({10, 14, 26}) /* SPI1 */
};
if (_running) {
return false;
} else if ((1 << pin) & valid[spi_get_index(_spi)]) {
if ((!_running) && ((1 << pin) & valid[spi_get_index(_spi)])) {
_SCK = pin;
return true;
} else {
return false;
}
if (_running) {
panic("FATAL: Attempting to set SPI%s.SCK while running", spi_get_index(_spi) ? "1" : "");
} else {
panic("FATAL: Attempting to set SPI%s.SCK to illegal pin %d", spi_get_index(_spi) ? "1" : "", pin);
}
return false;
}
bool SPIClassRP2040::setTX(pin_size_t pin) {
constexpr uint32_t valid[2] = { __bitset({3, 7, 19, 23}) /* SPI0 */,
__bitset({11, 15, 27}) /* SPI1 */
};
if (_running) {
return false;
} else if ((1 << pin) & valid[spi_get_index(_spi)]) {
if ((!_running) && ((1 << pin) & valid[spi_get_index(_spi)])) {
_TX = pin;
return true;
} else {
return false;
}
if (_running) {
panic("FATAL: Attempting to set SPI%s.TX while running", spi_get_index(_spi) ? "1" : "");
} else {
panic("FATAL: Attempting to set SPI%s.TX to illegal pin %d", spi_get_index(_spi) ? "1" : "", pin);
}
return false;
}
void SPIClassRP2040::begin(bool hwCS) {
+16 -10
View File
@@ -51,28 +51,34 @@ bool TwoWire::setSDA(pin_size_t pin) {
constexpr uint32_t valid[2] = { __bitset({0, 4, 8, 12, 16, 20, 24, 28}) /* I2C0 */,
__bitset({2, 6, 10, 14, 18, 22, 26}) /* I2C1 */
};
if (_running) {
return false;
} else if ((1 << pin) & valid[i2c_hw_index(_i2c)]) {
if ((!_running) && ((1 << pin) & valid[i2c_hw_index(_i2c)])) {
_sda = pin;
return true;
} else {
return false;
}
if (_running) {
panic("FATAL: Attempting to set Wire%s.SDA while running", i2c_hw_index(_i2c) ? "1" : "");
} else {
panic("FATAL: Attempting to set Wire%s.SDA to illegal pin %d", i2c_hw_index(_i2c) ? "1" : "", pin);
}
return false;
}
bool TwoWire::setSCL(pin_size_t pin) {
constexpr uint32_t valid[2] = { __bitset({1, 5, 9, 13, 17, 21, 25, 29}) /* I2C0 */,
__bitset({3, 7, 11, 15, 19, 23, 27}) /* I2C1 */
};
if (_running) {
return false;
} else if ((1 << pin) & valid[i2c_hw_index(_i2c)]) {
if ((!_running) && ((1 << pin) & valid[i2c_hw_index(_i2c)])) {
_scl = pin;
return true;
} else {
return false;
}
if (_running) {
panic("FATAL: Attempting to set Wire%s.SCL while running", i2c_hw_index(_i2c) ? "1" : "");
} else {
panic("FATAL: Attempting to set Wire%s.SCL to illegal pin %d", i2c_hw_index(_i2c) ? "1" : "", pin);
}
return false;
}
void TwoWire::setClock(uint32_t hz) {
+13
View 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);
}
}
@@ -0,0 +1,143 @@
// The speaker will play the tune to Happy Birthday continuously
// Author: Tony DiCola
// License: MIT (https://opensource.org/licenses/MIT)
#include <Arduino.h>
#ifdef USE_TINYUSB
// For Serial when selecting TinyUSB. Can't include in the core because Arduino IDE
// will not link in libraries called from the core. Instead, add the header to all
// the standard libraries in the hope it will still catch some user cases where they
// use these libraries.
// See https://github.com/earlephilhower/arduino-pico/issues/167#issuecomment-848622174
#include <Adafruit_TinyUSB.h>
#endif
// pin_buzzer should be defined by the supported variant e.g CPlay Bluefruit or CLUE.
// Otherwise please define the pin you would like to use for tone output
#ifndef PIN_BUZZER
#define PIN_BUZZER A0
#endif
uint8_t const pin_buzzer = PIN_BUZZER;
// A few music note frequencies as defined in this tone example:
// https://www.arduino.cc/en/Tutorial/toneMelody
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
// Define note durations. You only need to adjust the whole note
// time and other notes will be subdivided from it directly.
#define WHOLE 2200 // Length of time in milliseconds of a whole note (i.e. a full bar).
#define HALF WHOLE/2
#define QUARTER HALF/2
#define EIGHTH QUARTER/2
#define EIGHTH_TRIPLE QUARTER/3
#define SIXTEENTH EIGHTH/2
// Play a note of the specified frequency and for the specified duration.
// Hold is an optional bool that specifies if this note should be held a
// little longer, i.e. for eighth notes that are tied together.
// While waiting for a note to play the waitBreath delay function is used
// so breath detection and pixel animation continues to run. No tones
// will play if the slide switch is in the -/off position or all the
// candles have been blown out.
void playNote(int frequency, int duration, bool hold = false, bool measure = true) {
(void) measure;
if (hold) {
// For a note that's held play it a little longer than the specified duration
// so it blends into the next tone (but there's still a small delay to
// hear the next note).
tone(pin_buzzer, frequency, duration + duration / 32);
} else {
// For a note that isn't held just play it for the specified duration.
tone(pin_buzzer, frequency, duration);
}
delay(duration + duration / 16);
}
// Song to play when the candles are blown out.
void celebrateSong() {
// Play a little charge melody, from:
// https://en.wikipedia.org/wiki/Charge_(fanfare)
// Note the explicit boolean parameters in particular the measure=false
// at the end. This means the notes will play without any breath measurement
// logic. Without this false value playNote will try to keep waiting for candles
// to blow out during the celebration song!
playNote(NOTE_G4, EIGHTH_TRIPLE, true, false);
playNote(NOTE_C5, EIGHTH_TRIPLE, true, false);
playNote(NOTE_E5, EIGHTH_TRIPLE, false, false);
playNote(NOTE_G5, EIGHTH, true, false);
playNote(NOTE_E5, SIXTEENTH, false);
playNote(NOTE_G5, HALF, false);
}
void setup() {
// Initialize serial output and Circuit Playground library.
Serial.begin(115200);
pinMode(pin_buzzer, OUTPUT);
digitalWrite(pin_buzzer, LOW);
}
void loop() {
// Play happy birthday tune, from:
// http://www.irish-folk-songs.com/happy-birthday-tin-whistle-sheet-music.html#.WXFJMtPytBw
// Inside each playNote call it will play a note and drive the NeoPixel animation
// and check for a breath against the sound sensor. Once all the candles are blown out
// the playNote calls will stop playing music.
playNote(NOTE_D4, EIGHTH, true);
playNote(NOTE_D4, EIGHTH);
playNote(NOTE_E4, QUARTER); // Bar 1
playNote(NOTE_D4, QUARTER);
playNote(NOTE_G4, QUARTER);
playNote(NOTE_FS4, HALF); // Bar 2
playNote(NOTE_D4, EIGHTH, true);
playNote(NOTE_D4, EIGHTH);
playNote(NOTE_E4, QUARTER); // Bar 3
playNote(NOTE_D4, QUARTER);
playNote(NOTE_A4, QUARTER);
playNote(NOTE_G4, HALF); // Bar 4
playNote(NOTE_D4, EIGHTH, true);
playNote(NOTE_D4, EIGHTH);
playNote(NOTE_D5, QUARTER); // Bar 5
playNote(NOTE_B4, QUARTER);
playNote(NOTE_G4, QUARTER);
playNote(NOTE_FS4, QUARTER); // Bar 6
playNote(NOTE_E4, QUARTER);
playNote(NOTE_C5, EIGHTH, true);
playNote(NOTE_C5, EIGHTH);
playNote(NOTE_B4, QUARTER); // Bar 7
playNote(NOTE_G4, QUARTER);
playNote(NOTE_A4, QUARTER);
playNote(NOTE_G4, HALF); // Bar 8
celebrateSong();
// One second pause before repeating the loop and playing
delay(1000);
}