Parallel ADC conversion on K64

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

Parallel ADC conversion on K64

1,433 Views
yagohoffmann
Contributor II

Hi,

I'm trying to sample two ADC values as close as possible in time, so for that I tried to use a PDB to trigger both ADC0 and ADC1 at the same time. Both conversions are triggered, but when I look at the timings, the interrupt of ADC1 occurs 34us after ADC0 interrupt. The interrupt routine is very small and takes about 2us to execute. I'm using a gpio pin to measure the time between interruptions. Both interruptions priority are set to the same value. So I don't know if the conversions are occuring at the same time and only the interruptions are delayed or if there's a real delay between the conversions. Also tried using the same ADC with different channels and software trigger, the delay seems to be the same.

Using MK64FN1M0VLL12 micrcontroller with FreeRTOS and McuXpresso.

0 Kudos
8 Replies

1,416 Views
myke_predko
Senior Contributor III

@yagohoffmann 

Using a GPIO output is not a very accurate or reliable way of determining when something takes place.  

GPIO control signals (as do all control signals) are passed through the Crossbar switch and I believe that the maximum DO update speed is on the order of 2us; it caught my eye when you said your IRQ Handlers took 2us to execute (that seems unreasonably long even though you didn't give the SystemClock speed).  

A better and more accurate way of recording the time of events in the system is to use a PIT timer - running at around 10MHz (which will give you 100ns resolution).  In your PIT timer increment am integer counter and when you're interrupts are acknowledged and the Handler is executing save the integer counter value in an appropriate variable for the event.  

At the end of the test, you can inspect the contents of the event integer counter variables to understand much more accurately when events are happening in your system.  

Good luck!

1,393 Views
yagohoffmann
Contributor II

Hello @myke_predko , thanks for your attention and advices. I simplified the program to run one single sample at every PIT interrupt (130us). On the interrupt routine a software trigger starts the AD conversion and waits until it ends. To measure the time I used the counter of the PIT timer, storing it's value on the start of the conversion and on the end. It takes 39.3us to trigger the conversion and get the result, wich I think it's to much time for a single AD conversion. The long sample mode and hardware average mode are disabled in the AD configurations. Do you have any idea of why it is taking so much for a single conversion? 

Here's the code used in the PIT interrupt to read the ADC value:

value_before = PIT_GetCurrentTimerCount(PIT_PERIPHERAL, PIT_CHANNEL_0);
intStatus = PIT_GetStatusFlags(PIT_PERIPHERAL, PIT_CHANNEL_0);
PIT_ClearStatusFlags(PIT_PERIPHERAL, PIT_CHANNEL_0, intStatus);
ADC16_SetChannelConfig(ADC0_PERIPHERAL, ADC0_CH0_CONTROL_GROUP, &ADC0_channelsConfig[0]);
while (0U == (kADC16_ChannelConversionDoneFlag &
ADC16_GetChannelStatusFlags(ADC0_PERIPHERAL, ADC0_CH0_CONTROL_GROUP)))
{
}
conversionValue = ADC16_GetChannelConversionValue(ADC0_PERIPHERAL, ADC0_CH0_CONTROL_GROUP);
value_after = PIT_GetCurrentTimerCount(PIT_PERIPHERAL, PIT_CHANNEL_0);

0 Kudos

1,382 Views
myke_predko
Senior Contributor III

@yagohoffmann 

I don't think you're doing quite what I suggested - It looks like you're doing the ADC conversion inside the PIT Timer IRQ rather than checking the PIT Timer value with the ADC outside the PIT IRQ.  

I just tried my way and found that ADC operation (on a K22 running at 120MHz, which should be similar to your K64) was around 38-40us - in the code below, the difference in "runTimeCounter" is 19 or 20 between when I start the ADC Operation (my ADC16_SetChannelConfig call) and after the ADC16_GetChannelConversionValue method returns in my ADC IRQ.  I'm running FreeRTOS, so I think that explains why I'm returning a value that's a bit larger than yours.  

I've can cut down my PIT code that I think is relevant to you:

PIT Global Counter Variable:

uint32_t volatile runTimeCounter = 0;

PIT Timer 0 Initialization:

PIT_GetDefaultConfig(&pitConfig);

/* Init pit module */
PIT_Init(PIT
, &pitConfig);

/* Set timer period for channel 0 */
PIT_SetTimerPeriod(PIT
, kPIT_Chnl_0
, USEC_TO_COUNT(2U
, PIT_SOURCE_CLOCK));

/* Enable timer interrupts for channel 0 */
PIT_EnableInterrupts(PIT
, kPIT_Chnl_0
, kPIT_TimerInterruptEnable);

/* Enable at the NVIC */
EnableIRQ(PIT_IRQ_ID);

/* Start channel 0 */
PIT_StartTimer(PIT
, kPIT_Chnl_0);

PIT IRQ:

void PIT_RUNTASK_HANDLER(void) {

PIT_ClearStatusFlags(PIT
, kPIT_Chnl_0
, kPIT_TimerFlag);
++runTimeCounter;
/* Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F Store immediate overlapping
exception return operation might vector to incorrect interrupt */
#if defined __CORTEX_M && (__CORTEX_M == 4U)
__DSB();
#endif
}

And then, to read the "RTOS_RunTimeCounter", I use the inline method:

inline static uint32_t readRunTimeCounter() {
extern uint32_t runTimeCounter;
return runTimeCounter;
}

 

2us PIT Timer interval was the shortest I could do with my system remaining stable.  

I guess it's up to the NXP engineers to explain what's happening here - for my product, 40us or so converstion time is acceptable but I thought it was under 1us (as I think you do).  

1,375 Views
yagohoffmann
Contributor II

@myke_predko 

I tried the way you suggested but when excuting the pit interrupt at 100ns the processor was unable to do anything else than execute the interrupt routine. After the example you shared, I tried again in a new baremetal project but the problem persists. With 2us it executes well but I get less resolution than using the PIT counter.

Tested with interruptions and blocking method, but the convertion time doesn't change too much, taking around 39us, same as you.

Unfortunately for my application this is too much time, and honestly I don't think it's acceptable for a MCU like the K64, since the MC56F8000 line could do much faster convertions.

Maybe it's something related to the MCUXpresso SDK... I hope NXP could give us some ideas of what's happening.

Anyway, thank you again for your time and attention Myke! 

1,318 Views
Alexis_A
NXP TechSupport
NXP TechSupport

Hello @yagohoffmann,

One way to fasten the execution would be to use the DMA and the ADC in continuos conversion, this would ensure the ADC is working as it maximum capability and minimize the latency added for triggering in software the conversion.

You can find an example of this in the SDK.

Best Regards,

Alexis Andalon

0 Kudos

1,314 Views
myke_predko
Senior Contributor III

Hi @Alexis_A 

What would be the ADC speed by doing this?  

Would it drop down to less than 1us?

0 Kudos

1,420 Views
Alexis_A
NXP TechSupport
NXP TechSupport

Hello @yagohoffmann,

Are you using the same PDB channel with the same delay to trigger the conversion? Or channels with the different delay values? If it is the first one, is very likely that the conversion is done at the same time and only the interruptions are being delayed.

Best Regards,
Alexis Andalon

1,391 Views
yagohoffmann
Contributor II

Hello @Alexis_A , thanks for your attention. I also tried to trigger the conversion via software and wait until it ends, but it still takes about 40us to complete. I measured the time with the counter of a PIT timer. Long sample and hardware average modes are disabled on MCUXpresso  peripheral configuration view. Any idea of what might be causing this long conversion time?

 

Here's the code used in the PIT interrupt to read the ADC value:

value_before = PIT_GetCurrentTimerCount(PIT_PERIPHERAL, PIT_CHANNEL_0);
intStatus = PIT_GetStatusFlags(PIT_PERIPHERAL, PIT_CHANNEL_0);
PIT_ClearStatusFlags(PIT_PERIPHERAL, PIT_CHANNEL_0, intStatus);
ADC16_SetChannelConfig(ADC0_PERIPHERAL, ADC0_CH0_CONTROL_GROUP, &ADC0_channelsConfig[0]);
while (0U == (kADC16_ChannelConversionDoneFlag &
ADC16_GetChannelStatusFlags(ADC0_PERIPHERAL, ADC0_CH0_CONTROL_GROUP)))
{
}
conversionValue = ADC16_GetChannelConversionValue(ADC0_PERIPHERAL, ADC0_CH0_CONTROL_GROUP);
value_after = PIT_GetCurrentTimerCount(PIT_PERIPHERAL, PIT_CHANNEL_0);

0 Kudos