/* Copyright (c) 2015 Arduino LLC. All right reserved. SAMD51 support added by Adafruit - Copyright (c) 2018 Dean Miller for Adafruit Industries 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 */ #include "Tone.h" #include "variant.h" uint32_t toneMaxFrequency = F_CPU / 2; uint32_t lastOutputPin = 0xFFFFFFFF; volatile uint32_t *portToggleRegister; volatile uint32_t *portClearRegister; volatile uint32_t portBitMask; volatile int64_t toggleCount; volatile bool toneIsActive = false; volatile bool firstTimeRunning = false; #if defined(__SAMD51__) #define TONE_TC TC0 #define TONE_TC_IRQn TC0_IRQn #define TONE_TC_GCLK_ID TC0_GCLK_ID #define Tone_Handler TC0_Handler #define WAIT_TC16_REGS_SYNC(x) while(x->COUNT16.SYNCBUSY.bit.ENABLE); #else #define TONE_TC TC5 #define TONE_TC_IRQn TC5_IRQn #define Tone_Handler TC5_Handler #define WAIT_TC16_REGS_SYNC(x) while(x->COUNT16.STATUS.bit.SYNCBUSY); #endif #define TONE_TC_TOP 0xFFFF #define TONE_TC_CHANNEL 0 static inline void resetTC (Tc* TCx) { // Disable TCx TCx->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE; WAIT_TC16_REGS_SYNC(TCx) // Reset TCx TCx->COUNT16.CTRLA.reg = TC_CTRLA_SWRST; WAIT_TC16_REGS_SYNC(TCx) while (TCx->COUNT16.CTRLA.bit.SWRST); } void toneAccurateClock (uint32_t accurateSystemCoreClockFrequency) { toneMaxFrequency = accurateSystemCoreClockFrequency / 2; } void tone (uint32_t outputPin, uint32_t frequency, uint32_t duration) { // Avoid divide by zero error by calling 'noTone' instead if (frequency == 0) { noTone(outputPin); return; } // Configure interrupt request NVIC_DisableIRQ(TONE_TC_IRQn); NVIC_ClearPendingIRQ(TONE_TC_IRQn); if(!firstTimeRunning) { firstTimeRunning = true; NVIC_SetPriority(TONE_TC_IRQn, 5); #if defined(__SAMD51__) GCLK->PCHCTRL[TONE_TC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK0_Val | (1 << GCLK_PCHCTRL_CHEN_Pos); #else // Enable GCLK for TC4 and TC5 (timer counter input clock) GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TC4_TC5)); while (GCLK->STATUS.bit.SYNCBUSY); #endif } if (toneIsActive && (outputPin != lastOutputPin)) noTone(lastOutputPin); // // Calculate best prescaler divider and comparator value for a 16 bit TC peripheral // uint32_t prescalerConfigBits; uint32_t ccValue; ccValue = toneMaxFrequency / frequency - 1; prescalerConfigBits = TC_CTRLA_PRESCALER_DIV1; uint8_t i = 0; while(ccValue > TONE_TC_TOP) { ccValue = toneMaxFrequency / frequency / (2< 0 ? frequency * duration * 2 / 1000UL : -1LL); resetTC(TONE_TC); uint16_t tmpReg = 0; tmpReg |= TC_CTRLA_MODE_COUNT16; // Set Timer counter Mode to 16 bits #if defined(__SAMD51__) TONE_TC->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MFRQ; // Set TONE_TC mode as match frequency #else tmpReg |= TC_CTRLA_WAVEGEN_MFRQ; // Set TONE_TC mode as match frequency #endif tmpReg |= prescalerConfigBits; TONE_TC->COUNT16.CTRLA.reg |= tmpReg; WAIT_TC16_REGS_SYNC(TONE_TC) TONE_TC->COUNT16.CC[TONE_TC_CHANNEL].reg = (uint16_t) ccValue; WAIT_TC16_REGS_SYNC(TONE_TC) portToggleRegister = &(PORT->Group[g_APinDescription[outputPin].ulPort].OUTTGL.reg); portClearRegister = &(PORT->Group[g_APinDescription[outputPin].ulPort].OUTCLR.reg); portBitMask = (1ul << g_APinDescription[outputPin].ulPin); // Enable the TONE_TC interrupt request TONE_TC->COUNT16.INTENSET.bit.MC0 = 1; if (outputPin != lastOutputPin) { lastOutputPin = outputPin; digitalWrite(outputPin, LOW); pinMode(outputPin, OUTPUT); toneIsActive = true; } // Enable TONE_TC TONE_TC->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE; WAIT_TC16_REGS_SYNC(TONE_TC) NVIC_EnableIRQ(TONE_TC_IRQn); } void noTone (uint32_t outputPin) { /* 'tone' need to run at least once in order to enable GCLK for * the timers used for the tone-functionality. If 'noTone' is called * without ever calling 'tone' before then 'WAIT_TC16_REGS_SYNC(TCx)' * will wait infinitely. The variable 'firstTimeRunning' is set the * 1st time 'tone' is set so it can be used to detect wether or not * 'tone' has been called before. */ if(firstTimeRunning) { resetTC(TONE_TC); digitalWrite(outputPin, LOW); toneIsActive = false; } } #ifdef __cplusplus extern "C" { #endif void Tone_Handler (void) { if (toggleCount != 0) { // Toggle the ouput pin *portToggleRegister = portBitMask; if (toggleCount > 0) --toggleCount; // Clear the interrupt TONE_TC->COUNT16.INTFLAG.bit.MC0 = 1; } else { resetTC(TONE_TC); *portClearRegister = portBitMask; toneIsActive = false; } } #ifdef __cplusplus } #endif