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.

TM4C123GH6PM: ADC reads correctly once, then is much higher subsequently

Part Number: TM4C123GH6PM

I've spent a lot of time debugging this problem. Let me quickly explain, post source and wait for further help:

I have ADC0 and ADC1 reading samples from pins PE0 and PB5 respectively. When I step through the loop you'll find below, the ADC0 (PE0) result is exactly what I'm expecting, but during the first time through the loop I observe that 4-bits of "underflow" flags are set (ADC0 - USTAT) and all subsequent calls return a result that is approximately 0x1FF too high. This is not double the expected result, but something else.

The following diagram shows that I am expecting 0x234 as a result as hex(int((VOUT/3/3)*4096)) = 0x234. Generally, the first time though the loop, I get 0x22D - 0x236. Subsequent results are between 0x41D and 0x429 (not double the expected result). The voltage appearing at PE0 was verified with an o-scope and is as expected ( ~0.456V) - where the expected result is VOUT = VIN*(R2/(R1+R2)) = 0x4545V.

Here's a simple diagram of the resistor divider going into PE0:

---- VIN (5.0V from USB) ----

           R1 = 10K

           -------------------------------( VOUT ) PE0

          R2 = 1K

---------GND-------------

/************************************/
/*   Task                           */
/************************************/
Void BatteryMonitor(UArg arg0, UArg arg1)
{
    uint32_t u32Voltage;
    uint32_t u32Light;
    Error_Block ebBatMonAccess;

    int32_t debug;

    // Task Hello
    debugPrint("BatteryMonitor Task started\n");

    /* Construct a Semaphore object to be use as a resource lock, initial count 1 */
    /* Obtain instance handle */
    Error_init(&ebBatMonAccess);
    semBatMonAccess = Semaphore_create(1, NULL, &ebBatMonAccess);
    if ( semBatMonAccess == NULL ) {
        System_abort("ERROR: BatteryMonitor: Semaphore creation failed");
    }

    InitMonitorChannel();

    debugPrint("BatteryMonitor Task init done\n");

    // task loop
    while ( true ) {

        //
        // Trigger the ADC conversion.
        //
        ADCProcessorTrigger(ADC0_BASE, 3); // voltage
        //
        // Wait until the sample sequence has completed
        //
        while(!ADCIntStatus(ADC0_BASE, 3, false)) {}

        //
        // Read ADC Value.
        //
        debug = ADCSequenceDataGet(ADC0_BASE, 3, &u32Voltage);

        // From adc.c - ADCIntClear() description
        //! \note Because there is a write buffer in the Cortex-M processor, it may
        //! take several clock cycles before the interrupt source is actually cleared.
        ADCIntClear(ADC0_BASE, 3);

        debugPrint("INFO:BatteryMonitor:ADCSequenceDataGet()=%d,  u32Voltage = 0x%x\n", debug, u32Voltage);

        // mutex-like wait. Don't access if others are accessing
        Semaphore_pend(semBatMonAccess, BIOS_WAIT_FOREVER);
        adcVoltage = (uint16_t)(u32Voltage & 0xFFFF);
        Semaphore_post(semBatMonAccess);

        //
        // Trigger the ADC conversion.
        //
        ADCProcessorTrigger(ADC1_BASE, 0); // current
        //
        // Wait until the sample sequence has completed
        //
        while(!ADCIntStatus(ADC1_BASE, 0, false)) {}
        //
        // Read ADC Value.
        //
        ADCSequenceDataGet(ADC1_BASE, 0, &u32Light);

        // From adc.c - ADCIntClear() description
        //! \note Because there is a write buffer in the Cortex-M processor, it may
        //! take several clock cycles before the interrupt source is actually cleared.
        ADCIntClear(ADC1_BASE, 0);

        debugPrint("INFO:BatteryMonitor: u32Light = 0x%x\n", u32Light);

        // mutex-like wait. Don't access if others are accessing
        Semaphore_pend(semBatMonAccess, BIOS_WAIT_FOREVER);
        adcLight = (uint16_t)(u32Light & 0xFFFF);
        Semaphore_post(semBatMonAccess);

        Task_sleep(ADC_SLEEP);
    }
}

