Skip to main content
Nolan L
Associate II
February 17, 2017
Solved

STM32L432KC - Timer DMA Triggering ADC DMA?

  • February 17, 2017
  • 7 replies
  • 5412 views
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
    This topic has been closed for replies.
    Best answer by Nolan L
    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();
     }
    }
    �?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

    7 replies

    S.Ma
    Principal
    February 17, 2017
    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.

    Nolan L
    Nolan LAuthor
    Associate II
    February 17, 2017
    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!

    S.Ma
    Principal
    February 17, 2017
    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...