cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F4-Discovery ADC+DMA double buffer

kyaw
Associate II
Posted on March 16, 2016 at 16:40

I'm doing ADC + DMA on my STM32F4 Discovery. I have an array (arraySize = 20000) where the data from the ADC peripheral (PA7) is continuously sent to my RawADC array with a buffer size=arraySize=20000 (20000 * 16bit). The initialization and configuration codes are as below.

#define ADC1_DR ((uint32_t)0x4001244C)

#arraySize 20000

__IO uint16_t RawADC[arraySize];

__IO uint16_t Voltage[arraySize];

void

ADC_DMA_Init (

void

){

GPIO_InitTypeDef GPIO_InitStructure;

ADC_InitTypeDef ADC_InitStruct;

ADC_CommonInitTypeDef ADC_CommonInitStruct;

DMA_InitTypeDef DMA_InitStructure;

RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOA , ENABLE);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;

GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

GPIO_Init(GPIOA, &GPIO_InitStructure);

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);

DMA_StructInit(&DMA_InitStructure);

// Configure DMA2 - Stream 0

DMA_DeInit(DMA2_Stream0);

//Set DMA registers to default values

DMA_InitStructure.DMA_Channel = DMA_Channel_0;

DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;

//Source address for ADC pin

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

//Destination address

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;

DMA_InitStructure.DMA_BufferSize = arraySize;

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;

//source size - 16bit

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;

// destination size = 16bit

DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

DMA_InitStructure.DMA_Priority = DMA_Priority_High;

//High Priority

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);

//Initialize the DMA

DMA_Cmd(DMA2_Stream0, ENABLE);

//Enable the DMA2 - Stream 0

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

ADC_CommonInitStruct.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;

ADC_CommonInitStruct.ADC_Mode = ADC_Mode_Independent;

ADC_CommonInitStruct.ADC_Prescaler = ADC_Prescaler_Div8;

ADC_CommonInitStruct.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;

ADC_CommonInit(&ADC_CommonInitStruct);

/* Init ADC settings */

ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;

ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;

ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;

ADC_InitStruct.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;

ADC_InitStruct.ADC_NbrOfConversion = 1;

ADC_InitStruct.ADC_ScanConvMode = DISABLE;

ADC_Init(ADC1, &ADC_InitStruct);

ADC_RegularChannelConfig(ADC1,ADC_Channel_7,1,ADC_SampleTime_480Cycles);

// PA7

ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);

ADC_DMACmd(ADC1, ENABLE);

//Enable ADC1 DMA

ADC_Cmd(ADC1, ENABLE);

// Enable ADC1

ADC_SoftwareStartConv(ADC1);

// Start ADC1 conversion

}

 

In my main loop, I'd like to process data from the RawADC array such as converting to Voltage values or calculate RMS value from the array.

I'm currently using a for... loop to calculate instantaneous voltage values and put them in another array (Voltage[]).

for (int i=0; i<arraySize; i++){

Voltage[i] = (RawADC[i]/0xFFF) * 3;

}

The problem is that when the for loop is being executed, the values of RawADC[] keeps changing since the DMA is constantly updating this array. I'd like to use another DMA stream to copy from RawADC[] array to a bufferArray[] and use the latter to do processing.

My plan is to increase the RawADC array's size to hold more data and configure that array in FIFO mode. The bufferArray will have its size=arraySize=20000 and will take 20000 values from the RawADC array via DMA. I'll do the processing based on bufferArray[]. Any help to configure this is appreciated.

#stm32f4-discovery #adc #dma

4 REPLIES 4
Posted on March 16, 2016 at 21:18

ADC_InitStruct.ADC_ExternalTrigConv = DISABLE; // This doesn't look correct

So configure the DMA to do twice the buffer size, and process alternating HALVES of the buffer under the HT (Half Transfer) and TC (Transfer Complete) interrupts. You get to process the currently inactive half, and you have the time it takes to fill the other active half to get your work done.

I've posted dozens of examples demonstrating the use of HT/TC, review.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Posted on March 16, 2016 at 21:23

for (int i=0; i<arraySize; i++){
Voltage[i] = (RawADC[i]/0xFFF) * 3;
}

You might want to think about the ordering of the math there, or the use of floating point, and casting, so you don't lose precision.

