cancel
Showing results for 
Search instead for 
Did you mean: 

ADC sampling rate

dkilshtein9
Associate II
Posted on January 04, 2013 at 21:21

I need to sample 2 ADC channels at a rate of 200KHz I also need to sample another ADC channel at a rate of kHz.

Currently I use the same ADC (ADC3) for all 3 channels.

During the initialization, I supply the DMA with a buffer (of 9000 longs) and I set ADC_NbrOfConversion=3.

Everything looks fine (the whole buffer is filled)

The problem is that all channels are sampled with the same rate. Is there a way to sample the first two with a high rate and the other channel with lower rate? 

Is there a way of using different buffers (one buffer for the fast channels 1,2 and another buffer for the slow channel 3)?

When I look at the buffer I see:

Buff[0] = adc1_val

Buff[1] = adc2_val

Buff[2] = adc3_val

Buff[3] = adc1_val

Buff[4] = adc2_val

Buff[5] = adc3_val

I would like to see a buffer like that: 

Buff[0] = adc1_val

Buff[1] = adc2_val

Buff[2] = adc1_val

Buff[3] = adc2_val

Buff[4] = adc1_val

Buff[5] = adc2_val

Buff[6] = adc3_val

Buff[7] = adc1_val

Buff[8] = adc2_val

Buff[9] = adc1_val

Buff[10] = adc2_val

Buff[11] = adc1_val

Buff[12] = adc2_val

Buff[13] = adc3_val

Or two different buffers.

Here is the code i use to initialize the ADC:

void ADC_HW_Init(void)

{

  ADC_InitTypeDef       ADC_InitStructure;

  ADC_CommonInitTypeDef ADC_CommonInitStructure;

  DMA_InitTypeDef       DMA_InitStructure;

  GPIO_InitTypeDef      GPIO_InitStructure;

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2 | RCC_AHB1Periph_GPIOF | RCC_AHB1Periph_GPIOA, ENABLE);

  

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, ENABLE);

  

  DMA_InitStructure.DMA_Channel = DMA_Channel_2;  

  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC3_DR_ADDRESS;

  DMA_InitStructure.DMA_Memory0BaseAddr =  (uint32_t)&val[0];

  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;

  DMA_InitStructure.DMA_BufferSize = 9000

  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;

  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;

  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

  DMA_InitStructure.DMA_Priority = DMA_Priority_High;

  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;

  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;

  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;

  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;

  DMA_Init(DMA2_Stream0, &DMA_InitStructure);

 

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;

  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;

  GPIO_Init(GPIOF, &GPIO_InitStructure);

  

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_2;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;

  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;

  GPIO_Init(GPIOA, &GPIO_InitStructure);

  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;

  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;

  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;

  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;

  ADC_CommonInit(&ADC_CommonInitStructure);

  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;

  ADC_InitStructure.ADC_ScanConvMode = ENABLE;

  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;;

  

  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;

  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;

  ADC_InitStructure.ADC_NbrOfConversion = 3;

  ADC_InitStructure.ADC_ExternalTrigConv = 0;

  ADC_Init(ADC3, &ADC_InitStructure);

  ADC_RegularChannelConfig(ADC3, ADC_Channel_7, 1, ADC_SampleTime_56Cycles);

  ADC_RegularChannelConfig(ADC3, ADC_Channel_1, 2, ADC_SampleTime_56Cycles);

  ADC_RegularChannelConfig(ADC3, ADC_Channel_2, 3, ADC_SampleTime_56Cycles);

  ADC_DMARequestAfterLastTransferCmd(ADC3, ENABLE);

 

#stm32-adc #stm32-adc #stm32-adc #stm32-adc #stm32-adc #atm32-adc
16 REPLIES 16
frankmeyer9
Associate II
Posted on January 08, 2013 at 13:19

Now, about the ADC + timer, what would be better to create a timer interrupt and call ADC_SoftwareStartConv(ADCx) from the timer ISR (may not be as accurate due to a clock shift in a multi tasking environment) or to set the ADC to work with an external trigger (probably more accurate but harder to verify)?

Trying SW trigger, even from a timer interrupt, is a bad idea at 200kHz. First, you could have considerable jitter due to interrupt priorities. Not sure how this behaves in your RTOS environment. Second, you waste a lot of cycles in interrupt entry and exit (context saving). ...trigger (probably more accurate but harder to verify)? You can verify the whole sampling chain by toggling a GPIO pin in the DMA_TC interrupt. Following an example for the STM32F0. DMA configuration and some other stuff is left out.

 ADC_InitTypeDef ADC_InitStructure;
 DMA_InitTypeDef DMA_InitStructure;
 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
 TIM_OCInitTypeDef TIM_OCInitStructure; 
 /* TIM3 Configuration */
