Skip to main content
Associate
July 14, 2025
Solved

Using TIM1 CH1 External Clock on PE9 to Trigger DMA on GPIOE→IDR (Callbacks Never Fire)

  • July 14, 2025
  • 3 replies
  • 417 views

Hello everyone,

I’m working with an STM32H743VIHX and CubeMX 6.13.0-RC5, and I’m trying to capture a square-wave clock on PE9 (mapped to TIM1_CH1) so that each rising edge:

  1. Increments the TIM1 counter (External Clock Mode 1 via TI1FP1)

  2. Fires a DMA transfer from GPIOE->IDR into a circular buffer

  3. Invokes my HAL_DMA_RxHalfCpltCallback() / HAL_DMA_RxCpltCallback()

Even though I can see the TIM1 counter increment (and even wrap around), my DMA callbacks never get called.

Here’s what I’ve set up:

  • PE9 → AF1 → TIM1_CH1

  • Slave Mode

    • Slave Mode = External Clock Mode 1

    • Trigger = TI1FP1

    • Polarity = Rising

  • Channel 1

    • ICSelection = Direct TI1

    • ICPolarity = Rising

    • Filter = 0

  • DMA1_Stream1 is used for TIM1_CH1

  • NVIC: DMA1_Stream1_IRQn enabled

Has anyone successfully used TIM1_CH1 → DMA1_Stream1 on an STM32H7?

I’ll attach my full main.cand stm32h7xx_hal_msp.c, files below. Any help would be greatly appreciated!

Thank you in advance for your insights.

Best answer by johnotraffic

 

Update: Solved! Here’s what finally worked for TIM1_CH1 → DMA on every PE9 rising edge

The key was turning the CC1 events into TRGO pulses and wiring that into a trigger‐DMA stream.
Here’s the summary of what I changed:


1. TIM1 Initialization + Start DMA

static void MX_TIM1_Init(void)
{

 /* USER CODE BEGIN TIM1_Init 0 */

 /* USER CODE END TIM1_Init 0 */

 TIM_SlaveConfigTypeDef sSlaveConfig = {0};
 TIM_MasterConfigTypeDef sMasterConfig = {0};
 TIM_IC_InitTypeDef sConfigIC = {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 = 65535;
 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();
 }

 // 1) Input-Capture on CH1 so we get CC1 events from TI1FP1
 if (HAL_TIM_IC_Init(&htim1) != HAL_OK) {
 Error_Handler();
 }
 sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
 sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
 sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
 sConfigIC.ICFilter = 0;
 if (HAL_TIM_IC_ConfigChannel(&htim1, &sConfigIC, TIM_CHANNEL_1) != HAL_OK) {
 Error_Handler();
 }

 // 2) External clock mode 1: TI1FP1 (PE9 rising edge) → counter + CC1
 sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
 sSlaveConfig.InputTrigger = TIM_TS_TI1FP1;
 sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;
 sSlaveConfig.TriggerFilter = 0;
 if (HAL_TIM_SlaveConfigSynchro(&htim1, &sSlaveConfig) != HAL_OK) {
 Error_Handler();
 }

 // 3) Make CC1 mismatches ALSO generate a TRGO
 sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1; // TRGO on CC1
 sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
 if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) {
 Error_Handler();
 }
 /* USER CODE BEGIN TIM1_Init 2 */

 // 4) Arm the trigger-DMA (TIM1_TRIG) to copy GPIOE->IDR → portE_samples[]
 // and install your half/full callbacks in MSP-init.
 if (HAL_DMA_Start_IT(&hdma_tim1_trig,
 (uint32_t)&GPIOE->IDR,
 (uint32_t)portE_samples,
 PORT_E_SAMPLES_BUFFER_SIZE) != HAL_OK)
 {
 Error_Handler();
 }
 __HAL_TIM_ENABLE_DMA(&htim1, TIM_DMA_TRIGGER);

 // 5) Finally start the timer itself
 HAL_TIM_Base_Start(&htim1);

 /* USER CODE END TIM1_Init 2 */

}

