Using Non-Memory Mapped Flash for Storing Images
In this section we will discuss how to link all your images to a binary file that you can put in a non-memory mapped flash and how to use that file at runtime together with the bitmap cache. TouchGFX cannot draw bitmaps that are stored in non-memory mapped flash, but by caching the bitmaps in RAM we can make the bitmaps useable in the application.
See the article Caching Bitmaps for a general discussion on the bitmap cache.
In this article we assume that you have setup a bitmap cache, and that you want to store your bitmaps in non-memory mapped flash. This can be e.g. USB storage, NAND flash etc.
The goal is to link the images to a specific address, copy the images to a file, and help TouchGFX to copy from the file to the cache.
Copying bitmap data from flash to cache
Recall that when you cache a bitmap, TouchGFX copies the pixels from the original location to the bitmap cache.
This copying is done by calling this method from the HAL class:
HAL.hpp
bool HAL::blockCopy(void* RESTRICT dest, const void* RESTRICT src, uint32_t numBytes);
If your bitmaps are stored in normal addressable flash (like internal flash or memory mapped external flash), then the normal blockCopy function provided in the TouchGFX library is fine, and you do not need to do anything.
On the other hand, if your bitmaps is stored in storage that is not addressable, e.g. a filesystem, then the normal implementation is not sufficient and you need to provide an updated version that is able to read from your specific flash storage.
But first we need to make sure that our bitmaps is linked to a fixed address.
The BitmapDatabase table
All bitmaps in TouchGFX is generated to .cpp files in the folder generated/images/src. Here the bitmaps are represented as byte arrays.
These arrays are compiled by the C++ compiler as any other source code file and are linked into the application.
Here is a screenshot of a simple application with a Button and a TextureMapper showing a rotating logo:
This application uses 3 images: Button_Pressed, Button_Released, and Logo.
These 3 bitmaps are converted to .cpp files and linked in as part of the application. The images are referenced in a table called the bitmap_database. This table is located in the file BitmapDatabase.cpp. Here is the table from the above example (some details removed):
BitmapDatabase.cpp
extern const unsigned char _blue_buttons_round_edge_small[];
extern const unsigned char _blue_buttons_round_edge_small_pressed[];
extern const unsigned char _blue_logo_touchgfx_logo[];
const touchgfx::Bitmap::BitmapData bitmap_database[] =
{
{ _blue_buttons_round_edge_small, ... },
{ _blue_buttons_round_edge_small_pressed, ... },
{ _blue_logo_touchgfx_logo, ... }
};
The arrays declared first are the arrays containing the pixels of the individual bitmaps. These arrays are defined in other .cpp files. The bitmap_database array is holding the addresses of these arrays. TouchGFX uses this array to find the address of the pixels of a bitmap.
When the programmer requests a bitmap to be cached, TouchGFX finds the address of the bitmap in flash (in the bitmap_database array) and copies data from here.
Linker script modifications
The linker selects an address for the bitmaps. This selection is based on the section the bitmaps are placed in. All bitmaps in TouchGFX is by default put into the ExtFlashSection.
The standard linker scripts (here for GCC) puts this section into flash together with other read-only data.
In this example we will put the image data in the ExtFlashSection at address 0x24000000. You can select any address that is otherwise unused (not part of the code or data address space).
First we define a new memory area (USB-flash at address 0x24000000), in addition to the normal internal FLASH and RAM areas:
STM32F746.ld
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 320K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
USB(r) : ORIGIN = 0x24000000, LENGTH = 1M
}
Then we instruct the linker to put the ExtFlashSection in the USB area:
STM32F746.ld
ExtFlashSection :
{
*(ExtFlashSection ExtFlashSection.*)
} >USB
After linking we can check the addresses of the bitmaps by inspecting the map file (application.map). Here is the relevant part:
application.map
ExtFlashSection
0x24000000 0x23ec0
*(ExtFlashSection ExtFlashSection.*)
ExtFlashSection
0x24000000 0x10000 TouchGFX/build/.../Blue_Logo_touchgfx_logo.o
0x24000000 _blue_logo_touchgfx_logo
ExtFlashSection
0x24010000 0x9f60 TouchGFX/build/.../Blue_Buttons_Round_Edge_small.o
0x24010000 _blue_buttons_round_edge_small
ExtFlashSection
0x24019f60 0x9f60 TouchGFX/build/.../Blue_Buttons_Round_Edge_small_pressed.o
0x24019f60 _blue_buttons_round_edge_small_pressed
We can see here that the total size of the images is 0x23ec0 = 147.136 bytes. The 3 arrays holding the bitmaps are located sequentially from address 0x24000000.
Let's now assume the you want the bitmap data to go to a SD-card. We can extract the binary data for the bitmaps from the .elf file with a simple objcopy command:
$ arm-none-eabi-objcopy.exe --dump-section ExtFlashSection=images.bin TouchGFX/build/bin/target.elf
$ ls -l images.bin
-rw-r--r-- 1 christef Administrators 147136 Feb 20 11:56 images.bin
This gives us a file (images.bin) containing the image byte arrays only. This file can be copied to an USB flash, an SD-card, or even programmed to a flash chip.
The idea is now that when TouchGFX asks for data above address 0x24000000 we take the data from the images.bin file on the SD-card.
Modifying the BlockCopy function
Recall that when you cache a bitmap to RAM TouchGFX calls HAL::BlockCopy to get the data.
To get this linked to the data on your SD-card we can implement a new BlockCopy in your specific HAL class. Here we assume the class is called TouchGFXHAL (as generated by the TouchGFX Generator):
TouchGFXHal.hpp
class TouchGFXHAL : public TouchGFXGeneratedHAL
{
public:
...
virtual bool blockCopy(void* RESTRICT dest, const void* RESTRICT src, uint32_t numBytes);
}
TouchGFXHal.cpp
// This function is called whenever a bitmap is cached. Must copy a number of bytes from the (non-memory-mapped) source
// to the cache.
bool TouchGFXHAL::blockCopy(void* RESTRICT dest, const void* RESTRICT src, uint32_t numBytes)
{
// If requested data is located within the memory block we have assigned for ExtFlashSection,
// provide an implementation for data copying.
if (src >= 0x24000000 && src < 0x24100000)
{
void* dataOffset = src - 0x24000000;
// In this example we assume graphics is placed in SD card, and that we have an appropriate function
// for copying data from there.
sdcard_read(dest, dataOffset, numBytes);
return true;
}
else
{
// For all other addresses, just use the default implementation.
// This is important, as blockCopy is also used for other things in the core framework.
return HAL::blockCopy(dest, src, numBytes);
}
}
Now you can start caching bitmaps from the SD-card.
If TouchGFX tries to draw a bitmap that is not cached it will try to
read the pixels from the address found in the bitmap_database
table. This address will be in the range 0x24000000 - 0x24100000 and
the read will result in an exception.
Linking images to RAM
If your available RAM is big enough to hold all the images (in the above example that is more than 147.136 bytes) then you do not need to use the bitmap cache to copy the images.
The strategy is as follows:
- Select a fixed address and range in RAM for the images
- Remove that range from the RAM area in the linker script
- Create a new area IMAGES with the selected address and size
- Place the ExtFlashSection in IMAGES area
- Link the application and check the .map file
- Create the images.bin file from the application.elf
- Before TouchGFX is started, copy the whole images.bin file from the SD-card to the selected address in RAM
This solution is simple, but has some drawbacks:
- The available RAM must be big enough to hold all the images
- Start up time will be larger because of the copying from the SD-card (megabytes can take seconds)