-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.c
441 lines (371 loc) · 17.8 KB
/
main.c
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
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
/*
* res_2_voltage.c
* November 17, 2022
* Version 1.0
* Author: Jonathan Valdez (jonathanlvaldez@gmail.com)
*
* Description: A simple software that sampels the ADC values from the thermistors, and then
* will generate a PWM output to scale the temperature range to a 0 to 1V signal needed for
* the Gen 3 LS ECU O2 sensor voltage scale.
*
* Pinout:
* Inputs:
* - P1.3 : Thermistor 1
* - P1.4 : Thermistor 2
*
* Outputs:
* - P1.6/P2.0 : PWM Out 1
* - P1.7/P2.1 : PWM Out 2
*
*
* Software Details:
* - ADC is triggered by TB1.1 every 100 ms to start a sample.
* - Once an ADC sample is complete, an interrupt fires to tell software to calculate the
* PWM duty cycle needed to achieve desired voltage based on a resistance to temperature LUT.
*/
#include <driverlib.h>
#include <msp430.h>
#include <gpio.h>
#include <timer_b.h>
#include <adc.h>
// Output voltage of GPIOs (for generating analog voltage output)
//#define SYS_DEBUG_NO_TIMER_ADC // Enable this if you don't want to use any timers (for debug)
#define SYS_VCC_VOLTAGE_MV 3300
#define SYS_ADC_INPUT_PULL_UP_R 2490
#define SYS_ADC_MAX_VALUE 1024
#define SYS_PWM_MAX_VALUE 1024 // The maximum value that the PWM can generate
#define SYS_PWM_MAX_TARGET_VOLTAGE_MV 1000 // The output voltage we want for maximum temp value
#define SYS_PWM_MIN_TARGET_VOLTAGE_MV 0 // The output voltage we want for minimum temp value
#define SYS_PWM_MAX_TARGET_TEMP_F ((150 * 9 / 5) + 32) // The temperature we want to represent at the SYS_PWM_MAX_TARGET_VOLTAGE_MV
#define SYS_PWM_MIN_TARGET_TEMP_F ((0 * 9 / 5) + 32) // The temperature we want to represent at the SYS_PWM_MIN_TARGET_VOLTAGE_MV
// Each timer period is ~1 ms. We aim to sample every 100 ms
#define NUM_TIMER_OVERFLOWS_BETWEEN_SAMPLES 100
void initClocks(void);
void initGPIOs(void);
void initTimers(void);
void initADC(void);
void timerOverflowChecker(void);
/* Functions for math */
uint32_t calculateResistance(uint16_t adcSample);
int16_t calculateTemperature(uint32_t resistance);
uint16_t calculateTargetOutputVoltageFromTemp(int16_t temp);
uint16_t calculatePWMDutyCycleFromTargetOutputVoltage(uint16_t targetmv);
int16_t convertCtoF(int16_t tempC);
/* Initialize some global variables used to start the ADC samples and which input is sampled */
volatile uint8_t timerOverflowCount;
volatile uint8_t adcSampleInput1; // This is calling out channel 1, so that if it's 0, then we use index 0.
/* Initialize some global variables to store the ADC values */
volatile uint16_t adcSample[2];
volatile uint8_t adcNewSample;
int main(void)
{
WDTCTL = WDTPW | WDTHOLD; // Stop WDT
//initClocks(); // Initialize the MCU clocks
initGPIOs(); // Initialize the device GPIOs
// Disable the GPIO power-on default high-impedance mode to activate
// previously configured port settings
PM5CTL0 &= ~LOCKLPM5;
initADC(); // Initialize the ADC
timerOverflowCount = 0;
adcSampleInput1 = 0;
adcSample[0] = 0;
adcSample[1] = 0;
adcNewSample = 0;
initTimers(); // Initialize the PWM timers and ADC timers
__bis_SR_register(GIE);
Timer_B_startCounter(TIMER_B0_BASE, TIMER_B_CONTINUOUS_MODE); // Start the timer
while (1)
{
if (adcNewSample & 0x01)
{
/* New sample is here for input 1 for us to do some math and update the PWM output for */
uint32_t resistance = calculateResistance(adcSample[0]);
int32_t temperature = calculateTemperature(resistance);
uint16_t targetmv = calculateTargetOutputVoltageFromTemp(temperature);
uint16_t pwmCount = calculatePWMDutyCycleFromTargetOutputVoltage(targetmv);
Timer_B_setCompareValue(TIMER_B0_BASE, TIMER_B_CAPTURECOMPARE_REGISTER_1, pwmCount);
adcNewSample &= ~0x01;
}
if (adcNewSample & 0x02)
{
/* New sample is here for input 2 for us to do some math and update the PWM output for */
uint32_t resistance = calculateResistance(adcSample[1]);
int32_t temperature = calculateTemperature(resistance);
uint16_t targetmv = calculateTargetOutputVoltageFromTemp(temperature);
uint16_t pwmCount = calculatePWMDutyCycleFromTargetOutputVoltage(targetmv);
Timer_B_setCompareValue(TIMER_B0_BASE, TIMER_B_CAPTURECOMPARE_REGISTER_2, pwmCount);
adcNewSample &= ~0x02;
}
}
}
uint32_t calculateResistance(uint16_t adcSample)
{
int32_t samplemv = ((int32_t)adcSample * (int32_t)SYS_VCC_VOLTAGE_MV) / SYS_ADC_MAX_VALUE;
int32_t calculatedResistance = (-1*(int32_t)SYS_ADC_INPUT_PULL_UP_R*samplemv) /(samplemv-(int32_t)SYS_VCC_VOLTAGE_MV);
return calculatedResistance;
}
int16_t calculateTemperature(uint32_t resistance)
{
int32_t res = 0;
if (resistance <= 0x7FFFFFFF)
res = resistance;
#define numberOfMeasurements 20
/* Critical: Due to my laziness, this list should always have descending resistance values!!
* These are expected to be in degrees Celcius. Conversion happens later to gain more resolution */
const int32_t tempCurve[numberOfMeasurements][2] = {
{ -40, 96044 },
{ -30, 53674 },
{ -20, 26386 },
{ -10, 14822 },
{ 0, 91247 },
{ 10, 5244 },
{ 20, 3277 },
{ 30, 2108 },
{ 40, 1392 },
{ 50, 941 },
{ 60, 650 },
{ 70, 458 },
{ 80, 329 },
{ 90, 240 },
{ 100, 178 },
{ 110, 134 },
{ 120, 102 },
{ 130, 78 },
{ 140, 61 },
{ 150, 48 }
};
/* Now to interpolate to the closest value */
uint8_t closestLower = 255;
uint8_t i;
/* Find the first index that has a value below the given */
for (i = 0; i < numberOfMeasurements; i++)
{
if (tempCurve[i][1] <= res)
{
closestLower = i;
break;
}
}
/* A few checks for invalid corner cases */
if (closestLower == 255) // All values were above the measured resistance. Return last temp
return convertCtoF(tempCurve[numberOfMeasurements-1][0]);
if (closestLower == 0) // If first one was lower, then we measured higher resistance than we have. Return first
return convertCtoF(tempCurve[0][0]);
if (tempCurve[closestLower][1] == res) // If an exact match, save the CPU cycles and return the value
return convertCtoF(tempCurve[closestLower][0]);
/* Now we interpolate the found index, and 1 less than the index (should be above it) */
int32_t y1 = tempCurve[closestLower-1][1];
int32_t x1 = convertCtoF(tempCurve[closestLower-1][0]);
int32_t y2 = tempCurve[closestLower][1];
int32_t x2 = convertCtoF(tempCurve[closestLower][0]);
/* Shouldn't ever have the below be true */
if (y1 <= y2)
return convertCtoF(tempCurve[0][0]);
int32_t diff = y1 - y2;
int32_t measDiff = y1 - res;
int32_t diffX = x1 - x2;
int32_t interpVal = x1 - ((measDiff * diffX) / diff );
return (int16_t)(interpVal);
}
uint16_t calculateTargetOutputVoltageFromTemp(int16_t temp)
{
/* This function will take the temperature and return a PWM count with a linear temp range described in the defines at the top of this file */
if (temp <= SYS_PWM_MIN_TARGET_TEMP_F)
return SYS_PWM_MIN_TARGET_VOLTAGE_MV;
if (temp >= SYS_PWM_MAX_TARGET_TEMP_F)
return SYS_PWM_MAX_TARGET_VOLTAGE_MV;
int32_t tempRange = (int32_t)SYS_PWM_MAX_TARGET_TEMP_F - (int32_t)SYS_PWM_MIN_TARGET_TEMP_F;
int32_t targetmv = (((int32_t)temp - (int32_t)SYS_PWM_MIN_TARGET_TEMP_F)*(int32_t)SYS_PWM_MAX_TARGET_VOLTAGE_MV)/tempRange;
if (targetmv >= SYS_PWM_MAX_TARGET_VOLTAGE_MV)
return SYS_PWM_MAX_TARGET_VOLTAGE_MV;
if (targetmv <= SYS_PWM_MIN_TARGET_VOLTAGE_MV)
return SYS_PWM_MIN_TARGET_VOLTAGE_MV;
return (uint16_t)targetmv;
}
int16_t convertCtoF(int16_t tempC)
{
int16_t tempF = (tempC * 9 / 5) + 32;
return tempF;
}
uint16_t calculatePWMDutyCycleFromTargetOutputVoltage(uint16_t targetmv)
{
uint16_t mv = targetmv;
if (mv > SYS_PWM_MAX_TARGET_VOLTAGE_MV)
mv = SYS_PWM_MAX_TARGET_VOLTAGE_MV;
#if SYS_PWM_MIN_TARGET_VOLTAGE_MV > 0
else if (mv < SYS_PWM_MIN_TARGET_VOLTAGE_MV)
mv = SYS_PWM_MIN_TARGET_VOLTAGE_MV;
#endif
/* This function will return the PWM cycle count needed to get close to the target voltage */
uint32_t math = ((uint32_t)mv * (uint32_t)SYS_PWM_MAX_VALUE) / (uint32_t)SYS_VCC_VOLTAGE_MV;
if (math >= SYS_PWM_MAX_VALUE)
return SYS_PWM_MAX_VALUE;
return (uint16_t)math;
}
void initGPIOs(void)
{
/* Set all pins to input with weak pull up resistor */
GPIO_setAsInputPinWithPullDownResistor(GPIO_PORT_P1, GPIO_PIN1 | GPIO_PIN2 | GPIO_PIN3 | GPIO_PIN4 | GPIO_PIN5 | GPIO_PIN6 | GPIO_PIN7);
GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P1, GPIO_PIN0); // GPIO P1.0 gets a pull up since its connected to supply.
GPIO_setAsInputPinWithPullDownResistor(GPIO_PORT_P2, GPIO_PIN0 | GPIO_PIN1 | GPIO_PIN2 | GPIO_PIN3 | GPIO_PIN4 | GPIO_PIN5 | GPIO_PIN6 | GPIO_PIN7);
/* Configure ADC pins P1.3 and P1.4 as ADC inputs, P1.0 as VREF+ and P1.2 as VREF- */
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1, GPIO_PIN3 | GPIO_PIN4, GPIO_TERNARY_MODULE_FUNCTION);
//GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1, GPIO_PIN0 | GPIO_PIN2 | GPIO_PIN3 | GPIO_PIN4, GPIO_TERNARY_MODULE_FUNCTION);
/* Configure PWM outputs P1.6 and P1.7 as timer module outputs*/
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P1, GPIO_PIN6 | GPIO_PIN7, GPIO_SECONDARY_MODULE_FUNCTION);
//GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P2, GPIO_PIN0, GPIO_TERNARY_MODULE_FUNCTION);
}
void initClocks(void)
{
__bis_SR_register(SCG0); // disable FLL
CSCTL3 |= SELREF__REFOCLK; // Set REFO as FLL reference source
CSCTL1 = DCOFTRIMEN_1 | DCOFTRIM0 | DCOFTRIM1 | DCORSEL_3;// DCOFTRIM=3, DCO Range = 8MHz
CSCTL2 = FLLD_0 + 0xF3; // DCODIV = 8MHz
__delay_cycles(3);
__bic_SR_register(SCG0); // enable FLL
CSCTL4 = SELMS__DCOCLKDIV | SELA__REFOCLK; // set default REFO(~32768Hz) as ACLK source, ACLK = 32768Hz
// default DCODIV as MCLK and SMCLK source
P1DIR |= BIT0 | BIT1; // set ACLK and LED pin as output
P1SEL1 |= BIT1; // set ACLK pin as second function
PM5CTL0 &= ~LOCKLPM5; // Disable the GPIO power-on default high-impedance mode
// to activate previously configured port settings
}
void initTimers(void)
{
/* Use TimerA0 as the timer that is used to start the ADC sample
* Since there is only Timer B0.0 0.1 and 0.2, We use B0.1 and 0.2 as outputs
* using Output mode 7: reset/set
*
* Input clock: 2 Mhz, divider is 1 = 2MHz
*/
Timer_B_initContinuousModeParam param = { .clockSource = TIMER_B_CLOCKSOURCE_SMCLK,
.clockSourceDivider = TIMER_B_CLOCKSOURCE_DIVIDER_1,
.timerInterruptEnable_TBIE = TIMER_B_TBIE_INTERRUPT_DISABLE,
.startTimer = false,
.timerClear = TIMER_B_DO_CLEAR
};
Timer_B_stop(TIMER_B0_BASE); // Stop the timer
Timer_B_initContinuousMode(TIMER_B0_BASE, ¶m); // Initialize timer
Timer_B_selectCounterLength(TIMER_B0_BASE, TIMER_B_COUNTER_10BIT); // Set timer at 10 bit
Timer_B_selectLatchingGroup(TIMER_B0_BASE, TIMER_B_GROUP_NONE); // Make sure CCR registers are not grouped
/* Set the PWM outputs for both capture and compare to reset/set */
Timer_B_setOutputMode(TIMER_B0_BASE, TIMER_B_CAPTURECOMPARE_REGISTER_1, TIMER_B_OUTPUTMODE_RESET_SET); // Set output of TB0.1 = reset/set
Timer_B_setOutputMode(TIMER_B0_BASE, TIMER_B_CAPTURECOMPARE_REGISTER_2, TIMER_B_OUTPUTMODE_RESET_SET); // Set output of TB0.2 = reset/set
/* Set outputs to 1 (as close to 0 as we can get) */
Timer_B_setCompareValue(TIMER_B0_BASE, TIMER_B_CAPTURECOMPARE_REGISTER_1, 1); // Set output of TB0.1 to 1/1024 of 3.3 V = 3.2 mV
Timer_B_setCompareValue(TIMER_B0_BASE, TIMER_B_CAPTURECOMPARE_REGISTER_2, 1); // Set output of TB0.2 to 1/1024 of 3.3 V = 3.2 mV
Timer_B_clearTimerInterrupt(TIMER_B0_BASE); // Clear any interrupts that might be set
#ifndef SYS_DEBUG_NO_TIMER_ADC
Timer_B_enableInterrupt(TIMER_B0_BASE); // Enable timer interrupts (when timer overflows)
#endif
}
void initADC(void)
{
ADC_disable(ADC_BASE);
ADC_init(ADC_BASE,
ADC_SAMPLEHOLDSOURCE_SC,
ADC_CLOCKSOURCE_ADCOSC,
ADC_CLOCKDIVIDER_16);
ADC_enable(ADC_BASE);
/*
* Base Address for the ADC Module
* Sample/hold for 16 clock cycles
* Do not enable Multiple Sampling
*/
ADC_setupSamplingTimer(ADC_BASE,
ADC_CYCLEHOLD_16_CYCLES,
ADC_MULTIPLESAMPLESDISABLE);
ADC_setupSamplingTimer(ADC_BASE, ADC_CYCLEHOLD_16_CYCLES, ADC_MULTIPLESAMPLESDISABLE);
ADC_configureMemory(ADC_BASE, ADC_INPUT_A3, ADC_VREFPOS_AVCC, ADC_VREFNEG_AVSS);
ADC_clearInterrupt(ADC_BASE, ADC_OVERFLOW_INTERRUPT_FLAG | ADC_TIMEOVERFLOW_INTERRUPT_FLAG | ADC_ABOVETHRESHOLD_INTERRUPT_FLAG | ADC_BELOWTHRESHOLD_INTERRUPT_FLAG | ADC_INSIDEWINDOW_INTERRUPT_FLAG | ADC_COMPLETED_INTERRUPT_FLAG);
ADC_enableInterrupt(ADC_BASE, ADC_COMPLETED_INTERRUPT);
}
void startADCSample(uint8_t channelSelect)
{
ADC_disableConversions(ADC_BASE, false);
if (channelSelect == 0)
{
ADC_configureMemory(ADC_BASE, ADC_INPUT_A3, ADC_VREFPOS_AVCC, ADC_VREFNEG_AVSS);
} else {
ADC_configureMemory(ADC_BASE, ADC_INPUT_A4, ADC_VREFPOS_AVCC, ADC_VREFNEG_AVSS);
}
ADC_startConversion(ADC_BASE, ADC_SINGLECHANNEL);
}
void timerOverflowChecker(void)
{
timerOverflowCount++;
if (timerOverflowCount >= NUM_TIMER_OVERFLOWS_BETWEEN_SAMPLES)
{
// Then we want to start an ADC sample
adcSampleInput1 = !adcSampleInput1; // Flip which sample input is done next
startADCSample(adcSampleInput1);
timerOverflowCount = 0;
}
}
/* ADC interrupt routine */
#pragma vector=ADC_VECTOR
__interrupt void ADC_ISR(void)
{
switch(__even_in_range(ADCIV,ADCIV_ADCIFG))
{
case ADCIV_NONE:
break;
case ADCIV_ADCOVIFG:
break;
case ADCIV_ADCTOVIFG:
break;
case ADCIV_ADCHIIFG:
break;
case ADCIV_ADCLOIFG:
break;
case ADCIV_ADCINIFG:
break;
case ADCIV_ADCIFG:
// Check if this channel has had the previous value used. Lazy semaphore
if ((adcNewSample & (1 << adcSampleInput1)) == 0)
{
adcSample[adcSampleInput1] = ADCMEM0;
adcNewSample |= (1 << adcSampleInput1);
}
ADCIFG = 0;
break; // Clear CPUOFF bit from 0(SR)
default:
break;
}
}
#pragma vector=TIMER0_B0_VECTOR
__interrupt void TIMERB0_B0_ISR(void)
{
switch(__even_in_range(TB0IV,TB0IV_TBIFG))
{
case TB0IV_NONE: // No interrupt
break;
case TB0IV_TBCCR1: // CCR1 not used
break;
case TB0IV_TBCCR2: // CCR2 not used
break;
case TB0IV_TBIFG: // Overflow
timerOverflowChecker();
break;
default:
break;
}
}
#pragma vector=TIMER0_B1_VECTOR
__interrupt void TIMERB0_B1_ISR(void)
{
switch(__even_in_range(TB0IV,TB0IV_TBIFG))
{
case TB0IV_NONE: // No interrupt
break;
case TB0IV_TBCCR1: // CCR1 not used
break;
case TB0IV_TBCCR2: // CCR2 not used
break;
case TB0IV_TBIFG: // Overflow
timerOverflowChecker();
break;
default:
break;
}
}