Platform Integration Aspects: External Flash Loader

In embedded systems with limited on-chip flash memory, it may become necessary to store large GUI resources such as bitmap images, font data, and string constants in external memory. This scenario is particularly common when dealing with many fonts, multiple languages, or extensive image libraries that exceed the available internal flash capacity.

Embedded Wizard provides different solutions depending on your specific use case:

Use Case 1: Dynamic Runtime Images - If you want to load images dynamically during runtime (e.g. from an SD card via a file system), Extern Bitmap provides the suitable interface for that. This means you want to load images which are not part of your Embedded Wizard GUI project and which can be defined later by the user (e.g. a background image, a logo) or by an external application (e.g. maps). In this case, Extern Bitmap provides an interface that you can implement according to your needs (e.g. integrate a PNG decoder, a BMP decoder, a file system).

Use Case 2: Compile-Time Known Assets in Addressable Flash - If you want to store the assets in an external directly-addressable flash device (e.g. QSPI Flash, NOR Flash), then it is just an additional flash that is used by the linker without requiring special handling. The external flash must be programmed using an appropriate flash programming tool.

Use Case 3: Compile-Time Known Assets in Non-Addressable Flash - If you want to load assets (your bitmap resources and font resources) from an external non-directly-addressable flash device (e.g. SD Card, NAND Flash, SPI Flash), the function EwRegisterFlashAreaReader() has to be used. During runtime, all required resources are loaded automatically from the external flash on demand via the registered flash reader. In this case, the resources are part of your GUI project and already known at compile time.

IMPORTANT

The main difference between Use Case 1 and 2/3 is: Do you want to access images that are defined during runtime (images stored by user on an SD card) - then Extern Bitmap is necessary - or do you want to access images that are part of your Embedded Wizard GUI project and thus known at compile time - then Use Case 2 or 3 apply depending on whether your external flash is directly addressable or not.

This article focuses on Use Case 3 - storing compile-time known resources in non-directly-addressable external flash memory using the EwRegisterFlashAreaReader() function.

To make it a little easier to understand, here is a small example to put all strings, bitmap and font resources into an external SPI flash:

Even though the SPI flash is not directly addressable by MCU, it has to be located to some virtual address (which is not in conflict with other RAM, flash memory or registers).

Let's assume the address range 0x90000000....0x903FFFFF will be used as virtual address range of the external 4MB SPI flash.

Part 1 - Enhancing the application to load content from the external non-addressable flash memory

In this first part, we will modify the application code to support loading resources from external flash memory. This involves defining memory addresses, implementing the flash reader interface, and registering the flash loader with the Embedded Wizard runtime system.

Step 1: Add the following defines to your ewconfig.h file:

#define EW_EXT_FLASH_ADDR 0x90000000 #define EW_EXT_FLASH_SIZE 0x400000

Step 2: Implement a flash loader function that loads the requested block from the external non-addressable flash memory - you can use the following template. This template implements a 4-block cache system with FLASH_READER_BLOCK_SIZE bytes per block for efficient data access.

The declaration of the flash loader - ew_bsp_flash_reader.h:

#ifndef EW_BSP_FLASH_READER_H #define EW_BSP_FLASH_READER_H #ifdef __cplusplus extern "C" { #endif /******************************************************************************* * FUNCTION: * EwBspFlashReaderInit * * DESCRIPTION: * The function EwBspFlashReaderInit() is used to initialize a flash driver in * order to access an external flash that is not in the CPU address range. * Furthermore, a callback function is registered that is called by Graphics * Engine or Runtime Environment when a new block of data has to be read. * * ARGUMENTS: * aStartAddress - Address that is used by the linker as start address for the * external flash. The address cannot be accessed directly by CPU. * aSize - Size of the flash memory area in bytes. * * RETURN VALUE: * Returns 1 if successful, 0 otherwise. * *******************************************************************************/ int EwBspFlashReaderInit ( void* aStartAddress, int aSize ); #ifdef __cplusplus } #endif #endif /* EW_BSP_FLASH_READER_H */

The implementation of the flash loader - ew_bsp_flash_reader.c:

