cancel
Showing results for 
Search instead for 
Did you mean: 

STM32L432KC - Timer DMA Triggering ADC DMA?

Nolan L
Associate III
Posted on February 17, 2017 at 05:19

Hi,

On my larger project, I would like to be able to generate a timer that will trigger an ADC read on each rising edge. I was able to tackle this problem by creating a smaller project and executing it! Super excited.

My excitement was a bit dampened when I realized how many interrupts were firing fromthe timer. For this test, I am currently operating my timer at ~123kHz which equates to roughly 8.13uS. While this is fine running on a while(1) loop example, I don't think it will go well with my larger project running FreeRTOS.

Now comes the question:

Is there a way to set this up where the timer can generate a DMA to trigger the ADC read using the DMA? I would imagine this is the best path forward to minimize CPU usage if possible. That way, I am not firing my interrupts every 8.13uS. If not, what is the best engineering practice?

Any example code is greatly appreciated! I just couldn't seem to find any post in this community trying to user a timer with DMA access to trigger an ADC DMA.

According to RM0393 (Page 776), it looks as if I should be able to doan event trigger on TIM15:

• Interrupt/DMA generation on the following events:

– Update: counter overflow, counter initialization (by software or internal/external trigger)

– Trigger event (counter start, stop, initialization or count by internal/external trigger)

– Input capture

– Output compare

– Break input (interrupt request)

Note: I am currently not DMA-ing the ADC in this example. This will be done on the larger project!

I am not actually grabbing the ADC value but toggling a pin to know that the ADC is being called on the rising edge of the timer.

Main:

int main(void)
{
 /* Clock Inits*/
 /* Initialize all configured peripherals */
 MX_GPIO_Init();
 MX_DMA_Init();
 MX_TIM15_Init();
 MX_ADC1_Init();
 /* USER CODE BEGIN 2 */
 HAL_TIM_Base_Start_IT(&htim15);
 HAL_ADC_Start_IT(&hadc1);
 /* USER CODE END 2 */
 /* Infinite loop */
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

Callbacks:

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
 if (hadc->Instance==ADC1)
 {
 HAL_GPIO_TogglePin(USER_ADC_TOGGLE_PIN_GPIO_Port, USER_ADC_TOGGLE_PIN_Pin);
 }
}�?�?�?�?�?�?�?

Interrupt:

