cancel
Showing results for 
Search instead for 
Did you mean: 

STM32 ADC and DMA example please.

mbmail4
Associate II

Hi.

Has anyone got an example of how to trigger an ADC from a Timer, and, then use the DMA to transfer the results from the ADC to a buffer in RAM.

I've looked everywhere I know, and cant seem to find a working example of this type. I have tried to get my own brew working but so far I cant seem to crack it, can someone please help?

Cheers

mb.

6 REPLIES 6
oeliks
Senior

Using cubemx, hal? Show what You have so far.​

S.Ma
Principal

You will have to look at the project ADC examples of the corresponding Nucleo board as ADC with DMA (prefferably as circular buffer). Use normal channel trigger pin and wire it to a timer output channel so you can observe each stage.

mbmail4
Associate II

Hi. Here is what I have so far. I originally based my work on the STM32F4-Discovery example, ADC_RegularConversion_DMA. This example doesn't have a timer triggering the ADC, instead ADC1 is configured to convert continuously. Each time an end of conversion occurs the DMA transfers(DMA2 Stream0, Channel 0), in circular mode, the converted data from ADC1 DR register to the uhADCxConvertedValue variable in RAM.

So I'm half way there, but I cant seem to get the ADC to trigger off Timer2 in this case. I've made so many changes so far, so its possible I've mucked it up somehow.

Starting it all off with:

if(HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ADC_ConvertedValue, 16) != HAL_OK)

{

uart_puts(&huart2,"\r\nERROR: HAL_ADC_Start_DMA\r\n");

}

if(HAL_TIM_Base_Start_IT(&htim2) != HAL_OK)

{

uart_puts(&huart2,"\r\nERROR: HAL_TIM_Base_Start_IT\r\n");

}

---------------------------------------------------------------------------------------------------------

ADC:

 hadc1.Instance = ADC1;

 hadc1.Init.ClockPrescaler = ADC_CLOCKPRESCALER_PCLK_DIV4;

 hadc1.Init.Resolution = ADC_RESOLUTION_12B;

 hadc1.Init.ScanConvMode = DISABLE;

 hadc1.Init.ContinuousConvMode = DISABLE; /* Continuous mode disabled to have only 1 conversion at each conversion trig */

 hadc1.Init.DiscontinuousConvMode = DISABLE; /* Parameter discarded because sequencer is disabled */

 hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;

 hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO; /* Conversion start trigger at each external event */

 hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;

 hadc1.Init.NbrOfConversion = 1;

 hadc1.Init.DMAContinuousRequests = ENABLE;

 hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;

 if (HAL_ADC_Init(&hadc1) != HAL_OK)

 {

  Error_Handler();

 }

 /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.

 */

 sConfig.Channel = ADC_CHANNEL_9;

 sConfig.Rank = 1;

 sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;

 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)

 {

  Error_Handler();

 }

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

TIMER2:

 htim2.Instance = TIM2;

 htim2.Init.Prescaler = 0;

 htim2.Init.CounterMode = TIM_COUNTERMODE_UP;

 htim2.Init.Period = 1890;

 htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

 htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;

 if (HAL_TIM_Base_Init(&htim2) != HAL_OK)

 {

  Error_Handler();

 }

 sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;

 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;

 if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)

 {

  Error_Handler();

 }

----------------------------------------------------------------------------------------------------------------------------------

DMA:

  /* ADC1 DMA Init */

  /* ADC1 Init */

  hdma_adc1.Instance = DMA2_Stream0;

  hdma_adc1.Init.Channel = DMA_CHANNEL_0;

  hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;

  hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;

  hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;

  hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;

  hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;

  hdma_adc1.Init.Mode = DMA_NORMAL;

  hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;

  hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;

  if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)

  {

   Error_Handler();

  }

  __HAL_LINKDMA(hadc,DMA_Handle,hdma_adc1);

  /* ADC1 interrupt Init */

  HAL_NVIC_SetPriority(ADC_IRQn, 0, 0);

  HAL_NVIC_EnableIRQ(ADC_IRQn);

Thanks

Before the advent of Cube/HAL, I used to do something like this (for a F4 Discovery) :

/* configure the Timer3 to generate the periodic ADC trigger
 */
