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.

TI's ADC10 polling code does not work... a theory why

Other Parts Discussed in Thread: MSP430F2274, MSP430G2553, MSP430G2231

Here's a fact:  the code quoted everywhere for sampling the ADC10 (e.g. in Grace) does not work 100%:

// ADC Start Conversion - Software trigger
ADC10CTL0 |= ADC10SC;

// Loop until ADC10IFG is set indicating ADC conversion complete
while ((ADC10CTL0 & ADC10IFG) == 0);

// Read ADC conversion result from ADC10MEM
ADC_Conversion_Result = ADC10MEM;

At least, with this setup:  MSP430F2274, 1MHz MCLK, ADC running from ADC10SC/6, (ADC10SC also for trigger source and sample rate clock), S/H time 64x clocks.

The code that sleeps the processor and wake on interrupt does work:

    ADC10CTL0 |= ENC + ADC10SC;             // Sampling and conversion start

    __bis_SR_register(CPUOFF + GIE);        // LPM0 with interrupts enabled   
    value = ADC10MEM;
But the problem is, you can't always use that (e.g. in certain ISRs or when using certain other ISRs)
Here is code that works:
/**
 * sample an ADC channel and return the result.
 */
int readADC( int channel )
{
 int i;
 ADC10CTL0 &= ~ENC;     //disable so we can modify control bits
 ADC10CTL1 &= 0x0FFF;    //clear input source
 ADC10CTL1 |= channel;    //select new input
 ADC10CTL0 |= ENC | ADC10SC;   // Sampling and conversion start
 for( i = 0; i < 1000; i++); // wait for flag since we're running on a slow clock, can't check immediately
 while (!(ADC10CTL0 & ADC10IFG));
 return ADC10MEM;
}

