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.
Configuration
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".
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
:
TouchGFXConfiguration.cpp
static TouchGFXDataReader dataReader;
static LCD16bppSerialFlash display(dataReader);
static ApplicationFontProvider fontProvider;
...
void touchgfx_init()
{
...
hal.setDataReader(&dataReader);
fontProvider.setFlashReader(&dataReader);
...
}
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.
Implementation
The TouchGFXDataReader class implements the touchgfx::FlashDataReader interface. This interface has the following 4 methods that needs to be implemented on a specific hardware.
include/touchgfx/hal/FlashDataReader.hpp
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.
TouchGFX/target/generated/TouchGFXGeneratedDataReader.cpp
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:
Core/Src/MB1642BDataReader.c
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;
FLASH_CS_GPIO_Port->BRR = FLASH_CS_Pin;
*((__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.
Images
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:
gcc/STM32G071RBTX_FLASH.ld
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 36K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 128K
SPI_FLASH (r) : ORIGIN = 0x90000000, LENGTH = 8M
}
/* Sections */
SECTIONS
{
...
ExtFlashSection :
{
*(ExtFlashSection ExtFlashSection.*)
*(.gnu.linkonce.r.*)
. = ALIGN(0x4);
} >SPI_FLASH
FontFlashSection :
{
*(FontFlashSection FontFlashSection.*)
*(.gnu.linkonce.r.*)
. = ALIGN(0x4);
} >SPI_FLASH
}
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.