void  SampleTimerConfig (uint16_t smplperiod)
{
//  uint16_t                 PrescalerValue = 0;
//  NVIC_InitTypeDef         NVIC_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_OCInitTypeDef        TIM_OCInitStructure;
 
    RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM3, ENABLE);   /* TIM3 clock enable */
 
    /* TIM3 Configuration as trigger for the ADC */
    TIM_DeInit (TIM3);
    TIM_TimeBaseStructInit (&TIM_TimeBaseStructure);
    TIM_OCStructInit (&TIM_OCInitStructure);
 
    /* Time base configuration */
    TIM_TimeBaseStructure.TIM_Prescaler     = (SystemCoreClock / 1000000) - 1;   // 1.0 MHz
    TIM_TimeBaseStructure.TIM_Period        = smplperiod - 1;           // 1 MHz/period = smpl.freq
    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);
}
/* init PC0 and PC2 as ADC3 IN.10 / IN.12 with DMA configuration
 */
void  ADC_DMA_Config (void)
{
    ADC_InitTypeDef        ADC_InitStructure;
    ADC_CommonInitTypeDef  ADC_CommonInitStructure;
    DMA_InitTypeDef        DMA_InitStructure;
    GPIO_InitTypeDef       GPIO_InitStructure;
    NVIC_InitTypeDef       NVIC_InitStructure;
 
    /* Enable ADC3, DMA2 and GPIO clocks ****************************************/
    RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_DMA2 | RCC_AHB1Periph_GPIOC, ENABLE);
    RCC_APB2PeriphClockCmd (RCC_APB2Periph_ADC3, ENABLE);
 
    /* NVIC DMA interrupt setup - priority is 1, below sampling clock int. priority */
    NVIC_InitStructure.NVIC_IRQChannel                   = DMA2_Stream1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
    NVIC_Init (&NVIC_InitStructure);
 
#ifdef DEBUG_ADC_INTERRUPT
    /* NVIC ADC interrupt setup - priority is 1, below sampling clock int. priority */
    NVIC_InitStructure.NVIC_IRQChannel                   = ADC_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
    NVIC_Init (&NVIC_InitStructure);
#endif
 
    /* DMA2 Stream0 channel2 configuration **************************************/
    DMA_InitStructure.DMA_Channel            = DMA_Channel_2;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) ADC3_DR_ADDRESS;
    DMA_InitStructure.DMA_Memory0BaseAddr    = (uint32_t) &AdcBuffer;
    DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralToMemory;
    DMA_InitStructure.DMA_BufferSize         = ELFR_CFFT_LENGTH * AIN_CHANNELS;
    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_Stream1, &DMA_InitStructure);
    DMA_Cmd (DMA2_Stream1, ENABLE);
 
    /* Configure ADC3 Channels 10 + 12 pin as analog input **********************/
    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 (GPIOC, &GPIO_InitStructure);
 
    /* ADC Common Init **********************************************************/
    ADC_CommonInitStructure.ADC_Mode             = ADC_Mode_Independent;
    ADC_CommonInitStructure.ADC_Prescaler        = ADC_Prescaler_Div2;           // CLK = (APB2_CLK / 2)
    ADC_CommonInitStructure.ADC_DMAAccessMode    = ADC_DMAAccessMode_Disabled;
    ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_10Cycles;
    ADC_CommonInit (&ADC_CommonInitStructure);
 
    /* ADC3 Init ****************************************************************/
    /* the conversion start is triggered by SW from the timer interrupt TIM3 */
    ADC_InitStructure.ADC_Resolution           = ADC_Resolution_12b;
    ADC_InitStructure.ADC_DataAlign            = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_ScanConvMode         = ENABLE; // DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode   = DISABLE;
    ADC_InitStructure.ADC_NbrOfConversion      = AIN_CHANNELS;
    ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
    ADC_InitStructure.ADC_ExternalTrigConv     = ADC_ExternalTrigConv_T3_TRGO;
    ADC_Init (ADC3, &ADC_InitStructure);
 
    /* ADC3 regular channel7 configuration *************************************/
    ADC_RegularChannelConfig (ADC3, ADC_Channel_10, 1, ADC_SampleTime_112Cycles);
    ADC_RegularChannelConfig (ADC3, ADC_Channel_12, 2, ADC_SampleTime_112Cycles);
 
    ADC_DMARequestAfterLastTransferCmd (ADC3, ENABLE);
    ADC_DMACmd (ADC3, ENABLE);                         /* Enable ADC3 DMA  */
    ADC_Cmd    (ADC3, ENABLE);                         /* Enable ADC3      */
    TIM_Cmd    (TIM3, ENABLE);                         /* TIM3 enable counter, to trigger ADC */
    DMA_ITConfig (DMA2_Stream1, DMA_IT_TC, ENABLE);    /* interrupt notification when the buffer full */
 
