|
| 1 | +/* Tone.cpp |
| 2 | +
|
| 3 | + A Tone Generator Library - Modified for Energia |
| 4 | + Implements up to 3 (software) PWM outputs using TIMERA0 compare registers and IRQ. |
| 5 | + Can use any digital output pin for pulse generation |
| 6 | + |
| 7 | + (c) 2012 - Peter Brier. |
| 8 | +
|
| 9 | + Based on code Originally written by Brett Hagman |
| 10 | +
|
| 11 | + This library is free software; you can redistribute it and/or |
| 12 | + modify it under the terms of the GNU Lesser General Public |
| 13 | + License as published by the Free Software Foundation; either |
| 14 | + version 2.1 of the License, or (at your option) any later version. |
| 15 | +
|
| 16 | + This library is distributed in the hope that it will be useful, |
| 17 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 18 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 19 | + Lesser General Public License for more details. |
| 20 | +
|
| 21 | + You should have received a copy of the GNU Lesser General Public |
| 22 | + License along with this library; if not, write to the Free Software |
| 23 | + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| 24 | +
|
| 25 | +Version Modified By Date Comments |
| 26 | +------- ----------- -------- -------- |
| 27 | +0001 B Hagman 09/08/02 Initial coding |
| 28 | +0002 B Hagman 09/08/18 Multiple pins |
| 29 | +0003 B Hagman 09/08/18 Moved initialization from constructor to begin() |
| 30 | +0004 B Hagman 09/09/26 Fixed problems with ATmega8 |
| 31 | +0005 B Hagman 09/11/23 Scanned prescalars for best fit on 8 bit timers |
| 32 | + 09/11/25 Changed pin toggle method to XOR |
| 33 | + 09/11/25 Fixed timer0 from being excluded |
| 34 | +0006 D Mellis 09/12/29 Replaced objects with functions |
| 35 | +0007 M Sproul 10/08/29 Changed #ifdefs from cpu to register |
| 36 | +0008 P Brier 12/05/28 Modified for TI MSP430 processor |
| 37 | +0009 P Brier 12/05/29 Fixed problem with re-init of expired tone |
| 38 | +*************************************************/ |
| 39 | + |
| 40 | +#include "wiring_private.h" |
| 41 | +#include "pins_energia.h" |
| 42 | +#include "Energia.h" |
| 43 | + |
| 44 | +// local funcions |
| 45 | +static void initTimers(); |
| 46 | +static void setTimer(uint8_t n, unsigned int frequency, unsigned long duration); |
| 47 | +static void stopTimer(uint8_t n); |
| 48 | + |
| 49 | +// timer clock frequency set to clock/8, at F_CPU = 1MHZ this gives an output freq range of ~[1Hz ..65Khz] and at 16Mhz this is ~[16Hz .. 1MHz] |
| 50 | +#define F_TIMER (F_CPU/8L) |
| 51 | + |
| 52 | +#ifdef __MSP430_HAS_TA3__ |
| 53 | +#define AVAILABLE_TONE_PINS 3 |
| 54 | +#define SETARRAY(a) a,a,a |
| 55 | +#else |
| 56 | +#define AVAILABLE_TONE_PINS 2 |
| 57 | +#define SETARRAY(a) a,a |
| 58 | +#endif |
| 59 | + |
| 60 | + |
| 61 | +// tone_duration: |
| 62 | +// > 0 - duration specified |
| 63 | +// = 0 - stopped |
| 64 | +// < 0 - infinitely (until stop() method called, or new play() called) |
| 65 | + |
| 66 | +static uint8_t tone_state = 0; // 0==not initialized, 1==timer running |
| 67 | +static uint8_t tone_pins[AVAILABLE_TONE_PINS] = { SETARRAY(255) }; |
| 68 | +static uint8_t tone_bit[AVAILABLE_TONE_PINS] = { SETARRAY(255) }; |
| 69 | +volatile static uint16_t *tone_out[AVAILABLE_TONE_PINS] = { SETARRAY(0) }; |
| 70 | +static uint16_t tone_interval[AVAILABLE_TONE_PINS] = { SETARRAY(-1) }; |
| 71 | +static int16_t tone_periods[AVAILABLE_TONE_PINS] = { SETARRAY(0) }; |
| 72 | + |
| 73 | + |
| 74 | +/** |
| 75 | +*** tone() -- Output a tone (50% Dutycycle PWM signal) on a pin |
| 76 | +*** pin: This pin is selected as output |
| 77 | +*** frequency: [Hertz] |
| 78 | +** duration: [milliseconds], if duration <=0, then we output tone continously, otherwise tone is stopped after this time (output = 0) |
| 79 | +**/ |
| 80 | +void tone(uint8_t _pin, unsigned int frequency, unsigned long duration) |
| 81 | +{ |
| 82 | + uint8_t port = digitalPinToPort(_pin); |
| 83 | + if (port == NOT_A_PORT) return; |
| 84 | + |
| 85 | + // find if we are using it at the moment, if so: update it |
| 86 | + for (int i = 0; i < AVAILABLE_TONE_PINS; i++) |
| 87 | + { |
| 88 | + if (tone_pins[i] == _pin) |
| 89 | + { |
| 90 | + setTimer(i, frequency, duration); |
| 91 | + return; // we are done, timer reprogrammed |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + // new tone pin, find empty timer and set it |
| 96 | + for (int i = 0; i < AVAILABLE_TONE_PINS; i++) |
| 97 | + { |
| 98 | + if (tone_pins[i] == 255) |
| 99 | + { |
| 100 | + tone_pins[i] = _pin; |
| 101 | + tone_bit[i] = digitalPinToBitMask(_pin); |
| 102 | + tone_out[i] = portOutputRegister(port); |
| 103 | + if ( tone_state == 0 ) |
| 104 | + initTimers(); |
| 105 | + pinMode(_pin, OUTPUT); |
| 106 | + setTimer(i, frequency, duration); |
| 107 | + return; // we are done, timer set |
| 108 | + } |
| 109 | + } |
| 110 | + // if we exit here, no unused timer was found, nothing is done |
| 111 | +} |
| 112 | + |
| 113 | + |
| 114 | +/** |
| 115 | +*** noTone() - Stop outputting the tone on a pin |
| 116 | +**/ |
| 117 | +void noTone(uint8_t _pin) |
| 118 | +{ |
| 119 | + if ( _pin == 255 ) return; // Should not happen! |
| 120 | + for (int i = 0; i < AVAILABLE_TONE_PINS; i++) |
| 121 | + { |
| 122 | + if (tone_pins[i] == _pin) |
| 123 | + { |
| 124 | + tone_pins[i] = 255; |
| 125 | + stopTimer(i); |
| 126 | + } |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | + |
| 131 | +// Initialize the timers - Set mode and Enable IRQ |
| 132 | +static void inline initTimers() |
| 133 | +{ |
| 134 | + // disable IRQs |
| 135 | + TA0CCTL0 = 0; |
| 136 | + TA0CCTL1 = 0; |
| 137 | +#ifdef __MSP430_HAS_TA3__ |
| 138 | + TA0CCTL2 = 0; |
| 139 | +#endif |
| 140 | + TA0CTL = TACLR + TASSEL_2 + ID_3 + MC_2; // clear counter, source=SMCLK/8, mode=continous count up |
| 141 | + tone_state = 1; // init is done |
| 142 | +} |
| 143 | + |
| 144 | + |
| 145 | +// Set the timer interval and duration |
| 146 | +// frequency in [Hz] and duration in [msec] |
| 147 | +// we initialize the timer match value only if the tone was not running already, to prevent glitches when re-programming a running tone |
| 148 | +static void setTimer(uint8_t n, unsigned int frequency, unsigned long duration) |
| 149 | +{ |
| 150 | + if ( frequency <= 0 ) |
| 151 | + { |
| 152 | + tone_interval[n] = 0; |
| 153 | + tone_periods[n] = 0; |
| 154 | + return; |
| 155 | + } |
| 156 | + tone_interval[n] = F_TIMER / (2L*frequency); |
| 157 | + if ( duration > 0 ) |
| 158 | + tone_periods[n] = (duration * (F_TIMER/2)) / (1000L * tone_interval[n]); |
| 159 | + else |
| 160 | + tone_periods[n] = -1; |
| 161 | + switch( n ) // enable IRQ and set next match time in various timer compare registers (if we where not enabled already) |
| 162 | + { |
| 163 | + case 0: |
| 164 | + if ( ! (TA0CCTL0 & CCIE) ) TA0CCR0 = TAR + tone_interval[0]; |
| 165 | + TA0CCTL0 = CCIE; |
| 166 | + break; |
| 167 | + case 1: |
| 168 | + if ( !(TA0CCTL1 & CCIE) ) TA0CCR1 = TAR + tone_interval[1]; |
| 169 | + TA0CCTL1 = CCIE; |
| 170 | + break; |
| 171 | +#ifdef __MSP430_HAS_TA3__ |
| 172 | + case 2: |
| 173 | + if ( !(TA0CCTL2 & CCIE) ) TA0CCR2 = TAR + tone_interval[2]; |
| 174 | + TA0CCTL2 = CCIE; |
| 175 | + break; |
| 176 | +#endif |
| 177 | + } |
| 178 | +} |
| 179 | + |
| 180 | +/* stopTimer() - Disable timer IRQ */ |
| 181 | +static void inline stopTimer(uint8_t n) |
| 182 | +{ |
| 183 | + switch( n ) |
| 184 | + { |
| 185 | + case 0: TA0CCTL0 = 0; break; |
| 186 | + case 1: TA0CCTL1 = 0; break; |
| 187 | +#ifdef __MSP430_HAS_TA3__ |
| 188 | + case 2: TA0CCTL2 = 0; break; |
| 189 | +#endif |
| 190 | + } |
| 191 | + *tone_out[n] &= ~tone_bit[n]; |
| 192 | +} |
| 193 | + |
| 194 | + |
| 195 | +// Peform the isr magic, toggle output, decrease duation if > 0, and stop if duration == 0, continous if duration < 0 |
| 196 | +// set new interval - defined as macro to limit ISR overhead (at the expense of some code size) |
| 197 | +#define isrTimer(n,ccr) do { \ |
| 198 | + *tone_out[n] ^= tone_bit[n]; \ |
| 199 | + if ( tone_periods[n] == 0 ) stopTimer(n);\ |
| 200 | + else if ( tone_periods[n] > 0) tone_periods[n]--; \ |
| 201 | + ccr += tone_interval[n]; \ |
| 202 | +} while(0) |
| 203 | + |
| 204 | + |
| 205 | +// TIMERA vector (CCR0) |
| 206 | +__attribute__((interrupt(TIMER0_A0_VECTOR))) |
| 207 | +void TIMER0_A0_ISR(void) |
| 208 | +{ |
| 209 | + isrTimer(0, TA0CCR0); |
| 210 | +} |
| 211 | + |
| 212 | +// TAIV vector (CCR1/CCR2) |
| 213 | +__attribute__((interrupt(TIMER0_A1_VECTOR))) |
| 214 | +void TIMER0_A1_ISR(void) |
| 215 | +{ |
| 216 | + switch ( TAIV ) |
| 217 | + { |
| 218 | + case 0x2: isrTimer(1, TA0CCR1); break; // CCR1 |
| 219 | +#ifdef __MSP430_HAS_TA3__ |
| 220 | + case 0x4: isrTimer(2, TA0CCR2); break; // CCR2 |
| 221 | +#endif |
| 222 | + } |
| 223 | +} |
0 commit comments