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.

ADC10 Sequence of Channels Issue (MSP430F5508)

Other Parts Discussed in Thread: MSP430WARE

I'm having trouble figuring out how to determine which ADC10 channel's result is in the MEM0 register when doing a sequence of channels. I am using the F5508 device, starting at channel 10 (Temp Sensor) and working down to 0.

My code gets the samples in the ISR and attempts to read the ADC10MCTL0 register to get the channel, but even though I do this first, I don't get the "correct" value, I get the value after it's been decremented.

Some specifics: MCLK = 16MHz, SMCLK = 8MHz, ADC clock is SMCLK/8 = 1 MHz. See the attached code.

What is the best way to get the correllation between MEM0 and channel?

//-----------------------------------------------------------------------------
//          FUNCTION: InitAnalog()
//
// DESCRIPTION: This function is called to initialize this module
//
// RETURNS:     Nothing
//
//-----------------------------------------------------------------------------
void InitAnalog( void )
{
   struct s_TLV_ADC_Cal_Data  *ptr;
   UNSIGNED8   length;
   UNSIGNED8   index;

   // Initialze the static data structures
   calData = NULL;
   for (index=0; index<PFM_ADC_BUFSIZE; index++) SampleBuffer[index] = 0;

   // Setup and enable the reference supply
   REF_setReferenceVoltage(REF_BASE, PFM_REFSELECT);

   if (REF_isRefGenBusy(REF_BASE) == REF_NOTBUSY)
   {
      REF_enableReferenceVoltage(REF_BASE);
      usDelay(PFM_REF_SETTLE_US);
   }

   // Get the calibration data for the ADC and internal temp sensor
   TLV_getInfo(TLV_TAG_ADC10CAL, 0, &length, (unsigned int**)&ptr);

   // Get the calibration data from the chip's TLV data
   if (ptr != NULL)
   {
      calData = ptr;
      calADC20T30 = calData->adc_ref20_30_temp;
      calADC20T85 = calData->adc_ref20_85_temp;
      calTempScaleFactor1000x = ((INT32)(85-30)*1000)/(calADC20T85-calADC20T30);
   }
   else
   {
      calADC20T30 = 0;
      calADC20T85 = 1; // Value chosen to avoid DIV-BY-0 Error
      calTempScaleFactor1000x = 1;
   }

   // Setup the ADC10 parameters and enable the ADC
   ADC10_A_init(ADC10_A_BASE, ADC10_A_SAMPLEHOLDSOURCE_SC,
                PFM_ADC_CLKSRC, PFM_ADC_CLKDIV);
   ADC10_A_setDataReadBackFormat(ADC10_A_BASE, ADC10_A_UNSIGNED_BINARY);
   ADC10_A_setupSamplingTimer(ADC10_A_BASE, PFM_ADC_SMP_CYCLES,
                              ADC10_A_MULTIPLESAMPLESENABLE);
   ADC10_A_enable(ADC10_A_BASE);


FirstADCSampled = 0;
SequenceFlag = 0;

}

//-----------------------------------------------------------------------------
//          FUNCTION: StartADCConversion()
//
// DESCRIPTION: This function is called to start a conversion sequence in the
//              ADC module.
//
// RETURNS:     Nothing
//
//-----------------------------------------------------------------------------
void StartADCConversion(void)
{
   // Make sure that any pending interrupt flags are cleared
   ADC10_A_clearInterrupt(ADC10_A_BASE, ADC10IFG0);

   // Setup the starting channel and the +/- reference voltages
   ADC10_A_memoryConfigure(ADC10_A_BASE, PFM_ADCCHAN_START,
                           PFM_VREFPOS_SEL, PFM_VREFNEG_SEL);

   // Enable the interrupt source for conversion complete
   ADC10_A_enableInterrupt(ADC10_A_BASE, ADC10IFG0);

   // Kick off the actual conversion sequence
   ADC10_A_startConversion(ADC10_A_BASE, ADC10_A_SEQOFCHANNELS);

}


//-----------------------------------------------------------------------------
//          FUNCTION: getCurrentChannel()
//
// DESCRIPTION: This function gets the channel that the most recent conversion
//              was done on.
//
// RETURNS:     Channel
//
//-----------------------------------------------------------------------------

static UNSIGNED8 getCurrentChannel(void)
{
   return(HWREG(ADC10_A_BASE+OFS_ADC10MCTL0)&PFM_ADC_CHSEL_MASK);
}

