This section explains, by exemplifying with a clock application, how to configure and use Partial Frame Buffers, to lower memory requirements at the expense of some performance.
A video of the application running on the STM32L4R9Discovery evaluation kit can be seen below
Full-size Frame Buffer Memory
Normally, your frame buffer is a big memory array with enough memory to hold all the pixels available on your display. If you are running on a 24-bit display with a resolution of 480 x 272, a full-size frame buffer holds 480 x 272 x 3 bytes = 391.680 bytes.
Some applications may have 2- ("Double buffering") or even 3 frame buffers. The total memory requirement in these cases would then be 783.360 and 1.175.040 bytes.
TouchGFX writes pixel values to the frame buffer when drawing any part of the UI, after all drawing operations have completed, the frame buffer is transferred to the display. Typically, the whole frame buffer is transferred to the display even if only a part of the UI is updated. Generally, the framebuffer can be updated in many small blocks before itis transferred.
Update1, Update 2, Update 3, ..., Update N, Transfer to display
In some cases, particularly in low cost solutions with no external RAM, frame buffers are required to be small enough to allow the rest of the application to fit in the internal RAM together with the framebuffer. This is where partial frame buffers are useful.
Partial Frame Buffer Memory
Partial frame buffers allows a TouchGFX application to run on top of a few, less than full-size frame buffers. The number and size of the frame buffers are configurable. This technique can lower the memory requirements of an application by a substantial amount, but comes with some limitations:
- Partial frame buffers will only work on displays that have built-in memory. These are typically DSI displays or displays with a parallel bus connection (DBI type A/B, 8080/6800) or SPI-bus connection.
- Potential tearing for complex applications.
Rather than using a frame buffer representing every pixel on the display, partial frame buffers typically cover a smaller part. In the clock example used in this article, three frame buffers of 11.700 bytes each are used. This results in a memory footprint for frame buffers of 35.100 bytes.
Whenever the application needs to update a part of the UI, TouchGFX will select one of the configured, partial frame buffers, complete its drawing operation in the partial framebuffer, and transfer that part to the display. This is repeated for all areas of the UI that need to be rendered - This changes the formula for updating and transferring data to:
Update1, Transfer1, Update2, Transfer2, Update3, Transfer3, ..., UpdateN, TransferN
In some cases the transfer of one partial frame buffer can run while the update of the next buffer is running.
Contrary to using full-size frame buffers, TouchGFX will transfer parts of the UI as soon as they are updated, when using partial frame buffers. The display will show the received updates on its glass after at most 16 ms (for 60 fps displays) because the display needs to be refreshed regularly. Because of this, the first updates to the display can potentially be visible to the user before all updates have been transferred.
If the total sequence of draw operations and transfers take a long time to complete ( > 16 ms) it is highly possible that the user will see a combination of the previous frame and some of the new updates. This is called display tearing and is not desirable. For this reason, partial frame buffers are not suitable for applications that make use of complex animations that take a long time to render.
Display Update Example
Before we get into how to configure partial frame buffers in your application let's have a look at a concrete example showing a digital clock with a moving circle arc representing seconds. The green circle arc is moving 6 degrees each second and does a full rotation in a minute. The UI is built from four Widgets as seen in the image below:
Here is the code that updates the digital clock and circle arc:
The following images shows the areas that are updated in the first few seconds when the circle arc approaches the top and digital clock is updated (the grey rectangles). In the first two frames, only the seconds are changing (58 and 59 seconds). In the thirs the seconds reaches 60 and the hour and minutes text is updated:
The rectangles updated in the third image above are 154 x 60 pixels, 20 x 12 pixels, and 33 x 8 pixels. When using standard frame buffers these three rectangles would be drawn into the full frame buffer (overwriting the previous pixels), which would afterwards be transferred to the display. When using partial frame buffers, these three rectangles would be drawn into their own little frame buffers which would then immediately be transferred to the display and shown.
Configuring Partial Frame Buffers
There are two steps to configuring TouchGFX for partial frame buffers: Creating a frame buffer allocator object with a memory buffer, and configuring the TouchGFX HAL class to use it. Later we also need to write code to transmit the buffers to the display. The first two steps are typically done in the BoardConfiguration.cpp file.
Creating a frame buffer allocator as a global variable:
This frame buffer allocator allocates 2 blocks of each 10 x 390 x 3 bytes = 11.700 bytes.
Configure HAL to use it:
With this configuration TouchGFX will allocate small frame buffers and draw the UI in them. What is left now, is to transfer the small frame buffers to the display.
Lets first see the position and size of the two frame buffers allocated to draw the small circle updates (second image above):
|Rectangle 1||112||56||22||14||308 pixels = 924 bytes|
|Rectangle 2||153||42||29||11||319 pixels = 957 bytes|
Both these rectangles are so small, they can fit into the blocks allocated by the frame buffer allocator.
In the third image above, we have 3 updated rectangles: The small updates to the circle, and the larger rectangle covering the text:
|Rectangle 1||126||51||20||12||240 pixels = 720 bytes|
|Rectangle 2||165||42||33||8||264 pixels = 792 bytes|
|Rectangle 3||118||165||154||60||9.240 pixels = 27.720 bytes|
Again, the rectangle 1 and 2 are so small, they can fit into the blocks allocated by the frame buffer allocator, but frame buffer 3 is too large. This rectangle is to large and will be split into multiple rectangles that each can fit into the frame buffers (11.700 bytes).
Here we are updating 3 rectangles, but the allocator only has 2 blocks. In that situation, TouchGFX will wait for the first blocks to transferred and then reuse the blocks.
Transferring Frame Buffers to the Screen
TouchGFX will allocate a frame buffer from the FrameBufferAllocator, when a rectangle needs to be redrawn. After drawing to the buffer TouchGFX will call this method:
This function can be overridden in a HAL subclass to transfer the frame buffer to the screen. This special implementation is required for partial framebuffers to work. The following sections will illustrate how to configure this for the STM32G081 and STM32G071 evaluation kits with a SPI displays, and the STM32L4R9Discovery evaluation kit which has a DSI display.
Transferring Frame Buffers to the STM32G081 SPI Display
The STM32G081 evaluation kit has a SPI display. The basic principle is to start a DMA transfer to the display as soon as a block is drawn or when a transfer is completed if a new block is ready to be transferred.
First, when a rectangle is drawn, we start a transfer if none is already in progress:
The function LCDManager_SendFrameBufferBlockWithPosition starts a SPI transfer to the display using DMA. This function is highly dependent on the display and the GPIO configuration. It must be developed by the application programmer. The STM32G0 CubeFW HAL function HAL_SPI_Transmit_DMA is used to start the DMA.
The SPI transfer complete interrupt handler calls a function when the transfer is complete:
The LCDManager_TransferComplete functions starts a new transfer. An important piece here is to call freeBlockAfterTransfer. This will allow TouchGFX to reuse the just transmitted block for a new drawing.
Transferring Frame Buffers to the X-NUCLEO-GFX01M1 SPI Display
In this section we will discuss the application template for the STM32G071 nucleo board with the X-Nucleo-GFX01M1 expansion board. This expansion board, MB1642B, contains a 2.2" 240x320 SPI display and a 64-Mbit SPI NOR flash.
In this application template we use a C++ class from the framework to help managing the partial framebuffer blocks. This makes the code in the application template a little shorter.
The application template is build with the TouchGFX Generator. Read more about that here
The most important part is the flushFrameBuffer function:
Here we just call the PartialFrameBufferManager framework class to get the block transmitted.
In the TouchGFXGeneratedHAL::endFrame function we call PartialFrameBufferManager to get any remaining framebuffer blocks transmitted also:
The PartialFrameBufferManager uses three functions to interact with the display driver code. These must be implemented in the Application Template:
The code above just forwards the calls to C functions in the MB1642B driver code.
The implementation of this driver code depends highly on the display used. For the MB1642B module this involves two GPIO to control SPI chip select and data/command mode.
The transmission state is implemented using a volatile uint8t variable *IsTransmittingBlock*. This variable is set to 1 when a transmission is started and set to zero in the DMA callback:
As we see above, the DMA callback also calls the transfer complete callback. This function is implemented in the generated HAL:
The call to the PartialFrameBufferManager here makes it start a new transfer if possible.
Transferring Frame Buffers on DSI Display
The STM32L4R9Discovery evaluation kit uses a DSI display. The normal HAL class is called STM32HAL_DSI (located in STM32HAL_DSI.cpp).
We override the HAL::flushFrameBuffer method to notify the FrameBufferAllocator that a block has been drawn:
The FrameBufferAllocator subclass ManyBlockAllocator will call the global function FrameBufferAllocatorSignalBlockDrawn() when a block is ready for transfer. This method must be implemented in the BSP layer:
This function is calling the sendBlock function, unless a transfer is already ongoing on the DSI. For the first block drawn by TouchGFX, this will never be the case, so a transfer is started. If another block drawing is completed while the DSI transfer is still running, the block will be kept in the "ready to transfer state", and drawing will continue in another free block (if available).
When a DSI transfer is completed, we must first free the transferred block, so it can be reused for another rectangle, and then check to see if the next block is ready for transfer. This is all done in the ER interrupt:
The function sendBlock is more complicated. Here we configure the LTDC and DSI peripherals to transfer the framebuffer. We also configure the display to put the transferred data into the correct place in the display memory. This part of the code is dependent on the specific display. Check the display datasheet for the command specifications.
Transferring Frame Buffers on SPI Display
The STM32G081 evaluation kit has a SPI display. The principle for transferring the rectangles to the display is the same as for the DSI, but some details are different.
First, when a rectangle is drawn, we start a transfer if none is already in progress:
The function LCDManager_SendFrameBufferBlockWithPosition starts a SPI transfer to the display using DMA.
The SPI transfer complete handler calls a function when the transfer is complete:
The LCDManager_TransferComplete functions starts a new transfer:
In this article we saw how the partial frame buffer strategy can help lowering the memory requirements for platforms that have displays with integrated frame buffer memory.
The method for configuring and setting up partial framebuffers is the same across all platforms, but the method of sending the content of the blocks to the display varies. We saw how, for an LTDC/DSI based platform (STM32L4R9-DISCO) we were able to reconfigure the LTDC Layer to fit the next block ready for transfer on DSI, while on a platform with no LCD controller (STM32G081) we were able to send the blocks to the display using SPI.