000000widow-hackspacecon/fw/HackSpaceCon_AS7/Arduino/megatinycore/Tone.cpp

334 lines
19 KiB
C++

/* Tone.cpp
A Tone Generator Library
Written by Brett Hagman
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-------- ----------- -------- --------
0001 B Hagman 09/08/02 Initial coding
0002 B Hagman 09/08/18 Multiple pins
0003 B Hagman 09/08/18 Moved initialization from constructor to begin()
0004 B Hagman 09/09/26 Fixed problems with ATmega8
0005 B Hagman 09/11/23 Scanned prescalars for best fit on 8 bit timers
09/11/25 Changed pin toggle method to XOR
09/11/25 Fixed timer0 from being excluded
0006 D Mellis 09/12/29 Replaced objects with functions
0007 M Sproul 10/08/29 Changed #ifdefs from cpu to register
0008 S Kanemoto 12/06/22 Fixed for Leonardo by @maris_HY
0009 J Reucker 15/04/10 Issue #292 Fixed problems with ATmega8 (thanks to Pete62)
0010 jipp 15/04/13 added additional define check #2923
0011 E Roy 13/02/18 ported to ATmega4809
1.3.3 S Konde 3/30/21 Fixed bug where output could be left high.
Fixed bug that would cause anomalies on PORTB through use of
a NULL pointer, which points rght at the low IO space. Just return
if the pin isn't a valid pin.
Fix anomalous silent pointless interrupts for duration of a
frequency = 0 "tone" Now I just drive pin low and turn off timer.
You asked for 0 Hz? That's 0 Hz alright!
In all cases, we ave to avoid leaving it high because
that can damage speakers, so I am told..
Just return and turn off tone if it's the current tone pin
bit_mask was declared volatile; but it shouldn't.
_pin was not decared volatile when it should be.
Fix bug where high-pitch tones old overflow when calculating
duration (the intermediate 2 * frequency was an unsigned int,
even though next operation multipolied it by an unsigned long
- simply reoved the 2* altogether and divide by 500 at end instead
of 1000.
Long tones (where frequency * duration > 2.145 billion resulted in an
intermediate overflowing before the divide by 1000. removing the
2* doubled this limit. But high frequencies and long durations (where
frequency * duration would overflow a unsigned long) still break it.
I added a way to distribute the division before the multiplication
if the duration was long enough to start worrying about. But I decided
not to enable by on all parts - it is SUPPORT_LONG_TONES - if defined
and 1, when duration is over 2^16, we split that line into
(frequency/5) * (duration/100) This version was developed for DxCore
1.3.3 and megaTinyCore 2.3.0. The former always uses the bulkier
implementation, while the latter does only on parts with 16k+ flash.
1.3.6 S Konde 4/17/21 Fixed bug that would break compilation of all sketches if TCB0
was used for millis on parts without a TCB1. Now, we intentionally break
the compilation only if the user code actually calls tone() or noTone()
This version was developed for DxCore 1.3.6 and megaTinyCore 2.3.2
1.3.7 S Konde 7/21/21 reorganized code, improved comments. caught a couple of corner
cases which could cause odd behavior in obscure cases. Added a check
for ENABLE_TCB_PWM which will turn the restoration of PWM mode on and
off - will make it easier to move this to DxCore without breakage.
I think the cleanup more or less exactly got enough flash back that
the coverage of corner cases cost.
2.4.0 S Konde Spotted amd fixed a few improperly handled corner cases, and a lot of
reorganization of questionable value.
2.4.3 S Konde 11/14/21 - Removed debug code which had not been disabled and would
print garbage to the serial port.
**********************************************************************************************************/
/************************************************************************************************************
S. Konde 7/21/21:
I have to wonder a few things here:
1. We have option for available tone pins, though very little of whatever else
would have been needed to support it is present. I guess on DxCore, that *could*
be increased, but a great deal of plumbing for it would also be needed - multiple pins, multiple timers...
Generally "a damned big deal" to implement, with a large number of design decisions.
What is strange is that I can find no sign of Arduino having supported more than one simultaneous tone pin, which
is the only case where this #define makes sense. Unless there were in the past arduinos withonly certain pinsthat could play tone or something
have.
2. In light of the fact that tone does not let you output multiple tones on multiple pins at once, one could
argue that noTone() should shut off the tone and ignore the argument. But the official API says ignore
noTone(wrong_pin)
My tentative ruling is that:
A. Any polyphonic tone functionality belongs in a library, tone should never have AVAILABLE_TONE_PINS =/= 1
One approach would be to conditionally compile the ISRs for timers based on a #define
that would have to be used before the library is #included; that file could check for ARDUINO_MAIN, and define
the ISRs if and only if it is (otherwise it would cause multiple definition error if included by sketch and other library).
Another approach would have the functions to control the tone output driven by each timer each in a separate file, or have
multiple instances of a TCB tone class, each instantiated in it's own file. The stock core has done this with the
HardwareSerial class since the dawn of time. Thus also not creating ISRs for timers we're not 'tone()ing' with.
The separate files would also provide an efficient means of keeping the variables that track the state of each
timer's tone separate..
I suspect that one could do worse than using this file, with liberal addition of the static keyword, as a basis
for those timer-specific tone() libraries. Personally, I don't feel like I have a good idea of what features
would be desirable for a tone library of that sort. I'm not an audio guy, and I have never connected a buzzer to
an MCU. I test these libraries with the 'scope (hell, I wouldn't be able to tell a 1kHz tone from a hole in the
ground without looking at it). In that hypothetical library, one design decision would have to be how to deal with
the most important part, that being turning the noisy thing off - basically, whether to implement multiTone() and
noMultiTone(pin), or to have things like `toneTimerTCB0.tone()/toneTimerTCB0.noTone()`. I am inclined
to think the second approach is a more sound one. If anytone wants to write such a librarty, I'm happy to advise
B. For the default noTone() that does not involve a library, pin should he required. Arguanbly there should be two
versions of it, with and without a pin argument, where the latter would shut down tone wherever it is.
************************************************************************************************************/
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "Arduino.h"
#include "pins_arduino.h"
#define AVAILABLE_TONE_PINS 1
#if defined(MILLIS_USE_TIMERB0)
#if defined(TCB1)
#define USE_TIMERB1
#else
#define TONE_UNAVAILABLE
#endif
#else
#define USE_TIMERB0
#endif
#ifndef TONE_UNAVAILABLE
static volatile TCB_t* _timer =
#if defined(USE_TIMERB0)
&TCB0;
#endif
#if defined(USE_TIMERB1)
&TCB1;
#endif
volatile uint8_t _pin = NOT_A_PIN;
// timerx_toggle_count:
// > 0 - duration specified
// = 0 - stopped
// < 0 - infinitely (until stop() method called, or new tone() called)
volatile long timer_toggle_count;
volatile uint8_t *timer_outtgl_reg;
static uint8_t timer_bit_mask;
uint8_t timer_cycle_per_tgl;
uint8_t timer_cycle_per_tgl_count;
// helper functions
static void disableTimer();
#endif
// frequency (in hertz) and duration (in milliseconds).
#ifndef TONE_UNAVAILABLE
void tone(uint8_t pin, unsigned int frequency, unsigned long duration) {
// Believe it or not, we don't need to turn off interrupts!
if (frequency == 0) {
noTone(pin);
/* turn it off (frequency 0, right? Note that this won't do anything
* unless that was the pin outputting tone. If it wasn't nothing
* will change. But there's no way of outputting a "0 Hz frequency"
* and a 1 Hz frequency makes even less sense. (they did ask for 0)
* But outputting a 1 Hz frequency on nothing (bitmask was cleared)
* achieves nothing except generate do-nothing interrupts regularly.
* When it is clear that provided arguments are sufficiently wrong
* that you can't do anything useful with them, at least don't
* do useless things instead. */
return;
}
// Get pin related info
uint8_t bit_mask = digitalPinToBitMask(pin);
if (bit_mask == NOT_A_PIN) return;
/* Same logic as above holds here except what it did was actively
* disruptive. When library code takes user supplied pins and looks
* up bitmasks and position, always check that one of them isn;t
* NOT_A_PIN - before you get the PORT struct. Because the port
* struct function cannot have it's result used safely. For bad
* pins, yes it returns a null pointer, 0x0000. But not only is
* that a valid register, it's an SFR (VPORTA.DIR)! So we don't
* want to end up writing to that. The convention of NULL pointers
* being invalid to work with is NOT safe to assume on embedded
* systems. */
// Serial.println(frequency);
long toggle_count;
// Calculate the toggle count
if (duration > 0) { // Duration defined
#if defined(SUPPORT_LONG_TONES) && SUPPORT_LONG_TONES == 1
if (duration > 65536) {
toggle_count = (frequency / 5) * (duration / 100);
} else {
toggle_count = (frequency * duration) / 500;
}
#else
toggle_count = (frequency * duration) / 500;
#endif
} else {
// Duration not specified -> infinite
// Represented internally by toggle_count = -1
toggle_count = -1;
}
// Calculate compare value
int8_t divisionfactor = 0; // no prescale, toggles at twice the frequency
// Timer settings -- will be type B timer or bust....
uint32_t compare_val = ((F_CPU / frequency) >> 1);
// We're are however disabling the timer. There may be one final interrupt at the moment we do so
// but that's not a problem
if (compare_val < 0x10000) { /* previously this tested for divisionfactor == 1,
* but that relied on us having already gone through the while loop to
* adjust it, which we haven't done yet, but we want to do this *after* the
* timeconsuming division operation, but *before* we actually change any
* other settings, because this is the point at which we stop the timer -
* hence it needs to be the first to be set if we want to leave interrupts on*/
_timer->CTRLA = TCB_CLKSEL_DIV1_gc;
} else {
_timer->CTRLA = TCB_CLKSEL_DIV2_gc;
divisionfactor--;
}
while ((compare_val > 0x10000) && (divisionfactor < 6)) {
// If the "else" branch above was followed, this is always true initially.
compare_val = compare_val >> 1;
divisionfactor++;
}
if (--compare_val > 0xFFFF) {
// if still too high, divisionfactor reached 6 (each phase lasts 64 overflows), meaning the
// user is using tone() to generate something that is not, by any stretch of the word, a
// "tone", and they should be generating it through other means.
compare_val = 0xFFFF; // do the best we can
}
// Serial.println(compare_val);
// Serial.println(divisionfactor);
// Anyway - so we know that the new pin is valid....
if (_pin != pin) { // ...let's see if we're using it already.
if (_pin != NOT_A_PIN) { // If not - were we using one before?
// If we were, we're gonna be in a world of hurt if we don't
// turn it off before we actually start reconfiguring stuff
// *(timer_outtgl_reg - 5) = timer_bit_mask; // pinMode(_pin, INPUT); (write dirclr for old pin)
// Apparently maybe it is intended for the pin to be left as an output?
// It does need to be LOW though, otherwise it can damage some speakers!
*(timer_outtgl_reg - 1) = timer_bit_mask; // digitalWrite(_pin, LOW); (write outclr for old pin)
}
// whether or not we _were_ using a pin, we are now, so configure the new one as an output...
PORT_t *port = digitalPinToPortStruct(pin); // Since not known at compile time, use PORTs not VPORTS.
timer_bit_mask = bit_mask; // We no longer need old pin's bit_mask
timer_outtgl_reg = (volatile uint8_t *) &(port->OUTTGL); // or toggle register.
*(timer_outtgl_reg - 1) = bit_mask; // digitalWrite(pin, LOW);
*(timer_outtgl_reg - 6) = bit_mask; // pinMode(pin, OUTPUT);
}
// Save the results of our calculations
timer_toggle_count = toggle_count;
timer_cycle_per_tgl = 1 << divisionfactor; // 1, 2, 4, 8, 16, 32, or 64 - toggle pin once per this many cycles...
timer_cycle_per_tgl_count = timer_cycle_per_tgl; // running count of remaining toggle cycles.
_timer->CCMP = compare_val; // ...and each cycle is this many timer ticks long
_timer->CTRLB = TCB_CNTMODE_INT_gc; // Set mode to Periodic Interrupt mode.
_timer->CNT = 0; // Not strictly necessary, but ensures there's no glitch.
_pin = pin; // Record new pin number.
_timer->INTCTRL = TCB_CAPTEI_bm; // Enable the interrupt (flag is already cleared)
_timer->CTRLA |= TCB_ENABLE_bm; // Everything is ready - Enable timer!
}
#else
void tone(__attribute__ ((unused)) uint8_t pin, __attribute__ ((unused)) unsigned int frequency, __attribute__ ((unused)) unsigned long duration) {
badCall("TCB0 used for millis, no other TCBs on this part; tone requires exclusive use of a type B timer, use a differemt millis timer or a tinyAVR with a second TCB (any 2-series, or 1-series with 16k+ flash)");
}
#endif
// pin which currently is being used for a tone
#ifndef TONE_UNAVAILABLE
void noTone(uint8_t pin) {
if (pin == _pin) {
uint8_t old_SREG = SREG; // Save SREG
cli(); // Interrupts off
timer_toggle_count = 0; // clear this one
_pin = NOT_A_PIN;
disableTimer(); // End with pin LOW, otherwise can damage some speakers.
SREG = old_SREG;
}
}
#else
void noTone(__attribute__ ((unused)) uint8_t pin) {
badCall("TCB0 used for millis, no other TCBs on this part; tone requires exclusive use of a type B timer, use a differemt millis timer or a tinyAVR with a second TCB (any 2-series, or 1-series with 16k+ flash)");
}
#endif
#ifndef TONE_UNAVAILABLE
// helper function for ending tone.
/* Works for all timers -- the timer being disabled will go back to the
configuration it had on startup */
static void disableTimer() {
_timer->CTRLA = 0; // disable timer
_timer->INTCTRL = 0; // disable the timer interrupts, otherwise if something else configures it and assumes that it's in the reset configuration
// and so doesn't write INTCTRL (because it doesn't use interrupts), the tone ISR could be called inappropriately.
_timer->INTFLAGS = 0xFF; // Make sure the flags are cleared (flags can be set without their interrupt being enabled, these will fire as soon as it is)
_pin = NOT_A_PIN; // and clear _pin.
#if defined(ENABLE_TCB_PWM) && ENABLE_TCB_PWM == 1
// RESTORE PWM FUNCTIONALITY, for use with cores that use the TCBs for PWM.
// This section adds 12 words of flash and as many clock cycles, and could probably be done less expensively if the compiler was smarter about
// using ldd/std when it offered a speed/flash advantage. But it isn't, so it doesn't. =
_timer->CTRLB = (TCB_CNTMODE_PWM8_gc); // 8 bit PWM mode, but do not enable output yet, will do in analogWrite()
_timer->CCMPL = PWM_TIMER_PERIOD; // Assign 8-bit period, probably 254.
_timer->CCMPH = PWM_TIMER_COMPARE; // We need to set low byte in order for high byte to be written (see silicon errata).
_timer->CTRLA = (TCB_CLKSEL_CLKTCA_gc) | (TCB_ENABLE_bm); // Use TCA clock (250kHz) and enable
#endif
*(timer_outtgl_reg - 1) = timer_bit_mask; // Write OUTCLR, so we are sure to end with pin LOW.
}
#endif
#if !defined(TONE_UNAVAILABLE)
#if defined(USE_TIMERB0)
ISR(TCB0_INT_vect)
#elif defined(USE_TIMERB1)
ISR(TCB1_INT_vect)
#endif
{
if (!(--timer_cycle_per_tgl_count)) { // Are we ready to toggle? pre-decrement, then see if we're at 0 yet.
timer_cycle_per_tgl_count = timer_cycle_per_tgl; // reset countdown
*timer_outtgl_reg = timer_bit_mask; // toggle the pin
if (timer_toggle_count > 0) { // if duration was specified, decrement toggle count.
timer_toggle_count--;
} else if (timer_toggle_count == 0) { // If toggle count = 0 we are done.
disableTimer();
} // otherwise timer_toggle_count wasn't supplied, go on until noTone() called
}
_timer->INTFLAGS = TCB_CAPT_bm; // Clear flag
}
#else
void disableTimer() {
badCall("TCB0 used for millis, no other TCBs on this part; tone requires exclusive use of a type B timer, use a differemt millis timer or a tinyAVR with a second TCB (any 2-series, or 1-series with 16k+ flash)");
}
#endif