//-----------------------------------------------------------------------------
//          FUNCTION: ADC10_ISR()
//
// DESCRIPTION: This function is the interrupt service routine that handles
//              getting the ADC conversion results and stuffing them into
//              memory buffer
//
// RETURNS:     Nothing
//
//-----------------------------------------------------------------------------
#pragma vector=ADC10_VECTOR
static __interrupt void ADC10_ISR( void )
{
   UNSIGNED8   channel = getCurrentChannel();
   UNSIGNED16  adcValue = ADC10_A_getResults(ADC10_A_BASE);

//@@ Debug Code
   if (FirstADCSampled == 0) FirstADCSampled = channel;

   if (channel < PFM_ADC_BUFSIZE)
   {
      SampleBuffer[channel] = adcValue;
   }

//@@ Debug Code
   if (channel == 0)
   {
      SequenceFlag = 1;
   }
}

  • I should mention that I'm using the MSP430Ware DriverLib for F5xxx-6xxx. I don't believe that has anything to do with the issue I'm seeing, but is the source of all the ADC10_A_xxx, REF_xxx, etc function calls.

  • Here are the relevant defines and main() code that makes the call:

    //
    // Internal Voltage reference Module
    //
    #define PFM_REFSELECT      REF_VREF2_0V
    #define PFM_REF_SETTLE_US  75L

    //
    // ADC10 Module Settings
    //
    #define PFM_ADC_CLKSRC     ADC10_A_CLOCKSOURCE_SMCLK
    #define PFM_ADC_CLKDIV     ADC10_A_CLOCKDIVIDER_8

    // ADC Clock = 1Mhz, Sample time required is 10 uS, so need min 10 clocks
    #define PFM_ADC_SMP_CYCLES ADC10_A_CYCLEHOLD_16_CYCLES
    #define PFM_ADC_CHSEL_MASK 0x000F

    #define PFM_ADC_BUFSIZE    11
    #define PFM_TEMPERATURE    10
    #define PFM_BATTVOLTS1     0
    #define PFM_BATTVOLTS2     1
    #define PFM_CURRENT1       6
    #define PFM_CURRENT2       7
    #define PFM_VREFPOS_SEL    ADC10_A_VREFPOS_INT
    #define PFM_VREFNEG_SEL    ADC10_A_VREFNEG_AVSS
    #define PFM_ADCCHAN_START  ADC10_A_INPUT_TEMPSENSOR

    int main( void )
    {
       MODIFIES_INT_FLAGS;

       //
       // Do module initialization first
       //
       InitAnalog();


       _EINT();

       StartADCConversion();
      
       while (1)
       {
          ;
       }
    }

  • Well, I don't know why the code is doing things so overly complex, uisign ADC10 module base address, register offsets, calculating current channel, whatever.
    This would suit a library that doesn't know all these things at compile time and supports multiply types of ADC. But is just time-and space-wasting noise if all this is known at compile-time.

    Also, I think this is the reason of your problems. Think how long a conversion takes, and how long it takes to enter an ISR, call subbfuncitons, do all these calculations and then return from ISR.
    If you ensure that the start channel isn't larger than the buffer size (check it in ADC setup) or the buffer is 16 words long anyway, then you can reduce the ISR code to this:

    #pragma vector=ADC10_VECTOR
    static __interrupt void ADC10_ISR(void)
    {
      SampleBuffer[ADC10MCTL0&0x0f]=ADC10MEM0;
     if(!(ADC10MCTL0&0x0f))
      SequenceFlag = 1;
    }

    Or you use the DMA to transfer the data to memory once it is converted, and the DMA ISR tells you when done.

  • One more piece of additional information, looks like the compiler did it's best to read the register early in the ISR (the call is the 2nd asm instruction, and the ADC10MCTL0 read is the 1st asm instruction in the call). Also, not sure how accurate the CYCLECOUNTER is under debug, but it only increased 6 cycles to where the ADC10MCTL0 should have been captured.

    Disassembly from IAR debug session...

    static __interrupt void ADC10_ISR( void )
    {
    ADC10_ISR:
    ?cstart_end:
     00C124 145F pushm.a #6,R15
       UNSIGNED8 channel = getCurrentChannel();
     00C126 13B0 C3B8 calla #getCurrentChannel
     00C12A 4C4A mov.b R12,R10
       UNSIGNED16 adcValue = ADC10_A_getResults(ADC10_A_BASE);
     00C12C 403C 0740 mov.w #0x740,R12
     00C130 13B0 C224 calla #ADC10_A_getResults
    ....

    getCurrentChannel:
       return(HWREG(ADC10_A_BASE+OFS_ADC10MCTL0)&PFM_ADC_CHSEL_MASK);
     00C3B8 421C 074A mov.w &ADC10MCTL0,R12
     00C3BC F07C 000F and.b #0xF,R12
     00C3C0 0110 reta


    CYCLECOUNTER = 2776581 @ 00C124

    CYCLECOUNTER = 2776587 @ 00C3BC

  • Jens-Michael may have some additional follow-up input but regarding #2, I have to mention that the accuracy of the debug CYCLECOUNTER --- given the overall resources it has available for this type of calculation --- is sometimes suspect and should be used as a general guideline rather than an exact measurement.

    Obviously, with access to a real-time emulator, a cycle count would be very accurate.  But given the resources available with an inexpensive FET430, probably not the case, at least in general and certainly when more complex coding is involved.

    Thanks.

  • Jens-Michael,

    I made a slight tweak to the code to eliminate the call to the subroutine (see code posted below). Now, the read of ADC10MCTL0 is the second asm instruction in the ISR and I still get 0x09 instead of 0x0A. I can't eliminate the pushm.a #6,R15 instruction that sets up the ISR, so what chance do I have of reading the register correctly and knowing which channel was converted?

    And to address your comment regarding "code complexity", I'm using the MSP430Ware driverlib library so that I don't have to write all the low-level drivers myself. After all, isn't that why TI created it?

    Anyway, here is the IAR dump....

    ADC10_ISR:
     00C124 145F pushm.a #6,R15
       UNSIGNED8 channel = (HWREG(ADC10_A_BASE+OFS_ADC10MCTL0)&PFM_ADC_CHSEL_MASK);
     00C126 421A 074A mov.w &ADC10MCTL0,R10
     00C12A F07A 000F and.b #0xF,R10
       UNSIGNED16 adcValue = ADC10_A_getResults(ADC10_A_BASE);
     00C12E 403C 0740 mov.w #0x740,R12
     00C132 13B0 C226 calla #ADC10_A_getResults
       if (FirstADCSampled == 0) FirstADCSampled = channel;
     00C136 1840 93C2 242A tstx.b &FirstADCSampled
     00C13C 2003 jne 0xC144
       if (FirstADCSampled == 0) FirstADCSampled = channel;
     00C13E 1840 4AC2 242A movx.b R10,&FirstADCSampled
       if (channel < PFM_ADC_BUFSIZE)
     00C144 907A 000B cmp.b #0xB,R10
     00C148 2C05 jc 0xC154
       SampleBuffer[channel] = adcValue;

    ....

  • Brian Boorman said:
    Now, the read of ADC10MCTL0 is the second asm instruction in the ISR

    Well, I was ratehr talking about the total excution time of a conversion loop, including the ISR latency and any code inside the ISTR. If the ADC is converting faster than your ISR executes, it measn that you fall behind with every new conversion until you skip one. Sooner or later. Reordering instructions inside the ISR won't help and only move the problem a littel bit behind - if at all.

    However, IIRC the timing of the ADC should give you enough time.

    pushm.a pushes 6 registers *32bit on the stack (large data model). 12 Clock cycles. And 12 more on ISR exit. This is because the rest of your ISR clobbers 6 registers (4 of them are considered clobbered due to the funciton call, as funciton calls are always considered clobbering R12-R15. For this reason, funciton calls are discouraged inside ISRs.

  • Jens-Michael Gross said:

    However, IIRC the timing of the ADC should give you enough time.

    If you look back at what my initial post was asking about, then the ADC most certainly does not give enough time.

    In Figure 27-7 of SLAU208L (page 703) the state diagram shows that the channel bits get updated on the state transition back to "Sample Input" and this occurs 2 ADC10_CLK cycles after ADCMEM0 is written and ADC10IFG0 is set.

    The fact that you cannot read the channel associated with the MEM0 contents when in Sequence mode is what I believe to be a fundamental architectural shortcoming in the design of the ADC10 IP.

    Regardless, it is what it is, so I've worked around it by consuming another DMA channel.

  • Brian Boorman said:
    If you look back at what my initial post was asking about, then the ADC most certainly does not give enough time.


    The original settings configure that you have a new result every 29µs. Which is 464 MCLK cycle (on 16MHz). Plenty of time.
    Brian Boorman said:
    the channel bits get updated on the state transition back to "Sample Input" and this occurs 2 ADC10_CLK cycles after ADCMEM0 is written and ADC10IFG0 is set.
    Which is 2µs = 32 MCLK cycles. So depending on interrupt latency and current main code, you were sometimes reading the channel value related to the current ADC10MEM0 content, and sometimes the updated count. Well, this explains the confusion.

    Using DMA is surely a solution. But you could have simply counted the interrupts. The first interrupt after starting the ADC is channel x, the second is x-1 etc. After all, that's what the DMA controller does internally too :)
    (To be honest, I never cared for the channel bits. I even didn't notice that they are updated during the sequence. I always knew that I was starting with channel x and counting down, so every interrupt was one channel less until 0)

**Attention** This is a public forum