cembeddedembedded-linuxmicrocontrollerstm32f7

How to generate an exact 1 µs interrupt on STM32F7xx using hardware timers


I am new to interrupt-based programming.

In my current project, I need the interrupt generated exactly at 1 µs intervals. Below is the screenshot from the Clock Configuration tab in STM32CubeMX. I am using the TIM3 timer as it can generate the clock frequency of 1 µs.

Enter image description here

Below is the TIM3 configuration code.

static void MX_TIM3_Init(void)
{
    TIM_ClockConfigTypeDef sClockSourceConfig;
    TIM_MasterConfigTypeDef sMasterConfig;

    htim3.Instance = TIM3;
    htim3.Init.Prescaler = 1-1; // 0x00; // 0x36; || 0x00 // 1-1
    htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim3.Init.Period = 0xFFFF - 1;  // 0x64; || 0xd7 // 0xFFFF - 1
    htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;

    if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }

    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
    {
       _Error_Handler(__FILE__, __LINE__);
    }

    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }
}

I am calling the timer

HAL_TIM_IRQHandler(&htim3);
/* USER CODE BEGIN TIM3_IRQn 1 */

HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_6);

I see that the interrupt of duration 1.2 ms is generated. Why is this happening and how can I reduce the interrupt to a 1 µs duration? Is a change required in the timer frequency?

I am also using FreeRTOS, and other applications are also running on the microcontroller.


Solution

  • If your requirement is output with an accurate 500 kHz 1:1 mark/space signal (i.e., 1 µs high, 1 µs low), then doing that through interrupts while expecting your system to do other useful work is both impractical and unnecessary. The general-purpose timers have a output-compare function that can drive a GPIO pin directly without interrupts or software overhead.

    Only certain pins are connected to the Timer OC channels, so to drive PB6 in this case you would need to use TIM4 Channel 1.

    Also rather than determining and hard coding timer reload and pulse, you should use the available HAL RCC clock functions (HAL_RCC_GetPCLK1Freq() in this case) to calculate the values to avoid errors, and so that the code will be portable to other systems or will work correctly if you change your clock configuration.

    static void MX_TIM4_Init(void)
    {
        cost uint32_t PULSE_WIDTH = HAL_RCC_GetPCLK1Freq() * 2 / 1000000;
    
        htim4.Instance = TIM4;
        htim4.Init.Prescaler = 0;
        htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
        htim4.Init.Period = PULSE_WIDTH * 2;
        htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
        HAL_TIM_PWM_Init(&htim4);
    
        TIM_MasterConfigTypeDef sMasterConfig;
        sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
        sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
        HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig);
    
        TIM_OC_InitTypeDef sConfigOC;
        sConfigOC.OCMode = TIM_OCMODE_PWM1;
        sConfigOC.Pulse = PULSE_WIDTH;
        sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
        sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    
        HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_1);
    }
    

    Then elsewhere you need to configure PB6 as an output and start the timer:

      LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
      GPIO_InitStruct.Pin = GPIO_PIN_6;
      GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
      GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
      GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
      GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
      GPIO_InitStruct.Alternate = LL_GPIO_AF_2;
    
      LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB)
      LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    
    
      HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1);
    

    Thereafter, the signal will be maintained indefinitely on PB6 without any GPIO access or interrupt handling.