ADC examples - Electrical & Computer Engineering
Transcription
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!