Version: 4.15

Using Serial Flash for images and fonts

This section discusses how to use a serial flash (or other unmapped storage) to store images and fonts. The technique described here is especially usefull on STM32G0 and other deviced with very little RAM.

See the article Lowering Memory Usage with Partial Framebuffer for a introduction to partial framebuffers which are often used together with a serial flash.
See also the article Using Non-Memory Mapped Flash for storing images for an introduction to caching bitmaps from unmapped flash to RAM.


To use a serial flash with your TouchGFX application you must change the TouchGFX Generator configuration to enable the "External Data Reader" in "Additional Features".

Additional Features: Data Reader

With this feature enabled the TouchGFX Generator changes the configuration to use the LCD16bppSerialFlash LCD class. It also generates a subclass of the touchgfx::FlashDataReader:

static TouchGFXDataReader dataReader;
static LCD16bppSerialFlash display(dataReader);
static ApplicationFontProvider fontProvider;
void touchgfx_init()

This code creates an instance of the TouchGFXDataReader class and passes that instance to the display object, to the HAL object, and to the ApplicationFontProvider object. These three objects will use the dataReader object to access data in the serial flash. The data can be both images and font data.

The system programmer must finish the implementation of the TouchGFXDataReader class to actually read data from a flash.


The TouchGFXDataReader class implements the touchgfx::FlashDataReader interface. This interface has the following 4 methods that needs to be implemented on a specific hardware.

bool addressIsAddressable(const void* address)
void copyData(const void* src, void* dst, uint32_t bytes)
void startFlashLineRead(const void* src, uint32_t bytes)
const uint8_t* waitFlashReadComplete()

The addressIsAddressable method is used by the LCD16bppSerialFlash class to decide if some data can be directly read (i.e. is located in internal RAM or internal flash) or if it should be read through the dataReader object.

The copyData*` method is used to copy data synchronously from the flash to RAM. This function is typically used when the data is not further processed. E.g. when copying a solid image to a framebuffer.

The startFlashLineRead method is used when multiple lines of data are required from the flash. The startFlashLineRead method initiates a read of data. The method can initiate an asynchronous read and should return immediately after starting the read. The waitFlashReadComplete method should wait for the read to finish, and return a pointer to a buffer holding the data.

The LCD16bppSerialFlash can issue one flash read while processing the previously read data (in some situations). This means that at least two buffers are required in the dataReader to gain full concurrency.

The TouchGFX Generator generates the FlashDataReader in two classes: TouchGFXGeneratedDataReader and TouchGFXDataReader. The TouchGFXGeneratedDataReader is the superclass of the two and contains a default implementation. If that implementation is not suitable, the application programmer can change the implementation of the virtual functions in the TouchGFXDataReader class.

The TouchGFXGeneratedDataReader implementation calls C-functions to do the work. These application are implemented by the system programmer.

extern "C" __weak void DataReader_WaitForReceiveDone();
extern "C" __weak void DataReader_ReadData(uint32_t address24, uint8_t* buffer, uint32_t length);
extern "C" __weak void DataReader_StartDMAReadData(uint32_t address24, uint8_t* buffer, uint32_t length);
void TouchGFXGeneratedDataReader::startFlashLineRead(const void* src, uint32_t bytes)
/* Start transfer using DMA */
DataReader_StartDMAReadData((uint32_t)src, (readToBuffer1 ? buffer1 : buffer2), bytes);

The implementation is found in the MB1642BDataReader.c file:

void DataReader_StartDMAReadData(uint32_t address24, uint8_t* buffer, uint32_t length)
readDataDMA(address24, buffer, length);
void readDataDMA(uint32_t address24, uint8_t* buffer, uint32_t length)
// Pull Flash CS pin low
isReceivingData = 1;
*((__IO uint8_t*)&hspi2.Instance->DR) = CMD_READ;

This implementation is specific to the protocol used by the flash and the GPIO used for SPI and CS. All three C functions must be implemented for the TouchGFXGeneratedDataReader class to work.


As mentioned in the introduction the LCD16bppSerialFlash class can read image pixels through the dataReader object. For this to work we must change the lnker script to put images in an address range outside the internal flash range.

On the STM32G071 we have selected the address 0x90000000 as start address for the serial flash:

RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 36K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 128K
SPI_FLASH (r) : ORIGIN = 0x90000000, LENGTH = 8M
/* Sections */
ExtFlashSection :
*(ExtFlashSection ExtFlashSection.*)
. = ALIGN(0x4);
FontFlashSection :
*(FontFlashSection FontFlashSection.*)
. = ALIGN(0x4);

This puts the ExtFlashSection and FontFlashSection into the 0x90000000 address range.

The remaining task is to write the data to the external flash using a flasher tool.

A short description on writing flash loaders for STM32CubeProgrammer can be found in section 2.3.3 in this document:

UM2237 STMCubeProgrammer User Manual

Font data

The above linker script puts the font pixel data and the font character metadata (with and height) into the external flash (both types of data are in the FontFlashSection). This data is also read through the dataReader object when drawing characters on the Screen

If you are not using the "Unmapped Storage Format" for your you must change the linker script and the file include/touchgfx/hal/Config.hpp to move the font character metadata to internal flash.

See the article about Fonts in unmapped storage for more information on the font formats.