cancel
Showing results for 
Search instead for 
Did you mean: 

Timer triggered ADC acquisition through DMA

CDyer.1
Senior

Hi guys,

I have an application that requires TIM2 Ch2 to trigger ADC Ch3 every time the timer has a rising edge and in order to reduce the load on the CPU I'm doing it through DMA. Now I seem to have configured my application correctly as the ADC is operating correctly. I've put flags in both the DMA and ADC interrupt handlers to count how many times they are being called. The DMA interrupt handler is only ever called twice and the ADC interrupt handler is constantly being called. Now, what I would like to know is why the DMA isn't called at the same rate as the ADC and why is it called twice specifically? I have the DMA continuous requests enabled and thought that the process would be this:

  • Timer triggers ADC
  • ADC fills up buffer and sends interrupt
  • DMA transfers data from ADC to buffer and then sends interrupt
  • Repeat

Have I misunderstood something? Why isn't the DMA interrupt handler being called at the same rate as the ADC interrupt handler? This question is for understanding the process that is involved. I'm using HAL and STM32F767, here's the relevant code, thanks in advance:

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
volatile uint16_t ADC_Val[50];// = {0};
volatile uint16_t ADC_Total[260] = {0};
volatile uint16_t conversion_flag;
volatile uint32_t ADC_flag = 0;
volatile uint32_t DMA_flag = 0;
volatile uint16_t ADC_1[50];
volatile uint16_t ADC_2[50];
volatile uint16_t ADC_3[50];
volatile uint16_t ADC_4[50];
volatile uint16_t ADC_5[50];
 
 volatile uint32_t mean_calc_1 = 0;
volatile uint32_t mean_total_1 = 0;
/* USER CODE END 0 */
 
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 */
 
  /* USER CODE END SysInit */
 
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */
  HAL_ADC_Start_DMA(&hadc1, ADC_Val, sizeof(ADC_Val));
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
 
 
  /* 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_ADC1_Init(void)
{
 
  /* USER CODE BEGIN ADC1_Init 0 */
 
  /* USER CODE END ADC1_Init 0 */
 
  ADC_ChannelConfTypeDef sConfig = {0};
 
  /* USER CODE BEGIN ADC1_Init 1 */
 
  /* USER CODE END ADC1_Init 1 */
  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) 
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_CC2;
  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_3;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC1_Init 2 */
 
  /* USER CODE END ADC1_Init 2 */
 
}
 
/**
  * @brief TIM2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM2_Init(void)
{
 
  /* USER CODE BEGIN TIM2_Init 0 */
 
  /* USER CODE END TIM2_Init 0 */
 
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
 
  /* USER CODE BEGIN TIM2_Init 1 */
 
  /* USER CODE END TIM2_Init 1 */
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 0;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 20000;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 5000;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM2;
  if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM2_Init 2 */
 
  /* USER CODE END TIM2_Init 2 */
  HAL_TIM_MspPostInit(&htim2);
 
}
 
/** 
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void) 
{
 
  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();
 
  /* DMA interrupt init */
  /* DMA2_Stream0_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
 
}
void ADC_IRQHandler(void)
{
  /* USER CODE BEGIN ADC_IRQn 0 */
	//GPIOA->ODR |= (1 << 4);
  /* USER CODE END ADC_IRQn 0 */
  HAL_ADC_IRQHandler(&hadc1);
  /* USER CODE BEGIN ADC_IRQn 1 */
  ADC_flag ++;
  /* USER CODE END ADC_IRQn 1 */
}
 
/**
  * @brief This function handles TIM2 global interrupt.
  */
 
 
/**
  * @brief This function handles DMA2 stream0 global interrupt.
  */
void DMA2_Stream0_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2_Stream0_IRQn 0 */
	 
  /* USER CODE END DMA2_Stream0_IRQn 0 */
     HAL_DMA_IRQHandler(&hdma_adc1);
	DMA_flag ++;
  /* USER CODE BEGIN DMA2_Stream0_IRQn 1 */
 
    /* USER CODE BEGIN W1_UsageFault_IRQn 0 */
    /* USER CODE END W1_UsageFault_IRQn 0 */
 
  /* USER CODE END DMA2_Stream0_IRQn 1 */
}

 From msp as requested:

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();
    /**ADC1 GPIO Configuration    
    PA3     ------> ADC1_IN3 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
    /* 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_LOW;
    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);
  /* USER CODE BEGIN ADC1_MspInit 1 */
 
  /* USER CODE END ADC1_MspInit 1 */
  }
 
}
 
/**
* @brief ADC MSP De-Initialization
* This function freeze the hardware resources used in this example
* @param hadc: ADC handle pointer
* @retval None
*/
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* hadc)
{
  if(hadc->Instance==ADC1)
  {
  /* USER CODE BEGIN ADC1_MspDeInit 0 */
 
  /* USER CODE END ADC1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_ADC1_CLK_DISABLE();
  
    /**ADC1 GPIO Configuration    
    PA3     ------> ADC1_IN3 
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_3);
 
    /* ADC1 DMA DeInit */
    HAL_DMA_DeInit(hadc->DMA_Handle);
 
    /* ADC1 interrupt DeInit */
    HAL_NVIC_DisableIRQ(ADC_IRQn);
  /* USER CODE BEGIN ADC1_MspDeInit 1 */
 
  /* USER CODE END ADC1_MspDeInit 1 */
  }
 
}
 
