跳转到主要内容

使用串行闪存存储图像和字体

本节将讨论如何使用串行闪存(或其他非映射存储器)来存储图像和字体。 这里描述的技术在STM32G0和其他配备极小RAM的设备上特别有用。

有关部分帧缓存区(通常与串行Flash一起使用)的介绍,请参阅文章“使用部分帧缓存降低内存使用”。 另请参阅文章使用非存储映射存储图像,了解如何将位图从未映射Flash缓存到RAM。

配置

为了让TouchGFX应用使用串行闪存,您必须更改TouchGFX Generator的配置,启用“Additional Features”中的“External Data Reader”。

其它功能:数据读取器

启用此功能后,TouchGFX Generator更改配置,以便使用LCD16bppSerialFlash LCD类。 它还生成touchgfx::FlashDataReader的子类:

TouchGFXConfiguration.cpp
static TouchGFXDataReader dataReader;
static LCD16bppSerialFlash display(dataReader);
static ApplicationFontProvider fontProvider;
...
void touchgfx_init()
{
...
hal.setDataReader(&dataReader);
fontProvider.setFlashReader(&dataReader);
...
}

此代码创建TouchGFXDataReader类的实例,并将该实例传递给显示屏对象、HAL对象和ApplicationFontProvider对象。 这三个对象将使用dataReader对象访问串行闪存中的数据。 这些数据可以是图像和字体数据。

系统程序员必须完成 TouchGFXDataReader类的实现,才能真正从闪存中读取数据。

实现

TouchGFXDataReader类实现 touchgfx::FlashDataReader接口。 该接口有下列4种方法,需要在特定硬件上实现。

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()

LCD16bppSerialFlash类使用addressIsAddressable法来决定某些数据是否可以直接读取(即位于内部RAM或内部闪存中)或是否应通过dataReader对象读取。

copyData方法用于从闪存同步复制数据到RAM。 当不对数据进行进一步处理时,常使用此函数。 如 在将实图复制到帧缓冲时。

当需要从闪存获取多行数据时,使用startFlashLineRead法。 startFlashLineRead方法开始数据读取。 此方法可以开始异步读取,并且应在开始读取后立即返回。 waitFlashReadComplete方法应等待读取完成,并返回指向保存数据的缓冲区的指针。

LCD16bppSerialFlash可以在处理之前读取的数据时发起一次闪存读取(在某些情况下)。 这意味着dataReader中需要至少两个缓冲区来实现完全并发。

TouchGFX Generator生成分别属于两个类的FlashDataReaderTouchGFXGeneratedDataReaderTouchGFXDataReaderTouchGFXGeneratedDataReader是二者的子类,包含默认实现。 如果该实现不合适,应用程序员可以更改TouchGFXDataReader类中虚函数的实现。

TouchGFXGeneratedDataReader实现调用C函数来完成工作。 这些应用由系统程序员实现。

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);
}

此函数实现位于MB1642BDataReader.c文件中:

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;

...
}

此实现特定于闪存使用的协议以及SPI和CS使用的GPIO。 必须实现TouchGFXGeneratedDataReader类的全部三个C函数才能发挥作用。

图像

如简介中所述,LCD16bppSerialFlash类可通过dataReader对象读取图像像素。 为此,必须更改链接器脚本,以便将图像放在内部闪存范围之外的地址范围内。

在STM32G071上,我们选择了地址0x90000000作为串行闪存的起始地址:

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
}

这会将ExtFlashSectionFontFlashSection放入0x90000000地址范围。

余下的任务是用Flasher工具将数据写入外部闪存。

关于STM32CubeProgrammer的编写flash loader的简要说明,可以在以下文档的第2.3.3节中找到:UM2237 STMCubeProgrammer用户手册

字体数据

以上链接脚本将字体像素数据和字体字符元数据(宽和高)放入外部闪存(两类数据都在FontFlashSection中)。 在屏幕上绘制字符时,也通过dataReader对象读取此数据

如果您未使用“非映射存储格式”,则必须更改链接脚本和文件include/touchgfx/hal/Config.hpp,以便将字体字符元数据移动到内部闪存。

参见非映射存储中的字体一文获取关于字体格式的更多信息。