This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Measuring external voltage

Other Parts Discussed in Thread: CONTROLSUITE

I am using the adc_soc example to convert an analog voltage signal to a digital one. The pins that the example uses are ADCINA (J1.6) and ADCINA2 (J1.8). I first connected 

J1.6 : 3.3 V pin

J1.8 : GND pin 

and received very nice digital values of ~4096 and 0, respectively. What is important is that each of these functions individually. In other words, if I disconnect pin J1.8, for example, I still have a reading of 4096 V from J1.6.

 However, what I really want to do is measure the voltage signal from a battery pack (which is also divided by a voltage divider) which is equal to ~2.88 V. So, I connected

 J1.6 : 2.88 V

J1.8 : analog GND

 and received digital values of ~3200 and ~250, respectively. Moreover, when I measure these pins individually these values change to ~1900 and ~1800.

I realize that I need a good reference value (or I need to incorporate this into my code), but I am uncertain how I should proceed. I read that I need to do something with VREFLO and VREFHI, but that there are no pins designated for VREFLO and VREFHI. How should I set these reference values?

  • I read that I need to do something with VREFLO and VREFHI, but that there are no pins designated for VREFLO and VREFHI. How should I set these reference values?

    Usually you don't have to fiddle with these parameters. A stable Vref is enough for correct ADC readings. If your hardware has some noise or if the input signal itself is bit distorted then this might disturb ADC values for which you can use an external RC filter. The other way to eliminate the noise is to calculate the average of 'n' digital values.

    Regards,

    Gautam

  • Hi Laura,

    For the software in ControlSUITE for F28027, the default operation of the ADC will be internal reference mode, in which case you will not have to worry about supplying VREFHI/VREFHLO voltages.  The reference range will be VSSA to VSSA + 3.3V.  

    On this device VREFHI is double-bonded to channel A0, so you could supply a VREFHI voltage on this pin and then change the software to external reference mode if desired (but I don't think this is necessary for what you are trying to accomplish). 

    As far as the issue you are having, there are a couple things you can check:

    *Ensure that you signals are actually connected to the pins you are converting. I think it is very strange that you could convert 250 if the ADC input is connected to ground, much less 1800.  This is similar behavior to what I would expect if you were converting a floating input.  Can you elaborate a little bit on what the difference is between 'GND pin' in the first experiment and 'analog GND' is in the second experiment?

    *Ensure that the sampling code is meeting the first sample erratum (code provided in ControlSUITE will discard the first sample, but this may not still be the case if you have modified the code).

    It is also worth pointing out that if the resistance of your voltage divider is too high, you may not be able to get good conversion results.  See figure 6-26 in the datasheet for the ADC input model.  Whatever input you are driving to the ADC needs to ensure that the Ch capacitor is charged to within 1/2 LSB (about 400uV in internal reference mode) of its final value in the allotted S+H time (117ns if using the default/minimum ACQPS setting of 6).   

  • Thank you so much for your suggestions.

    To answer your questions: 'GND pin' is simply the GND pin on the C2000 on the bottom right-hand side next to 3V3. Analog ground is the attached to the negative side of the battery.

    I think that I simply need to connect the GND pin on the LaunchPad to the analog GND of the battery. I have attached two pictures. The first is of the connections. I used 20 and 30 ohm resistors to create a voltage of 2.87 V (as verified by the multimeter) going into J1.6. The other picture is of the results when J1.6 is connected and J1.8 is not. Surprisingly, J1.8 also has constant values around 2100 even when it is not connected. 

    The average of voltage 1 is 3213.

    3213*3.3/4096 = 2.59 V. This isn't exactly correct.  I updated these values and took the average several times and achieved the same result each time. 

    Any thoughts/ideas? I very much appreciate it.

     

  • Any thoughts/ideas? I very much appreciate it.

    Did you check the waveform of the signal fed to ADC (after the divider circuitry) on DSO?

    What is your observation?

    Regards,

    Gautam

  • Laura,

    I think your signal connections are OK.  You are feeding the signal input into J1.6, which corresponds to ADCINA4, so please confirm that your software is using the following:

    ADC_setSocChanNumber (myAdc, ADC_SocNumber_1, ADC_SocChanNumber_A4);

    Also, based on your picture, I think you are using 31K ohm resistor dividers (picture is a little fuzzy, but I read the color code as orange-brown-orange). I think this might be too high.  Try adding a 10nF-20nF capacitor at the output of your voltage divider as a charge sharing capacitor and/or reducing the resistor values.     

  • I have now simplified the set-up as much as possible. I have pins J5.1 and J5.2 powering a potentiometer. I am measuring the potentiometer voltage changes using a multimeter and ADCINA1 (J5.5). I plotted the results and found a linear trend, which eliminates the noise idea. I looked at the documentation: 

    TMS320x2802x, 2803x Piccolo Analog-to-Digital Converter (ADC) and Comparator Reference guide

    I also tried:

    -adding in InitAdc() and AdcOffsetSelfCal()

    -setting an external voltage (instead of using the internal one).

    -tried the setup with two different c2000 F28027 LaunchPads

    I have attached the code that I am using, although the only change that I have made from the example is converting the digital values of Voltage 1 into mV.

    I feel at this point that I am probably just missing something very obvious. Further ideas greatly appreciated!

  • //#############################################################################
    //
    // File: f2802x_examples_ccsv4/adc_soc/Example_F2802xAdcSoc.c
    //
    // Title: F2802x ADC Start-Of-Conversion (SOC) Example Program.
    //
    // Group: C2000
    // Target Device: TMS320F2802x
    //
    //! \addtogroup example_list
    //! <h1>ADC Start-Of-Conversion (SOC)</h1>
    //!
    //! Interrupts are enabled and the ePWM1 is setup to generate a periodic
    //! ADC SOC - ADCINT1. Two channels are converted, ADCINA4 and ADCINA2.
    //!
    //! Watch Variables:
    //!
    //! - Voltage1[10] - Last 10 ADCRESULT0 values
    //! - Voltage2[10] - Last 10 ADCRESULT1 values
    //! - ConversionCount - Current result number 0-9
    //! - LoopCount - Idle loop counter
    //
    // (C) Copyright 2012, Texas Instruments, Inc.
    //#############################################################################
    // $TI Release: f2802x Support Library v210 $
    // $Release Date: Mon Sep 17 09:13:31 CDT 2012 $
    //#############################################################################

    #include "DSP28x_Project.h" // Device Headerfile and Examples Include File

    #include "f2802x_common/include/adc.h"
    #include "f2802x_common/include/clk.h"
    #include "f2802x_common/include/flash.h"
    #include "f2802x_common/include/gpio.h"
    #include "f2802x_common/include/pie.h"
    #include "f2802x_common/include/pll.h"
    #include "f2802x_common/include/pwm.h"
    #include "f2802x_common/include/wdog.h"

    // Prototype statements for functions found within this file.
    interrupt void adc_isr(void);
    void Adc_Config(void);

    // Global variables used in this example:
    uint16_t LoopCount;
    uint16_t ConversionCount;
    uint16_t Voltage1[10];
    uint16_t Voltage2[10];

    ADC_Handle myAdc;
    CLK_Handle myClk;
    FLASH_Handle myFlash;
    GPIO_Handle myGpio;
    PIE_Handle myPie;
    PWM_Handle myPwm1;

    void main(void)
    {

    //InitAdc();
    //AdcOffsetSelfCal();

    CPU_Handle myCpu;
    PLL_Handle myPll;
    WDOG_Handle myWDog;

    // Initialize all the handles needed for this application
    myAdc = ADC_init((void *)ADC_BASE_ADDR, sizeof(ADC_Obj));
    myClk = CLK_init((void *)CLK_BASE_ADDR, sizeof(CLK_Obj));
    myCpu = CPU_init((void *)NULL, sizeof(CPU_Obj));
    myFlash = FLASH_init((void *)FLASH_BASE_ADDR, sizeof(FLASH_Obj));
    myGpio = GPIO_init((void *)GPIO_BASE_ADDR, sizeof(GPIO_Obj));
    myPie = PIE_init((void *)PIE_BASE_ADDR, sizeof(PIE_Obj));
    myPll = PLL_init((void *)PLL_BASE_ADDR, sizeof(PLL_Obj));
    myPwm1 = PWM_init((void *)PWM_ePWM1_BASE_ADDR, sizeof(PWM_Obj));
    myWDog = WDOG_init((void *)WDOG_BASE_ADDR, sizeof(WDOG_Obj));

    // Perform basic system initialization
    WDOG_disable(myWDog);
    CLK_enableAdcClock(myClk);
    (*Device_cal)();

    //Select the internal oscillator 1 as the clock source
    CLK_setOscSrc(myClk, CLK_OscSrc_Internal);

    // Setup the PLL for x12 /2 which will yield 60Mhz = 10Mhz * 12 / 2
    PLL_setup(myPll, PLL_Multiplier_12, PLL_DivideSelect_ClkIn_by_2);

    // Disable the PIE and all interrupts
    PIE_disable(myPie);
    PIE_disableAllInts(myPie);
    CPU_disableGlobalInts(myCpu);
    CPU_clearIntFlags(myCpu);

    // If running from flash copy RAM only functions to RAM
    #ifdef _FLASH
    memcpy(&RamfuncsRunStart, &RamfuncsLoadStart, (size_t)&RamfuncsLoadSize);
    #endif

    // Setup a debug vector table and enable the PIE
    PIE_setDebugIntVectorTable(myPie);
    PIE_enable(myPie);

    // Register interrupt handlers in the PIE vector table
    PIE_registerPieIntHandler(myPie, PIE_GroupNumber_10, PIE_SubGroupNumber_1, (intVec_t)&adc_isr);

    // Initialize the ADC
    ADC_enableBandGap(myAdc);
    ADC_enableRefBuffers(myAdc);
    ADC_powerUp(myAdc);
    ADC_enable(myAdc);
    ADC_setVoltRefSrc(myAdc, ADC_VoltageRefSrc_Int);

    // Enable ADCINT1 in PIE
    PIE_enableAdcInt(myPie, ADC_IntNumber_1);
    // Enable CPU Interrupt 1
    CPU_enableInt(myCpu, CPU_IntNumber_10);
    // Enable Global interrupt INTM
    CPU_enableGlobalInts(myCpu);
    // Enable Global realtime interrupt DBGM
    CPU_enableDebugInt(myCpu);

    LoopCount = 0;
    ConversionCount = 0;

    // Configure ADC
    //Note: Channel ADCINA4 will be double sampled to workaround the ADC 1st sample issue for rev0 silicon errata
    ADC_setIntPulseGenMode(myAdc, ADC_IntPulseGenMode_Prior); //ADCINT1 trips after AdcResults latch
    ADC_enableInt(myAdc, ADC_IntNumber_1); //Enabled ADCINT1
    ADC_setIntMode(myAdc, ADC_IntNumber_1, ADC_IntMode_ClearFlag); //Disable ADCINT1 Continuous mode
    ADC_setIntSrc(myAdc, ADC_IntNumber_1, ADC_IntSrc_EOC2); //setup EOC2 to trigger ADCINT1 to fire
    ADC_setSocChanNumber (myAdc, ADC_SocNumber_0, ADC_SocChanNumber_A1); //set SOC0 channel select to ADCINA4
    ADC_setSocChanNumber (myAdc, ADC_SocNumber_1, ADC_SocChanNumber_A1); //set SOC1 channel select to ADCINA4
    //ADC_setSocChanNumber (myAdc, ADC_SocNumber_2, ADC_SocChanNumber_A2); //set SOC2 channel select to ADCINA2
    ADC_setSocTrigSrc(myAdc, ADC_SocNumber_0, ADC_SocTrigSrc_EPWM1_ADCSOCA); //set SOC0 start trigger on EPWM1A, due to round-robin SOC0 converts first then SOC1
    ADC_setSocTrigSrc(myAdc, ADC_SocNumber_1, ADC_SocTrigSrc_EPWM1_ADCSOCA); //set SOC1 start trigger on EPWM1A, due to round-robin SOC0 converts first then SOC1
    ADC_setSocTrigSrc(myAdc, ADC_SocNumber_2, ADC_SocTrigSrc_EPWM1_ADCSOCA); //set SOC2 start trigger on EPWM1A, due to round-robin SOC0 converts first then SOC1, then SOC2
    ADC_setSocSampleWindow(myAdc, ADC_SocNumber_0, ADC_SocSampleWindow_7_cycles); //set SOC0 S/H Window to 7 ADC Clock Cycles, (6 ACQPS plus 1)
    ADC_setSocSampleWindow(myAdc, ADC_SocNumber_1, ADC_SocSampleWindow_7_cycles); //set SOC1 S/H Window to 7 ADC Clock Cycles, (6 ACQPS plus 1)
    ADC_setSocSampleWindow(myAdc, ADC_SocNumber_2, ADC_SocSampleWindow_7_cycles); //set SOC2 S/H Window to 7 ADC Clock Cycles, (6 ACQPS plus 1)

    // Enable PWM clock
    CLK_enablePwmClock(myClk, PWM_Number_1);

    // Setup PWM
    PWM_enableSocAPulse(myPwm1); // Enable SOC on A group
    PWM_setSocAPulseSrc(myPwm1, PWM_SocPulseSrc_CounterEqualCmpAIncr); // Select SOC from from CPMA on upcount
    PWM_setSocAPeriod(myPwm1, PWM_SocPeriod_FirstEvent); // Generate pulse on 1st event
    PWM_setCmpA(myPwm1, 0x0080); // Set compare A value
    PWM_setPeriod(myPwm1, 0xFFFF); // Set period for ePWM1
    PWM_setCounterMode(myPwm1, PWM_CounterMode_Up); // count up and start
    CLK_enableTbClockSync(myClk);

    // Wait for ADC interrupt
    for(;;)
    {
    LoopCount++;
    }

    }


    interrupt void adc_isr(void)
    {

    //discard ADCRESULT0 as part of the workaround to the 1st sample errata for rev0
    Voltage1[ConversionCount] = ADC_readResult(myAdc, ADC_ResultNumber_1)*3.3/4096*1000;
    //Voltage2[ConversionCount] = ADC_readResult(myAdc, ADC_ResultNumber_2);

    // If 10 conversions have been logged, start over
    if(ConversionCount == 9)
    {
    ConversionCount = 0;
    }
    else ConversionCount++;

    // Clear ADCINT1 flag reinitialize for next SOC
    ADC_clearIntFlag(myAdc, ADC_IntNumber_1);
    // Acknowledge interrupt to PIE
    PIE_clearInt(myPie, PIE_GroupNumber_10);

    return;
    }

  • Laura, 

    You may want to be careful with this operation:

    Voltage1[ConversionCount] = ADC_readResult(myAdc, ADC_ResultNumber_1)*3.3/4096*1000;

    Because the F28027 does not have a floating point unit.  You may want to multiply by 3300 (cast to 32 bit) and then divide by 4096.    

    Otherwise your code seems fine.  Your graph shows the ADC is basically operating OK, but the gain error is >2% and the offset error is about 24mV (datasheet gain error is 1.4%, but over the full temperature and supply range, and offset error is 16mV, if offset-calibration is performed, but again over the full device conditions)? 

    Have you tried supplying the ADC using the DC setting of a function generator?  This may have more drive strength and a lower source impedance.  

     

  • Thanks for your suggestion of 3.3/4096*1000 being changed. I also tried using a function generator and still have the offset of 24mV. The good news is that it seems to be precise, if not accurate.

  • Laura,

    Ok, next step then is offset self-calibration.  Take a look at the provided example code to understand basically how this works.  You can run that code at the beginning of your program, after device calibration function has run.

    Do be careful, as the function was meant to be a demonstration and not a call and forget type of function.  As such, there may be some leftover configurations that could affect your sampling if you don't undo them (particularly the ADCINT triggers).  

    This should improve the offset performance, but for your end application you will probably want to  periodically sample the VREFLO voltage and use it to re-calibrate the offset. 

  • Thank you Devin. To what example code are you referring? I looked in ControlSuite and did not see anything labeled offset calibration, but perhaps I am looking in the wrong place. 

  • void AdcOffsetSelfCal(), which is in F2802x_Adc.c

  • I found it. It is the AdcOffsetSelfCal() function in DSP2802x(3x)_Adc.c in the common header file, right? I think I will take your latter suggestion and try to periodically sample the VREFLO voltage and use it to re-calibrate the offset. I'll let you know how it goes. Hoping to get this done soon!

  • I tried placing the AdcOffsetCal() right below the  (*Device_cal)(); I tinkered with it for a while and I am still getting the same types of values. Very odd. So, I may just begin to take measurements that I can use to create a calibration function in the code. If you have any more advice I am always open to hear it. Thank you for your help.

    -Laura

  • Hi Laura,

    If you open up the offset calibration, you should be able to see how to convert on the internal VREFLO connection.  

    After you run the offset self-calibration, try converting this voltage a couple times. If it isn't really close to 0, then something is not working right.