C2000WARE: F28P55x: ADC_setVREF() selects the wrong FSR in external reference mode

Part Number: C2000WARE
Other Parts Discussed in Thread: TMS320F28P550SG,

Device: TMS320F28P550SG (F28P55x)
C2000Ware: 26.01.00.00 (also present in 26.00.00.00 and 5.05.00.00)
File: driverlib/f28p55x/driverlib/adc.c, ADC_setVREF() at line 202

Summary
-------
ADC_setVREF() writes the reference-range bits from the refVoltage value alone,
without considering refMode. Because the two range enumerations alias, this
selects the wrong full-scale range whenever the external reference is used.

Issue 1 - external-mode range is inverted
-----------------------------------------
adc.h:445-450 defines both range sets over the same two values:

    ADC_REFERENCE_3_3V      = 0U,
    ADC_REFERENCE_2_5V      = 1U,
    ADC_REFERENCE_VREFHI    = 0U,
    ADC_REFERENCE_2xVREFHI  = 1U

adc.c:267 then decides the ANAREFPCTL range bits [14:10] on that value alone:

    if(refVoltage == ADC_REFERENCE_3_3V)   // i.e. value == 0
    {
        ... |= ((ASYSCTL_ANAREF_ADCA | ... ) << 10);   // set
    }
    else
    {
        ... &= ~((ASYSCTL_ANAREF_ADCA | ... ) << 10);  // clear
    }

Setting those bits selects the *higher* range. In internal mode the higher
range is 3.3 V, which is value 0, so the mapping is correct. In external mode
the higher range is 2x VREFHI, which is value 1 - so the mapping is inverted:

    ADC_setVREF(base, ADC_REFERENCE_EXTERNAL, ADC_REFERENCE_VREFHI)
        -> sets the bits -> FSR = 2 x VREFHI

Requesting a 1x VREFHI full scale therefore configures 2x VREFHI. On our board
(external reference, VREFHI - VREFLO = 3.0 V) the readings were consistent with
a 6.0 V FSR - roughly half the expected value - and no calibration constant
corrects it, because the error is in the reference configuration rather than
the scaling. Explicitly clearing the range bits restored agreement with the
3.0 V reference.

A single mode-blind comparison cannot be correct for both sets. The branch
needs to depend on refMode:

    if(refMode == ADC_REFERENCE_EXTERNAL)
    {
        setBits = (refVoltage == ADC_REFERENCE_2xVREFHI);
    }
    else
    {
        setBits = (refVoltage == ADC_REFERENCE_3_3V);
    }

Issue 2 - VDDA branch writes the wrong register
------------------------------------------------
In the ADC_REFERENCE_VDDA branch, adc.c:250 and adc.c:256 both write
ANAREFPCTL. The second should be ANAREFNCTL, matching the INTERNAL and
EXTERNAL branches above it, which each write both registers. As written,
ANAREFNCTL is never updated for VDDA mode.

Issue 3 - documentation contradicts itself
-------------------------------------------
adc.h:4097 states of refVoltage: "This is ignored when the reference mode is
external." The implementation applies it unconditionally, and the \note at
adc.h:4108-4113 gives the FSR rule for the external values. The parameter
description appears to be the incorrect one.

That same note also says "When the \e refMode parameter is
ADC_REFERENCE_VREFHI" - ADC_REFERENCE_VREFHI is a refVoltage value, not a
refMode value.

Questions
---------
1. Is the intended register semantics that bits [14:10] set = higher range in
   both modes (3.3 V internal, 2x VREFHI external)? That is what our results
   imply, but I could not find it stated explicitly in the TRM.
