实战经验 | 一种基于外部Flash分时复用的TouchGFX工程无感升级方法

01、Flash无感升级应用

有客户提出在TouchGFX正常显示UI界面的同时,对存放素材的外部Flash进行升级,例如更换一些背景图片等,并且要求Flash升级的过程中,UI界面能够流畅的显示界面,也就是说在外部Flash升级的过程中,MCU仍能正常读取外部Flash的内容。

针对这种无感升级的应用,本文档介绍了一种基于Flash分时复用的TouchGFX工程无感升级方法。

02、Flash无感升级实现流程

2.1. 嵌入式最小系统原理

一个典型的嵌入式图形系统主要划分为4个主要部分:MCU/FLASH/RAM/Display。

Flash是放置所有静态数据的部分,图像、字体和文本。对于非常简单的应用,内部FLASH就足够了。而大多数情况下,内部FLASH很少大到足以容纳所有图形资产,因此会扩展外部FLASH用于存储图形资产。这里讲的无感升级就是针对图形资产存放在外部Flash的这种情况。

1.png

▲ 图1. 嵌入式图形系统框图

TouchGFX的工作原理,只有开始渲染的时候才会去访问Flash读取素材。那么在渲染的时候要访问Flash,在Flash升级的时候也要访问Flash,那么如何处理两种的操作呢?

UI任务在每次渲染时候,一般只有ms级的时间会访问外部Flash,其他时间不访问外部Flash。这里我们采用分时复用的方式,添加一把资源锁来控制对外部Flash的访问。当TouchGFX引擎访问外部Flash前,先获取资源锁,渲染结束的时候释放资源锁。如果有升级需求,升级程序访问外部Flash时,也要先获取资源锁再进行擦写操作。

2.png

▲ 图2. Flash分时复用流程

升级Flash内容涉及到Flash的擦除和编程操作,为了减轻对TouchGFX显示效果的影响,Flash的擦除和写操作都是按照最小单位进行,扇区擦除的单位是4K,写操作的单位是page,256字节。由于TouchGFX Task对Flash的读取操作一般是在内存映射模式下进行,所以在擦除和写操作的开始阶段先要将外部Flash从内存映射模式切换到间接模式,在操作的末尾再切换回内存映射模式。

03、代码实现过程

本文档在STM32U575-GFX02Z1开发板实现了Flash无感升级的过程,这里主要列出实现的关键步骤,详细代码可参考附件中的代码。

3.1. 新建工程

从TouchGFX Designer基于NUCLEO-U575ZI+GFX02Z1新建工程。

3.png

▲ 图3. 新建工程

3.2. 修改IOC配置

打开IOC文件,选择X-CUBE-DISPLAY插件,配置外部Flash,用于生成和本开发板相关的Flash驱动接口代码,如果是其他板子,可以照着这个框架替换成自己的Flash。

4.png

▲ 图4. 修改IOC配置

3.3. 代码修改

当检测到按键时,执行升级Flash任务。Flash升级任务入口函数:

void StartDefaultTask(void *argument)
{
    /* USER CODE BEGIN defaultTask */
    /* Infinite loop */
    for(;;)
    {
        if(HAL_GPIO_ReadPin(BUTTON_USER_GPIO_Port, BUTTON_USER_Pin) != GPIO_PIN_RESET)
        {
            OSPI_NORFlash_Upgrade();
        }
        osDelay(1);
    }
    /* USER CODE END defaultTask */
}

升级Flash操作,擦除时按照扇区进行擦除(sector:4K字节),写入时按照页进行写入(256字节),这样可以最大程度减少单次操作Flash的时间,降低升级过程对Flash的占用率,让渲染过程能及时访问Flash进行绘图,从而保证UI的性能与效果。FlashStartAddress是要更新的图片在Flash映射时的地址,size是数据大小。

Flash升级参考代码:

/* USER CODE BEGIN 4 */
void OSPI_NORFlash_Upgrade(void)
{
    uint32_t Size = sizeof(Image1);uint32_t FlashStartAddress = 0x90000000;
    uint32_t FlashEndAddress = (FlashStartAddress + Size);
    uint32_t EraseStartAddress = FlashStartAddress;
    uint32_t EraseEndAddress = EraseStartAddress + MX25L6433F_SECTOR_4K;
    uint32_t WriteStartAddress = FlashStartAddress;
    uint32_t WriteEndAddress = FlashStartAddress + Size;
    uint8_t * write_addr = (uint8_t *)Image1;
    int status;
    do
    {
        if(SectorErase (EraseStartAddress, EraseEndAddress) == BSP_ERROR_BUSY)continue;
        EraseEndAddress = EraseEndAddress + MX25L6433F_SECTOR_4K;EraseStartAddress = EraseStartAddress + MX25L6433F_SECTOR_4K;
        osDelay(1000);} while (EraseEndAddress<=FlashEndAddress);
        do{if(Write (WriteStartAddress, 256, (uint8_t *)write_addr, 0) == BSP_ERROR_BUSY)continue;
        write_addr = write_addr +256;WriteStartAddress = WriteStartAddress + 256;osDelay(100);
    } 
    while (WriteStartAddress<=FlashEndAddress);
}
/* USER CODE END 4 */