If you take out the delay loop, you will always get ADC10MEM == 0.
My theory is that ADC10SC is slow compared to MCLK and the ADC10IFG is taking a while to set after conversion start, and is being rushed past and missed unless you delay a bit.
Note: this routine is 'blocking', that is it'll sit there and make your whole program wait til the conversion is done.  For my application that's acceptable.
Perhaps someone from TI can comment.
Regards,
Marc
  • I use this function for the MSP430 Launchpad (MSP430G2231,  MSP430G22452, MSP430G2553). 

    uint16_t ADC_single_meas(uint16_t chan) {
      ADC10CTL0 &= ~ENC;  
      ADC10CTL0 = ADC10SHT_3
                 | ADC10ON  
                 | SREF_0; 
      ADC10CTL1 = ADC10SSEL_0 + chan; 
      ADC10CTL0 |= ENC + ADC10SC;  
      while (ADC10CTL1 & BUSY);   
      return ADC10MEM;     
    }

    Best regards,

    István Cserny

  • I think what you are saying is, after “ADC10CTL0 |= ENC | ADC10SC;”, ADC10IFG is initially set. It takes some time before it is cleared and is set again after the conversion finishes. 
    If that were true, then you should have used “ADC10CTL0 = ADC10CTL0 & ~ADC10IFG | ENC | ADC10SC;” instead.
  • bowerymarc said:
    ADC10CTL1 |= channel; //select new input

    I don' tknow your main code, but don't you think this should be

    ADC10CTL1 |= (channel <<12)?

    If channel is 0..15, your original code would change the the CONSEQx bits and the clock source but keep the selected channel at 0.

  • You are right, the channel number should be shifted left by 12. Therefore I use the predefined macros INCH_0 -  INCH_11 as calling parameter, For example:

    data = ADC_single_meas(INCH_5);  

    Best regards,

    István Cserny

  • Hi István, you didn't say what clocks for processor and ADC... 

    Interesting, this method isn't documented at all but I do see it (or ADC10BUSY, same thing) used in some of the sample code.... anyone from TI care to comment on the disparity?

  • Jens, that should be obvious.  anyhow that's off topic. 

  •   I'll try that next time I have the code up.

  • bowerymarc said:
    Hi István, you didn't say what clocks for processor and ADC... 

    MCLK = SMCLK = DCO 1 MHZ (factory calibrated). I thing, the ASC10OSC was selected for the ADC. The program source is available here.

  • Istvan, yes this is the example code I was talking about.  It's very weird that TI uses that method but in the manual and e.g. Grace example code and other places insist that you must check  ADC10IFG, and BUSY is only used to know when to update things for e.g. DTC mode.  Quite opaque.

    However next time I have the code open I'll try this method as well.

  • bowerymarc said:
    Grace example code and other places insist that you must check  ADC10IFG, and BUSY is only used to know when to update things

    I think I know the reason of that. Testing the BUSY flag works well for a simple conversion, as it was shown above, but didn't worked for me in the case of repeated conversion. In my repeated conversion example program called 4_ADC_multi_ref1 I also had to test the ADC10IFG flag.

  • Istvan Cserny said:

    Testing the BUSY flag works well for a simple conversion, as it was shown above, but didn't worked for me in the case of repeated conversion. In my repeated conversion example program called 4_ADC_multi_ref1 I also had to test the ADC10IFG flag.

    From what I read that's correct, BUSY will stay busy during a multiple conversion.  I note that in the Grace code example for single conversion they still use ADC10IFG and not BUSY.

    I suspect this problem is due to "t(sync)" as shown in figure 22-3 in slau144h for example (MSP430F2274).  But TI doesn't get into the specifics anywhere I can find.

    So, did you run into problems in your code, testing ADC10IFG?

  • bowerymarc said:
    did you run into problems in your code, testing ADC10IFG?

    I have never tried it for the case of single conversions.

  • After re-reading the initial post...

    bowerymarc said:
    My theory is that ADC10SC is slow compared to MCLK and the ADC10IFG is taking a while to set after conversion start, and is being rushed past and missed unless you delay a bit

    The ADC is an independent hardware. Liek an external device. It is configured and fired. And will eventually produce a result. However, when the result is produced, depends on configuration. And is independent of the CPU.

    For the CPU, all ADC registers, config and result, are jsu tmemory locations like normal ram.

    When the ADC is doen with its work, it will signal it by setting the ADC10IFG bit. This is why you have to wait for it to be set before you can read the result. Neithe ris the result there instantaneous, nor does the CPU auto-insert any wait states when reading ADC10MEM.

    Your onfiguration programmed 64 clock cycles for sampling. Then 13 more come for the conversion itself. So after starting the conversion, it takes 77 clock cycles until the conversion result is available. On default ~5MHz clock this is ~16µs.

    The for loop isn't reliable. Depending on compiler optimization settings, it may be eliminated completely as it has no impact on the program state (and the compiler doesn't know that it is your intention to waste time)

    I think, the problem is that you never reset ADC10IFG. It is not reset when you start a new conversion. It is only reset when 1) the ISR executes or 2) you manually reset it.
    Since you don't do any of the two, ADC10IFG is always set and your code fails without the loop because the code simply always falls throught he while.

    Do ADC10CTL0 &= ~ADC10IFG; either right before starting the conversion or after the while loop (before returning the result).

    BTW: the ADC10_A in 5x family clears ADC10IFG when you actually read ADC10MEM. However, this is because the ADC10 has DTC,  which requires a different interrupt handling for multiple conversions and sequences, while the ADC10_A uses generic DMA instead.

  • Istvan Cserny said:

      ADC10CTL0 |= ENC + ADC10SC;  
      while (ADC10CTL1 & BUSY);   
      return ADC10MEM;     
    }

    That works for me, thanks.

    old_cow_yellow said:
    I think what you are saying is, after “ADC10CTL0 |= ENC | ADC10SC;”, ADC10IFG is initially set. It takes some time before it is cleared and is set again after the conversion finishes. 
    If that were true, then you should have used “ADC10CTL0 = ADC10CTL0 & ~ADC10IFG | ENC | ADC10SC;” instead.

    That works for me too, thanks!

**Attention** This is a public forum