/*void ADC0_ISR(void)
{
    ADC0_ISC_R |= 0x01; //write one to clear the interrupt
}

void ADC1_ISR(void)
{
    ADC1_ISC_R |= 0x01; //write one to clear the interrupt
}*/


/************************************/
/*   Helper Functions               */
/************************************/
static void InitMonitorChannel(void)
{
    uint32_t debug;

    SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0); // This is for battery monitoring
    while(!SysCtlPeripheralReady(SYSCTL_PERIPH_ADC0)) {}

    ADCClockConfigSet(ADC0_BASE, ADC_CLOCK_SRC_PIOSC | ADC_CLOCK_RATE_FULL, 1);

    GPIOPinTypeGPIOInput(GPIO_PORTE_BASE, GPIO_PIN_0);
    GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_0); // Battery Voltage Monitoring

    ADCReferenceSet(ADC0_BASE, ADC_REF_INT);
    ADCSequenceDisable(ADC0_BASE, 3); // voltage monitor
    ADCSequenceConfigure(ADC0_BASE, 3, ADC_TRIGGER_PROCESSOR, 0); // voltage monitor on sequence 3
    ADCSequenceStepConfigure(ADC0_BASE, 3, 0, ADC_CTL_IE | ADC_CTL_END |  ADC_CTL_CH3); // voltage monitor
    ADCSequenceEnable(ADC0_BASE, 3); // voltage monitor


    SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC1);
    while(!SysCtlPeripheralReady(SYSCTL_PERIPH_ADC1)) {}

    ADCClockConfigSet(ADC1_BASE, ADC_CLOCK_SRC_PIOSC | ADC_CLOCK_RATE_FULL, 1);

    GPIOPinTypeGPIOInput(GPIO_PORTB_BASE, GPIO_PIN_5);
    GPIOPinTypeADC(GPIO_PORTB_BASE, GPIO_PIN_5); // Current Monitoring

    ADCReferenceSet(ADC1_BASE, ADC_REF_INT);
    ADCSequenceDisable(ADC1_BASE, 0); // voltage monitor
    ADCSequenceConfigure(ADC1_BASE, 0, ADC_TRIGGER_PROCESSOR, 0); // current monitor on sequence 4
    ADCSequenceStepConfigure(ADC1_BASE, 0, 0, ADC_CTL_IE | ADC_CTL_END |  ADC_CTL_CH11); // current monitor
    ADCSequenceEnable(ADC1_BASE, 0); // current monitor

    // debug:
    debug = ADCReferenceGet(ADC0_BASE);
    switch (debug) {
        case ADC_REF_INT:
            debugPrint("DEBUG:BatteryMonitor: ADCReferenceGet(ADC0_BASE) = ADC_REF_INT\n");
            break;
        case ADC_REF_EXT_3V:
            debugPrint("DEBUG:BatteryMonitor: ADCReferenceGet(ADC0_BASE) = ADC_REF_EXT_3V\n");
            break;
        case ADC_REF_EXT_1V:
            debugPrint("DEBUG:BatteryMonitor: ADCReferenceGet(ADC0_BASE) = ADC_REF_EXT_1V\n");
            break;
        default:
            debugPrint("DEBUG:BatteryMonitor: ADCReferenceGet(ADC0_BASE) = super ****ed\n");
            break;
    }

    debug = ADCReferenceGet(ADC1_BASE);
    switch (debug) {
        case ADC_REF_INT:
            debugPrint("DEBUG:BatteryMonitor: ADCReferenceGet(ADC1_BASE) = ADC_REF_INT\n");
            break;
        case ADC_REF_EXT_3V:
            debugPrint("DEBUG:BatteryMonitor: ADCReferenceGet(ADC1_BASE) = ADC_REF_EXT_3V\n");
            break;
        case ADC_REF_EXT_1V:
            debugPrint("DEBUG:BatteryMonitor: ADCReferenceGet(ADC1_BASE) = ADC_REF_EXT_1V\n");
            break;
        default:
            debugPrint("DEBUG:BatteryMonitor: ADCReferenceGet(ADC1_BASE) = super ****ed\n");
            break;
    }
}

  • It looks like you are using an RTOS. Is ADC0 used for any other measurements? If your sample time is too short for your source impedance, and a higher voltage is measured before you measure the 0.456V, the sample capacitor inside of the ADC will not have sufficient time to stabilize to the new voltage. If this is the case, you can reduce source impedance or increase sample time.
  • ADC0 is only connected to PE0 (AIN3). ADC1 is used for PB5 (AIN11). The PE0 line is only connected to the incoming power supply, which presently is the 5V USB line, so there should be no issue with things like settling times, because it never changes in a meaningful way.
  • To split the question of hardware/software, can you run the attached program? It just does continuous conversions on AIN3 from PE0. It outputs text on UART0, PA1. If that pin is not available, you should remove the calls to ConfigureUART and UARTprintf.

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

    Use the "file" "Import" feature of Code Composer to add this project to your workspace.

  • The variables "average1" and "average2" are generally sitting around 0x24F, which is pretty much what we expected.

    adc = 0x24F
    vadc = 3.3*(adc/4096.0)
    // vadc -> 0.47615V
    r1 = 10000.0
    r2 = 1000.0
    vin = vadc*(r1+r2)/r2
    // vin -> 5.238V

  • I measured "vadc" (AIN3 in my schematic) with my o-scope and I quickly estimated that it is ~476mV. So, your code is producing accurate readings on my hardware.
  • I found the source of the error, although I don't thoroughly understand it.

    I disabled all the other tasks in my TI-RTOS and the ADC error went away. I re-enabled them one by one until it reappeared. At that point I found the exact line that caused introduced the error in the ADC readings:

    GPIOPinTypeGPIOOutput(GPIO_PORTE_BASE,  GPIO_PIN_3); // NOTE: Never drive this line high. Open-drain drive only

    If I locked this task in a while loop immediately after this line, the ADC error presents itself. As you can see in the comment, I should have been using the following code instead:

    GPIOPinTypeGPIOOutputOD(GPIO_PORTE_BASE,  GPIO_PIN_3); // NOTE: Never drive this line high. Open-drain drive only

    Once I fixed this line and unlocked that task and allowed it to run, the rest of the project and ADC readings went as expected. Previously, I had configured this pin in EK_TM4C123GXL.c as follows:

    GPIO_PinConfig gpioPinConfigs[] = {
        ....
        GPIOTiva_PE_3 | GPIO_CFG_OUT_OD_NOPULL | GPIO_CFG_OUT_STR_MED | GPIO_CFG_OUT_HIGH,
        ....
    }

    I probably am not using the legacy example code correctly by both configuring the pin in this structure as well as with the GPIOPinTypeGPIOOutputOD(), but I'm not sure why it would have thrown off other pins in the port in this way.

    Either way, the code appears to be working correctly now.

  • Hi Mark,
    If you had a drive conflict on PE3, the microcontroller driving high (instead of open drain) and the external circuitry driving low, this might create a drop in the 3.3V supply locally on the chip. This in turn might pull your internal reference down resulting in high than expected conversion values.
  • So, I'm still having a problem. Before, I thought I solved my question because I found that I was driving PE3 high as an output instead of an open-drain output. However, at the time, I did not have the radio mounted to the board. Now that I do, the radio is pulling PE3 high (it's the radio's reset line) and I can successfully configure it as an open drain.

    The problem is, if that line is high at all, my ADC reading on PE0 is wrong. I've pulled chips off my board looking for short circuits or any possible way that 3V occurring on PE3 (pin6) can be affecting my ADC readings on PE0 (pin 9). The traces on the board do not go near each other and the voltage occurring at PE0(AIN3) is still correct according to my o-scope. So, the voltage occurring at PE0 does not change whether or not I have the radio installed. Either case, it is fine. But if I do have the radio installed, the ADC reading is wrong.

    I've isolated the effect down to the reset line (PE3) of the radio. I disconnected lines until I was sure which pin was causing the error.  Furthermore, if I hold the radio in reset by holding the line down with the following, the error does not occur.

    How can a voltage appearing at PE3 affect my ADC reading at PE0?

    #define PORT_RADIO_CONTROL      GPIO_PORTE_BASE
    #define PIN_RADIO_RESET         GPIO_PIN_3
    
    GPIOPinTypeGPIOOutputOD(PORT_RADIO_CONTROL,  PIN_RADIO_RESET); // NOTE: Never drive this line high. Open-drain drive only
    GPIOPinWrite(PORT_RADIO_CONTROL, PIN_RADIO_RESET, 0); // pull the reset line down! No ADC error. Let it rise and it will die!

  • Are you sure that the radio is not driving that line? Perhaps try adding a small series resistor so that you could calculate the current being sunk by PE3.
  • The radio IS driving that line. The radio has a pull-up on its reset line. Their datasheet indicates that we should either connect it to a physical switch to GND or drive it with a open drain output (which I am doing).

    Let me clarify something that isn't in TI's documentation:

    After PE3 is configured as an open drain output, the pin will be left floating (pulled high by the radio) if I do the following:

    GPIOPinWrite(PORT_RADIO_CONTROL, PIN_RADIO_RESET, PIN_RADIO_RESET); // Allow pin to float

    According to my o-scope, the pin sits at a high voltage with this setting. It also messes up my ADC.

    The o-scope shows the pin is pulled to GND with the following:

    GPIOPinWrite(PORT_RADIO_CONTROL, PIN_RADIO_RESET, 0); // pull into RESET

    Is my understanding of an open drain output correct here? You first configure it with

    GPIOPinTypeGPIOOutputOD(PORT_RADIO_CONTROL,  PIN_RADIO_RESET); // NOTE: Never drive this line high. Open-drain drive only

    Then you either let it float to whatever voltage it is driven to externally or you can tie it to GND via the open drain. Right? If that's true, it seems to me that using GPIOPinWrite() would have the opposite affect, where a '1' would tie it to GND and a '0' would let it float, but the o-scope shows what's described above.

  • No, writing a 1, makes the pin open drain, which allows the pin to be pulled high. Writing a 0 causes the pin to drive low.
  • Well, that's definitely not what I'm seeing. When launching the debugger, and all of the pins are tristated prior to configuration, the reset line is pulled up as expected. But the code above is what I'm seeing. Looking at the registers, GPIO_PORTE->GPIO_ODR = 0x08 and GPIO_DIR = 0x3E, which means pin 3 (PE3) is definitely configured as an open drain output and PE0 (AIN3) is an input.

    Any idea what I'm doing wrong? Was I using the above functions correctly, or are there other functions required for open drain outputs?

    I've spent a lot of time on this and I'm inclined to scrap the board and move the ADC pin to PORTB.
  • I don't know what is going wrong in your setup. I took the example file"single_ended.c" from C:\ti\TivaWare_C_Series-2.1.4.178\examples\peripherals\adc and changed it to read AIN3 on PE0. I then configured PE3 as an open drain output. I have an external pullup resistor on PE3. The code toggles PE3 low while it does an ADC conversion on PE0. It goes low for 5uS and the conversions happen every 250mS. Everything works as expected with no interference between the PE3 open drain output and the PE0 analog input.

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