#include "ewconfig.h" #include "ewrte.h" #include "YourSpiFlashDriver.h" #include "ew_bsp_flash_reader.h" #if EW_USE_EXTERNAL_FLASH == 1 /* block size that has to be read at once from the external flash and the size of a single cach line - must be power of 2 */ #define FLASH_READER_BLOCK_SIZE 64 /* the flash reader works with 4 cache lines (= 4 blocks that are cached) and the start address of each cache line has to be dword aligned */ static unsigned int Cache[ 4 * FLASH_READER_BLOCK_SIZE / sizeof( unsigned int )]; static unsigned int FlashAddr[ 4 ]; /* origin addresses of all cached blocks */ static void* CacheAddr[ 4 ]; /* start addresses of corrresponding cache line */ /******************************************************************************* * FUNCTION: * EwBspFlashReaderProc * * DESCRIPTION: * This callback function is used to read data from the external flash memory, * which cannot be accessed directly by the CPU. Embedded Wizard invokes this * function when accessing strings or bitmap/font data automatically to 'map' * the flash contents into the CPU address space. In this way, the not directly * accessible Flash data can be processed by Embedded Wizard. * * ARGUMENTS: * aAddress - The address to load the corresponding area from the Flash. * aSize - The size of the area intended for the access expressed in bytes. * If this parameter is > 0, the function should load as much as necessary * Flash pages to fully include the area 'aAddress .. (aAddress+aSize)'. * If this parameter is <= 0, the function should limit to load only the * Flash page containing aAddress. * * RETURN VALUE: * Returns the address of the location in an internal buffer corresponding to * the original aAddress from the Flash. If the loading fails or aAddress does * not lie within the Flash memory, the function should return aAddress without * any changes on it. * *******************************************************************************/ const void* EwBspFlashReaderProc( const void* aAddress, int aSize ) { int i; void* tmp; /* split start address in block number and offset */ unsigned int block = (unsigned int)aAddress &~ ( FLASH_READER_BLOCK_SIZE - 1 ); unsigned int offset = (unsigned int)aAddress & ( FLASH_READER_BLOCK_SIZE - 1 ); if ( aSize <= 0 ) { /* search immediately for already cached content - for best cache performance */ for ( i = 0; i < 4; i++ ) { if ( block == FlashAddr[ i ]) return CacheAddr[ i ] + offset; } aSize = 1; } /* determine the number of requested blocks */ unsigned int last = (unsigned int)( aAddress + aSize - 1 ) &~ ( FLASH_READER_BLOCK_SIZE - 1 ); unsigned int noOfBlocks = ( last - block ) / FLASH_READER_BLOCK_SIZE + 1; /* in case the requested address area fits into one block, one cache line is used */ if ( noOfBlocks == 1 ) { /* search for larger cached content that fits into one block */ if ( aSize > 1 ) for ( i = 0; i < 4; i++ ) { if ( block == FlashAddr[ i ]) return CacheAddr[ i ] + offset; } /* forget the oldest cache line, shift the entries and prepare the newest */ tmp = CacheAddr[ 3 ]; for ( i = 3; i > 0; i-- ) { FlashAddr[ i ] = FlashAddr[ i - 1 ]; CacheAddr[ i ] = CacheAddr[ i - 1 ]; } CacheAddr[ 0 ] = tmp; FlashAddr[ 0 ] = block; /* load the requested flash block into the free cache line */ YourSpiFlashDriver_ReadDataDma( block, (uint8_t*)CacheAddr[ 0 ], FLASH_READER_BLOCK_SIZE ); while( YourSpiFlashDriver_ReceiveActive() == 1 ) ; return CacheAddr[ 0 ] + offset; } else if ( noOfBlocks > 4 ) { EwPrint( "EwBspFlashReaderProc: Requested size does not fit into cache!\n" ); return aAddress; } /* otherwise, several blocks have to be loaded continuously at start of cache memory */ for ( i = 0; i < 4; i++ ) { /* search for cache index that corresponds to start of cache memory */ if ( CacheAddr [ i ] == Cache ) break; } tmp = CacheAddr [ i ]; while ( noOfBlocks-- ) { /* load the requested flash block into the free cache line */ YourSpiFlashDriver_ReadDataDma( block, (uint8_t*)CacheAddr[ i ], FLASH_READER_BLOCK_SIZE ); while( YourSpiFlashDriver_ReceiveActive() == 1 ) ; /* update the flash address that correspond to the cache line */ FlashAddr[ i ] = block; /* next junk of data */ block += FLASH_READER_BLOCK_SIZE; i = ( i + 1 ) % 4; } return tmp + offset; } #endif /******************************************************************************* * FUNCTION: * EwBspFlashReaderInit * * DESCRIPTION: * The function EwBspFlashReaderInit() is used to initialize a flash driver in * order to access an external flash that is not in the CPU address range. * Furthermore, a callback function is registered that is called by Graphics * Engine or Runtime Environment when a new block of data has to be read. * * ARGUMENTS: * aStartAddress - Address that is used by the linker as start address for the * external flash. The address cannot be accessed directly by CPU. * aSize - Size of the flash memory area in bytes. * * RETURN VALUE: * Returns 1 if successful, 0 otherwise. * *******************************************************************************/ int EwBspFlashReaderInit( void* aStartAddress, int aSize ) { #if EW_USE_EXTERNAL_FLASH == 1 if (((unsigned int)aStartAddress % FLASH_READER_BLOCK_SIZE ) || ( aSize % FLASH_READER_BLOCK_SIZE )) { EwPrint( "EwBspFlashReaderInit: Invalid address or size!\n" ); return 0; } /* intialize SPI driver to access external flash device */ YourSpiFlashDriver_PeripherieInit(); /* initalize the flash addresses and the pointers to the cache lines */ FlashAddr[ 0 ] = 0xFFFFFFFF; FlashAddr[ 1 ] = 0xFFFFFFFF; FlashAddr[ 2 ] = 0xFFFFFFFF; FlashAddr[ 3 ] = 0xFFFFFFFF; CacheAddr[ 0 ] = (void*)Cache; CacheAddr[ 1 ] = CacheAddr[ 0 ] + FLASH_READER_BLOCK_SIZE; CacheAddr[ 2 ] = CacheAddr[ 1 ] + FLASH_READER_BLOCK_SIZE; CacheAddr[ 3 ] = CacheAddr[ 2 ] + FLASH_READER_BLOCK_SIZE; /* register the callback for the flash loader */ EwRegisterFlashAreaReader( EwBspFlashReaderProc, aStartAddress, aStartAddress + aSize, FLASH_READER_BLOCK_SIZE ); #endif return 1; }

