
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. 嵌入式图形系统框图
TouchGFX的工作原理,只有开始渲染的时候才会去访问Flash读取素材。那么在渲染的时候要访问Flash,在Flash升级的时候也要访问Flash,那么如何处理两种的操作呢?
UI任务在每次渲染时候,一般只有ms级的时间会访问外部Flash,其他时间不访问外部Flash。这里我们采用分时复用的方式,添加一把资源锁来控制对外部Flash的访问。当TouchGFX引擎访问外部Flash前,先获取资源锁,渲染结束的时候释放资源锁。如果有升级需求,升级程序访问外部Flash时,也要先获取资源锁再进行擦写操作。
▲ 图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. 新建工程
3.2. 修改IOC配置
打开IOC文件,选择X-CUBE-DISPLAY插件,配置外部Flash,用于生成和本开发板相关的Flash驱动接口代码,如果是其他板子,可以照着这个框架替换成自己的Flash。
▲ 图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)。