2013-01-04 12:21 PM
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_valBuff[1] = adc2_valBuff[2] = adc3_valBuff[3] = adc1_valBuff[4] = adc2_valBuff[5] = adc3_valI would like to see a buffer like that: Buff[0] = adc1_valBuff[1] = adc2_valBuff[2] = adc1_valBuff[3] = adc2_valBuff[4] = adc1_valBuff[5] = adc2_valBuff[6] = adc3_valBuff[7] = adc1_valBuff[8] = adc2_valBuff[9] = adc1_valBuff[10] = adc2_valBuff[11] = adc1_valBuff[12] = adc2_valBuff[13] = adc3_valOr 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-adc2013-01-08 04:19 AM
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);
2013-01-08 08:19 AM
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 doneTIM3_IRQHandler – timer 3 updateNow I see some strange behavior that I cannot understandLED1 (timer 3 update interrupt) toggles exactly at 200KHzLED2 (transfer done) – toggles at 200-212MHZShouldn’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);}2013-01-08 10:36 AM
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...
2013-01-08 01:45 PM
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.2013-01-08 11:32 PM
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.2016-01-19 09:11 PM
Hi! Daniel,
Could you paste the completed source code that you modified finally. I guess it is very helpful to newcomers. Thanks!2016-01-19 09:33 PM
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.