#ifdef DEBUG_ADC_INTERRUPT
    ADC_ITConfig (ADC3, ADC_IT_EOC, ENABLE);          /* interrupt notification after sampling sequence */
#endif
}

mbmail4
Associate II

Thanks Ozone for your setup example. I made some changes with no luck, so I need to have a closer look. Funny though how it always works fine when running in continuous conversion mode, with DMA? or just in ADC trigger mode from TIM2, and xfers to Ram done from the ADC callback.?

mb

KnarfB
Principal III

My code works (for STM32F042), but I get "Post is too long" error when I try to insert everything. Here are the parts:

int main(void)
{
  /* USER CODE BEGIN 1 */
 
  /* USER CODE END 1 */
  
 
  /* MCU Configuration--------------------------------------------------------*/
 
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
 
  /* USER CODE BEGIN Init */
 
  /* USER CODE END Init */
 
  /* Configure the system clock */
  SystemClock_Config();
 
  /* USER CODE BEGIN SysInit */
  MX_DMA_Init();
  /* USER CODE END SysInit */
 
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  MX_DMA_Init();
  MX_ADC_Init();
  MX_TIM1_Init();
  /* USER CODE BEGIN 2 */
 
  HAL_UART_Transmit(&huart2, "let's go!\r\n", 12, 100 );
  HAL_TIM_Base_Start(&htim1);
  HAL_ADC_Start_DMA(&hadc, (uint32_t*)adc_buf, ADC_BUF_LEN);
  /* USER CODE END 2 */
 
 
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
 
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
static void MX_ADC_Init(void)
{
 
  /* USER CODE BEGIN ADC_Init 0 */
 
  /* USER CODE END ADC_Init 0 */
 
  ADC_ChannelConfTypeDef sConfig = {0};
 
  /* USER CODE BEGIN ADC_Init 1 */
 
  /* USER CODE END ADC_Init 1 */
  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) 
  */
  hadc.Instance = ADC1;
  hadc.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc.Init.Resolution = ADC_RESOLUTION_12B;
  hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;
  hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc.Init.LowPowerAutoWait = DISABLE;
  hadc.Init.LowPowerAutoPowerOff = DISABLE;
  hadc.Init.ContinuousConvMode = DISABLE;
  hadc.Init.DiscontinuousConvMode = DISABLE;
  hadc.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T1_TRGO;
  hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  hadc.Init.DMAContinuousRequests = ENABLE;
  hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  if (HAL_ADC_Init(&hadc) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure for the selected ADC regular channel to be converted. 
  */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC_Init 2 */
 
  /* USER CODE END ADC_Init 2 */
 
}
 
/**
  * @brief TIM1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM1_Init(void)
{
 
  /* USER CODE BEGIN TIM1_Init 0 */
 
  /* USER CODE END TIM1_Init 0 */
 
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
 
  /* USER CODE BEGIN TIM1_Init 1 */
 
  /* USER CODE END TIM1_Init 1 */
  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 0;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 999;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM1_Init 2 */
 
  /* USER CODE END TIM1_Init 2 */
 
}
/* USER CODE BEGIN 4 */
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc) {
  HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
  HAL_UART_Transmit_DMA(&huart2, &adc_buf[0], (ADC_BUF_LEN/2)*sizeof(adc_buf[0]) );
}
 
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
  HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
  HAL_UART_Transmit_DMA(&huart2, &adc_buf[ADC_BUF_LEN/2], (ADC_BUF_LEN/2)*sizeof(adc_buf[0]) );
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(hadc->Instance==ADC1)
  {
  /* USER CODE BEGIN ADC1_MspInit 0 */
 
  /* USER CODE END ADC1_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_ADC1_CLK_ENABLE();
  
    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**ADC GPIO Configuration    
    PA0     ------> ADC_IN0 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
    /* ADC1 DMA Init */
    /* ADC Init */
    hdma_adc.Instance = DMA1_Channel1;
    hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_adc.Init.MemInc = DMA_MINC_ENABLE;
    hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_adc.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_adc.Init.Mode = DMA_CIRCULAR;
    hdma_adc.Init.Priority = DMA_PRIORITY_LOW;
    if (HAL_DMA_Init(&hdma_adc) != HAL_OK)
    {
      Error_Handler();
    }
 
    __HAL_LINKDMA(hadc,DMA_Handle,hdma_adc);
 
  /* USER CODE BEGIN ADC1_MspInit 1 */
 
  /* USER CODE END ADC1_MspInit 1 */
  }
 
}

DMA is circular. hth

KnarfB