/* 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 #include #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