cancel
Showing results for 
Search instead for 
Did you mean: 

ADC TIMER triggered DMA doesn't starts, STM32F401

CCont.1
Associate II

Hi everyone!

I am trying to write a simple code that starts an ADC DMA based on timer temporization.

In order to do this on a Nucleo-STM32F401RE board I use:

TIM2, channel 1, output compare no output, mode frozen;

ADC1, channel 0, timer 2 trigger out event, rising edge;

DMA2 Stream0, mode circular.

Looking at the debug the code works fine, timer2 starts and fire interrupts if they are enable, ADC conversion enable bit change its value in the register after the start instruction, but the DMA callback and ADC conversions never happen so the conversion buffer still empty.

There is someone who could take a look into the code and help me?

Below the code:

TIMER 2 INIT:

void MX_TIM2_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
 
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 100;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 100;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_OC_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_TIMING;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_OC_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
}

ADC 1 INIT:

void MX_ADC1_Init(void)
{
  ADC_ChannelConfTypeDef sConfig = {0};
 
  /** 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_DIV2;
  hadc1.Init.Resolution = ADC_RESOLUTION_8B;
  hadc1.Init.ScanConvMode = DISABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DMAContinuousRequests = DISABLE;
  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_0;
  sConfig.Rank = 1;
  sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

DMA INIT:

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

MAIN AND CALLBACK:

#define DEBUG 1
 
#define UART_RX_BUF_SIZE 1
#define UART_TIMEOUT 0X00000FFF
#define ADC_BUF_SIZE 5
 
volatile uint8_t UART_rx_buf[UART_RX_BUF_SIZE] = {0};
volatile uint8_t UART_rx_pending = 0;
const volatile char UART_tx_start[1] = "U";
volatile uint8_t UART_timeout = 0;
 
volatile uint16_t ADC_buf[ADC_BUF_SIZE] = {0};
 
int main(void)
{
  HAL_Init();
 
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART2_UART_Init();
  MX_ADC1_Init();
  MX_TIM2_Init();
 
#ifdef DEBUG
  DBGMCU->APB1FZ |= DBGMCU_APB1_FZ_DBG_TIM2_STOP;
#endif
 
  /*INITIALIZATION: SEND A CHARACTER AND WAIT FOR A RESPONSE*/
  HAL_Delay(10);
  UART_timeout = HAL_UART_Transmit(&huart2, (uint8_t*)UART_tx_start, (uint16_t)1, (uint32_t)UART_TIMEOUT);
  UART_timeout = HAL_UART_Receive(&huart2, (uint8_t*)UART_rx_buf, (uint16_t)UART_RX_BUF_SIZE, (uint32_t)UART_TIMEOUT);
  	  if(UART_timeout == HAL_TIMEOUT)  UART_config_timeout();
 
  /*DSO START CONFIGURATION*/
  TIMER_CONFIG(UART_rx_buf[0]);		//SET TIMER PRSC AND ARR
 
  HAL_UART_Receive_IT(&huart2, (uint8_t*)UART_rx_buf, (uint16_t)UART_RX_BUF_SIZE);	//UART RX INTERUPT ENABLE
 
  HAL_ADC_Start_DMA(&hadc1, (uint32_t*) ADC_buf, ADC_BUF_SIZE);		//START ADC-DMA REQUEAST
  HAL_TIM_Base_Start(&htim2);	//START TIMER
 
  while (1)
  {
	  if(UART_rx_pending == 1)	{
 
		  	UART_rx_pending = 0;
		  	/*MANAGE RX DATA*/
			HAL_UART_Receive_IT(&huart2, (uint8_t*)UART_rx_buf, (uint16_t)UART_RX_BUF_SIZE);
	  }
  }
}
 
============================================================================
 
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)  {
	int i=0;
	i++;
};
 
============================================================================

The USART communication is just to set the ARR and PSC of the timer, but for debug, they are set at 0x63 (100)

Thanks to all!

1 ACCEPTED SOLUTION

Accepted Solutions
DavidAlfa
Senior II

Your issue might be related to an incorrect configuration of DMAContinuousRequests and ADC ContinuousMode.

  

With ADC ContinuousMode enabled, the first TRGO signal will make the ADC to run free and convert as fast as it cans.

With ADC ContinuousMode disabled, each TRGO pulse will trigger a single ADC conversion.

If DMAContinuousRequests is enabled, DMA will never stop (the callbacks will be called on each half/full transfer), the buffer will be constantly overwritten receiving new data. The DMA should be set in Circular mode to restart the buffer address when it reaches its end, otherwise it will cause a DMA overflow error.

If DMAContinuousRequests is disabled, it will only do this once, after the specified length transfer.

After that, if you want to start a new ADC transfer, you will have to reset the ADC with ADC_STOP_DMA and then call ACD_START_DMA again, it can be done on the callback itself.

In your code, if I understood your intentions correctly, you want the DMA to fill the buffer continuosly, not just once.

So you should enable DMAContinuousRequests and configure the DMA in circular mode. That should do it.

Remember that the ADC DMA "Length" parameter is specified in DMA words, not in 8/16/32bit data sizes.