TIM_TimeBaseStructInit (&TIM_TimeBaseStructure);
TIM_OCStructInit (&TIM_OCInitStructure);
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 0x4E1; 
// get 125 us
TIM_TimeBaseStructure.TIM_Prescaler = 0x2; 
// update rate of 625us (2.560 kHz)
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0; 
// at a 16MHz timer clock
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
/* TIM3 TRGO selection */
TIM_SelectOutputTrigger (TIM3, TIM_TRGOSource_Update);
/* -------- ADC1 Configuration -------- */
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
/* trigger on Timer 3 */
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising; 
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; 
/* 12 bit, right aligned */
ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Upward;
ADC_Init (ADC1, &ADC_InitStructure);
/* configure one transfer after each conversion sequence, and enable */
ADC_DMARequestModeConfig (ADC1, ADC_DMAMode_Circular);
ADC_DMACmd (ADC1, ENABLE);
/* Enable DMA1 Channel Transfer Complete interrupt */
DMA_ITConfig (DMA1_Channel1, DMA_IT_TC, ENABLE);
/* no explicit calibration required for F0 ADC, load calibration constants */
ADC_GetCalibrationFactor(ADC1);
ADC_Cmd (ADC1, ENABLE); 
/* Enable ADC1 */
while
(!ADC_GetFlagStatus (ADC1, ADC_FLAG_ADEN)); 
/* Wait the ADCEN flag */
TIM_Cmd (TIM3, ENABLE); 
/* TIM3 enable counter */
/* Software Start Conv - enables the selected hardware trigger */
ADC_StartOfConversion(ADC1);

dkilshtein9
Associate II
Posted on January 08, 2013 at 17:19

I used the code bellow to initialize the ADC3 for a sample time of 200KHz using an external trigger.

I initialize timer 3 and I also enabled the following interrupts (for debug purposes only):

DMA2_Stream0_IRQHandler – DMA transfer done

TIM3_IRQHandler         – timer 3 update

Now I see some strange behavior that I cannot understand

LED1 (timer 3 update interrupt) toggles exactly at 200KHz

LED2 (transfer done) – toggles at 200-212MHZ

Shouldn’t both LEDs toggle at the same frequency? LED2 frequency changes as I change ADC_SampleTime_28Cycles and ADC_Prescaler.

How can I be certain the ADC actually samples at 200KHz??

I set the DMA_BufferSize = 1 to a single byte just for the measure purposes but what I'm actually planning on doing is:

Disable both interrupts and set the buffer size to 200000.

Then every second I expect this buffer to fill up with ADC samples at 200KHz.

Then I can save all my ADC results (the 200000 buffer) to a file.

Since the ADC frequency changes I'm not sure I will be able to do that.

//////////////////////ADC init////////////////////////////////

static void ADC3_HwInit(void)

{

  ADC_InitTypeDef       ADC_InitStructure;

  ADC_CommonInitTypeDef ADC_CommonInitStructure;

  DMA_InitTypeDef       DMA_InitStructure;

  GPIO_InitTypeDef      GPIO_InitStructure;

  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

  NVIC_InitTypeDef  NVIC_InitStructure;

  uint16_t Period;

     

  //Enable ADC3, DMA2, Timer and GPIO clocks

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2 | RCC_AHB1Periph_GPIOF , ENABLE);

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, ENABLE);

  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

  

  //TIMER 3 

  Period = 25; 

  TIM_TimeBaseStructure.TIM_Prescaler = (SystemCoreClock / 10000000) - 1; // 1 MHz

  TIM_TimeBaseStructure.TIM_Period = Period - 1;                          //1MHz /25 = 400KHz

  TIM_TimeBaseStructure.TIM_ClockDivision = 0;

  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

  TIM_SelectOutputTrigger (TIM3, TIM_TRGOSource_Update);

  TIM_ARRPreloadConfig(TIM3, ENABLE);

      

  //DMA2 Stream0 channel2

  DMA_InitStructure.DMA_Channel = DMA_Channel_2;  

  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC3_DR_ADDRESS;

  DMA_InitStructure.DMA_Memory0BaseAddr =  ULTRA_FSMC_SRAM_ADDRESS;

  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;

  DMA_InitStructure.DMA_BufferSize = 1;//Ultra_BufferSize;

  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;

  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;

  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

  DMA_InitStructure.DMA_Priority = DMA_Priority_High;

  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;

  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;

  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;

  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;

  DMA_Init(DMA2_Stream0, &DMA_InitStructure);

 

  //IOs

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;

  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;

  GPIO_Init(GPIOF, &GPIO_InitStructure);

 

  // ADC 3

  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;

  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div8;

     

 ADC_CommonInitStructure.ADC_DMAAccessMode =      ADC_DMAAccessMode_Disabled;

 ADC_CommonInit(&ADC_CommonInitStructure);

  ADC_InitStructure.ADC_Resolution = ADC_Resolution_8b;

  ADC_InitStructure.ADC_ScanConvMode = DISABLE;

  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;

  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;

  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;

  ADC_InitStructure.ADC_NbrOfConversion = 1;

  ADC_InitStructure.ADC_ExternalTrigConv     = ADC_ExternalTrigConv_T3_TRGO;

  

  ADC_Init(ADC3, &ADC_InitStructure);

  ADC_RegularChannelConfig(ADC3, ADC_Channel_7, 1, ADC_SampleTime_28Cycles);

  ADC_DMARequestAfterLastTransferCmd(ADC3, ENABLE);

 

  //IRQs  

  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

  NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn ;

  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 13;

  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

  NVIC_Init(&NVIC_InitStructure);

  DMA_ITConfig (DMA2_Stream0, DMA_IT_TC, ENABLE);

  

          //debug only

  NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;

  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

  NVIC_Init(&NVIC_InitStructure);

  TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE) ;

  //Start

  DMA_Cmd(DMA2_Stream0, ENABLE);

  ADC_DMACmd(ADC3, ENABLE);

  ADC_Cmd(ADC3, ENABLE);

  TIM_Cmd(TIM3, ENABLE);

  ADC_SoftwareStartConv(ADC3);

}

