2025-02-24 2:11 AM - edited 2025-02-24 2:13 AM
Hello,
I have a STM32F777 with ST7789 Display controller over SPI.
To write the Framebuffer i have the following code:
void TouchGFXHAL::flushFrameBuffer(const touchgfx::Rect& rect)
{
// Get Framebuffer Address
uint8_t* frameBufferAddress = (uint8_t*)HAL::lockFrameBuffer();
// Calc start address
uint8_t* fbPtr = advanceFrameBufferToRect(frameBufferAddress, rect
// Set window
DisplaySetWindow(rect.x, rect.y, rect.width, rect.height);
// Start DMA Transfer
DisplaySendDataDMA(fbPtr, rect.width * rect.height * 2);
// Wait for DMA Transfer end
if (osSemaphoreWait(DisplayTransferSemHandle, osWaitForever) == pdTRUE)
{
HAL::unlockFrameBuffer();
HAL::flushFrameBuffer(rect);
}
}
I can't build the code because the function advanceFrameBufferToRect() is defined as inline but without implementation in the header. The implementation in the source file is not accesible with the inline statement.
How can I fix this error? Is the code above correct?
Thanks for your reply!
2025-02-25 4:57 AM
Hello @Sandro_K ,
Have you looked at the TouchGFX SPI guide ?
Regards,
2025-02-25 5:38 AM
Thanks for your reply. Yes, i have looked at the SPI guide but there ist not much information.
In the TouchGFXHAL.cpp file at function flushFrameBuffer(...) there is some information:
// Calling parent implementation of flushFrameBuffer(const touchgfx::Rect& rect).
//
// To overwrite the generated implementation, omit the call to the parent function
// and implement the needed functionality here.
// Please note, HAL::flushFrameBuffer(const touchgfx::Rect& rect) must
// be called to notify the touchgfx framework that flush has been performed.
// To calculate the start address of rect,
// use advanceFrameBufferToRect(uint8_t* fbPtr, const touchgfx::Rect& rect)
// defined in TouchGFXGeneratedHAL.cpp
I tried to implement these instructions. So I need the function advanceFrameBufferToRect().
And there comes the point. The function prototype is specified as inline function in the header file of the library. The implementation is in the .cpp file but this is not working because the function is defined as inline. I think there is a bug in the library.
Either the function is implemented directly as an inline function in the header or the keyword inline is omitted.
2025-02-25 7:48 AM
Well you can check what you have in TouchGFXGeneratedHAL.hpp.
Personally, on the STM32C071 TBS which use an SPI display I have that:
The inline is not omitted and the function is not implemented in the hpp.
What do you have in your project?
Here is an example of implementation of flushFrameBuffer.
In the code you shared, on line 7, why is there no closing parenthesis and semicolon?
uint8_t* fbPtr = advanceFrameBufferToRect(frameBufferAddress, rect
Regards,
2025-02-25 10:50 PM - edited 2025-02-26 12:57 AM
This is my complete code:
void TouchGFXHAL::flushFrameBuffer(const touchgfx::Rect& rect)
{
// Calling parent implementation of flushFrameBuffer(const touchgfx::Rect& rect).
//
// To overwrite the generated implementation, omit the call to the parent function
// and implement the needed functionality here.
// Please note, HAL::flushFrameBuffer(const touchgfx::Rect& rect) must
// be called to notify the touchgfx framework that flush has been performed.
// To calculate the start address of rect,
// use advanceFrameBufferToRect(uint8_t* fbPtr, const touchgfx::Rect& rect)
// defined in TouchGFXGeneratedHAL.cpp
// Get framebuffer
uint8_t* frameBufferAddress = (uint8_t*)HAL::lockFrameBuffer();
// calculate start address
uint8_t* fbPtr = advanceFrameBufferToRect(frameBufferAddress, rect);
// Set the region on the display
DisplaySetWindow(rect.x, rect.y, rect.width, rect.height);
// Start DMA transfer
DisplaySendDataDMA(fbPtr, rect.width * rect.height * 2); // 2 Bytes per Pixel (RGB565)
// Wait for DMA transfer end
if(osSemaphoreAcquire(DisplayTransferSemHandle, osWaitForever) == pdTRUE)
{
// DMA transfer finished
HAL::unlockFrameBuffer();
HAL::flushFrameBuffer(rect);
}
}
The closing parenthesis and semicolon was a copy & paste error.
But with the function above i get the following error:
How can the code work if it is implemented as inline without implementation in the .hpp file? This is against the cpp definitions.
(I use STM32CubeIDE 1.17.0)
2025-02-25 10:57 PM - edited 2025-02-25 10:59 PM
See also: Inline Functions, C++ FAQ
How do you tell the compiler to make a member function inline?
The declaration of an inline member function looks just like the declaration of a non-inline member function:
class Fred {
public:
void f(int i, char c);
};
But when you define an inline member function (the {...} part), you prepend the member function’s definition with the keyword inline, and you (almost always) put the definition into a header file:
inline
void Fred::f(int i, char c)
{
// ...
}
The reason you (almost always) put the definition (the {...} part) of an inline function in a header file is to avoid “unresolved external” errors from the linker. That error will occur if you put the inline function’s definition in a .cpp file and if that function is called from some other .cpp file.
The hint in the article to the unresolved externals is exactly the error that occurs in my project
2025-03-04 1:46 AM - edited 2025-03-04 1:48 AM
>What do you have in your project?
Maybe there is a difference in the STM32F7 library?
TouchGFXGeneratedHAL.cpp:
inline uint8_t* TouchGFXGeneratedHAL::advanceFrameBufferToRect(uint8_t* fbPtr, const touchgfx::Rect& rect) const
{
// Advance vertically Advance horizontally
fbPtr += rect.y * lcd().framebufferStride() + rect.x * 2;
return fbPtr;
}
TouchGFXGeneratedHAL.hpp:
/**
*
* @fn inline uint8_t* TouchGFXGeneratedHAL::advanceFrameBufferToRect(uint8_t* fbPtr, const touchgfx::Rect& rect) const;
*
* @brief This function calculates the offset in the framebuffer address according to "rect" coordinates.
*
* This function is typically for users who need to transfer framebuffer data to
* a display from within flushFrameBuffer(Rect& rect). While HAL::lockFrameBuffer()
* returns a pointer to the current drawing framebuffer, users must manually calculate
* the offset from that pointer to the Rect to transfer. This function will advance the offset
* in the framebuffer equal to the rect's upper left corner (x, y).
*
*
* fbPtr Pointer to the start of the framebuffer, coordinates (0, 0)
* rect The area of the screen expressed in absolute coordinates, which has to be transformed to address.
*
*/
inline uint8_t* advanceFrameBufferToRect(uint8_t* fbPtr, const touchgfx::Rect& rect) const;
2025-03-06 4:31 AM
Hello Sandro
It looks like a mistake on the Generator. Maybe the function was only intended to be used in TouchGFXGeneratedHAL.cpp. I see no reason for that. We will change it in next release...
Meanwhile, I think you should just copy the function implementation to TouchGFXHAL.cpp.
Another thing:
When you copy pixels to the display like above, you are never drawing pixels and transmitting at the same time. This hurts performance. Try change the code so wait only if it is still running:
if (dmatransferrunning) { wait on transfer semaphore }
setDisplayWindow(...)
sendPixelDataDMA(...)
return; // leave DMA running.
In endFrame you must also wait for the transfer to complete.
One more thing:
With above code, you send pixels to the display uncoordinated with the display refresh. This can give tearing on the display if you have animations.
It is a better solution to use Partial Framebuffer for GRAM displays or otherwise coordinate the transmission.
But, if you do not see any problems in your application (it depends on the graphics you are drawing), then stick with what you have (simple).
A few articles for reference:
https://support.touchgfx.com/docs/basic-concepts/framebuffer#framebuffer-strategies
Best Regards