summaryrefslogtreecommitdiffstats
path: root/lib/msp430/Tone.cpp
blob: dd4aaebb12839ee308ce2731bf9d4662d4fba470 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
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
  }  
}