2. Is a fix planned? We are carrying a local replacement in the meantime.

  • Could you please explain what issue are you facing ?

    Regards,

    Sumit

  • I have an external reference for my ADC, and VREFH - VREFL = 3 V.

    So I tried:

    ADC_setVREF(ADCA_BASE, ADC_REFERENCE_EXTERNAL, ADC_REFERENCE_VREFHI);

    With the above ref settings, for an input voltage of 2.88 V, the raw ADC reading is 1947, whereas I would expect a value close to 4000.

    In this configuration, the register values are:

    ANAREFPCTL = 0x7D55

    ANAREFNCTL = 0x0155

    So the problem is ADC result is approximately half of the expected value. Changing ADC_REFERENCE_VREFHI to

    ADC_REFERENCE_2xVREFHI is giving me raw ADC value close to 4000, but that looks like a hacky workarond. 
  • Is it control card or launchpad on which you are observing this or is it custom board? Are you able to reproduce this on either our evm like control card or launchpad?

    What pin package you are using for this?

    Regards,

    Sumit

  • I'm using my custom PCB with the 100-pin TMS320F28P550SG. I haven't tried the external reference with the LaunchPad, as the required external vref circuitry is available only on my custom PCB.

    As I mentioned in my initial message, I strongly believe there are bugs in ADC_setVREF(). Therefore, I created a custom function, ADC_setVREF_corrected(), as attached below. This fixed my issue.

    I hope you can go through the code and identify the differences, as they should be self-explanatory.

    /**
     * @brief Select the analog reference mode and range. Local replacement for ADC_setVREF().
     *
     * Driverlib's ADC_setVREF() gets the external-mode range backwards. The two range
     * enumerators alias -- ADC_REFERENCE_3_3V and ADC_REFERENCE_VREFHI are both 0,
     * ADC_REFERENCE_2_5V and ADC_REFERENCE_2xVREFHI are both 1 -- and it writes the range bits
     * from that value alone, without looking at the mode. The bit means opposite things in the
     * two modes, so asking for ADC_REFERENCE_VREFHI in external mode sets the bits and selects
     * 2x VREFHI: a 6.0 V full scale where 3.0 V was asked for. This version branches on the
     * mode first, then writes the bits to match.
     *
     * It also fixes a second slip: driverlib's VDDA branch writes ANAREFPCTL twice and never
     * touches ANAREFNCTL.
     *
     * TODO: delete this and go back to ADC_setVREF() once TI corrects the driverlib. The
     * driverlib parameter names and structure are kept deliberately, so the two stay easy to
     * diff against each other.
     *
     * @param base       ADC module base address. Only checked, never used: the registers below
     *                   are device-wide, covering ADCA..ADCE together.
     * @param refMode    ADC_REFERENCE_INTERNAL, ADC_REFERENCE_EXTERNAL or ADC_REFERENCE_VDDA
     * @param refVoltage internal: ADC_REFERENCE_3_3V or ADC_REFERENCE_2_5V.
     *                   external: ADC_REFERENCE_VREFHI (full scale = VREFHI) or
     *                   ADC_REFERENCE_2xVREFHI (full scale = 2 x VREFHI).
     */
    static void ADC_setVREF_corrected(uint32_t base, ADC_ReferenceMode refMode, ADC_ReferenceVoltage refVoltage)
    {
        PIXII_ASSERT(base != 0UL);
    
        EALLOW;
    
        //
        // Reference source. Each branch covers all five ADCs, because these registers do.
        //
        if (refMode == ADC_REFERENCE_INTERNAL)
        {
            HWREGH(ANALOGSUBSYS_BASE + ASYSCTL_O_ANAREFPCTL) &=
                ~(ASYSCTL_ANAREF_INTREF_ADCA | ASYSCTL_ANAREF_INTREF_ADCB | ASYSCTL_ANAREF_INTREF_ADCC | ASYSCTL_ANAREF_INTREF_ADCD |
                  ASYSCTL_ANAREF_INTREF_ADCE);
    
            HWREGH(ANALOGSUBSYS_BASE + ASYSCTL_O_ANAREFNCTL) &=
                ~(ASYSCTL_ANAREF_INTREF_ADCA | ASYSCTL_ANAREF_INTREF_ADCB | ASYSCTL_ANAREF_INTREF_ADCC | ASYSCTL_ANAREF_INTREF_ADCD |
                  ASYSCTL_ANAREF_INTREF_ADCE);
        }
        else if (refMode == ADC_REFERENCE_EXTERNAL)
        {
            HWREGH(ANALOGSUBSYS_BASE + ASYSCTL_O_ANAREFPCTL) =
                (HWREGH(ANALOGSUBSYS_BASE + ASYSCTL_O_ANAREFPCTL) & ~0x3FFU) |
                (ASYSCTL_ANAREF_VREFHI_ADCA | ASYSCTL_ANAREF_VREFHI_ADCB | ASYSCTL_ANAREF_VREFHI_ADCC | ASYSCTL_ANAREF_VREFHI_ADCD |
                 ASYSCTL_ANAREF_VREFHI_ADCE);
    
            HWREGH(ANALOGSUBSYS_BASE + ASYSCTL_O_ANAREFNCTL) =
                (HWREGH(ANALOGSUBSYS_BASE + ASYSCTL_O_ANAREFNCTL) & ~0x3FFU) |
                (ASYSCTL_ANAREF_VREFHI_ADCA | ASYSCTL_ANAREF_VREFHI_ADCB | ASYSCTL_ANAREF_VREFHI_ADCC | ASYSCTL_ANAREF_VREFHI_ADCD |
                 ASYSCTL_ANAREF_VREFHI_ADCE);
        }
        else
        {
            HWREGH(ANALOGSUBSYS_BASE + ASYSCTL_O_ANAREFPCTL) |=
                (ASYSCTL_ANAREF_VDDA_ADCA | ASYSCTL_ANAREF_VDDA_ADCB | ASYSCTL_ANAREF_VDDA_ADCC | ASYSCTL_ANAREF_VDDA_ADCD |
                 ASYSCTL_ANAREF_VDDA_ADCE);
    
            HWREGH(ANALOGSUBSYS_BASE + ASYSCTL_O_ANAREFNCTL) |=
                (ASYSCTL_ANAREF_VDDA_ADCA | ASYSCTL_ANAREF_VDDA_ADCB | ASYSCTL_ANAREF_VDDA_ADCC | ASYSCTL_ANAREF_VDDA_ADCD |
                 ASYSCTL_ANAREF_VDDA_ADCE);
        }
    
        //
        // Reference range, ANAREFPCTL bits 14:10. This is the half driverlib gets wrong: the
        // same bit value means "3.3 V" internally and "2 x VREFHI" externally, so which
        // enumerator sets it depends on the mode.
        //
        const uint16_t range_mask =
            (uint16_t)((ASYSCTL_ANAREF_ADCA | ASYSCTL_ANAREF_ADCB | ASYSCTL_ANAREF_ADCC | ASYSCTL_ANAREF_ADCD | ASYSCTL_ANAREF_ADCE)
                       << 10);
        int set_range_bits;
    
        if (refMode == ADC_REFERENCE_EXTERNAL)
        {
            // Set doubles the full scale to 2 x VREFHI; clear leaves it at VREFHI.
            set_range_bits = (refVoltage == ADC_REFERENCE_2xVREFHI) ? 1 : 0;
        }
        else
        {
            // Set selects the 3.3 V internal reference; clear selects 2.5 V.
            set_range_bits = (refVoltage == ADC_REFERENCE_3_3V) ? 1 : 0;
        }
    
        if (set_range_bits != 0)
        {
            HWREGH(ANALOGSUBSYS_BASE + ASYSCTL_O_ANAREFPCTL) |= range_mask;
        }
        else
        {
            HWREGH(ANALOGSUBSYS_BASE + ASYSCTL_O_ANAREFPCTL) &= (uint16_t)~range_mask;
        }
    
        EDIS;
    }
    


  • Now I see what you explaining. You are right, I found that this is known bug with remedy discussed in the following two e2e discussion.

     TMS320F28P550SJ: ADC reference voltage configuration 

     TMS320F28P550SJ: About ADC external reference 

    Hardware configuration is slightly different in P55x than its predecessor. That's the main origin of this API bug.

    You can keep the remedy that you found to address this issue.

    Regards,

    Sumit