ADC examples - Electrical & Computer Engineering

ECE2049: Embedded Computing in Engineering Design
C Term -- Spring 2015
Lecture #16: Interrupts and Event Driven Code
Reading for Today:
Reading for Next Class:
HW #4 (on web):
Lab #2 (on web):
Lab #3 (on web):
Example code
Review all since exam 1
Due THURSDAY 2/19/15 to box by AK-011 by 4 pm
Report due 2/20/15 --> to box by MY office by 4pm
Report due 3/6/15
Exam #2 – FRIDAY 2/20/2015
Last Class: Setting the ADC12 registers
>> ADC12 set-up for single channel, single conversion (Use code examples)
>> Add new requirements as needed starting from working code
Ex: Starting with the code from Current Meter example how should we configure the
ADC12 registers to take a single measurement from the Internal Temperature Sensor?
--> First need to figure out how Temperature Sensor works!
#include <msp430.h>
// Temperature Sensor Calibration Reading at 30 deg C (for
// Vref+=1.5V) is stored at addr 1A1Ah. See end of data sheet for
// TLV table mapping
#define CALADC12_15V_30C *((unsigned int *)0x1A1A)
// Temperature Sensor Calibration Reading at 85 deg C (for
// Vref+=1.5V) is stored at addr 1A1Ch See device datasheet for
// TLV table mapping
#define CALADC12_15V_85C *((unsigned int *)0x1A1C)
unsigned int in_temp;
int main(void)
{
volatile float temperatureDegC, temperatureDegF, degC_per_bit;
WDTCTL = WDTPW + WDTHOLD;
// Stop WDT
degC_per_bit = ((float)(85.0 – 30.0))/
((float)(CALADC12_15V_85C-CALADC12_15V_30C));
// Reset REFMSTR to hand over control of internal reference
// voltages to ADC12_A control registers
REFCTL0 &= ~REFMSTR;
// Internal ref is on and set to 1.5V
ADC12CTL0 = ADC12SHT0_9 | ADC12REFON | ADC12ON;
ADC12CTL1 = ADC12SHP;
// Enable sample timer
ADC12MCTL0 = ADC12SREF_1 + ADC12INCH_10;
__delay_cycles(100);
// delay to allow Ref to settle
ADC12CTL0 |= ADC12ENC;
// Enable conversion
while(1)
{
ADC12CTL0 &= ~ADC12SC;
ADC12CTL0 |= ADC12SC;
// clear the start bit
// Sampling and conversion start
// Single conversion (single channel)
// Poll busy bit waiting for conversion to complete
while (ADC12CTL1 & ADC12BUSY)
__no_operation();
in_temp = ADC12MEM0;
// Read results from conversion
temperatureDegC=(float)(((long)in_temp-CALADC12_15V_30C)
*degC_per_bit + 30.0;
// Temperature in Fahrenheit = (9/5)*Tc + 32
temperatureDegF = temperatureDegC * 9.0/5.0 + 32.0;
__no_operation();
}
}
// SET BREAKPOINT HERE
Ex: Now, what if you wanted to monitor both the current through the sensing resistor and the internal temperature of the chip at the same time? How should we set up the ADC registers to do that?
>> This is a multiple channel, single conversion problem
>> What settings are changed from the single channel problem?
#define MA_PER_BIT
0.244
// =1.0A/4096
// Temperature Sensor Calibration readings for 2.5V from TLV
#define CALADC12_25V_30C *((unsigned int *)0x1A22)
#define CALADC12_25V_85C *((unsigned int *)0x1A24)
unsigned int
in_current,in_temp;
float milliamps, tempC;
// Reset REFMSTR to hand over control of internal reference
// voltages to ADC12_A control registers
REFCTL0 &= ~REFMSTR;
// Initialize control register ADC12CTL0 = 0000 1001 0111 0000
// SHT0x=9h (384 clk cycles), MCS=1=burst thru selected chans.,
// REF2_5V = 1 (2.5V), REFON = 1 = use internal reference volts
// and ADC12ON = 1 = turn ADC on
ADC12CTL0=ADC12SHT0_9|ADC12REFON|ADC12REF2_5V|ADC12ON|
ADC12MSC;
// Initialize control register ADC12CTL1 = 0000 0010 0000 0010
// ADC12CSTART ADDx = 0000 = start conversion with ADC12MEM0,
// ADC12SHSx = 00 = use SW conversion trigger, ADC12SC bits
// ADC12SHP = 1 = SAMPCON signal sourced from sampling timer,
// ADC12ISSH = 0 = sample input signal not inverted,
// ADC12DIVx = 000= divide ADC12CLK by 1,
// ADC12SSEL=00= ADC clock ADC12OSC (~5 MHz),
// ADC12CONSEQx = 01 = sequence of channels converted once
// ADC12BUSY = 0 = no ADC operation active
ADC12CTL1 = ADC12SHP+ADC12CONSEQ_1;
// Set conversion memory control registers for the 2 channels
// ADC12MCTL0: EOS = 0, SREF =001 = voltage refs = GND to Vref+
// INCHx = 0000
ADC12MCTL0 = ADC12SREF_1 + ADC12INCH_0;
// ADC12MCTL1: EOS = 1, SREF =001 = voltage refs = GND to Vref+
// INCHx = 1010
ADC12MCTL1 = ADC12SREF_1 + ADC12INCH_10 + ADC12EOS;
// Set Port 6 Pins 0 to FUNCTION mode (=1) for ADC12
P6SEL = P6SEL | BIT0;
// Forever loop to take measurements
while (1)
{ //Enable and start single burst conversion
ADC12CTL0 |= ADC12SC + ADC12ENC;
while (ADC12CTL1 & ADC12BUSY) // poll busy bit
__no_operation();
in_current = ADC12MEM0 & 0x0FFF; // keep only low 12 bits
in_temp = ADC12MEM1 & 0x0FFF; // keep only low 12 bits
milliamps = (float)in_current * MA_PER_BIT;
tempC = (float)(((long)in_temp-CALADC12_25V_30C)*(85 - 30))/
(CALADC12_25V_85C - CALADC12_25V_30C) + 30.0;
}
Using ADC12 Interrupts ->> Examples so far have used software setting of ADC12SC bit to start conversion and
then we've been polling ADC12BUSY bit to see when conversion is complete like...
//Enable and start (single) conversion
ADC12CTL0 |= ADC12SC + ADC12ENC;
// Poll busy bit waiting for conversion to complete
while (ADC12CTL1 & ADC12BUSY)
__no_operation();
>> This, like the swDelay() function is demo.c, is example of “Busy Waiting”
--> No useful CPU work occurs!
>> The main purpose of on-chip peripherals like the Timer and ADC is to remove
burdens from and provide services to the CPU
--> Having the CPU wait in a polling loop for a peripheral to complete is
something like the having the Queen waiting on the maid!
>> Interrupts = External signals requesting CPU action
>> Request are initiated outside of CPU and tend to occur asynchronously
>> Can be accepted and serviced by appropriate Interrupt Service Routine (ISR)
-- Non-Maskable Interrupts (NMI) = Can not be disabled
-- Must be serviced (highest priorities)
-- Maskable interrupts can be disabled
-- Must be individually enabled in order to be recognized and
serviced
>> ISR are like functions (labeled sub-blocks of code that execute and return)
>> Sources of interrupts are HARDWIRED in the MSP430
--> Only certain devices can generate interrupts
--> Interrupts have a fixed priority ranking that is SET during processor design
>> Fixed Addresses (and names in msp430f5529.h) are associated with each interrupt
source
--> INTERRUPT VECTOR TABLE – Resides in Highest Addresses of FLASH
(see table)
-----------------------------------------------------Using Interrupts with the ADC12 to signal end of conversion
>> Set up control registers ADC12CTL0 and ADC12CTL1 and ADC12MCTLx same as
before when we used polling
>> Must also set ADC12IE register bit corresponding to the last MEMx register used in
conversion.
--> RESET and NMI can not be ignored!
// Single channel, single converion (internal temp. sensor)
// to ADC12MEM1 register
REFCTL0 &= ~REFMSTR;
ADC12CTL0 = ADC12SHT0_9 | ADC12REFON | ADC12ON;
ADC12CTL1 = ADC12SHP + ADC12CSTARTADD_1;
ADC12MCTL1 = ADC12SREF_1 + ADC12INCH_10;
ADC12IE = BIT1;
_BIS_SR(GIE);
. . .
// using ADC12MEM1 for conversion result
// enable interrupt for MEM1
// Global Interrupt enable
ADC12CTL0 |= ADC12SC + ADC12ENC;
//Start conversion
. . .
// Else where in the code define ISR
#pragma vector=ADC12_VECTOR
__interrupt void ADC12ISR(void)
{
// Interrupt is generated when conversion (or last
// conversion if multi-channel) is complete so just
// read the results
adc_inTemp = ADC12MEM1;
// Move results to global
// variable adc_inTemp
}
--------------------------------------------------// Possible multi-channel ISR for current & temp sensors
// MEM1 still the LAST channel converted so ADC12IE = BIT1;
// (But first channel to convert is MEM0 so use ADC12CSTARTADD_0)
#pragma vector=ADC12_VECTOR
__interrupt void ADC12ISR (void)
{
// Interrupt is generated when conversion (or last
// conversion if multi-channel) is complete
adc_inCurr = ADC12MEM0;
// Move results to global
adc_inTemp = ADC12MEM1; // variables
}
Using TimerA2 ISR to “schedule” ADC measurements
>> Configure Timer A2 to measure desired interval (e.g. 1/4 sec)
void config_timerA2(void)
{ // Use ACLK (TASSEL_1),clock divider of 1 (ID_0), and start
// timer counting in Up mode (MC_1)
TA2CTL = TASSEL_1 + MC_1 + ID_0;
TA2CCR0 = 8191;
// 8191+1 ACLK periods = 0.25 seconds
TA2CCTL0 = CCIE;
// TA2CCR0 interrupt enabled on Timer A2
}
>>Inside Timer A2 ISR start ADC12 conversion
// Timer A2 interrupt service routine
#pragma vector=TIMER2_A0_VECTOR
__interrupt void Timer_A2_ISR(void) {
timer++;
// interrupts occur every 0.25 sec probably
// would still want to keep track of time
ADC12CTL0 |= ADC12SC + ADC12ENC;
//
//
//
//
//
//
// Start conversion
Could either POLL the ADC12BUSY bit here or use ADC12
interrupts in which case you'd need to implement ADC ISR
DON'T DO BOTH! Do not poll ADC if ADC interrupts are
configured and enabled!!
This assumes ADC12 control registers have already been
configured
}
Your main() might look like this -- Assuming ADC12 measurements are being
initiated in TimerA2 ISR and ADC12 interrupts are enabled ...
void main() {
// variable declarations
. . .
// stop watch dog
. . .
// do other initializations
_BIS_SR(GIE);
// Global Interrupt enable
adc12_config();
run_timerA2();
. . .
while (1)
{
. . .
if (timer == displayTime)
{
display_current_ADC_readings();
timer = 0;
// ... do other stuff . . .
}
// .... do other stuff ....
}
}
>> Notice that there is no busy waiting and no ADC12 polling!