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.

TM4C129ENCPDT: Occasional dropout reading when doing ADC

Part Number: TM4C129ENCPDT

Hi all.  I'm having a some ADC problems with this board, and I haven't found a solution.  I've done similar ADC tasks on some previous boards and not had this problem.  Same processor as well.  At this point, I've stripped it down to the most basic ADC code, and still see the issue.

I'm reading values from a 2 axis analog joystick.  I get 0-3V from it on each axis.  I've stripped the code down to just read one axis right now, since I can't figure out what is going on.

#define SEQ_NUM 3

uint32_t pui32ADC0Value[4];
uint32_t pui32ADC1Value[4];

void joystick_init(void) {
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);
SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);
GPIOPinTypeADC(GPIO_PORTD_BASE, GPIO_PIN_2);
ADCReferenceSet(ADC0_BASE, ADC_REF_EXT_3V);

ADCSequenceDisable(ADC0_BASE, SEQ_NUM);
ADCSequenceConfigure(ADC0_BASE, SEQ_NUM, ADC_TRIGGER_PROCESSOR, 0);
ADCSequenceStepConfigure(ADC0_BASE, SEQ_NUM, 0, ADC_CTL_CH13 | ADC_CTL_IE | ADC_CTL_END);

ADCSequenceEnable(ADC0_BASE, SEQ_NUM);
ADCIntClear(ADC0_BASE, SEQ_NUM);


}

void joystick_thread(void) {

while (1) {

ADCProcessorTrigger(ADC0_BASE, SEQ_NUM);

while (!ADCIntStatus(ADC0_BASE, SEQ_NUM, false)) {
}

ADCIntClear(ADC0_BASE, SEQ_NUM);
ADCSequenceDataGet(ADC0_BASE, SEQ_NUM, pui32ADC0Value);

inputvalue = (pui32ADC0Value[0] & 0xFFF);

sprintf(string, "raw = %d", inputvalue);
send_string_msg(STRING_COMMAND, 0, string);

SysCtlDelay(ui32SysClock / 120);

}

}

The send_string_msg() sends the value to a LED display.

 

It all seems really basic, but most of the time, I get the values expected from the ADC, but maybe 10% of the time, I get a value, 0, 3, 7, 15, 31, 63, 127, 255, you get the idea.  This causes havoc with the controls system.

It doesn't seem to matter what delay I put in the loop.  I've tried doing sequences, using sequence number 0 or 1 for example, and reading 4 values.  I've tried using the oversampling, but then I just get a value below the expected since more low values are averaged.  I've tried adding the ADC_CTL_SHOLD_16 values in the sequence step.  If I do a sequence of 4 steps for example, any one of the 4 samples at any time might be correct, or might be low, so say, one or two are low, the other two are expected.

