270 likes | 411 Views
Lecture 18: ADC Implementation http://www.analog.com/library/analogDialogue/archives/39-06/data_conversion_handbook.html. Lecturers: Professor John Devlin Mr Robert Ross. Overview. Using a MAX1111 Using the internal ADC. MAX1111.
E N D
Lecture 18: ADC Implementationhttp://www.analog.com/library/analogDialogue/archives/39-06/data_conversion_handbook.html Lecturers: Professor John Devlin Mr Robert Ross
Overview • Using a MAX1111 • Using the internal ADC
MAX1111 • If a microprocessor (or a microcontroller without an ADC) is used, an external ADC will be required (like the MAX1111) • Features of the MAX1111 • 8 Bit • Successive Approximation Conversion • 8 Channel • SPI Interface • Max Conversion time: 20μs
MAX1111 has 8 input channels which are multiplexed to a hold circuit The user can alternately select any channel for sampling MAX1111 – Analog Input MUX
A +2.048V Reference voltage is internally generated and can be connected to REFIN to be used as the reference voltage Alternately a different reference voltage may be used MAX1111 – REF Voltage
Track/Hold Circuitry When not converting, tracks the analog input voltage While converting holds and stores the value of the analog input voltage MAX1111 – T/H
Track and Hold Circuits • Holds input voltage constant for the conversion period.
Successive Approximation Block Performs Binary search Uses REFIN as the reference voltage Supplies result to output shift register MAX1111 – SAR ADC
MSP430 Internal ADC • The MSP430F2013 has a 16 Bit sigma-delta ADC • 8 Channel • Internal temperature sensor • Internal reference (1.2V) • Input range = 0-600mV (Gain = 1) • Internal Clock divider
MSP430 ADC – CodeLED Temp increase display #include <msp430x20x3.h> #define ADCDeltaOn 31 static unsigned int LastADCVal; void main(void) { BCSCTL2 |= DIVS_3; WDTCTL = WDT_MDLY_32; IE1 |= WDTIE; P1DIR |= 0x01; SD16CTL = SD16REFON +SD16SSEL_1; SD16INCTL0 = SD16INCH_6; SD16CCTL0 = SD16SNGL + SD16IE ; _BIS_SR(LPM0_bits + GIE); }
MSP430 ADC – CodeLED Temp increase displayInterrupt routines #pragma vector=SD16_VECTOR __interrupt void SD16ISR(void) { if (SD16MEM0 <= LastADCVal + ADCDeltaOn) P1OUT &= ~0x01; else P1OUT |= 0x01; LastADCVal = SD16MEM0; } // Watchdog Timer interrupt service routine #pragma vector=WDT_VECTOR __interrupt void watchdog_timer(void) { SD16CCTL0 |= SD16SC; }
MSP430 ADC – Circuit Diagram 3.3V MSP430F2013 4.7K A4: P1.1 1K
RESET MOV.W #0280h,SP ; Set stackpointer (128B RAM device) stopWDT MOV.W #WDTPW+WDTHOLD,&WDTCTL ; Stop watchdog timer setupP1 MOV.B #11111101b,&P1DIR ; Set P1.0 -> P1.7 as outputs, P1.1 as input BIS.B #00010010b,&P1SEL ; P1.1 and P1.4 TA/SMCLK options setupP2 BIS.B #0C0h,&P2DIR ; Set P2.6 -> P2.7 as outputs Set_clock ; Set to calibrated 1MHz Clock MOV.B &CALBC1_1MHZ,&BCSCTL1 ; Set range; DCO = 1 MHz MOV.B &CALDCO_1MHZ,&DCOCTL ; Set DCO step + modulation setupADC MOV.W #0000000011010100b,&SD16CTL ; SMCLK, Ref On MOV.W #0001000000000010b,&SD16CCTL0 ; Unipolar, Start conversion MOV.B #00000100b,&SD16INCTL0 ; A4 (connected to P1.1) MSP430 ADC – Code
MOV.W #0FFFFh, R7 init MOV.W #04h, R6 ; AND SD16CCTL0, R6 ; Mask out other bits to get SD16IFG JNZ read_adc ; ADC conversion completed init2 MOV.W R7, R4 XOR.B #01h, &P1OUT ; Toggle Pin main DEC R4 JNZ main JMP init read_adc MOV.W &SD16MEM0, R7 ; Read ADC value from MEM0 JMP init2 MSP430 ADC – Code
Summary • The MAX1111 is an example of an external 8 bit serial ADC • Most microcontrollers have an internal ADC, which is simple to setup and use. • The MSP430F2013 has a 16 bit, 8 channel ADC
// eZ430led2.c - self-dimming LED on P1.0 in eZ430 using SD16A // PWM controlled by software, about 100Hz from ACLK = VLO, // SD16A measures light during off phase of PWM // Calibrated 8MHz DCO, no crystal, ACLK from VLO, power from JTAG (SBW) // J H Davies, 2007-09-30; IAR Kickstart version 3.42A PAGE 469 => LED TOUCH MOVIE , SLAC136 //---------------------------------------------------------------------- #include <io430x20x3.h> // Header file for this device #include <intrinsics.h> // Intrinsic functions #include <stdint.h> // Standard integer types #define LED_OUT P1OUT_bit.P1OUT_0 // Output to LED on P1.0 #define LED_ANALOG SD16AE_bit.SD16AE0 // Enable analog input from LED #define RANGE 4096 // Dynamic range of values from SD16 #define PWM_MAX 128 // Maximum value of duty cycle #define DIVISOR (RANGE/PWM_MAX) // For converting SD16 -> PWM #define SENSE_TIME 2 // Cycles of TACLK needed for SD16 uint16_t dutyCycle = PWM_MAX; // Duty cycle computed from SD16 // Start at maximum (but LED off) void main (void) { WDTCTL = WDTPW | WDTHOLD; // Stop watchdog BCSCTL1 = CALBC1_8MHZ; // Calibrated range for DCO DCOCTL = CALDCO_8MHZ; // Calibrated tap and modulation BCSCTL2 = DIVS_3; // SMCLK = DCO / 8 = 1MHz BCSCTL3 = LFXT1S_2; // Select ACLK from VLO (no crystal) P2SEL = 0; // Digital i/o rather than crystal P2REN = BIT6|BIT7; // Pull Rs on unused pins (6 and 7) P1REN = ~BIT0; // Pull Rs on all pins except 0 P1DIR = BIT0; // To drive LED on P1.0 LED_OUT = 0; // LED initially off (active high) // Configure SD16A: clock from SMCLK, no division, internal reference on SD16CTL = SD16XDIV_0 | SD16DIV_0 | SD16SSEL_1 | SD16REFON; // Unipolar, single convs, OSR = 32, interrupts on finish SD16CCTL0 = SD16UNI | SD16SNGL | SD16OSR_32 | SD16IE; // PGA gain = 16, input channel A0+/-, result after 4th conversion SD16INCTL0 = SD16GAIN_16 | SD16INCH_0 | SD16INTDLY_0; // Timer_A for software-assisted PWM using channel 1, up to TACCR0 mode TACCR0 = PWM_MAX + SENSE_TIME; // Overall period TACCR1 = dutyCycle; // Initial duty cycle TACCTL1 = CCIE; // Interrupts on compare // Start Timer_A from ACLK, undivided, up mode, clear, interrupts TACTL = TASSEL_1 | ID_0 | MC_1 | TACLR | TAIE; for (;;) { // Loop forever __low_power_mode_3(); // All action in interrupts } }
//----------------------------------------------------------------------//---------------------------------------------------------------------- // Interrupt service routine for CCIFG1 and TAIFG; share vector //---------------------------------------------------------------------- #pragma vector = TIMERA1_VECTOR __interrupt void TIMERA1_ISR (void) // Shared ISR for CCIFG1 and TAIFG { switch (__even_in_range(TAIV, TAIV_TAIFG)) { // Acknowledges int case 0: // No interrupt pending break; // No action case TAIV_TAIFG: // TAIFG vector // Start PWM duty cycle by turning LED on and updating duty cycle LED_OUT = 1; // Turn LED on; duty cycle always > 0 TACCR1 = dutyCycle; // Update duty cycle from SD16 reading break; case TAIV_CCIFG1: // CCIFG1 vector // Finish PWM duty cycle by turning off LED, then measuring light level LED_OUT = 0; // End of duty cycle: Turn off LED LED_ANALOG = 1; // Switch LED to SD16A input A0+ SD16CCTL0_bit.SD16SC = 1; // Start SD16A conversion // Change mode from LPM3 to LPM0 on exit to provide SMCLK for SD16 __bic_SR_register_on_exit(LPM3_bits); __bis_SR_register_on_exit(LPM0_bits); break; default: // Should not be possible: ignore break; } } //---------------------------------------------------------------------- // ISR for SD16A: compute new duty cycle in range [1, PWM_MAX] //---------------------------------------------------------------------- #pragma vector = SD16_VECTOR __interrupt void SD16_ISR (void) // Acknowledged when SD16MEM0 read { static uint16_t floor = 0xFFFF - RANGE; // Dark reading from SD16 LED_ANALOG = 0; // Switch LED back to digital output if (SD16MEM0 < floor) { // Update floor if new reading is lower floor = SD16MEM0; dutyCycle = 1; // Minimum value; never go down to 0 } else if (SD16MEM0 >= (floor + RANGE - DIVISOR)) { dutyCycle = PWM_MAX; // Maximum value (saturates) } else { dutyCycle = (SD16MEM0 - floor) / DIVISOR + 1; } // Change mode from LPM0 to LPM3 on exit: SMCLK no longer needed __bic_SR_register_on_exit(LPM0_bits); // (not really necessary) __bis_SR_register_on_exit(LPM3_bits); }
//******************************************************************************//****************************************************************************** // MSP430F20x3 Demo - SD16A, Obtain LED-generated voltage, set LED // brightness accordingly // // Description: The voltage generated by the LED is measured using the // SD16_A. Based on the result, the LED brightness is adjusted using PWM. // The LED PWM frequency is 50Hz. An LED voltage reading is obtained // every 200ms. Based on the defined "Min" and "Max" reference values, // the LED active duty cycle is adjusted according to the current light // conditions. The darker the ambient light is, the brigther the LED will // get illuminated. After starting the code, the board must be exposed // to darkness for a short moment in order to calibrate the LED's offset // voltage. The VLO is used to clock Timer_A, which is used for both // PWM generation but also to derive the timings. A calibration process // is implemented to accomodate for the variations in VLO frequency. // Normal operating mode is LPM3. // // ACLK = VLO ~ 12kHz, MCLK = SMCLK = SD16CLK = Calibrated 1MHz //// MSP430F20x3 // ------------------ // /|\| XIN|- // | | | // --|RST XOUT|- \ | / Light // | | \|/ Source // | | ----O---- // | P1.0/A0+|<-->LED /|\ // | | / | \ // // Andreas Dannenberg // Texas Instruments Inc. // June 2007 // Built with IAR Embedded Workbench Version: 3.42A //******************************************************************************
#include "msp430x20x3.h" // RESULT_MIN determines the SD16 result that is equivalent to maximum // LED brightness. With increasing ambient light intensity, RESULT_DELTA // determines after how many SD16 LSB counts based on RESULT_MIN the LED // is switched off completely. #define RESULT_MIN 10000 #define RESULT_DELTA 230 extern unsigned int Measure_VLO_SW(void); // External function to measure // speed of the VLO // (implemented in Measure_VLO_SW.s43) int Log[16]; // Debug buffer unsigned int LogPtr = 0; void main(void) { unsigned int VLO_Period_Length; int Temp; int Min = RESULT_MIN; // Assign initial limits int Max = RESULT_MIN + RESULT_DELTA; WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer // Setup GPIO P1DIR = 0xFF; // All P1.x outputs P1OUT = 0; // All P1.x reset P2DIR = 0xFF; // All P2.x outputs P2OUT = 0; // All P2.x reset
// Setup Clock System VLO_Period_Length = Measure_VLO_SW(); // Determine VLO period in us BCSCTL1 = CALBC1_1MHZ; // Set DCO = 1MHz DCOCTL = CALDCO_1MHZ; BCSCTL3 |= LFXT1S_2; // ACLK = VLO // Setup Timer_A TACCR0 = 20000 / VLO_Period_Length - 1; // Period length = 20ms, f = 50Hz TACCTL1 = CCIE; // TACCR1 interrupt enabled TACTL = TASSEL_1 + MC_1 + TAIE; // ACLK, Up mode, Overflow int // Setup SD16_A SD16CTL = SD16SSEL_1; // Use SMCLK SD16INCTL0 = SD16GAIN_4 + SD16INCH_0; // Use channel A0, gain 4x SD16CCTL0 = SD16SNGL + SD16DF + SD16IE; // Single conversion, 2s compl., // 256OSR, enable interrupts
while (1) { __bis_SR_register(LPM3_bits + GIE); // Wait for conversion result Temp = SD16MEM0; // Get result Log[LogPtr++] = Temp; // Log data for test purposes LogPtr &= 0x0f; // Wrap buffer pointer // Re-adjust boundaries in case of a new low-light condition if (Temp < Min) // Lower minimum found? { Min = Temp; // Re-adjust boundaries Max = Temp + RESULT_DELTA; } // Limit measured value to Max boundary if (Temp > Max) Temp = Max; // Calculate PWM duty cycle based on the relative brightness compared // to the Min / Max limits and assign result to TACCR1. // // TACCR1 Max - SD16_Result // -------- = ------------------- // TACCR0 Max - Min TACCR1 = (long)TACCR0 * ((long)Max - Temp) / ((long)Max - Min); } }
// Timer_A interrupt service routine #pragma vector=TIMERA1_VECTOR __interrupt void Timer_A1_ISR(void) { static unsigned int TA_PrdCtr = 0; switch (__even_in_range(TAIV, 0x0e)) { case 0x02 : // TACCR1 CCIFG P1OUT &= ~0x01; // Disable LED on P1.0 TA_PrdCtr++; // Increment Timer_A period counter if (TA_PrdCtr == 10) // 200ms wrap (50Hz / 10 = 5Hz) { TA_PrdCtr = 0; // Reset Timer_A period counter SD16AE |= SD16AE0; // Enable analog function for P1.0 SD16CTL |= SD16REFON; // Enable SD16_A 1.2V Vref SD16CCTL0 |= SD16SC; // Start 1st SD16 conversion // Switch over to LPM0 on exit, as the DCO is sourcing the SD16_A __bic_SR_register_on_exit(LPM3_bits); __bis_SR_register_on_exit(LPM0_bits); } break; case 0x0a : // TAIFG if (TACCR1) // LED duty cycle > 0? { P1OUT |= 0x01; // Enable LED on P1.0 } break; } }
// SD16_A interrupt service routine #pragma vector = SD16_VECTOR __interrupt void SD16_ISR(void) { static unsigned char Flag = 0; // Flag is used to distinguish // between 1st and 2nd conversion if (Flag) // Flag set? -> "Real Conversion" { SD16AE &= ~SD16AE0; // Disable analog function for P1.0 SD16CTL &= ~SD16REFON; // Disable voltage reference __bic_SR_register_on_exit(LPM0_bits); // Wake-up MCU } else // Flag clear? -> "Settling Timer" { SD16CCTL0 |= SD16SC; // Start 2nd SD16 conversion } // (this is our "real" conversion) Flag ^= 0xff; SD16CCTL0 &= ~SD16IFG; // Clear interrupt flag }