Skip to main content
Visitor
September 14, 2026
Question

STM32H743 LTDC Line Interrupt - Task loop returns 3-4ms instead of mathematically expected 16.6ms

  • September 14, 2026
  • 2 replies
  • 16 views

Hello everyone,

I am working with an STM32H743 running at its maximum frequency of 480 MHz. I am trying to implement VBlank synchronization using the LTDC Line Interrupt and CMSIS-RTOS v1 (FreeRTOS). My goal is to unblock a display/rendering task at every frame interval.

According to my hardware configuration, the frame time should be 16.6ms (60Hz refresh rate). The mathematical formula confirms this:

  • Total Width = 920, Total Height = 542, Pixel Clock = 30 MHz.
  • (920 * 542) / 30,000,000 Hz = 0.01662s -> 16.62ms.

My FreeRTOS configuration is strictly set to configTICK_RATE_HZ = 1000 (1 tick = 1ms).

The Problem & Debugging History:
Initially, I placed three hardware breakpoints in my code to track execution flow:

  • (1) Right at osSemaphoreWait in the task loop.
  • (2) Inside the HAL_LTDC_LineEventCallback after re-programming the line.
  • (3) Right at the gm_sprintf line where time delta is formatted.

If the synchronization loop worked correctly, the debugger should have hit these breakpoints sequentially in a 1, 2, 3, 1, 2, 3, 1, 2, 3... pattern. However, the actual sequence captured by the debugger was 1, 2, 2, 2, 2, 2... it completely skipped (3) and flooded the ISR callback continuously.

Interestingly, when I remove breakpoints (1) and (2) and leave only breakpoint (3) active, the debugger catches breakpoint (3) perfectly without any issues. However, when stopped at (3), the measured time variable reads constantly 0ms. This happens because halting the core inside the task doesn't stop the LTDC hardware, which keeps flooding the NVIC with pending line interrupts in the background while the core is stopped.

To bypass this debugger behavior and see what happens at full hardware speed, I removed all breakpoints and logged the values directly into a volatile RAM array (time_history). After letting the program run freely at 480 MHz and pausing the core, the results changed: both HAL_GetTick() and osKernelSysTick() now constantly log a delta value of 3 or 4ms (with the very first loop sometimes recording a 2ms). The task successfully blocks and wakes up, but it appears to unblock way too fast (every 3-4ms) instead of waiting for the full 16.6ms frame duration.

What I have verified so far:

  1. Safe OS Startup: The LTDC Line Interrupt configuration is explicitly placed inside the RTOS task rather than MX_LTDC_Init. This ensures the hardware interrupt never fires before the FreeRTOS scheduler starts, preventing a configASSERT crash on an unallocated NULL semaphore structure.
  2. No Callback Typos: USE_HAL_LTDC_REGISTER_CALLBACKS is set to 0U, and the weak callback is overwritten using the correct name HAL_LTDC_LineEventCallback.

 

Here is my exact, unmodified code configuration:

#define  USE_HAL_LTDC_REGISTER_CALLBACKS    0U /* LTDC register callback enabled    */



osSemaphoreDef(VBlankSem);
VBlankSemHandle = osSemaphoreCreate(osSemaphore(VBlankSem), 1);