2. DMA (Triggered by TRGO)

 
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
{
 GPIO_InitTypeDef GPIO_InitStruct = {0};
 if(htim_base->Instance==TIM1)
 {
 /* USER CODE BEGIN TIM1_MspInit 0 */

 /* USER CODE END TIM1_MspInit 0 */
 /* Peripheral clock enable */
 __HAL_RCC_TIM1_CLK_ENABLE();

 __HAL_RCC_GPIOE_CLK_ENABLE();
 /**TIM1 GPIO Configuration
 PE9 ------> TIM1_CH1
 */
 GPIO_InitStruct.Pin = L_CLK_RX_Pin;
 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
 GPIO_InitStruct.Pull = GPIO_NOPULL;
 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
 GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;
 HAL_GPIO_Init(L_CLK_RX_GPIO_Port, &GPIO_InitStruct);

 /* TIM1 DMA Init */
 /* TIM1_TRIG Init */
 hdma_tim1_trig.Instance = DMA1_Stream1;
 hdma_tim1_trig.Init.Request = DMA_REQUEST_TIM1_TRIG;
 hdma_tim1_trig.Init.Direction = DMA_PERIPH_TO_MEMORY;
 hdma_tim1_trig.Init.PeriphInc = DMA_PINC_DISABLE;
 hdma_tim1_trig.Init.MemInc = DMA_MINC_ENABLE;
 hdma_tim1_trig.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
 hdma_tim1_trig.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
 hdma_tim1_trig.Init.Mode = DMA_CIRCULAR;
 hdma_tim1_trig.Init.Priority = DMA_PRIORITY_HIGH;
 hdma_tim1_trig.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
 if (HAL_DMA_Init(&hdma_tim1_trig) != HAL_OK)
 {
 Error_Handler();
 }

 __HAL_LINKDMA(htim_base,hdma[TIM_DMA_ID_TRIGGER],hdma_tim1_trig);

 /* USER CODE BEGIN TIM1_MspInit 1 */
 extern void HAL_DMA_RxHalfCpltCallback(DMA_HandleTypeDef *hdma);
 extern void HAL_DMA_RxCpltCallback(DMA_HandleTypeDef *hdma);
	// Register your callbacks now, before you ever start the DMA:
	HAL_DMA_RegisterCallback(&hdma_tim1_trig,
						 HAL_DMA_XFER_HALFCPLT_CB_ID,
						 HAL_DMA_RxHalfCpltCallback);
	HAL_DMA_RegisterCallback(&hdma_tim1_trig,
						 HAL_DMA_XFER_CPLT_CB_ID,
						 HAL_DMA_RxCpltCallback);

	// Enable the IRQ line for that stream
	HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 0, 0);
	HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);

 /* USER CODE END TIM1_MspInit 1 */

 }

}

Result

  • PE9 rising edge → TI1FP1 external clock tick

  • CC1 match → Because CC1S=01, the timer captures and also generates TRGO (MMS=OC1)

  • TRGO → Fires the TIM1_TRIG DMA request

  • DMA (Circular) → Reads GPIOE->IDR into portE_samples[]

  • HAL_DMA_RxHalfCpltCallback / HAL_DMA_RxCpltCallback now fire as expected!

Hope this helps anyone else wrestling with TIMx + trigger-DMA on the H7 series.

3 replies

TDK
Super User
July 14, 2025

Ensure buffer is somewhere that DMA can access.

When code fails to behave as expected, debug it and examine the state handles for evidence of errors. Implement error callbacks.

"If you feel a post has answered your question, please click ""Accept as Solution""."
johnotrafficAuthorBest answer
Associate
July 15, 2025

 

Update: Solved! Here’s what finally worked for TIM1_CH1 → DMA on every PE9 rising edge

The key was turning the CC1 events into TRGO pulses and wiring that into a trigger‐DMA stream.
Here’s the summary of what I changed:


1. TIM1 Initialization + Start DMA

static void MX_TIM1_Init(void)
{

 /* USER CODE BEGIN TIM1_Init 0 */

 /* USER CODE END TIM1_Init 0 */

 TIM_SlaveConfigTypeDef sSlaveConfig = {0};
 TIM_MasterConfigTypeDef sMasterConfig = {0};
 TIM_IC_InitTypeDef sConfigIC = {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 = 65535;
 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();
 }

 // 1) Input-Capture on CH1 so we get CC1 events from TI1FP1
 if (HAL_TIM_IC_Init(&htim1) != HAL_OK) {
 Error_Handler();
 }
 sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
 sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
 sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
 sConfigIC.ICFilter = 0;
 if (HAL_TIM_IC_ConfigChannel(&htim1, &sConfigIC, TIM_CHANNEL_1) != HAL_OK) {
 Error_Handler();
 }

 // 2) External clock mode 1: TI1FP1 (PE9 rising edge) → counter + CC1
 sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
 sSlaveConfig.InputTrigger = TIM_TS_TI1FP1;
 sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;
 sSlaveConfig.TriggerFilter = 0;
 if (HAL_TIM_SlaveConfigSynchro(&htim1, &sSlaveConfig) != HAL_OK) {
 Error_Handler();
 }

 // 3) Make CC1 mismatches ALSO generate a TRGO
 sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1; // TRGO on CC1
 sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
 if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) {
 Error_Handler();
 }
 /* USER CODE BEGIN TIM1_Init 2 */

 // 4) Arm the trigger-DMA (TIM1_TRIG) to copy GPIOE->IDR → portE_samples[]
 // and install your half/full callbacks in MSP-init.
 if (HAL_DMA_Start_IT(&hdma_tim1_trig,
 (uint32_t)&GPIOE->IDR,
 (uint32_t)portE_samples,
 PORT_E_SAMPLES_BUFFER_SIZE) != HAL_OK)
 {
 Error_Handler();
 }
 __HAL_TIM_ENABLE_DMA(&htim1, TIM_DMA_TRIGGER);

 // 5) Finally start the timer itself
 HAL_TIM_Base_Start(&htim1);

 /* USER CODE END TIM1_Init 2 */

}

