xref: /ArduinoCore-samd/cores/arduino/Tone.cpp (revision 46f6cad4)
1/*
2  Copyright (c) 2015 Arduino LLC.  All right reserved.
3
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Lesser General Public
6  License as published by the Free Software Foundation; either
7  version 2.1 of the License, or (at your option) any later version.
8
9  This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12  See the GNU Lesser General Public License for more details.
13
14  You should have received a copy of the GNU Lesser General Public
15  License along with this library; if not, write to the Free Software
16  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17*/
18
19#include "Tone.h"
20#include "variant.h"
21
22#define WAIT_TC16_REGS_SYNC(x) while(x->COUNT16.STATUS.bit.SYNCBUSY);
23
24uint32_t toneMaxFrequency = F_CPU / 2;
25uint32_t lastOutputPin = 0xFFFFFFFF;
26
27volatile uint32_t *portToggleRegister;
28volatile uint32_t *portClearRegister;
29volatile uint32_t portBitMask;
30volatile int64_t toggleCount;
31volatile bool toneIsActive = false;
32volatile bool firstTimeRunning = false;
33
34#define TONE_TC         TC5
35#define TONE_TC_IRQn    TC5_IRQn
36#define TONE_TC_TOP     0xFFFF
37#define TONE_TC_CHANNEL 0
38
39void TC5_Handler (void) __attribute__ ((weak, alias("Tone_Handler")));
40
41static inline void resetTC (Tc* TCx)
42{
43  // Disable TCx
44  TCx->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE;
45  WAIT_TC16_REGS_SYNC(TCx)
46
47  // Reset TCx
48  TCx->COUNT16.CTRLA.reg = TC_CTRLA_SWRST;
49  WAIT_TC16_REGS_SYNC(TCx)
50  while (TCx->COUNT16.CTRLA.bit.SWRST);
51}
52
53void toneAccurateClock (uint32_t accurateSystemCoreClockFrequency)
54{
55  toneMaxFrequency = accurateSystemCoreClockFrequency / 2;
56}
57
58void tone (uint32_t outputPin, uint32_t frequency, uint32_t duration)
59{
60
61  // Avoid divide by zero error by calling 'noTone' instead
62  if (frequency == 0)
63  {
64    noTone(outputPin);
65    return;
66  }
67
68  // Configure interrupt request
69  NVIC_DisableIRQ(TONE_TC_IRQn);
70  NVIC_ClearPendingIRQ(TONE_TC_IRQn);
71
72  if(!firstTimeRunning)
73  {
74    firstTimeRunning = true;
75
76    NVIC_SetPriority(TONE_TC_IRQn, 0);
77
78    // Enable GCLK for TC4 and TC5 (timer counter input clock)
79    GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TC4_TC5));
80    while (GCLK->STATUS.bit.SYNCBUSY);
81  }
82
83  if (toneIsActive && (outputPin != lastOutputPin))
84    noTone(lastOutputPin);
85
86  //
87  // Calculate best prescaler divider and comparator value for a 16 bit TC peripheral
88  //
89
90  uint32_t prescalerConfigBits;
91  uint32_t ccValue;
92
93  ccValue = toneMaxFrequency / frequency - 1;
94  prescalerConfigBits = TC_CTRLA_PRESCALER_DIV1;
95
96  uint8_t i = 0;
97
98  while(ccValue > TONE_TC_TOP)
99  {
100    ccValue = toneMaxFrequency / frequency / (2<<i) - 1;
101    i++;
102    if(i == 4 || i == 6 || i == 8) //DIV32 DIV128 and DIV512 are not available
103     i++;
104  }
105
106  switch(i-1)
107  {
108    case 0: prescalerConfigBits = TC_CTRLA_PRESCALER_DIV2; break;
109
110    case 1: prescalerConfigBits = TC_CTRLA_PRESCALER_DIV4; break;
111
112    case 2: prescalerConfigBits = TC_CTRLA_PRESCALER_DIV8; break;
113
114    case 3: prescalerConfigBits = TC_CTRLA_PRESCALER_DIV16; break;
115
116    case 5: prescalerConfigBits = TC_CTRLA_PRESCALER_DIV64; break;
117
118    case 7: prescalerConfigBits = TC_CTRLA_PRESCALER_DIV256; break;
119
120    case 9: prescalerConfigBits = TC_CTRLA_PRESCALER_DIV1024; break;
121
122    default: break;
123  }
124
125  toggleCount = (duration > 0 ? frequency * duration * 2 / 1000UL : -1LL);
126
127  resetTC(TONE_TC);
128
129  uint16_t tmpReg = 0;
130  tmpReg |= TC_CTRLA_MODE_COUNT16;  // Set Timer counter Mode to 16 bits
131  tmpReg |= TC_CTRLA_WAVEGEN_MFRQ;  // Set TONE_TC mode as match frequency
132  tmpReg |= prescalerConfigBits;
133  TONE_TC->COUNT16.CTRLA.reg |= tmpReg;
134  WAIT_TC16_REGS_SYNC(TONE_TC)
135
136  TONE_TC->COUNT16.CC[TONE_TC_CHANNEL].reg = (uint16_t) ccValue;
137  WAIT_TC16_REGS_SYNC(TONE_TC)
138
139  portToggleRegister = &(PORT->Group[g_APinDescription[outputPin].ulPort].OUTTGL.reg);
140  portClearRegister = &(PORT->Group[g_APinDescription[outputPin].ulPort].OUTCLR.reg);
141  portBitMask = (1ul << g_APinDescription[outputPin].ulPin);
142
143  // Enable the TONE_TC interrupt request
144  TONE_TC->COUNT16.INTENSET.bit.MC0 = 1;
145
146  if (outputPin != lastOutputPin)
147  {
148    lastOutputPin = outputPin;
149    digitalWrite(outputPin, LOW);
150    pinMode(outputPin, OUTPUT);
151    toneIsActive = true;
152  }
153
154  // Enable TONE_TC
155  TONE_TC->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE;
156  WAIT_TC16_REGS_SYNC(TONE_TC)
157
158  NVIC_EnableIRQ(TONE_TC_IRQn);
159}
160
161void noTone (uint32_t outputPin)
162{
163  /* 'tone' need to run at least once in order to enable GCLK for
164   * the timers used for the tone-functionality. If 'noTone' is called
165   * without ever calling 'tone' before then 'WAIT_TC16_REGS_SYNC(TCx)'
166   * will wait infinitely. The variable 'firstTimeRunning' is set the
167   * 1st time 'tone' is set so it can be used to detect wether or not
168   * 'tone' has been called before.
169   */
170  if(firstTimeRunning)
171  {
172    resetTC(TONE_TC);
173    digitalWrite(outputPin, LOW);
174    toneIsActive = false;
175  }
176}
177
178#ifdef __cplusplus
179extern "C" {
180#endif
181
182void Tone_Handler (void)
183{
184  if (toggleCount != 0)
185  {
186    // Toggle the ouput pin
187    *portToggleRegister = portBitMask;
188
189    if (toggleCount > 0)
190      --toggleCount;
191
192    // Clear the interrupt
193    TONE_TC->COUNT16.INTFLAG.bit.MC0 = 1;
194  }
195  else
196  {
197    resetTC(TONE_TC);
198    *portClearRegister = portBitMask;
199    toneIsActive = false;
200  }
201}
202
203#ifdef __cplusplus
204}
205#endif
206