Cannot read an ADC in a timer ISR

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Cannot read an ADC in a timer ISR

Jump to solution
4,907 Views
clivepalmer
Contributor III

I am trying to read a few ADCs channels on a 4KHZ ish update rate from a timed interrupt.

I can read the ADC using my command line parser. I have also checked my TPM timer ISR and it works fine.

and hits the ISR every 244us (4KHZ = 250us)

The ADC has been set for a conversion time of 30uS. I have checked my calculation with the freescale ADC calculator and it should be correct.

I want to read 4 ADC channels (30us * 4 = 120us)

My ADC init routine is:-

void adc_init(void)
{

SIM_SCGC6 |= SIM_SCGC6_ADC0_MASK; //  enable clock

ADC0_CFG1 = ADC_CFG1_ADICLK(0)   // bus clock 24MHZ
     | ADC_CFG1_ADIV(1)     //ADCK = input clock/2
     | ADC_CFG1_MODE(1);     //12-bit mode

    ADC0_CFG2 &= ~(ADC_CFG2_MUXSEL_MASK  // channel A
             | ADC_CFG2_ADACKEN_MASK);  //asynchronous clock output disabled
    ADC0_CFG2 |= ADC_CFG2_ADHSC_MASK; //HI speed mode

    ADC0_SC2 &= ~ADC_SC2_ADTRG_MASK; // SW trigger
    ADC0_SC2 |= ADC_SC2_REFSEL(0); //voltage reference source VREFH&L

    ADC0_SC3 = ADC_SC3_ADCO_MASK  //continuous conversion
   | ADC_SC3_AVGE_MASK  //hardware average is enabled
   | ADC_SC3_AVGS(2);   //16 averages
    ADC0_SC1A = ADC_SC1_ADCH(31); // disable module
}

At the moment Im just calling a simple ADC read function.

unsigned int adc_read(uint8_t channel)      //for CHANNEL-A
{
   CRITICAL_SECTION_BEGIN;
   ADC0_SC1A = ADC_SC1_ADCH(31);           //disable adc

   ADC0_SC1A = channel;

   while((ADC0_SC1A & ADC_SC1_COCO_MASK) == 0){}; // wait for conversion complete int to clear
   CRITICAL_SECTION_END;
   return ADC0_RA;
}

Am I being dumb. Is there some good reason why I cannot read an ADC channel from my TPM ISR?

0 Kudos
1 Solution
2,887 Views
ndavies
Contributor V

Clive, Have you looked into the possibility that you are suffering from interrupt starvation or interrupt priority blocking.

The way your code is structured you will be spending 13 percent of the CPUs time in an interrupt ISR waiting for the ADC done bit to toggle. This is with just reading 1 ADC channel in the ISR. When reading 4 channels in the interrupt service routine, You will be spending 50% of the CPUs bandwidth in an ISR, waiting for the ADC done bit to toggle 4 times. I'm sure your program has other things it needs to get done in that time. Since you are in the interrupt, anything non-interrupt or at a lower interrupt priority will be blocked. Spending 50% or your time in 1 interrupt is a usually a problem.

You current code is

Every 244 uS read (timer ISR)

     Read 4 ADC channels. Each read takes 30uS Leading to 120uS spent in ISR

A better algorithm would be to also use the ADC done interrupt as well as the timer interrupt

Every 244 uS  (Timer ISR)

     Start Read of ADC channel 1

Every time the ADC done interrupt occurs (ADC done ISR)

     Read Data from previously started ADC read

     if (not last ADC channel)

          Start Next ADC channel read

With This improved algorithm you will not be stuck in an ISR for 50% of your CPUs available time. The interrupt switching time will be lower than the 120uS wasted waiting for the ADC done bit toggles.  A further improvement would be to use a hardware trigger to the ADC. The timer trigger would then use no CPU cycles. The ADC reads could also be DMA transfers also using no CPU cycles to take the reading. I use the K60 parts so I don't know what DMA sources are available on the KL02.

At 48Mhhz, That 30uS ADC wait loop has blocked the program for 1440 CPU clock cycles that  could have been used for other processing.

Norm

View solution in original post

0 Kudos
23 Replies
197 Views
clivepalmer
Contributor III

Hi Bob,

Thanks for your response. The printf is commented out and not used. I take on board your comments about functions; although in all my years of coding I have and everyone else I have worked with seems to have broken this rule. :smileyhappy:

The adc_read code is posted at the top of the posting. I have timed this function on a CRO and it takes 30us (which was the design) so it can never come close to exceeding the ISR time period. I had removed the code from the function at one point in time and had it located directly in the ISR. The outcome was the same.

If I remove the adc_read function, It only takes one line of code in the ISR to start the ADC transfer to break the code.

ADC0_SC1A = channel;

0 Kudos
197 Views
jeremyzhou
NXP Employee
NXP Employee

Hi Clive,

In the your last reply, you mentioned just add ADC0_SC1A = channel; in TPM interrupt routine, the code would also break, is itn't really?

I will create a demo to test this issue with FRDMKL05 and update you after I get result.
Have a great day,
Ping

-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------

0 Kudos
197 Views
clivepalmer
Contributor III

Hi Ping,

Yes that correct. I look forward to your findings.

Thanks

Clive

0 Kudos