summaryrefslogtreecommitdiffstats
path: root/lib/msp430/wiring_analog.c
blob: 1451ad7f02b3a568d0edddef4726ce20f3022d13 (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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
/*
  ************************************************************************
  *	wiring_analog.c
  *
  *	Arduino core files for MSP430
  *		Copyright (c) 2012 Robert Wessels. All right reserved.
  *
  *
  ***********************************************************************
  Derived from:
  wiring_analog.c - analog input and output
  Part of Arduino - http://www.arduino.cc/

  Copyright (c) 2005-2006 David A. Mellis

  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., 59 Temple Place, Suite 330,
  Boston, MA  02111-1307  USA
*/

#include "wiring_private.h"
#include "pins_energia.h"

#if defined(__MSP430_HAS_ADC10__) && !defined(ADC10ENC)
#define ADC10ENC ENC 
#endif
#if defined(__MSP430_HAS_ADC10__) && !defined(ADC10MEM0)
#define ADC10MEM0 ADC10MEM 
#endif
#if defined(__MSP430_HAS_ADC10_B__)
#define REFV_MASK 0x70
#define REF_MASK 0x31;
#endif

#if defined(__MSP430_HAS_ADC10__) || defined(__MSP430_HAS_ADC10_B__)
uint16_t analog_reference = DEFAULT, analog_period = F_CPU/490, analog_div = 0, analog_res=255; // devide clock with 0, 2, 4, 8
#endif

void analogReference(uint16_t mode)
{
	// can't actually set the register here because the default setting
	// will connect AVCC and the AREF pin, which would cause a short if
	// there's something connected to AREF.
	analog_reference = mode;
}

//TODO: Can be a lot more efficiant.
//      - lower clock rated / input devider to conserve Energia.
//      - pin configuration logic.

// Note set frequency before sending analog value
// Lowest fequency is defined by clock frequency F_CPU, and max counter value 2^16-1
// fmin = F_CPU / 2^16
void analogFrequency(uint32_t freq)
{
  if ( freq <= F_CPU/(4*65334L) ) { analog_div = ID_3; freq *=8; }  
  else if ( freq <= F_CPU/(2*65334L) ) { analog_div = ID_2; freq *=4; }
  else if ( freq <= F_CPU/(4*65334L) ) { analog_div = ID_1; freq *=2; }
  analog_period = F_CPU/freq;
}

// Set the resulution (nr of counts for 100%), default = 255, large values may not work at all frequencies
void analogResolution(uint16_t res)
{
  analog_res = res;
}

//Arduino specifies ~490 Hz for analog out PWM so we follow suit.
#define PWM_PERIOD analog_period // F_CPU/490
#define PWM_DUTY(x) ( (unsigned long)x*PWM_PERIOD / (unsigned long)analog_res )
void analogWrite(uint8_t pin, int val)
{
        pinMode(pin, OUTPUT); // pin as output

 	if (val == 0)
	{
		digitalWrite(pin, LOW); // set pin to LOW when duty cycle is 0
                                        // digitalWrite will take care of invalid pins
	}
	else if (val == analog_res)
	{
		digitalWrite(pin, HIGH); // set pin HIGH when duty cycle is 255
                                         // digitalWrite will take care of invalid pins
	}
	else
	{

	        uint8_t bit = digitalPinToBitMask(pin); // get pin bit
	        uint8_t port = digitalPinToPort(pin);   // get pin port
	        volatile uint8_t *sel;
                
                if (port == NOT_A_PORT) return; // pin on timer?
               
	        sel = portSelRegister(port); // get the port function select register address
		*sel |= bit;                 // set bit in pin function select register  

                switch(digitalPinToTimer(pin)) {                // which timer and CCR?
 			//case: T0A0                            // CCR0 used as period register
			case T0A1:                              // TimerA0 / CCR1
                                TA0CCR0 = PWM_PERIOD;           // PWM Period
                                TA0CCTL1 = OUTMOD_7;            // reset/set
                                TA0CCR1 = PWM_DUTY(val);       // PWM duty cycle
                                TA0CTL = TASSEL_2 + MC_1 + analog_div;       // SMCLK, up mode
                                break;
#if defined(__MSP430_HAS_TA3__) || defined(__MSP430_HAS_T0A3__) 
 			case T0A2:                              // TimerA0 / CCR2
                                TA0CCR0 = PWM_PERIOD;           // PWM Period
                                TA0CCTL2 = OUTMOD_7;            // reset/set
                                TA0CCR2 = PWM_DUTY(val);       // PWM duty cycle
                                TA0CTL = TASSEL_2 + MC_1+ analog_div;       // SMCLK, up mode
                                break;
#endif
#if defined(__MSP430_HAS_T1A3__) 
 			//case: T1A0                            // CCR0 used as period register
			case T1A1:                              // TimerA1 / CCR1
                                TA1CCR0 = PWM_PERIOD;           // PWM Period
                                TA1CCTL1 = OUTMOD_7;            // reset/set
                                TA1CCR1 = PWM_DUTY(val);       // PWM duty cycle
                                TA1CTL = TASSEL_2 + MC_1+ analog_div;       // SMCLK, up mode
                                break;
 			case T1A2:                              // TimerA1 / CCR2
                                TA1CCR0 = PWM_PERIOD;           // PWM Period
                                TA1CCTL2 = OUTMOD_7;            // reset/set
                                TA1CCR2 = PWM_DUTY(val);       // PWM duty cycle
                                TA1CTL = TASSEL_2 + MC_1+ analog_div;       // SMCLK, up mode
                                break;
#endif
#if defined(__MSP430_HAS_T2A3__)  
 			//case: T2A0                            // CCR0 used as period register
			case T2A1:                              // TimerA2 / CCR1
                                TA2CCR0 = PWM_PERIOD;           // PWM Period
                                TA2CCTL1 = OUTMOD_7;            // reset/set
                                TA2CCR1 = PWM_DUTY(val);       // PWM duty cycle
                                TA2CTL = TASSEL_2 + MC_1+ analog_div;       // SMCLK, up mode
                                break;
 			case T2A2:                              // TimerA2 / CCR2
                                TA2CCR0 = PWM_PERIOD;           // PWM Period
                                TA2CCTL2 = OUTMOD_7;            // reset/set
                                TA2CCR2 = PWM_DUTY(val);       // PWM duty cycle
                                TA2CTL = TASSEL_2 + MC_1+ analog_div;       // SMCLK, up mode
                                break;
#endif
#if defined(__MSP430_HAS_T0B3__) 
 			//case: T0B0                            // CCR0 used as period register
 			case T0B1:                              // TimerB0 / CCR1
                                TB0CCR0 = PWM_PERIOD;           // PWM Period
                                TB0CCTL1 = OUTMOD_7;            // reset/set
                                TB0CCR1 = PWM_DUTY(val);       // PWM duty cycle
                                TB0CTL = TBSSEL_2 + MC_1+ analog_div;       // SMCLK, up mode
                                break;
 			case T0B2:                              // TimerB0 / CCR1
                                TB0CCR0 = PWM_PERIOD;           // PWM Period
                                TB0CCTL2 = OUTMOD_7;            // reset/set
                                TB0CCR2 = PWM_DUTY(val);       // PWM duty cycle
                                TB0CTL = TBSSEL_2 + MC_1+ analog_div;       // SMCLK, up mode
                                break;
#endif
#if defined(__MSP430_HAS_T1B3__) 
 			//case: T1B0                            // CCR0 used as period register
 			case T1B1:                              // TimerB0 / CCR1
                                TB1CCR0 = PWM_PERIOD;           // PWM Period
                                TB1CCTL1 = OUTMOD_7;            // reset/set
                                TB1CCR1 = PWM_DUTY(val);       // PWM duty cycle
                                TB1CTL = TBSSEL_2 + MC_1+ analog_div;       // SMCLK, up mode
                                break;
 			case T1B2:                              // TimerB0 / CCR1
                                TB1CCR0 = PWM_PERIOD;           // PWM Period
                                TB1CCTL2 = OUTMOD_7;            // reset/set
                                TB1CCR2 = PWM_DUTY(val);       // PWM duty cycle
                                TB1CTL = TBSSEL_2 + MC_1+ analog_div;       // SMCLK, up mode
                                break;
#endif
#if defined(__MSP430_HAS_T2B3__) 
 			//case: T1B0                            // CCR0 used as period register
 			case T2B1:                              // TimerB0 / CCR1
                                TB2CCR0 = PWM_PERIOD;           // PWM Period
                                TB2CCTL1 = OUTMOD_7;            // reset/set
                                TB2CCR1 = PWM_DUTY(val);       // PWM duty cycle
                                TB2CTL = TBSSEL_2 + MC_1+ analog_div;       // SMCLK, up mode
                                break;
 			case T2B2:                              // TimerB0 / CCR1
                                TB2CCR0 = PWM_PERIOD;           // PWM Period
                                TB2CCTL2 = OUTMOD_7;            // reset/set
                                TB2CCR2 = PWM_DUTY(val);       // PWM duty cycle
                                TB2CTL = TBSSEL_2 + MC_1+ analog_div;       // SMCLK, up mode
                                break;
#endif
 
                        case NOT_ON_TIMER:                      // not on a timer output pin
			default:                                // or TxA0 pin
				if (val <= (analog_res >> 1)) {
					digitalWrite(pin, LOW); // 
				} else {
					digitalWrite(pin, HIGH);
				}
                }
        }
}

uint16_t analogRead(uint8_t pin)
{
// make sure we have an ADC
#if defined(__MSP430_HAS_ADC10__) || defined(__MSP430_HAS_ADC10_B__)
    //  0000 A0
    //  0001 A1
    //  0010 A2
    //  0011 A3
    //  0100 A4
    //  0101 A5
    //  0110 A6
    //  0111 A7
    //  1010 Internal temperature sensor

    //TODO: Only int. temp. sensor requires Tsample > 30us.
    // The below ADC configuration applies this rule to all channels right now.
    // ADC10CLK = 5MHz / 5 = 1Mhz
    // Tsample = S&H / ADC10CLK = 64 / 1 MHz = 64 us
    // Tconvert = 13 / ADC10CLK = 13 / 1 MHz = 13 us
    // Total time per sample = Tconvert + Tsample = 64 + 13 = 67 us = ~15k samples / sec

    ADC10CTL0 &= ~ADC10ENC;                 // disable ADC
    ADC10CTL1 = ADC10SSEL_0 | ADC10DIV_5;   // ADC10OSC as ADC10CLK (~5MHz) / 5
#if defined(__MSP430_HAS_ADC10__)
    ADC10CTL0 = analog_reference |          // set analog reference
            ADC10ON | ADC10SHT_3 | ADC10IE; // turn ADC ON; sample + hold @ 64 × ADC10CLKs; Enable interrupts
    ADC10CTL1 |= (pin << 12);               // select channel
    ADC10AE0 = (1 << pin);                  // Disable input/output buffer on pin
#endif
#if defined(__MSP430_HAS_ADC10_B__)
    while(REFCTL0 & REFGENBUSY);            // If ref generator busy, WAIT
    REFCTL0 |= analog_reference & REF_MASK; // Set reference using masking off the SREF bits. See Energia.h.
    ADC10MCTL0 = pin | (analog_reference & REFV_MASK); // set channel and reference 
    ADC10CTL0 = ADC10ON | ADC10SHT_4;       // turn ADC ON; sample + hold @ 64 × ADC10CLKs
    ADC10CTL1 |= ADC10SHP;                  // ADCCLK = MODOSC; sampling timer
    ADC10CTL2 |= ADC10RES;                  // 10-bit resolution
    ADC10IFG = 0;                           // Clear Flags
    ADC10IE |= ADC10IE0;                    // Enable interrupts
#endif
    __delay_cycles(128);                    // Delay to allow Ref to settle
    ADC10CTL0 |= ADC10ENC | ADC10SC;        // enable ADC and start conversion
    while (ADC10CTL1 & ADC10BUSY) {         // sleep and wait for completion
        __bis_SR_register(CPUOFF + GIE);    // LPM0 with interrupts enabled
    }

#if defined(__MSP430_HAS_ADC10__)
    /* POWER: Turn ADC and reference voltage off to conserve power */
    ADC10CTL0 &= ~(ADC10ON | REFON);
#endif

#if defined(__MSP430_HAS_ADC10_B__)
    /* POWER: Turn ADC and reference voltage off to conserve power */
    ADC10CTL0 &= ~(ADC10ON);
    REFCTL0 &= ~REFON;
#endif
    return ADC10MEM0;  // return sampled value after returning to active mode in ADC10_ISR
#else
    // no ADC
    return 0;
#endif
}

__attribute__((interrupt(ADC10_VECTOR)))
void ADC10_ISR(void)
{
#if defined(__MSP430_HAS_ADC10)
    __bic_SR_register_on_exit(CPUOFF);        // return to active mode
#endif

#if defined(__MSP430_HAS_ADC10_B__)

    switch(ADC10IV,12) {
        case  0: break;                          // No interrupt
        case  2: break;                          // conversion result overflow
        case  4: break;                          // conversion time overflow
        case  6: break;                          // ADC10HI
        case  8: break;                          // ADC10LO
        case 10: break;                          // ADC10IN
        case 12:
                 __bic_SR_register_on_exit(CPUOFF);        // return to active mode
                 break;                          // Clear CPUOFF bit from 0(SR)                         
        default: break;
    }

    ADC10IFG = 0;                           // Clear Flags
#endif
}