Step 3: Implement the necessary YourSpiFlashDriver_Function() to make the transfer via SPI.

Step 4: Register the flash loader within the function EwInit() within the file ewmain.c. It is important that this registration happens after calling EwInitRuntimeEnvironment() and before calling EwInitGraphicsEngine():

... /* initialize Runtime Environment */ EwInitRuntimeEnvironment( 0 ); /* initialize flash reader for accessing flash that is not in CPU address range */ EwBspFlashReaderInit( (void*)EW_EXT_FLASH_ADDR, EW_EXT_FLASH_SIZE ); /* initialize the Graphics Engine */ EwInitGraphicsEngine( 0 ); ...

That's all from the application side. Now let's prepare the binary:

Part 2 - Building the binaries for the internal flash and for the external non-addressable flash memory

In this second part, we will configure the build system to generate separate binaries for the internal flash (containing the application code) and the external flash (containing the GUI resources). This involves modifying linker scripts, configuring section names, and splitting the final binary into two parts that can be flashed independently.

Step 1: Enhance your linker script to use the external flash memory - in case of IAR the linker script (*.icf) should look like:

define symbol __ICFEDIT_region_EXTERNAL_FLASH_start__ = 0x90000000; define symbol __ICFEDIT_region_EXTERNAL_FLASH_end__ = 0x903FFFFF; define region EXTERNAL_FLASH_region = mem:[from __ICFEDIT_region_EXTERNAL_FLASH_start__ to __ICFEDIT_region_EXTERNAL_FLASH_end__]; place in EXTERNAL_FLASH_region { readonly section .SectionEwResource };

For GCC linker scripts (*.ld), add the following:

MEMORY { /* ... existing memory regions ... */ EXTERNAL_FLASH (r) : ORIGIN = 0x90000000, LENGTH = 4M } SECTIONS { /* ... existing sections ... */ .external_flash : { *(.SectionEwResource) } > EXTERNAL_FLASH }

For other toolchains, adapt the syntax accordingly to define the virtual memory region and place the .SectionEwResource section into it.

Step 2: Add the following defines to your ewconfig.h file:

#define EW_USE_EXTERNAL_FLASH 1 #if EW_USE_EXTERNAL_FLASH == 1 #define EW_BITMAP_PIXEL_SECTION_NAME .SectionEwResource #define EW_FONT_PIXEL_SECTION_NAME .SectionEwResource #define EW_FONT_DATA_SECTION_NAME .SectionEwResource #define EW_CONST_STRING_SECTION_NAME .SectionEwResource #endif

These macros define the section names for different resource types in the external flash. The ewconfig.h file is the central location for all target-specific settings and contains general configuration settings for the target system, memory ranges, display parameters, and Graphics Engine settings. For a complete overview of all available configuration options, see the Target Configuration chapter.

Step 3: Rebuild your application. As a result you will get one binary, that contains code and constant data within the normal flash and all strings, bitmap resources and font pixels located within the virtual address space of the external flash.

Step 4: Split the binary into two binaries. One binary containing the data of the external flash content and one binary without the external flash content. This can be done by using the tool objcopy to extract the .SectionEwResource section into a separate binary file. For SD cards, tools like Win32DiskImager can be used for bitwise copying.

Step 5: Load your flash loader into the MCU and flash the ExtFlash.bin file => strings, bitmaps and fonts are now in the external flash

Step 6: Download the binary containing code and other constant data into the normal flash and start the application.

IMPORTANT

In order to use the external flash reader for constant surfaces (by using DirectAccess mode for bitmap resources), it is necessary to rebuild the Graphics Engine with the macro EW_USE_READER_FOR_CONST_SURFACES being defined. You can add this macro in the file ewconfig.h. Using DirectAccess mode for bitmap resources from external non-directly addressable flash means that a bitmap is drawn directly from the external flash into the framebuffer. This can be done only by CPU and not by a GPU, because the GPU is not able to call the flash loader to load the next block from the flash. Therefore, it is recommended to use Compressed mode for the bitmaps instead. The decompression should only happen once the bitmap is loaded into RAM - then it should remain for all drawing operations (depending on your settings of EW_MAX_SURFACE_CACHE_SIZE). For detailed information about available configuration options, please refer to the Target Configuration chapter.