Voltage[i] = (uint16_t)(((uint32_t)RawADC[i] * 3000) / 4096); // would give you millivolts

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
kyaw
Associate II
Posted on March 17, 2016 at 06:34

First of all, thanks for the reply, Clive.

I've removed ADC_ExternalTrigConv. And thanks for pointing out the precision in conversion of the voltage. May I ask where I can find the examples for using HT/TC interrupts?

Posted on March 17, 2016 at 13:38

I post them to the forum here, and they spread from there. The forum is heavily spidered by Google, and that's what I use to find my own posts.

This should be illustrative

// STM32 ADC Sample @ 200 KHz (PC.1) STM32F4 Discovery - mailto:sourcer32@gmail.com
// Assumptions per system_stm32f4xx.c CPU @ 168 MHz, APB2 @ 84 MHz (/2), APB1 @ 42 MHz (/4)
#include 'stm32f4_discovery.h'
/**************************************************************************************/
void RCC_Configuration(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
}
/**************************************************************************************/
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* ADC Channel 11 -> PC1
*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
/**************************************************************************************/
void ADC_Configuration(void)
{
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_InitTypeDef ADC_InitStructure;
/* ADC Common Init */
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 = DISABLE; // 1 Channel
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // Conversions Triggered
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfConversion = 1;
ADC_Init(ADC1, &ADC_InitStructure);
/* ADC1 regular channel 11 configuration */
ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_15Cycles); // PC1
/* Enable DMA request after last transfer (Single-ADC mode) */
ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
/* Enable ADC1 DMA */
ADC_DMACmd(ADC1, ENABLE);
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
}
/**************************************************************************************/
#define BUFFERSIZE 400 // 200KHz x2 HT/TC at 1KHz
__IO uint16_t ADCConvertedValues[BUFFERSIZE];
static void DMA_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_Channel = DMA_Channel_0;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&ADCConvertedValues[0];
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
DMA_InitStructure.DMA_BufferSize = BUFFERSIZE; // Count of 16-bit words
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_HalfWord;
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);
/* Enable DMA Stream Half / Transfer Complete interrupt */
DMA_ITConfig(DMA2_Stream0, DMA_IT_TC | DMA_IT_HT, ENABLE);
/* DMA2_Stream0 enable */
DMA_Cmd(DMA2_Stream0, ENABLE);
}
/**************************************************************************************/
void TIM2_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/* Time base configuration */
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = (84000000 / 200000) - 1; // 200 KHz, from 84 MHz TIM2CLK (ie APB1 = HCLK/4, TIM2CLK = HCLK/2)
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
/* TIM2 TRGO selection */
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update); // ADC_ExternalTrigConv_T2_TRGO
/* TIM2 enable counter */
TIM_Cmd(TIM2, ENABLE);
}
/**************************************************************************************/
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Enable the DMA Stream IRQ Channel */
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/**************************************************************************************/
void DMA2_Stream0_IRQHandler(void) // Called at 1 KHz for 200 KHz sample rate, LED Toggles at 500 Hz
{
/* Test on DMA Stream Half Transfer interrupt */
if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_HTIF0))
{
/* Clear DMA Stream Half Transfer interrupt pending bit */
DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_HTIF0);
/* Turn LED3 off: Half Transfer */
STM_EVAL_LEDOff(LED3);
// Add code here to process first half of buffer (ping)
}
/* Test on DMA Stream Transfer Complete interrupt */
if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0))
{
/* Clear DMA Stream Transfer Complete interrupt pending bit */
DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);
/* Turn LED3 on: End of Transfer */
STM_EVAL_LEDOn(LED3);
// Add code here to process second half of buffer (pong)
}
}
/**************************************************************************************/ 
int main(void)
{
RCC_Configuration();
GPIO_Configuration();
NVIC_Configuration();
TIM2_Configuration();
DMA_Configuration();
ADC_Configuration();
STM_EVAL_LEDInit(LED3); /* Configure LEDs to monitor program status */
STM_EVAL_LEDInit(LED4);
STM_EVAL_LEDOn(LED3); /* Turn LED3 on, 500 Hz means it working */
/* Start ADC1 Software Conversion */
ADC_SoftwareStartConv(ADC1);
while(1)
{
}
}

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