Check DMA modes

  • DMA mode1, HalfWord(16bit), storing single 12-bit ADC value.
  • DMA mode 2, Word (32bit), storing two 12-bit ADC values.
  • DMA mode 3, HalfWord(16bit), storing two 6-8bit ADC values.

Check the reference manual for the ADC modes: Interleaved, Dual, Alternate modes... DMA should be configured accordingly to the ADC mode.

Don't use the DMA_Stream IRQ, the HAL automatically handles it, also clears the DMA flags.

When you execute HAL_ADC_Start_DMA, it sets the DMA callbacks to its own, private callbacks.

Finally when the DMA triggers a callback, the HAL will redirect those callbacks to the ADC callbacks.

(Yep, callbacks everywhere)

Also, remember to clear the timer flag before starting the timebase with __HAL_TIM_CLEAR_FLAG(TIM_FLAG_UPDATE);

Otherwise you'll get an interruption just after enabling the timer that might not be adequate in your code.

I know it's a bit overwhelming at first, but with some practice you will end understanding the HAL and its working methods.

View solution in original post

5 REPLIES 5
TDK
Guru

> sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;

This connects the TRGO signal output to the reset event. If you want TRGO to trigger once per update, then this should be TIM_TRGO_UPDATE. If you want it connected to channel 1 compare, it should be TIM_TRGO_OC1.

Get the TIM/ADC working before you worry about UART. You can debug and inspect registers to ensure it's working.

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

Thank you, yes UART is isn't used at all, I forgot to comment it, anyway after the timeout the code go on.

I have done a few changes:

TIM_TRGO_RESET with TIM_TRGO_UPDATE, but it doesn't works yet,

I have started TIMER before ADC instead the posted main,

HAL_NVIC_ClearPendingIRQ(DMA2_Stream0_IRQn), clear DMA IRQ in the ISR.

void DMA2_Stream0_IRQHandler(void)
{
  HAL_NVIC_ClearPendingIRQ(DMA2_Stream0_IRQn);
  HAL_DMA_IRQHandler(&hdma_adc1);
 }

Now it works but only one end conversion DMA call is performed, after that the timer still running, but it dosen't start a conversion.

I saw that after the DMA callback in TIM2 -> SR all CCFxIF from 1 to 4 are asserted, but also if I clear they in the ISR the DMA is called only once, there is same others flag to clear in order to start a new process?

Pretty sure you should not be calling HAL_NVIC_ClearPendingIRQ within DMA2_Stream0_IRQHandler. Generate code from CubeMX and see what it does. There are also some examples in the repository you can lean on:

https://github.com/STMicroelectronics/STM32CubeF4/blob/b5abca20c9676b04f8d2885a668a9b653ee65705/Projects/STM324xG_EVAL/Examples/ADC/ADC_RegularConversion_DMA/Src/main.c

https://github.com/STMicroelectronics/STM32CubeF4/blob/b5abca20c9676b04f8d2885a668a9b653ee65705/Projects/STM324xG_EVAL/Examples/ADC/ADC_TriggerMode/Src/main.c

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

Your issue might be related to an incorrect configuration of DMAContinuousRequests and ADC ContinuousMode.

  

With ADC ContinuousMode enabled, the first TRGO signal will make the ADC to run free and convert as fast as it cans.

With ADC ContinuousMode disabled, each TRGO pulse will trigger a single ADC conversion.

If DMAContinuousRequests is enabled, DMA will never stop (the callbacks will be called on each half/full transfer), the buffer will be constantly overwritten receiving new data. The DMA should be set in Circular mode to restart the buffer address when it reaches its end, otherwise it will cause a DMA overflow error.

If DMAContinuousRequests is disabled, it will only do this once, after the specified length transfer.

After that, if you want to start a new ADC transfer, you will have to reset the ADC with ADC_STOP_DMA and then call ACD_START_DMA again, it can be done on the callback itself.

In your code, if I understood your intentions correctly, you want the DMA to fill the buffer continuosly, not just once.

So you should enable DMAContinuousRequests and configure the DMA in circular mode. That should do it.

Remember that the ADC DMA "Length" parameter is specified in DMA words, not in 8/16/32bit data sizes.

Check DMA modes

  • DMA mode1, HalfWord(16bit), storing single 12-bit ADC value.
  • DMA mode 2, Word (32bit), storing two 12-bit ADC values.
  • DMA mode 3, HalfWord(16bit), storing two 6-8bit ADC values.

Check the reference manual for the ADC modes: Interleaved, Dual, Alternate modes... DMA should be configured accordingly to the ADC mode.

Don't use the DMA_Stream IRQ, the HAL automatically handles it, also clears the DMA flags.

When you execute HAL_ADC_Start_DMA, it sets the DMA callbacks to its own, private callbacks.

Finally when the DMA triggers a callback, the HAL will redirect those callbacks to the ADC callbacks.

(Yep, callbacks everywhere)

Also, remember to clear the timer flag before starting the timebase with __HAL_TIM_CLEAR_FLAG(TIM_FLAG_UPDATE);

Otherwise you'll get an interruption just after enabling the timer that might not be adequate in your code.

I know it's a bit overwhelming at first, but with some practice you will end understanding the HAL and its working methods.

CCont.1
Associate II

Yes! Thank you!

I read what you have written and changed the peripherals settings, now it works well!

Thank you, you claryfied lots of dubts!