//////////////////////ISR////////////////////////////////

void DMA2_Stream0_IRQHandler(void)

{

    /* Clear the Interrupt flag */

    DMA_ClearFlag(DMA2_Stream0, DMA_FLAG_TCIF0);

   STM_EVAL_LEDToggle(LED2);

}

void TIM3_IRQHandler(void)

{

   TIM_ClearITPendingBit(TIM3, TIM_IT_Update);

   STM_EVAL_LEDToggle(LED1);

}

frankmeyer9
Associate II
Posted on January 08, 2013 at 19:36

LED1 (timer 3 update interrupt) toggles exactly at 200KHz

 

LED2 (transfer done) – toggles at 200-212MHZ

 

 

Shouldn’t both LEDs toggle at the same frequency? LED2 frequency changes as I change ADC_SampleTime_28Cycles and ADC_Prescaler.

 

How can I be certain the ADC actually samples at 200KHz??

...toggles at 200-212MHZ

You probably meant kHz here...

But a LED toggle jitter is no proof of an ADC jitter. I suspect a interrupt priority issue, as both are supposed to fire at 200kHz rate.

So you have seen that the timer triggers correctly, I suggest to turn this interrupt off.

To make certain that the ADC samples at a correct rate, you can feed a reference signal, and check the sampled results. For instance, a triangular signal of 20 ... 50kHz.

Disable both interrupts and set the buffer size to 200000.

 

Then every second I expect this buffer to fill up with ADC samples at 200KHz.

 

Then I can save all my ADC results (the 200000 buffer) to a file.

 

Since the ADC frequency changes I'm not sure I will be able to do that.

 

That would need sufficient RAM to accomodate this buffer.

Getting out the ADC data in real-time at an equivalent rate of 200kHz is not trivial...

dkilshtein9
Associate II
Posted on January 08, 2013 at 22:45

Yes of course I meant kHz 🙂

I have a big external RAM to store ADC samples (I have 3 ADC channels) and real time images.

I just feel very insecure about the sample rate. I'm not sure that the code I posted above actually creates a 200KHz sample rate. I would really appreciate if some one could confirm I made all initialization of the ADC+timer correctly.

frankmeyer9
Associate II
Posted on January 09, 2013 at 08:32

With a proper test signal, you can verify the whole processing chain, from signal conditioning up to buffer storage.

When using a input signal frequency of 200kHz / (2 * N), you should see a periodic signal in your sample.

And using a triangular signal, the difference of two adjacent samples is proportional to the sample period.

This of course requires a accurate and stable signal generator.

axl
Associate
Posted on January 20, 2016 at 06:11

Hi! Daniel,

Could you paste the completed source code that you modified finally.

I guess it is very helpful to newcomers.

Thanks!

Posted on January 20, 2016 at 06:33

This thread is over 3 years old, don't pin your hopes on it.

I've posted a whole bunch of ADC+TIM+DMA examples. Suggest you search around on the forum a bit, or be more specific about which STM32 you're using, and what your requirements are.

https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Flat.aspx?RootFolder=https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/ADC%20trigger%20on%20timer%20update&FolderCTID=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C3CDE46A77F0FFD06506F5B&currentviews=502

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..