static void MX_LTDC_Init(void)
{

  /* USER CODE BEGIN LTDC_Init 0 */

  /* USER CODE END LTDC_Init 0 */

  LTDC_LayerCfgTypeDef pLayerCfg = {0};

  /* USER CODE BEGIN LTDC_Init 1 */

  /* USER CODE END LTDC_Init 1 */
  hltdc.Instance = LTDC;
  hltdc.Init.HSPolarity = LTDC_HSPOLARITY_AL;
  hltdc.Init.VSPolarity = LTDC_VSPOLARITY_AL;
  hltdc.Init.DEPolarity = LTDC_DEPOLARITY_AL;
  hltdc.Init.PCPolarity = LTDC_PCPOLARITY_IPC;
  hltdc.Init.HorizontalSync = 0;
  hltdc.Init.VerticalSync = 0;
  hltdc.Init.AccumulatedHBP = 80;
  hltdc.Init.AccumulatedVBP = 40;
  hltdc.Init.AccumulatedActiveW = 880;
  hltdc.Init.AccumulatedActiveH = 520;
  hltdc.Init.TotalWidth = 920;
  hltdc.Init.TotalHeigh = 542;
  hltdc.Init.Backcolor.Blue = 0;
  hltdc.Init.Backcolor.Green = 0;
  hltdc.Init.Backcolor.Red = 0;
  if (HAL_LTDC_Init(&hltdc) != HAL_OK)
  {
    Error_Handler();
  }
  pLayerCfg.WindowX0 = 0;
  pLayerCfg.WindowX1 = 800;
  pLayerCfg.WindowY0 = 0;
  pLayerCfg.WindowY1 = 480;
  pLayerCfg.PixelFormat = LTDC_PIXEL_FORMAT_ARGB8888;
  pLayerCfg.Alpha = 255;
  pLayerCfg.Alpha0 = 0;
  pLayerCfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_CA;
  pLayerCfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_CA;
  pLayerCfg.FBStartAdress = FB_A;
  pLayerCfg.ImageWidth = 800;
  pLayerCfg.ImageHeight = 480;
  pLayerCfg.Backcolor.Blue = 0;
  pLayerCfg.Backcolor.Green = 0;
  pLayerCfg.Backcolor.Red = 0;
  if (HAL_LTDC_ConfigLayer(&hltdc, &pLayerCfg, 0) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN LTDC_Init 2 */

  /* USER CODE END LTDC_Init 2 */

}

void HAL_LTDC_LineEventCallback(LTDC_HandleTypeDef *hltdc)
{
    HAL_LTDC_ProgramLineEvent(hltdc, hltdc->Init.AccumulatedActiveH + 1);

    osSemaphoreRelease(VBlankSemHandle); // (2) Breakpoint here
}

void StartDefaultTask(void const * argument)
{
    uint32_t ts, time;
    char str_buffer;

    HAL_LTDC_ProgramLineEvent(&hltdc, hltdc.Init.AccumulatedActiveH + 1);

    for(;;)
    {
        ts = osKernelSysTick();

        osSemaphoreWait(VBlankSemHandle, osWaitForever); // (1) Breakpoint here
        //osDelay(20); // test

        time = osKernelSysTick() - ts;

        gm_sprintf(str_buffer, "Time: %03dms", time); // (3) Breakpoint here
    }
};


 

(Note: In my final implementation, the initialization snippet includes a dummy wait right after osSemaphoreCreate to consume the initial token and force the semaphore count to 0, ensuring it is blocked on the first loop iteration).

 

My Questions for the Community:

  1. Since the hardware configuration guarantees a 16.6ms cycle based on the 30 MHz clock, why do both HAL_GetTick() and osKernelSysTick() report an execution delta of only 3 to 4ms when logged without breakpoints? Could HAL_LTDC_ProgramLineEvent() be re-triggering multiple times per frame or short-cycling the line counter if called directly inside the callback while the requested line is still active?
  2. Given that the Cortex-M7 core runs at 480 MHz (16 CPU cycles per single LTDC pixel clock tick), is a race condition or lack of clock domain crossing synchronization expected when using HAL_LTDC_ProgramLineEvent() inside the ISR?
  3. Is passing hltdc.Init.AccumulatedActiveH + 1 (line 521) into HAL_LTDC_ProgramLineEvent() correct for triggering strictly at the beginning of the Vertical Front Porch on the STM32H743?
  4. What is the recommended way to prevent the LTDC line interrupt from flooding the MCU and triggering multiple times per frame on high-performance Cortex-M7 cores?

Any insights or similar experiences with STM32H7 LTDC interrupt pacing would be highly appreciated.

Thank you!

 

2 replies

MM..1
Super User
September 14, 2026

My tip check what line is writed into line ISR reg, plus this hw peripherals not stop on break point = not debug with breakpoints. Best method is toggle gpio.

Next point line ISR in real use is best for mark vertical active area ahead vertical blank … and 542 vs 480 lines...

mƎALLEm
ST Technical Moderator
September 14, 2026

Hello ​@jabu74 and welcome to the ST community,

I’ve moved your post to STM32 MCUs Embedded software forum board as it was posted in STM32CubeIDE which is not a CubeIDE related question/issue.

I’ve also edited your post to be inline with the community rules especially the one about the code sharing.

In next time please use the code button to paste your code.

Thank you for your understanding & good luck.

To give better visibility on the answered topics, please click "Best answer" on the reply which solved your issue or answered your question.