2026-04-15 5:29 PM
Hi STM Forum,
I'm integrating a custom QSPI display controller with TouchGFX and ThreadX. I need to convert the TouchGFX framebuffer into RGB666 (6 bits per color) for a custom display interface that runs over SPI/QSPI.
Here is my IOC. As you can see, I currently have only the QSPI and the clock connected.
I believe the framebuffer is defined in TouchGFXGeneratedHAL.cpp as:
LOCATION_PRAGMA_NOLOAD("TouchGFX_Framebuffer")uint32_t frameBuf[(320 * 240 * 2 + 3) / 4 * 2] LOCATION_ATTRIBUTE_NOLOAD("TouchGFX_Framebuffer")
My target app currently runs on an STM32F7. I also have a GFX01M2 display on a Nucleo H503, but the SPI display example is of limited use because it appears to use many more GPIOs than I have available in my application (I only have QSPI).
I have the following questions that will help me integrate TouchGFX into this project:
Before I start digging into rabbit holes, I thought posting here might help others with the same issue.
Thank you in advance for your help.
2026-04-17 3:01 AM
Hello fcz,
If your display can support it, I would generally recommend you either use use 16 bit or 24 bit colors instead, since this is directly supported by TouchGFX.
But if you need to do this manual color conversion etc., I will recommend you to look at our "NUCLEO-U575ZI + GFX02Z1" application template available in TouchGFX Designer. This uses double framebuffer and FMC 8-bit interface to transfer the pixels, which is very similar to QSPI. Here the framebuffer swap is synchronized with the Tearing Effect signal from the display:
I think you could use the same synchronizing mechanisms and then you just need to make your own implementation of the LCD_IOSendDataDMA function, which fits your display interface.
Best regards
Peter
2026-04-17 9:14 AM
Thank you Peter for your response.
I did something very similar, but since I do not have a standard display
(hence no LCD_SignalTearingEffectEvent method) nor a LCD sync pin, I ended up creating a 60Hz timer and calling
HAL::getInstance()->vSync();
touchgfx::OSWrappers::signalVSync();from HAL_TIM_PeriodElapsedCallback.
For what concern the showing of the framebuffer, the display I have is only 6 bit, so I ended up converting from argb2222,
masking out the alpha channel with the following method:
uint8_t* argb222ToRgb222(int width, int height) {
auto fb = HAL::getInstance()->lockFrameBuffer();
int totalPixels = HAL::DISPLAY_WIDTH * HAL::DISPLAY_HEIGHT;
// Allocate memory for the new array
uint8_t* rgb6bit = (uint8_t*)malloc(totalPixels * sizeof(uint8_t));
if (rgb6bit == NULL) return NULL;
// Treat the source as a byte array to process pixel-by-pixel
uint8_t* src=(uint8_t*)fb;
for (int i = 0; i < totalPixels; i++) {
// Mask out the top 2 bits (Alpha) to keep only the 6-bit RGB data
rgb6bit[i] = src[i] & 0x3F;
}
HAL::getInstance()->unlockFrameBuffer();
return rgb6bit;
}
I am not sure I am handling the framebuffer correctly, because it seems that often the framebuffer cannot be unlocked and gets stuck over there.
However after much struggling I see some progress. Thanks!
2026-04-20 1:24 AM
Ok. If you do not have any tearing effect signal available, you will probably risk some display tearing since your 60 Hz is not synchronized with the actual refresh of the LCD. But you can still use the logic that is implemented in the LCD_SignalTearingEffectEvent function in the code snippet in my first message. You just execute it in your timer event instead of an external interrupt event.
The logic implemented in this example (line 502-513) uses the swapFramebuffers instead of lock/unlock. After calling this (if needed), the pixel data to be transmitted can be access directly using the TFTframebuffer point like shown in line 512.
Are you sure that it is necessary to set the two "alpha bits" to be zero? I could imaging that your display does not care about the value of these two bits.
2026-04-21 4:59 PM - edited 2026-04-21 5:00 PM
Thank you, Peter.
If I pass the buffer to my display unchanged, nothing is shown.
Fortunately, I can process the framebuffer in place without copying with a slightly modified method similar to the above.
It also seems that calling vSync() and signalVSync() from a timer does not force the framebuffer to refresh.
I have a TouchGFX app with a simple test animation: the first frame displays correctly, but afterward the framebuffer appears not to be updated by TouchGFX, so the animation does not play, although vSync() is being called by the timer callback.
The first frame displays correctly, but after that it looks like the framebuffer is no longer updated by TouchGFX, so the animation is not shown.
Any idea?
2026-04-24 1:02 AM
Ok. Can you debug the application and see if TouchGFX is beeing "ticked"?
You could e.g. look for if it reaches something like line 10 in this code:
void touchgfx_taskEntry()
{
/*
* Main event loop will check for VSYNC signal, and then process next frame.
*
* Note This function returns immediately if there is no VSYNC signal.
*/
if (OSWrappers::isVSyncAvailable())
{
hal.backPorchExited();
}
}