summaryrefslogtreecommitdiffstats
path: root/lib/msp430/Tone.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/msp430/Tone.cpp')
-rwxr-xr-xlib/msp430/Tone.cpp223
1 files changed, 223 insertions, 0 deletions
diff --git a/lib/msp430/Tone.cpp b/lib/msp430/Tone.cpp
new file mode 100755
index 0000000..dd4aaeb
--- /dev/null
+++ b/lib/msp430/Tone.cpp
@@ -0,0 +1,223 @@
+/* Tone.cpp
+
+ A Tone Generator Library - Modified for Energia
+ Implements up to 3 (software) PWM outputs using TIMERA0 compare registers and IRQ.
+ Can use any digital output pin for pulse generation
+
+ (c) 2012 - Peter Brier.
+
+ Based on code Originally 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
+
+Version Modified By Date Comments
+------- ----------- -------- --------
+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 P Brier 12/05/28 Modified for TI MSP430 processor
+0009 P Brier 12/05/29 Fixed problem with re-init of expired tone
+*************************************************/
+
+#include "wiring_private.h"
+#include "pins_energia.h"
+#include "Energia.h"
+
+// local funcions
+static void initTimers();
+static void setTimer(uint8_t n, unsigned int frequency, unsigned long duration);
+static void stopTimer(uint8_t n);
+
+// 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]
+#define F_TIMER (F_CPU/8L)
+
+#ifdef __MSP430_HAS_TA3__
+#define AVAILABLE_TONE_PINS 3
+#define SETARRAY(a) a,a,a
+#else
+#define AVAILABLE_TONE_PINS 2
+#define SETARRAY(a) a,a
+#endif
+
+
+// tone_duration:
+// > 0 - duration specified
+// = 0 - stopped
+// < 0 - infinitely (until stop() method called, or new play() called)
+
+static uint8_t tone_state = 0; // 0==not initialized, 1==timer running
+static uint8_t tone_pins[AVAILABLE_TONE_PINS] = { SETARRAY(255) };
+static uint8_t tone_bit[AVAILABLE_TONE_PINS] = { SETARRAY(255) };
+volatile static uint8_t *tone_out[AVAILABLE_TONE_PINS] = { SETARRAY(0) };
+static uint16_t tone_interval[AVAILABLE_TONE_PINS] = { SETARRAY(-1) };
+static int16_t tone_periods[AVAILABLE_TONE_PINS] = { SETARRAY(0) };
+
+
+/**
+*** tone() -- Output a tone (50% Dutycycle PWM signal) on a pin
+*** pin: This pin is selected as output
+*** frequency: [Hertz]
+** duration: [milliseconds], if duration <=0, then we output tone continously, otherwise tone is stopped after this time (output = 0)
+**/
+void tone(uint8_t _pin, unsigned int frequency, unsigned long duration)
+{
+ uint8_t port = digitalPinToPort(_pin);
+ if (port == NOT_A_PORT) return;
+
+ // find if we are using it at the moment, if so: update it
+ for (int i = 0; i < AVAILABLE_TONE_PINS; i++)
+ {
+ if (tone_pins[i] == _pin)
+ {
+ setTimer(i, frequency, duration);
+ return; // we are done, timer reprogrammed
+ }
+ }
+
+ // new tone pin, find empty timer and set it
+ for (int i = 0; i < AVAILABLE_TONE_PINS; i++)
+ {
+ if (tone_pins[i] == 255)
+ {
+ tone_pins[i] = _pin;
+ tone_bit[i] = digitalPinToBitMask(_pin);
+ tone_out[i] = portOutputRegister(port);
+ if ( tone_state == 0 )
+ initTimers();
+ pinMode(_pin, OUTPUT);
+ setTimer(i, frequency, duration);
+ return; // we are done, timer set
+ }
+ }
+ // if we exit here, no unused timer was found, nothing is done
+}
+
+
+/**
+*** noTone() - Stop outputting the tone on a pin
+**/
+void noTone(uint8_t _pin)
+{
+ if ( _pin == 255 ) return; // Should not happen!
+ for (int i = 0; i < AVAILABLE_TONE_PINS; i++)
+ {
+ if (tone_pins[i] == _pin)
+ {
+ tone_pins[i] = 255;
+ stopTimer(i);
+ }
+ }
+}
+
+
+// Initialize the timers - Set mode and Enable IRQ
+static void inline initTimers()
+{
+ // disable IRQs
+ TA0CCTL0 = 0;
+ TA0CCTL1 = 0;
+#ifdef __MSP430_HAS_TA3__
+ TA0CCTL2 = 0;
+#endif
+ TA0CTL = TACLR + TASSEL_2 + ID_3 + MC_2; // clear counter, source=SMCLK/8, mode=continous count up
+ tone_state = 1; // init is done
+}
+
+
+// Set the timer interval and duration
+// frequency in [Hz] and duration in [msec]
+// we initialize the timer match value only if the tone was not running already, to prevent glitches when re-programming a running tone
+static void setTimer(uint8_t n, unsigned int frequency, unsigned long duration)
+{
+ if ( frequency <= 0 )
+ {
+ tone_interval[n] = 0;
+ tone_periods[n] = 0;
+ return;
+ }
+ tone_interval[n] = F_TIMER / (2L*frequency);
+ if ( duration > 0 )
+ tone_periods[n] = (duration * (F_TIMER/2)) / (1000L * tone_interval[n]);
+ else
+ tone_periods[n] = -1;
+ switch( n ) // enable IRQ and set next match time in various timer compare registers (if we where not enabled already)
+ {
+ case 0:
+ if ( ! (TA0CCTL0 & CCIE) ) TA0CCR0 = TA0R + tone_interval[0];
+ TA0CCTL0 = CCIE;
+ break;
+ case 1:
+ if ( !(TA0CCTL1 & CCIE) ) TA0CCR1 = TA0R + tone_interval[1];
+ TA0CCTL1 = CCIE;
+ break;
+#ifdef __MSP430_HAS_TA3__
+ case 2:
+ if ( !(TA0CCTL2 & CCIE) ) TA0CCR2 = TA0R + tone_interval[2];
+ TA0CCTL2 = CCIE;
+ break;
+#endif
+ }
+}
+
+/* stopTimer() - Disable timer IRQ */
+static void inline stopTimer(uint8_t n)
+{
+ switch( n )
+ {
+ case 0: TA0CCTL0 = 0; break;
+ case 1: TA0CCTL1 = 0; break;
+#ifdef __MSP430_HAS_TA3__
+ case 2: TA0CCTL2 = 0; break;
+#endif
+ }
+ *tone_out[n] &= ~tone_bit[n];
+}
+
+
+// Peform the isr magic, toggle output, decrease duation if > 0, and stop if duration == 0, continous if duration < 0
+// set new interval - defined as macro to limit ISR overhead (at the expense of some code size)
+#define isrTimer(n,ccr) do { \
+ *tone_out[n] ^= tone_bit[n]; \
+ if ( tone_periods[n] == 0 ) stopTimer(n);\
+ else if ( tone_periods[n] > 0) tone_periods[n]--; \
+ ccr += tone_interval[n]; \
+} while(0)
+
+
+// TIMERA vector (CCR0)
+__attribute__((interrupt(TIMER0_A0_VECTOR)))
+void TIMER0_A0_ISR(void)
+{
+ isrTimer(0, TA0CCR0);
+}
+
+// TAIV vector (CCR1/CCR2)
+__attribute__((interrupt(TIMER0_A1_VECTOR)))
+void TIMER0_A1_ISR(void)
+{
+ switch ( TA0IV )
+ {
+ case 0x2: isrTimer(1, TA0CCR1); break; // CCR1
+#ifdef __MSP430_HAS_TA3__
+ case 0x4: isrTimer(2, TA0CCR2); break; // CCR2
+#endif
+ }
+}