Maybe the solution is right in front of me and I'm not seeing it, having looked at this for a while.  Any help is appreciated.

  • Hi,

      Your code is rather simple and I can't spot anything wrong with what you show. Perhaps there is something wrong with ADCCLK. Can you show how you set up SYSCLK and if you use ADCClockConfigSet to configure the ADCCLK?

  • I meant to say 0-6V on each axis.  It's 3V when centered.  If it's not being moved, I generally see around 2056 as an output.

  • Another thing is that you said it was working with other board. Can you try some experiments.

      - Run your newly stripped down code on the LaunchPad board? What do you see?

      - Run your past working code on your new board?

      - Run your newly stripped down code on your past working board?

      I hope with these experiments, they will provide some insights for troubleshooting.  

  • ui32SysClock = SysCtlClockFreqSet(SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_25MHZ | SYSCTL_CFG_VCO_480, 120000000);

    I'm not using ADCClockConfigSet.

  • I initially took my working code from another board, which was reading a difference sensor, but was also scaled down to approximately the same voltage range.  The port/pins were different, so I changed those, and the ADC channel, and expected it to work.  I got exactly the same issues I'm seeing now.  It "mostly" works, but with the dropouts.

    This is a custom board.  I might have an old Launchpad board somewhere, but haven't seen it in maybe 5 years.  The a lot of the board design for this board was from the previous one.  In general, we try to maintain the same CAN ports for example.  This time, because of some other peripherals on the board, we had to move a few things around.

  • Hi,

      I don't really see a problem with the clock configuration. If you don't use ADCClockConfigSet to specify the clock source for ADC then it defaults to PIOSC which is fine. At 16Mhz PIOSC will provide for 1Msps if you keep everything at default.

  • I initially took my working code from another board, which was reading a difference sensor, but was also scaled down to approximately the same voltage range.  The port/pins were different, so I changed those, and the ADC channel, and expected it to work.  I got exactly the same issues I'm seeing now.  It "mostly" works, but with the dropouts.

    Can you also try to tie your ADC input to either 0, 1.65V and 3.3V. Do you consistently read around 0, 0x7FF or 0xFFF from pui32ADC0Value[0]?

    Please also try this TivaWare example and what does it show? You can find the example in C:\ti\TivaWare_C_Series-2.2.0.295\examples\peripherals\adc\single_ended.

    #include <stdbool.h>
    #include <stdint.h>
    #include "inc/hw_memmap.h"
    #include "driverlib/adc.h"
    #include "driverlib/gpio.h"
    #include "driverlib/pin_map.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/uart.h"
    #include "utils/uartstdio.h"
    
    //*****************************************************************************
    //
    //! \addtogroup adc_examples_list
    //! <h1>Single Ended ADC (single_ended)</h1>
    //!
    //! This example shows how to setup ADC0 as a single ended input and take a
    //! single sample on AIN0/PE3.
    //!
    //! This example uses the following peripherals and I/O signals.  You must
    //! review these and change as needed for your own board:
    //! - ADC0 peripheral
    //! - GPIO Port E peripheral (for AIN0 pin)
    //! - AIN0 - PE3
    //!
    //! The following UART signals are configured only for displaying console
    //! messages for this example.  These are not required for operation of the
    //! ADC.
    //! - UART0 peripheral
    //! - GPIO Port A peripheral (for UART0 pins)
    //! - UART0RX - PA0
    //! - UART0TX - PA1
    //!
    //! This example uses the following interrupt handlers.  To use this example
    //! in your own application you must add these interrupt handlers to your
    //! vector table.
    //! - None.
    //
    //*****************************************************************************
    
    //*****************************************************************************
    //
    // This function sets up UART0 to be used for a console to display information
    // as the example is running.
    //
    //*****************************************************************************
    void
    InitConsole(void)
    {
        //
        // Enable GPIO port A which is used for UART0 pins.
        // TODO: change this to whichever GPIO port you are using.
        //
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
    
        //
        // Configure the pin muxing for UART0 functions on port A0 and A1.
        // This step is not necessary if your part does not support pin muxing.
        // TODO: change this to select the port/pin you are using.
        //
        GPIOPinConfigure(GPIO_PA0_U0RX);
        GPIOPinConfigure(GPIO_PA1_U0TX);
    
        //
        // Enable UART0 so that we can configure the clock.
        //
        SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
    
        //
        // Use the internal 16MHz oscillator as the UART clock source.
        //
        UARTClockSourceSet(UART0_BASE, UART_CLOCK_PIOSC);
    
        //
        // Select the alternate (UART) function for these pins.
        // TODO: change this to select the port/pin you are using.
        //
        GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
    
        //
        // Initialize the UART for console I/O.
        //
        UARTStdioConfig(0, 115200, 16000000);
    }
    
    //*****************************************************************************
    //
    // Configure ADC0 for a single-ended input and a single sample.  Once the
    // sample is ready, an interrupt flag will be set.  Using a polling method,
    // the data will be read then displayed on the console via UART0.
    //
    //*****************************************************************************
    int
    main(void)
    {
    #if defined(TARGET_IS_TM4C129_RA0) ||                                         \
        defined(TARGET_IS_TM4C129_RA1) ||                                         \
        defined(TARGET_IS_TM4C129_RA2)
        uint32_t ui32SysClock;
    #endif
    
        //
        // This array is used for storing the data read from the ADC FIFO. It
        // must be as large as the FIFO for the sequencer in use.  This example
        // uses sequence 3 which has a FIFO depth of 1.  If another sequence
        // was used with a deeper FIFO, then the array size must be changed.
        //
        uint32_t pui32ADC0Value[1];
    
        //
        // Set the clocking to run at 20 MHz (200 MHz / 10) using the PLL.  When
        // using the ADC, you must either use the PLL or supply a 16 MHz clock
        // source.
        // TODO: The SYSCTL_XTAL_ value must be changed to match the value of the
        // crystal on your board.
        //
    #if defined(TARGET_IS_TM4C129_RA0) ||                                         \
        defined(TARGET_IS_TM4C129_RA1) ||                                         \
        defined(TARGET_IS_TM4C129_RA2)
        //
        // Note: SYSCTL_CFG_VCO_240 is a new setting provided in TivaWare 2.2.x and
        // later to better reflect the actual VCO speed due to SYSCTL#22.
        //
        ui32SysClock = SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ |
                                           SYSCTL_OSC_MAIN |
                                           SYSCTL_USE_PLL |
                                           SYSCTL_CFG_VCO_240), 20000000);
    #else
        SysCtlClockSet(SYSCTL_SYSDIV_10 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN |
                       SYSCTL_XTAL_16MHZ);
    #endif
    
        //
        // Set up the serial console to use for displaying messages.  This is
        // just for this example program and is not needed for ADC operation.
        //
        InitConsole();
    
        //
        // Display the setup on the console.
        //
        UARTprintf("ADC ->\n");
        UARTprintf("  Type: Single Ended\n");
        UARTprintf("  Samples: One\n");
        UARTprintf("  Update Rate: 250ms\n");
        UARTprintf("  Input Pin: AIN0/PE3\n\n");
    
        //
        // The ADC0 peripheral must be enabled for use.
        //
        SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);
    
        //
        // For this example ADC0 is used with AIN0 on port E7.
        // The actual port and pins used may be different on your part, consult
        // the data sheet for more information.  GPIO port E needs to be enabled
        // so these pins can be used.
        // TODO: change this to whichever GPIO port you are using.
        //
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE);
    
        //
        // Select the analog ADC function for these pins.
        // Consult the data sheet to see which functions are allocated per pin.
        // TODO: change this to select the port/pin you are using.
        //
        GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_3);
    
        //
        // Enable sample sequence 3 with a processor signal trigger.  Sequence 3
        // will do a single sample when the processor sends a signal to start the
        // conversion.  Each ADC module has 4 programmable sequences, sequence 0
        // to sequence 3.  This example is arbitrarily using sequence 3.
        //
        ADCSequenceConfigure(ADC0_BASE, 3, ADC_TRIGGER_PROCESSOR, 0);
    
        //
        // Configure step 0 on sequence 3.  Sample channel 0 (ADC_CTL_CH0) in
        // single-ended mode (default) and configure the interrupt flag
        // (ADC_CTL_IE) to be set when the sample is done.  Tell the ADC logic
        // that this is the last conversion on sequence 3 (ADC_CTL_END).  Sequence
        // 3 has only one programmable step.  Sequence 1 and 2 have 4 steps, and
        // sequence 0 has 8 programmable steps.  Since we are only doing a single
        // conversion using sequence 3 we will only configure step 0.  For more
        // information on the ADC sequences and steps, reference the datasheet.
        //
        ADCSequenceStepConfigure(ADC0_BASE, 3, 0, ADC_CTL_CH0 | ADC_CTL_IE |
                                 ADC_CTL_END);
    
        //
        // Since sample sequence 3 is now configured, it must be enabled.
        //
        ADCSequenceEnable(ADC0_BASE, 3);
    
        //
        // Clear the interrupt status flag.  This is done to make sure the
        // interrupt flag is cleared before we sample.
        //
        ADCIntClear(ADC0_BASE, 3);
    
        //
        // Sample AIN0 forever.  Display the value on the console.
        //
        while(1)
        {
            //
            // Trigger the ADC conversion.
            //
            ADCProcessorTrigger(ADC0_BASE, 3);
    
            //
            // Wait for conversion to be completed.
            //
            while(!ADCIntStatus(ADC0_BASE, 3, false))
            {
            }
    
            //
            // Clear the ADC interrupt flag.
            //
            ADCIntClear(ADC0_BASE, 3);
    
            //
            // Read ADC Value.
            //
            ADCSequenceDataGet(ADC0_BASE, 3, pui32ADC0Value);
    
            //
            // Display the AIN0 (PE3) digital value on the console.
            //
            UARTprintf("AIN0 = %4d\r", pui32ADC0Value[0]);
    
            //
            // This function provides a means of generating a constant length
            // delay.  The function delay (in cycles) = 3 * parameter.  Delay
            // 250ms arbitrarily.
            //
    #if defined(TARGET_IS_TM4C129_RA0) ||                                         \
        defined(TARGET_IS_TM4C129_RA1) ||                                         \
        defined(TARGET_IS_TM4C129_RA2)
            SysCtlDelay(ui32SysClock / 12);
    #else
            SysCtlDelay(SysCtlClockGet() / 12);
    #endif
        }
    }

  • I have tied the ADC input to a known good bench supply, and at 3.3V for example, I still see the drop outs.  At 0V, I will see smaller blips, and less frequently.  I might see some value under 64 pop up occasionally, but much less frequently that I see at the 3V.  At 6V, where I should see a steady 4095, I see more frequent drops to lower values.

    I'm not able to run that code without a lot of changes, as I don't have those pins available, no uart, etc.  The only real difference I see are different values in SysCtlClockFreqSet(). 

    I appreciate your help trying to figure this out.

  • I wonder what is your external impedance on the ADC input pin? If it is too large from your voltage divider then it can affect the amount of time it takes to charge transfer the input voltage to the internal sampling capacitor. Compare your new board with your past working board. Do you see any significant difference? Preferably the external impedance to the pin should be small. 

     One more question is how many of these new boards do you have? Can you repeat the same problem on all the boards or just one board?

  • Can you also try to take sample at half the rate for experiment? This allows more time for the input voltage to settle which may give a clue as to if the external impedance is an issue. 

    ADCClockConfigSet(ADC0_BASE, ADC_CLOCK_SRC_PIOSC | ADC_CLOCK_RATE_HALF, 1);

  • I just tried this.  No apparent change in the lower values.  I also multiplied the delay by 10, so I'd get 1 second samples.  I see no change there either, still maybe one in five is below expected.  

    To answer the previous question, we usually get a handful of uncoated boards for development.  I sent the same code to the EE that designed the board, and he ran it on his sample with the same results.  He only had a bench supply to try with it.

  • Perhaps a few more things to check and experiment.

    - Is your external reference voltage stable?

    - Can you comment out ADCReferenceSet(ADC0_BASE, ADC_REF_EXT_3V) so it will use internal reference. 

    - You currently use ADC_CTL_CH13. Why don't you try other channels like ADC_CTL_CH0 and tie it to GND, 1.65V and 3.3V. What conversion values do you get?

     - For experiment, if possible, can you add a op-amp before the ADC pin?

      - I will really suggest if you can find the LaunchPad and run your code on it. It will help isolate if the problem is board related or software related. At the moment, I can't find anything wrong with your code though. 

  • I did try using the external reference, but I see the same fluctuations.

    I don't know how this image will come through, but this is the input line directly from the joystick to the pin on the M4.

  • I did try using the external reference, but I see the same fluctuations.

    I suppose you meant internal reference. 

    I don't know how this image will come through, but this is the input line directly from the joystick to the pin on the M4.

    Looks like you have a op-amp and low external impedance from what I can tell. 

    I really don't know what is wrong. I don't know if there is any cross-talk with CH13 you have.  Please do try other channels as I suggested and LaunchPad if you have one. For the TivaWare example I showed earlier, you just need to remove anything that is not available on your board. I hope you have AIN0 on your board that you can do experiment with - by tying them to either 0V, 1.65V or 3.3V. If AIN0, try any other available channels on your board and see if you can replicate the problem like CH13. 

  • Hi James,

    It seems your axis pots code has no debounce for control of unexpected pot flutter from the amplifier if any exists at all. Should Charles ideas not produce expected results I would check the errata sheet for input channels to avoid, if any. I can post a code piece that debounces the channel input removing flutter elements, just say so. 

  • That would be great to take a look at.  Thanks.

  • We found a way to bypass something else on the board and get directly to PE3/AIN0, so I changed the code to read from there instead.  The exact same issue, so I'm thinking that there must be something interrupting the conversion periodically.  The project has some other things going on, so I'm going to create a separate project just for this and see if it cleans anything up.  Thanks for your help.

  • Hi James,

      I'm going to close this thread for now. If you have some update, you can just write to this post again and it will automatically reopen the thread. 

  • Copy to project folder call init() then call debounce function with raw sequencer array variable cast like so, Mydebpouncedpot32bitVar=calltodebounce(rawadcpotdata). It can be called in the ADC trigger interrupt handler and returns debounced pot data from analog input.

    /cfs-file/__key/communityserver-discussions-components-files/908/analog_2D00_pot.zip