渲染开始前,获取Flash操作的资源锁,保证Flash仅为渲染操作使用。

begainFrame函数:

bool TouchGFXHAL::beginFrame()
{
    int ret;
    if(MEM_OS_TryLock(0, MEM_OS_TIMEOUT_BUSY) != MEM_OS_ERROR_NONE)
    {
        ret = BSP_ERROR_BUSY;
    }
    else
    {
        refreshRequested = false;
        return TouchGFXGeneratedHAL::beginFrame();
    }
}

渲染结束后,释放Flash资源锁。

endFrame函数:

void TouchGFXHAL::endFrame()
{
    TouchGFXGeneratedHAL::endFrame();
    if (frameBufferUpdatedThisFrame)
    {
        refreshRequested = true;
    }
    MEM_OS_Unlock(0);
}

同样,对应扇区擦除和写操作接口函数,开始前也要首先获取Flash资源锁,同时将Flash切换为间接模式,擦除或者写操作结束后,将Flash切换回内存映射模式,再释放Flash资源锁。

扇区擦除函数

/**
    * @brief Sector erase.
    * @param EraseStartAddress : erase start address
    * @param EraseEndAddress : erase end address
    * @retval None
*/
KeepInCompilation int SectorErase (uint32_t EraseStartAddress ,uint32_t EraseEndAddress)
{
    int32_t ret = BSP_ERROR_NONE;
    uint32_t BlockAddr;
    uint32_t BlockStartAddress = EraseStartAddress - EraseStartAddress % MX25L6433F_SECTOR_4K;//MX25L6433F_SECTOR_4K,MX25L6433F_BLOCK_64K
    if(MEM_OS_TryLock(0, MEM_OS_TIMEOUT_BUSY) != MEM_OS_ERROR_NONE)
    {
        ret = BSP_ERROR_BUSY;
    }
    else
    {
        HAL_OSPI_Abort(&hospi1);
        /* Configure CR register with functional mode as indirect-mode */
        CLEAR_BIT(hospi1.Instance->CR, OCTOSPI_CR_FMODE_0);
        do
        {
            BlockAddr = BlockStartAddress & 0x00FFFFFF;
            ret = BSP_MEM_BlockErase(0, BlockAddr, MX25L6433F_SECTOR_4K);
            //MX25L6433F_ERASE_64K
            BlockStartAddress+=MX25L6433F_SECTOR_4K;//MX25L6433F_BLOCK_64K
        } while ((EraseEndAddress>=BlockStartAddress) && (BSP_ERROR_NONE == ret));
        BSP_MEM_EnableMemoryMappedMode(0);
        MEM_OS_Unlock(0);
        BSP_RETURN(ret);
    }
}

Flash写函数:

/**
    * @brief Program memory.
    * @param Address: page address
    * @param Size : size of data
    * @param buffer : pointer to data buffer
    * @retval 1 : Operation succeeded
    * @retval 0 : Operation failed
*/
KeepInCompilation int Write (uint32_t Address, uint32_t Size, uint8_t* buffer, uint32_tblock_cnt)
{
    int32_t ret = BSP_ERROR_NONE;
    uint32_t end_addr, current_size;
    uint8_t *data_addr;
    UNUSED(block_cnt);
    if(MEM_OS_TryLock(0, MEM_OS_TIMEOUT_BUSY) != MEM_OS_ERROR_NONE)
    {
        ret = BSP_ERROR_BUSY;
    }
    else
    {
        HAL_OSPI_Abort(&hospi1);
        /* Configure CR register with functional mode as indirect-mode */
        CLEAR_BIT(hospi1.Instance->CR, OCTOSPI_CR_FMODE_0);
        /* Initialize the address variables */
        Address = Address & 0x00FFFFFF;
        end_addr = Address + Size;
        data_addr = buffer;
        /* Calculation of the size between the write address and the end of the page */
        current_size = MX25L6433F_PAGE_SIZE - (Address % MX25L6433F_PAGE_SIZE);
        /* Check if the size of the data is less than the remaining place in the page */
        if (current_size > Size)
        {
            current_size = Size;
        }
        /* Perform the write page by page */
        do
        {
            ret = BSP_MEM_WriteData(0, data_addr, Address, current_size);
            /* Update the address and size variables for next page programming */
            Address += current_size;
            data_addr += current_size;
            current_size = ((Address + MX25L6433F_PAGE_SIZE) > end_addr) ? (end_addr - Address) : 
            MX25L6433F_PAGE_SIZE;
        }  while ((Address < end_addr) && (BSP_ERROR_NONE == ret));
        BSP_MEM_EnableMemoryMappedMode(0);
        MEM_OS_Unlock(0);
        BSP_RETURN(ret);
    }
}

04、小结

以上就是TouchGFX应用Flash无感升级的关键步骤,附件中的代码可以直接运行在NUCLEO-U575ZI+GFX02Z1开发板上,在升级的时候,TouchGFX显示是很流畅的。

来源:STM32

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。