/**
* @brief TIM_PWM MSP Initialization
* This function configures the hardware resources used in this example
* @param htim_pwm: TIM_PWM handle pointer
* @retval None
*/
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* htim_pwm)
{
  if(htim_pwm->Instance==TIM2)
  {
  /* USER CODE BEGIN TIM2_MspInit 0 */
 
  /* USER CODE END TIM2_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_TIM2_CLK_ENABLE();
    /* TIM2 interrupt Init */
    HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);
  /* USER CODE BEGIN TIM2_MspInit 1 */
 
  /* USER CODE END TIM2_MspInit 1 */
  }
 
}

8 REPLIES 8
TDK
Guru

You initialize DMA2_Stream1 within MX_DMA_Init, but I don't see you initializing DMA2_Stream0 anywhere.

Looks like you've edited these a bit. There should be a call to HAL_DMA_IRQHandler() from within DMA2_Stream0_IRQHandler. Without this, the flags never get cleared and the ISR will get called continuously.

> HAL_ADC_Start_DMA(&hadc1, ADC_Val, sizeof(ADC_Val));

You don't list how ADC_Val is defined, but this is probably going to cause a buffer overrun. You're producing 16-bit values (12 bits + padding), so your array is only going to hold sizeof(ADC_Val)/2 of them.

There's also some initialization done within HAL_ADC_MspInit() which you should check or post.

If you feel a post has answered your question, please click "Accept as Solution".

Hi, thanks for the reply. You're correct, in an attempt to make the code more readable I accidentally removed the HAL_DMA_IRQHandler() call. It's really difficult to see what one is doing with that tiny box they give you to post code.. I've also cleaned up the DMA initialisation as I was previously using mem2mem but i've since dropped that and am now only using periph2mem. I've also added what you've asked for. Thanks for the help.

Also, would it be possible to clarify something else for me? Is the ADC_IRQHandler() called when the timer triggers the ADC or when the ADC has finished a conversion?

TDK
Guru

> It's really difficult to see what one is doing with that tiny box they give you to post code

You can minimize/close panels to get more space. The workspace is very configurable.

> Is the ADC_IRQHandler() called when the timer triggers the ADC or when the ADC has finished a conversion?

You could configure the ADC to trigger an interrupt at every conversion or never. See the ADC_IER register.

Here's what the HAL documentation says:

     *** DMA mode IO operation ***    
     ==============================
     [..]    
       (+) Start the ADC peripheral using HAL_ADC_Start_DMA(), at this stage the user specify the length 
           of data to be transferred at each end of conversion 
       (+) At The end of data transfer by HAL_ADC_ConvCpltCallback() function is executed and user can 
           add his own code by customization of function pointer HAL_ADC_ConvCpltCallback 
       (+) In case of transfer Error, HAL_ADC_ErrorCallback() function is executed and user can 
           add his own code by customization of function pointer HAL_ADC_ErrorCallback
       (+) Stop the ADC peripheral using HAL_ADC_Stop_DMA()

Which would make me guess the ADC interrupt is never called during normal operation. It's probably getting called for you due to ADC overrun, since you didn't have DMA set up correctly.

You could look at the ADC_IER register to see which interrupts are enabled.

If you feel a post has answered your question, please click "Accept as Solution".

> It's really difficult to see what one is doing with that tiny box they give you to post code

>You can minimize/close panels to get more space. The workspace is very configurable.

I was referring to posting code on this website, I think you're referring to CubeIDE?

>See the ADC_IER register.

This doesn't seem to be present in the STM32F767 as it isn't in the datasheet? Checking OVRIE in the ADC_CR1 it is set to 0 i.e. overrun interrupts aren't enabled.

>since you didn't have DMA set up correctly.

In the code I posted to the forum I had accidentally removed the HAL_DMA_IRQHandler() call to the forum post only, not the actual running code sorry if i didn't make that clear.

As stated in my original post, I thought the code seems to be working fine. The ADC interrupt gets triggered at the intended time due to the rising edge of TIM2 and I had all my data manipulation occurring in the interrupt handler. Now that I've moved all my data manipulation in to the HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) like it should be, it's again only running through it twice despite the ADC interrupt being triggered as expected. So it's not working correctly. I don't have overrun interrupts enabled on the ADC so I don't think ithe ADC is being triggered due to that.

S.Ma
Principal

You should first use a physical GPIO output of the TIMER ADC trigger generator, then connect it to the ADC Trigger input pin.

Use the DMA in cyclic mode over a ram buffer

Normally, you don't need any ADC interrupt, you will get 2 DMA interrupts, half and full transfer complete.

This way you get 1/2 updated for processing and 1/2 being filled. This should give you more time to get things smoothely running,

S.Ma
Principal

Once all settled, use the internal signals to connect timer to adc and save the GPIO used for monitoring/debug

Ahhhhh, as soon as you mentioned DMA in cyclic mode I realised what I had done wrong! It was in normal mode. This is why I was getting 2 DMA interrupts only (like you said, half and full) and then nothing but ADC interrupts for the remainder of the programs life. I think you get an ADC interrupt everytime the timer fires an update? Either way, my flags in the interrupt handlers are showing that the DMA and ADC interrupt handlers are triggering now but with the DMA at twice the ferquency ( as expected i believe?) Thanks a lot!