2026-01-20 2:09 AM - last edited on 2026-01-23 3:26 AM by mƎALLEm
Hello everyone,
I'm seeking advice on video playback implementation using TouchGFX 4.25 with a Riverdi RVT121HVSFWN00 display (12.1" LCD with STM32H7 MCU).
**Current working setup:**
- TouchGFX version: 4.25
- Display: Riverdi RVT121HVSFWN00
- Current video configuration: 480x272 resolution
- FFmpeg command that works:
ffmpeg -i video2.mp4 -s 480x272 -vcodec mjpeg -q:v 1 -pix_fmt yuv420p -color_range 1 -strict -1 -an output.avi
This works perfectly with the Video widget alongside other UI elements (backgrounds, buttons, etc.)
When I try to increase the video resolution to 800x800 pixels, I only get a black screen. No video is displayed.
**What I've tried:**
1. Modified FFmpeg command for 800x800:
ffmpeg -i input.mp4 -vf "scale=800:800:force_original_aspect_ratio=decrease,pad=800:800:(ow-iw)/2:(oh-ih)/2" \
-c:v libxvid -qscale:v 5 -c:a libmp3lame -b:a 128k output.avi
```
2. Also tried various combinations with `mjpeg` codec and different pixel formats
**My questions:**
1. Are there hardware limitations with the STM32H7 (likely in the Riverdi display) that prevent 800x800 video playback?
2. Do I need additional configuration in TouchGFX for higher resolution videos?
3. Could this be a memory issue (SDRAM configuration maybe)?
4. Are there specific codec/pixel format requirements for larger videos in TouchGFX?
**Additional context:**
- The display has a native resolution of 1280x800
- I'm using the Video widget within a screen containing other graphical elements
- The 480x272 video works flawlessly, suggesting the basic setup is correct
**What I suspect:**
- Possible memory constraints (800x800 RGB565 = ~1.25MB per frame)
- Maybe SDRAM isn't properly configured for larger video buffers
- Could be a decoding performance issue with higher resolution
Has anyone successfully implemented 800x800 or similar high-resolution video playback on STM32H7 with TouchGFX? Any guidance on:
- Optimal FFmpeg parameters for larger videos
- TouchGFX configuration adjustments
- Memory management strategies
- Hardware limitations to consider
Any help or insights would be greatly appreciated!
Thank you in advance.
2026-01-20 2:54 AM
Hello @Sasa1234.
If you are using the TBS available in TouchGFX Designer, you need to update the video configuration in X-CUBE-TOUCHGFX in STM32CubeMX. The current configuration is set up for single-buffer video, but the buffer size is only 640x480 pixels, so the video does not fit. You need to update the buffer size, and ensure that the linker and MPU configuration are aligned with the new size.
Alternatively, use the Direct to Framebuffer video decoding strategy instead. Then you avoid the video buffer completely.
Best regards,
Johan
2026-01-20 8:19 AM
Hi @JohanAstrup ,
I try to change from X-CUBE-TOUCHGFX some parameters like Buffer Height and Buffer Width and Buffer Height but nothing changes and it does not work!!!However there is a way to change manually ,in files generated by touchgfx designer, files in order to use your suggestions.
Thanks you again,
Sasà
2026-01-22 6:41 AM
Hi @JohanAstrup ,
I have a little progress. By using Direct to Frame strategies, I obtain a screen with green lines at top of the display. I modified TouchGFXGeneratedHAl.cpp and linker file.May I miss something? Thank you for your Help!!!!!
/**
******************************************************************************
* File Name : TouchGFXGeneratedHAL.cpp
******************************************************************************
* This file is generated by TouchGFX Generator 4.25.0. Please, do not edit!
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#include <TouchGFXGeneratedHAL.hpp>
#include <touchgfx/hal/OSWrappers.hpp>
#include <gui/common/FrontendHeap.hpp>
#include <touchgfx/hal/GPIO.hpp>
#include <HardwareMJPEGDecoder.hpp>
#include <DedicatedBufferVideoController.hpp>
#include <stm32h7xx_hal.h>
HardwareMJPEGDecoder mjpegdecoder1;
namespace
{
//LOCATION_PRAGMA_NOLOAD("Video_RGB_Buffer")
//uint32_t videoRGBBuffer[153600] LOCATION_ATTRIBUTE_NOLOAD("Video_RGB_Buffer");
//DedicatedBufferController<1, 640, 480, 640 * 2U, Bitmap::RGB565> videoController;
DedicatedBufferController<1, 800, 800, 800 * 2U, Bitmap::RGB565> videoController;
}
//Singleton Factory
VideoController& VideoController::getInstance()
{
return videoController;
}
#include "stm32h7xx.h"
#include "stm32h7xx_hal_ltdc.h"
using namespace touchgfx;
namespace
{
static uint16_t lcd_int_active_line;
static uint16_t lcd_int_porch_line;
}
void TouchGFXGeneratedHAL::initialize()
{
HAL::initialize();
registerEventListener(*(Application::getInstance()));
setFrameBufferStartAddresses((void*)0xD0000000, (void*)0xD02EE000, (void*)0);
/*
* Add DMA2D to hardware decoder
*/
mjpegdecoder1.addDMA(dma);
/*
* Add hardware decoder to video controller
*/
videoController.addDecoder(mjpegdecoder1, 0);
// videoController.setRGBBuffer((uint8_t*)videoRGBBuffer, sizeof(videoRGBBuffer));
videoController.setRGBBuffer((uint8_t*)LTDC_Layer1->CFBAR, 800*800*2);
}
void TouchGFXGeneratedHAL::configureInterrupts()
{
NVIC_SetPriority(DMA2D_IRQn, 9);
NVIC_SetPriority(LTDC_IRQn, 9);
}
void TouchGFXGeneratedHAL::enableInterrupts()
{
NVIC_EnableIRQ(DMA2D_IRQn);
NVIC_EnableIRQ(LTDC_IRQn);
}
void TouchGFXGeneratedHAL::disableInterrupts()
{
NVIC_DisableIRQ(DMA2D_IRQn);
NVIC_DisableIRQ(LTDC_IRQn);
}
void TouchGFXGeneratedHAL::enableLCDControllerInterrupt()
{
lcd_int_active_line = (LTDC->BPCR & LTDC_BPCR_AVBP_Msk) - 1;
lcd_int_porch_line = (LTDC->AWCR & LTDC_AWCR_AAH_Msk) - 1;
/* Sets the Line Interrupt position */
LTDC->LIPCR = lcd_int_active_line;
/* Line Interrupt Enable */
LTDC->IER |= LTDC_IER_LIE;
}
bool TouchGFXGeneratedHAL::beginFrame()
{
return HAL::beginFrame();
}
void TouchGFXGeneratedHAL::endFrame()
{
HAL::endFrame();
videoController.endFrame();
dma.start();
}
uint16_t* TouchGFXGeneratedHAL::getTFTFrameBuffer() const
{
return (uint16_t*)LTDC_Layer1->CFBAR;
}
void TouchGFXGeneratedHAL::setTFTFrameBuffer(uint16_t* adr)
{
LTDC_Layer1->CFBAR = (uint32_t)adr;
/* Reload immediate */
LTDC->SRCR = (uint32_t)LTDC_SRCR_IMR;
}
void TouchGFXGeneratedHAL::flushFrameBuffer(const touchgfx::Rect& rect)
{
HAL::flushFrameBuffer(rect);
}
bool TouchGFXGeneratedHAL::blockCopy(void* RESTRICT dest, const void* RESTRICT src, uint32_t numBytes)
{
return HAL::blockCopy(dest, src, numBytes);
}
void TouchGFXGeneratedHAL::InvalidateCache()
{
// Because DMA2D access main memory directly, the DCache must be invalidated
// becuase it could hold a wrong image of the framebuffer. That's done
// using the function SCB_CleanInvalidateDCache(). Remember to enable
// "CPU Cache" in the "System Core" settings for "Cortex M7" in CubeMX
// in order for this function call to work.
if (SCB->CCR & SCB_CCR_DC_Msk)
{
SCB_CleanDCache_by_Addr((uint32_t*)LTDC_Layer1->CFBAR, 800*800*2);
//SCB_CleanInvalidateDCache();
}
}
void TouchGFXGeneratedHAL::FlushCache()
{
// If the framebuffer is placed in Write-Back cached memory (e.g. SRAM) then
// the DCache must be flushed prior to DMA2D accessing it. That's done
// using the function SCB_CleanInvalidateDCache(). Remember to enable
// "CPU Cache" in the "System Core" settings for "Cortex M7" in CubeMX in
// order for this function call to work.
if (SCB->CCR & SCB_CCR_DC_Msk)
{
SCB_CleanInvalidateDCache();
}
}
extern "C" void videoTaskFunc(void* argument)
{
videoController.decoderTaskEntry();
}
extern "C"
{
void HAL_LTDC_LineEventCallback(LTDC_HandleTypeDef* hltdc)
{
if (!HAL::getInstance())
{
return;
}
if (LTDC->LIPCR == lcd_int_active_line)
{
//entering active area
HAL_LTDC_ProgramLineEvent(hltdc, lcd_int_porch_line);
HAL::getInstance()->vSync();
OSWrappers::signalVSync();
// Swap frame buffers immediately instead of waiting for the task to be scheduled in.
// Note: task will also swap when it wakes up, but that operation is guarded and will not have
// any effect if already swapped.
HAL::getInstance()->swapFrameBuffers();
GPIO::set(GPIO::VSYNC_FREQ);
}
else
{
//exiting active area
HAL_LTDC_ProgramLineEvent(hltdc, lcd_int_active_line);
// Signal to the framework that display update has finished.
HAL::getInstance()->frontPorchEntered();
GPIO::clear(GPIO::VSYNC_FREQ);
}
}
}
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
/*
******************************************************************************
**
** File : LinkerScript.ld
**
** Author : STM32CubeIDE
**
** Abstract : Linker script for STM32H7 series
** 1024Kbytes FLASH
** 800Kbytes RAM
**
** Set heap size, stack size and stack location according
** to application requirements.
**
** Set memory bank area and size if external memory is used.
**
** Target : STMicroelectronics STM32
**
** Distribution: The file is distributed as is without any warranty
** of any kind.
**
*****************************************************************************
** @attention
**
** Copyright (c) 2023 STMicroelectronics.
** All rights reserved.
**
** This software is licensed under terms that can be found in the LICENSE file
** in the root directory of this software component.
** If no LICENSE file comes with this software, it is provided AS-IS.
**
*****************************************************************************
*/
/* Entry Point */
ENTRY(Reset_Handler)
/* Highest address of the user mode stack */
_estack = ORIGIN(RAM_D1) + LENGTH(RAM_D1); /* end of "RAM_D1" Ram type memory */
_Min_Heap_Size = 0xc000; /* required amount of heap */
_Min_Stack_Size = 0xc800; /* required amount of stack */
/* Memories definition */
MEMORY
{
RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 512K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K /* Memory is divided. Actual start is 0x08000000 and actual length is 2048K */
DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K
RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K
ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K
QUADSPI (r) : ORIGIN = 0x90000000, LENGTH = 64M
SDRAM (xrw) : ORIGIN = 0xD0000000, LENGTH = 8000K
/* SDRAM2 (xrw) : ORIGIN = 0xD05DC000, LENGTH = 2192K*/
}
/* Sections */
SECTIONS
{
/* The startup code into "FLASH" Rom type memory */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
/* The program code and other data into "FLASH" Rom type memory */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .; /* define a global symbols at end of code */
} >FLASH
/* Constant data into "FLASH" Rom type memory */
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH
.ARM.extab : {
. = ALIGN(4);
*(.ARM.extab* .gnu.linkonce.armextab.*)
. = ALIGN(4);
} >FLASH
.ARM : {
. = ALIGN(4);
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
. = ALIGN(4);
} >FLASH
.preinit_array :
{
. = ALIGN(4);
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
. = ALIGN(4);
} >FLASH
.init_array :
{
. = ALIGN(4);
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
. = ALIGN(4);
} >FLASH
.fini_array :
{
. = ALIGN(4);
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
. = ALIGN(4);
} >FLASH
/* Used by the startup to initialize data */
_sidata = LOADADDR(.data);
/* Initialized data sections into "RAM" Ram type memory */
.data :
{
. = ALIGN(4);
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
*(.RamFunc) /* .RamFunc sections */
*(.RamFunc*) /* .RamFunc* sections */
. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} >RAM_D1 AT> FLASH
/* Uninitialized data section into "RAM" Ram type memory */
. = ALIGN(4);
.bss :
{
/* This is used by the startup in order to initialize the .bss section */
_sbss = .; /* define a global symbol at bss start */
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .; /* define a global symbol at bss end */
__bss_end__ = _ebss;
} >RAM_D1
/* User_heap_stack section, used to check that there is enough "RAM" Ram type memory left */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM_D1
.lwip_sec (NOLOAD) : {
. = ABSOLUTE(0x30000000);
*(.RxDecripSection)
. = ABSOLUTE(0x30000200);
*(.TxDecripSection)
. = ABSOLUTE(0x30000400);
*(.Rx_PoolSection)
} >RAM_D2 AT> FLASH
/* Remove information from the compiler libraries */
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
.ARM.attributes 0 : { *(.ARM.attributes) }
TouchGFX_Framebuffer (NOLOAD) :
{ . = ALIGN(32);
*(TouchGFX_Framebuffer TouchGFX_Framebuffer.*)
*(.gnu.linkonce.r.*)
. = ALIGN(32);
} >SDRAM
/*
BufferSection (NOLOAD) :
{
*(Video_RGB_Buffer Video_RGB_Buffer.*)
*(.gnu.linkonce.r.*)
. = ALIGN(0x4);
} >SDRAM2
*/
ExtFlashSection :
{
*(ExtFlashSection ExtFlashSection.*)
*(.gnu.linkonce.r.*)
. = ALIGN(0x4);
} >QUADSPI
}
2026-01-23 3:07 AM
It does not sound very good, if generation in CubeMX does not trigger any code changes, when you change parameters ..
Can you try to start a new project and verify if it works then?
Does that also mean that you have changed to Direct to Framebuffer manually - not using STM32CubeMX?
Best regards,
Johan