void TIM1_BRK_TIM15_IRQHandler(void)
{
 HAL_GPIO_TogglePin(USER_TIMER_TOGGLE_PIN_GPIO_Port, USER_TIMER_TOGGLE_PIN_Pin);
 HAL_TIM_IRQHandler(&htim15);
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

Timer / ADC Setup:

static void MX_TIM15_Init(void)
{
 TIM_MasterConfigTypeDef sMasterConfig;
 TIM_OC_InitTypeDef sConfigOC;
 TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
 htimInstance = TIM15;
 htimInit.Prescaler = 47;
 htimInit.CounterMode = TIM_COUNTERMODE_UP;
 htimInit.Period = 1;
 htimInit.ClockDivision = TIM_CLOCKDIVISION_DIV1;
 htimInit.RepetitionCounter = 0;
 if (HAL_TIM_PWM_Init(&htim15) != HAL_OK)
 {
 Error_Handler();
 }
 sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
 if (HAL_TIMEx_MasterConfigSynchronization(&htim15, &sMasterConfig) != HAL_OK)
 {
 Error_Handler();
 }
 sConfigOC.OCMode = TIM_OCMODE_PWM1;
 sConfigOC.Pulse = 0;
 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
 sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
 sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
 sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
 if (HAL_TIM_PWM_ConfigChannel(&htim15, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
 {
 Error_Handler();
 }
 sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
 sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
 sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
 sBreakDeadTimeConfig.DeadTime = 0;
 sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
 sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
 sBreakDeadTimeConfig.BreakFilter = 0;
 sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
 if (HAL_TIMEx_ConfigBreakDeadTime(&htim15, &sBreakDeadTimeConfig) != HAL_OK)
 {
 Error_Handler();
 }
}
static void MX_ADC1_Init(void)
{
 ADC_ChannelConfTypeDef sConfig;
 /**Common config 
 */
 hadc1.Instance = ADC1;
 hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
 hadc1.Init.Resolution = ADC_RESOLUTION_12B;
 hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
 hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
 hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
 hadc1.Init.LowPowerAutoWait = DISABLE;
 hadc1.Init.ContinuousConvMode = DISABLE;
 hadc1.Init.NbrOfConversion = 1;
 hadc1.Init.DiscontinuousConvMode = ENABLE;
 hadc1.Init.NbrOfDiscConversion = 1;
 hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T15_TRGO;
 hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
 hadc1.Init.DMAContinuousRequests = DISABLE;
 hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
 hadc1.Init.OversamplingMode = DISABLE;
 if (HAL_ADC_Init(&hadc1) != HAL_OK)
 {
 Error_Handler();
 }
 /**Configure Regular Channel 
 */
 sConfig.Channel = ADC_CHANNEL_8;
 sConfig.Rank = 1;
 sConfig.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
 sConfig.SingleDiff = ADC_SINGLE_ENDED;
 sConfig.OffsetNumber = ADC_OFFSET_NONE;
 sConfig.Offset = 0;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 Error_Handler();
 }
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

#•-interruptdma-generation-on-the-following-events:-–-up
1 ACCEPTED SOLUTION

Accepted Solutions
Posted on February 17, 2017 at 23:21

Based on my last statement/question, I decided to do a bit more digging and trial and error.

It turns out I am capable of tying the two 'pins' together internally without having to physically go outside of the MCU and back into another pin all the while bypassinginterrupt. Sounds too good to be true haha.

I configured it in the following fashion:

  • TIM15 PWM with no output @ 1MHz. Enabled Master/Slave Mode sync via TRGO.
  • De-selected Timer 15 interrupt.
  • TIM15 Trigger Event Selection is Compare Pulse (OC1)
  • ADC1 will conduct Regular Conversion Mode on trigger 'Timer 15 Trigger Out Event' on the rising edge.
  • Set up ADC1 for DMA transfer on whole word and a circular buffer.

I was capable of reading out ADC values at a 1Msps while offloading all the work to the DMA.

I'll post my code in hopes that it may help someone else!

Main:

uint32_t DMA_ADCvalues[1024];
int main(void)
{
 /* Clocks and Initializations of Peripherals */
 /* USER CODE BEGIN 2 */
 HAL_TIM_Base_Start_IT(&htim15);
 HAL_ADC_Start_DMA(&hadc1, (uint32_t *)DMA_ADCvalues, 1024);
 /* USER CODE END 2 */
 while(1);
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

Callbacks:

void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
//HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
HAL_GPIO_TogglePin(USER_TEST_GPIO_Port, USER_TEST_Pin);
}�?�?�?�?�?�?�?�?�?

ADC/Timer Setup:

/* ADC1 init function */
static void MX_ADC1_Init(void)
{
 ADC_ChannelConfTypeDef sConfig;
 /**Common config 
 */
 hadc1.Instance = ADC1;
 hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
 hadc1.Init.Resolution = ADC_RESOLUTION_12B;
 hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
 hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
 hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
 hadc1.Init.LowPowerAutoWait = DISABLE;
 hadc1.Init.ContinuousConvMode = DISABLE;
 hadc1.Init.NbrOfConversion = 1;
 hadc1.Init.DiscontinuousConvMode = DISABLE;
 hadc1.Init.NbrOfDiscConversion = 1;
 hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T15_TRGO;
 hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
 hadc1.Init.DMAContinuousRequests = ENABLE;
 hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
 hadc1.Init.OversamplingMode = DISABLE;
 if (HAL_ADC_Init(&hadc1) != HAL_OK)
 {
 Error_Handler();
 }
 /**Configure Regular Channel 
 */
 sConfig.Channel = ADC_CHANNEL_8;
 sConfig.Rank = 1;
 sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5;
 sConfig.SingleDiff = ADC_SINGLE_ENDED;
 sConfig.OffsetNumber = ADC_OFFSET_NONE;
 sConfig.Offset = 0;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 Error_Handler();
 }
}
/* TIM15 init function */
static void MX_TIM15_Init(void)
{
 TIM_MasterConfigTypeDef sMasterConfig;
 TIM_OC_InitTypeDef sConfigOC;
 TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
 htimInstance = TIM15;
 htimInit.Prescaler = 0;
 htimInit.CounterMode = TIM_COUNTERMODE_UP;
 htimInit.Period = 63;
 htimInit.ClockDivision = TIM_CLOCKDIVISION_DIV1;
 htimInit.RepetitionCounter = 0;
 if (HAL_TIM_PWM_Init(&htim15) != HAL_OK)
 {
 Error_Handler();
 }
 sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1;
 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
 if (HAL_TIMEx_MasterConfigSynchronization(&htim15, &sMasterConfig) != HAL_OK)
 {
 Error_Handler();
 }
 sConfigOC.OCMode = TIM_OCMODE_PWM1;
 sConfigOC.Pulse = 0;
 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
 sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
 sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
 sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
 if (HAL_TIM_PWM_ConfigChannel(&htim15, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
 {
 Error_Handler();
 }
 sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
 sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
 sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
 sBreakDeadTimeConfig.DeadTime = 0;
 sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
 sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
 sBreakDeadTimeConfig.BreakFilter = 0;
 sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
 if (HAL_TIMEx_ConfigBreakDeadTime(&htim15, &sBreakDeadTimeConfig) != HAL_OK)
 {
 Error_Handler();
 }
}
�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

View solution in original post

7 REPLIES 7
S.Ma
Principal
Posted on February 17, 2017 at 09:01

No interrupt is needed when directly going to the HW assisted functions, 1 Mega samples per second is achievable.

And to debug it, make the timer generate 1 MHz pulses on an output channel. Connect this output pin to the ADC trigger input channel. Use the ADC DMA to store the sampled data into a RAM buffer. This way, can cut the trigger wire and debug easily, can manually trigger the ADC or check the Timer pulses without cascading the ADC/DMA.

Posted on February 17, 2017 at 11:23

Thanks for the quick response KIC8462852 EPIC204278916.

I want to make sure I am understanding you correctly.

With your proposed setup, I will need to drive a PWM on an external physical pin @ 1MHz (to Achieve 1 Mega Sample / Second for argument sake) into an external physical input pin for the ADC (EXTI line 11)?

Thanks! I am just trying to wrap my head around this!

Posted on February 17, 2017 at 15:59

You got it right. And it works if all the settings are correctly done. (the ADC sampling cycle must be faster than the pulse frequency).

This way, later on, you can also save power as probably SYSCLK won't need to be so high due to some interrupt latency...

Posted on February 17, 2017 at 17:03

Thanks. That makes perfect sense and looking at the STM32Cube, the

ADC1_EXTI11 pin showed up 

once I selected 'Regular Conversion Trigger' on ADC1.

Unfortunately it seems that I have to choose between 'Regular Conversion Trigger' for ADC1 or CAN1 master mode. Both are necessary.

I can get Injected Conversion Trigger (ADC1_EXTI15) but I don't think that is exactly what I want as I want to be sampling at a higher rate. (I believe injection lets me interrupt my current ADC read to prioritize another channel but sample rate stays the same) This seems like another question for another thread.

Thanks again for your support!!!!

Posted on February 17, 2017 at 23:21

Based on my last statement/question, I decided to do a bit more digging and trial and error.

It turns out I am capable of tying the two 'pins' together internally without having to physically go outside of the MCU and back into another pin all the while bypassinginterrupt. Sounds too good to be true haha.

I configured it in the following fashion:

  • TIM15 PWM with no output @ 1MHz. Enabled Master/Slave Mode sync via TRGO.
  • De-selected Timer 15 interrupt.
  • TIM15 Trigger Event Selection is Compare Pulse (OC1)
  • ADC1 will conduct Regular Conversion Mode on trigger 'Timer 15 Trigger Out Event' on the rising edge.
  • Set up ADC1 for DMA transfer on whole word and a circular buffer.

I was capable of reading out ADC values at a 1Msps while offloading all the work to the DMA.

I'll post my code in hopes that it may help someone else!

Main:

uint32_t DMA_ADCvalues[1024];
int main(void)
{
 /* Clocks and Initializations of Peripherals */
 /* USER CODE BEGIN 2 */
 HAL_TIM_Base_Start_IT(&htim15);
 HAL_ADC_Start_DMA(&hadc1, (uint32_t *)DMA_ADCvalues, 1024);
 /* USER CODE END 2 */
 while(1);
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

Callbacks:

void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
//HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
HAL_GPIO_TogglePin(USER_TEST_GPIO_Port, USER_TEST_Pin);
}�?�?�?�?�?�?�?�?�?

ADC/Timer Setup:

/* ADC1 init function */
static void MX_ADC1_Init(void)
{
 ADC_ChannelConfTypeDef sConfig;
 /**Common config 
 */
 hadc1.Instance = ADC1;
 hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
 hadc1.Init.Resolution = ADC_RESOLUTION_12B;
 hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
 hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
 hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
 hadc1.Init.LowPowerAutoWait = DISABLE;
 hadc1.Init.ContinuousConvMode = DISABLE;
 hadc1.Init.NbrOfConversion = 1;
 hadc1.Init.DiscontinuousConvMode = DISABLE;
 hadc1.Init.NbrOfDiscConversion = 1;
 hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T15_TRGO;
 hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
 hadc1.Init.DMAContinuousRequests = ENABLE;
 hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
 hadc1.Init.OversamplingMode = DISABLE;
 if (HAL_ADC_Init(&hadc1) != HAL_OK)
 {
 Error_Handler();
 }
 /**Configure Regular Channel 
 */
 sConfig.Channel = ADC_CHANNEL_8;
 sConfig.Rank = 1;
 sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5;
 sConfig.SingleDiff = ADC_SINGLE_ENDED;
 sConfig.OffsetNumber = ADC_OFFSET_NONE;
 sConfig.Offset = 0;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 Error_Handler();
 }
}
/* TIM15 init function */
static void MX_TIM15_Init(void)
{
 TIM_MasterConfigTypeDef sMasterConfig;
 TIM_OC_InitTypeDef sConfigOC;
 TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
 htimInstance = TIM15;
 htimInit.Prescaler = 0;
 htimInit.CounterMode = TIM_COUNTERMODE_UP;
 htimInit.Period = 63;
 htimInit.ClockDivision = TIM_CLOCKDIVISION_DIV1;
 htimInit.RepetitionCounter = 0;
 if (HAL_TIM_PWM_Init(&htim15) != HAL_OK)
 {
 Error_Handler();
 }
 sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1;
 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
 if (HAL_TIMEx_MasterConfigSynchronization(&htim15, &sMasterConfig) != HAL_OK)
 {
 Error_Handler();
 }
 sConfigOC.OCMode = TIM_OCMODE_PWM1;
 sConfigOC.Pulse = 0;
 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
 sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
 sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
 sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
 if (HAL_TIM_PWM_ConfigChannel(&htim15, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
 {
 Error_Handler();
 }
 sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
 sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
 sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
 sBreakDeadTimeConfig.DeadTime = 0;
 sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
 sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
 sBreakDeadTimeConfig.BreakFilter = 0;
 sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
 if (HAL_TIMEx_ConfigBreakDeadTime(&htim15, &sBreakDeadTimeConfig) != HAL_OK)
 {
 Error_Handler();
 }
}
�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

Posted on February 19, 2017 at 18:51

Good job! It is the mature way of doing it, less GPIO, less debugging facilities, ready for final implementation.

Posted on December 21, 2017 at 16:19

Wow! I am impressed at how you laid out your question.  This is how forum knowledge is built!  Kudos for taking the time to post code in such an organized and thoughtful way.