2. DMA (Triggered by TRGO)

 
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
{
 GPIO_InitTypeDef GPIO_InitStruct = {0};
 if(htim_base->Instance==TIM1)
 {
 /* USER CODE BEGIN TIM1_MspInit 0 */

 /* USER CODE END TIM1_MspInit 0 */
 /* Peripheral clock enable */
 __HAL_RCC_TIM1_CLK_ENABLE();

 __HAL_RCC_GPIOE_CLK_ENABLE();
 /**TIM1 GPIO Configuration
 PE9 ------> TIM1_CH1
 */
 GPIO_InitStruct.Pin = L_CLK_RX_Pin;
 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
 GPIO_InitStruct.Pull = GPIO_NOPULL;
 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
 GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;
 HAL_GPIO_Init(L_CLK_RX_GPIO_Port, &GPIO_InitStruct);

 /* TIM1 DMA Init */
 /* TIM1_TRIG Init */
 hdma_tim1_trig.Instance = DMA1_Stream1;
 hdma_tim1_trig.Init.Request = DMA_REQUEST_TIM1_TRIG;
 hdma_tim1_trig.Init.Direction = DMA_PERIPH_TO_MEMORY;
 hdma_tim1_trig.Init.PeriphInc = DMA_PINC_DISABLE;
 hdma_tim1_trig.Init.MemInc = DMA_MINC_ENABLE;
 hdma_tim1_trig.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
 hdma_tim1_trig.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
 hdma_tim1_trig.Init.Mode = DMA_CIRCULAR;
 hdma_tim1_trig.Init.Priority = DMA_PRIORITY_HIGH;
 hdma_tim1_trig.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
 if (HAL_DMA_Init(&hdma_tim1_trig) != HAL_OK)
 {
 Error_Handler();
 }

 __HAL_LINKDMA(htim_base,hdma[TIM_DMA_ID_TRIGGER],hdma_tim1_trig);

 /* USER CODE BEGIN TIM1_MspInit 1 */
 extern void HAL_DMA_RxHalfCpltCallback(DMA_HandleTypeDef *hdma);
 extern void HAL_DMA_RxCpltCallback(DMA_HandleTypeDef *hdma);
	// Register your callbacks now, before you ever start the DMA:
	HAL_DMA_RegisterCallback(&hdma_tim1_trig,
						 HAL_DMA_XFER_HALFCPLT_CB_ID,
						 HAL_DMA_RxHalfCpltCallback);
	HAL_DMA_RegisterCallback(&hdma_tim1_trig,
						 HAL_DMA_XFER_CPLT_CB_ID,
						 HAL_DMA_RxCpltCallback);

	// Enable the IRQ line for that stream
	HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 0, 0);
	HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);

 /* USER CODE END TIM1_MspInit 1 */

 }

}

Result

  • PE9 rising edge → TI1FP1 external clock tick

  • CC1 match → Because CC1S=01, the timer captures and also generates TRGO (MMS=OC1)

  • TRGO → Fires the TIM1_TRIG DMA request

  • DMA (Circular) → Reads GPIOE->IDR into portE_samples[]

  • HAL_DMA_RxHalfCpltCallback / HAL_DMA_RxCpltCallback now fire as expected!

Hope this helps anyone else wrestling with TIMx + trigger-DMA on the H7 series.

Technical Moderator
July 15, 2025

Hello @johnotraffic 

Does the DMA transfer complete interrupt occur?

"To give better visibility on the answered topics, please click on ""Accept as Solution"" on the reply which solved your issue or answered your question.Saket_Om"
Associate
July 15, 2025

Hi @Saket_Om ,
Yes, both HAL_DMA_RxHalfCpltCallback and HAL_DMA_RxCpltCallback are called.