Use external Flash, STM32 F746NG_DISCO Board

Hello, I am using PlatformIO with Arduino Framework and disco_f746ng board. My board is very similar to the origin discovery board but has additional 16MB external flash ram.
My program does not fit into the internal 1MB flash. How can I tell PlatformIO to use the external flash as well?

Thank you

You can fake the amount of flash memory PlatformIO thinks the device has through upload.maximum_size, as documented.

; fake 16Mbyte flash capacity
board_upload.maximum_size = 16777216

However, that gives you a .bin file that is too big for internal flash if it’s over 1MB. If you say you have an external flash I assume you have already a sort of bootloader that can execute the program stored in the bigger 16MByte flash though and a mechnism to burn that program into external flash.

No, I’m afraid I don’t know how to do that. Do you have some advice for me here for the right approach?

Oh, well… that’s not so trivial. You definitely need a good understanding of the QSPI peripheral, memory maps, interrupt vector tables, linker scripts and bootloaders to get a working “execute code from QSPI” demo. You should start with resources like

Note that writing data into the QSPI connected to your STM32F7 chip cannot be done by the tools available by default in PlatformIO (aka, OpenOCD). You’ll need external flash loder programs as referenced in the above resources.

Additionally, you’ll likely need to modify the default linkerscript since that sets the base address of flash / execution at internal flash, not where your QSPI flash will be memory-mapped. You can place a modified copy the linker script in your project and refer to it via board_build.ldscript.

Long story short, from my point of view, what you need to execute the application from flash is

  • the ExtMem_Boot project adapted for your chip to be burned into internal flash
  • the linker script adapted for the QSPI flash memory address for the firmware that’s supposed to go into the QSPI (see here, here)
  • a way to program the resulting .bin file from the QSPI firmware build into the external flash

If the reason why your application isn’t fitting into flash is because there is large constant data (like, images etc.), it might be easier for you to just move this data into the external flash once and then load that in by the firmware that can then fit into internal flash.

Good luck on your journey.

Ok thank you very much for this very detailed good answer and advice. For STM32F7xx devices, is the QSPI interface always the only or typical way to connect external flash? I ask, because I am currently not sure which interface my custom disco board uses for his external flash.

Anyway, I will check the big constant data (images) thing first.

In addition:

Are there also some good instructions how to write and then read images from external flash?

QSPI is a typical way. It all depends on the exact flash device you have, and what it supports. QSPI are usually low-cost, easy to integrate flash devices. There are of course parallel Flash devices, with tons of pins, for which the FMC (flexible memory controller) peripheral can be used for integrating.

The STM32F746 Discovery board you refer to has a Micron N25Q128A Quad-SPI flash with 128MBit (=16 MByte) capacity and is logically connected to the QSPI interface of the microcontroller, as evident in the user manual chapter 4.14.

Binary image files can be written to supported flash devices and microcontrollers via the stnadard STM32CubeProgrammer tool. As you can see, it has an external loader available for the flash chip on the STM32F746G-DISCO.

In case you have designed your custom board with a different flash chip, or one that is parallel, and is not supported in the STM32CubeProgrammer, you will of course need to write your own external loader programmer. The 5-part STM32 learning series linked aboves walks one through that.

Ok, thank you very much. That already helps me a lot.Several images are stored in C files in the following form:

#include <lvgl.h>

#ifndef LV_ATTRIBUTE_MEM_ALIGN
#define LV_ATTRIBUTE_MEM_ALIGN
#endif

#ifndef LV_ATTRIBUTE_IMG__BG1_ALPHA_480X272
#define LV_ATTRIBUTE_IMG__BG1_ALPHA_480X272
#endif

    const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_IMG__BG1_ALPHA_480X272 uint8_t _bg1_alpha_480x272_map[] = {
    #if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP == 0
      /*Pixel format: Alpha 8 bit, Red: 5 bit, Green: 6 bit, Blue: 5 bit*/
      0xe4, 0x10, 0xff, 0xe4, 0x10, 0xff, 0xe4, 0x10, 0xff, 0xe4, ...   0x10, 0xff, 
    #endif
    };

    const lv_img_dsc_t _bg1_alpha_480x272 = {
      .header.always_zero = 0,
      .header.w = 480,
      .header.h = 272,
      .data_size = 130560 * LV_COLOR_SIZE / 8,
      .header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA,
      .data = _bg1_alpha_480x272_map,
    };

So now I need to figure out two things:

  1. How and may I convert this C file directly into a binary file?
  2. How do I then read these files via QSPI… that will probably not be easy either. I have to enable the QSPI hardware for sure, but how the function has to look like, which then fetches these files via QSPI correctly from the flash. This will probably be complex, unless I find somewhere already a similar example…

There’s a million ways to do that. One is to copy-paste the uint8_t data array into a C file and write a program that writes the binary content again in an image file. Using e.g. the displayed data, one can write a small utility

#include <stdio.h>
#include <stdint.h>

uint8_t _bg1_alpha_480x272_map[] = {
	0xe4, 0x10, 0xff, 0xe4, 0x10, 0xff, 0xe4, 0x10, 0xff, 0xe4, /*... */  0x10, 0xff, 
};

typedef struct { 
uint8_t* data; size_t len; char name[128];
} buf_elem;

#define xstr(s) str(s)
#define str(s) #s
#define ADD_ELEM(arr) { (arr), (sizeof(arr)), str(arr) }

buf_elem buffers_to_convert[] = {
  ADD_ELEM(_bg1_alpha_480x272_map)
};

int main() {
	FILE *fp;
	fp = fopen("qspi_image.bin","wb");
	size_t off = 0;
	for(int i=0; i < sizeof(buffers_to_convert)/sizeof(*buffers_to_convert); i++) {
		buf_elem e = buffers_to_convert[i];
		printf("Writing %s at offset %zu, %zu bytes.\n", e.name, off, e.len);
		fwrite(e.data,e.len,1,fp);
		off += e.len;
	}
	fclose(fp);
	printf("Done.\n");
	return 0;
}
C:\Users\Max\temp\img_conv>gcc -o converter converter.c && converter.exe
Writing _bg1_alpha_480x272_map at offset 0, 12 bytes.
Done.

C:\Users\Max\temp\img_conv>hexdump -C qspi_image.bin
00000000  e4 10 ff e4 10 ff e4 10  ff e4 10 ff              |ä.ÿä.ÿä.ÿä.ÿ|
0000000c

Please read through the examples I’ve referenced above and especially the video series. They show how to activate the QSPI peripheral and have it go into memory mapped mode. This means that if you initialize the peripheral correctly, the contents of the QSPI memory will just appear starting at a certain memory address (QSPI_BASE = 0x90000000). This means that you can just remove the data array from the C file and change

with the memory address at which the original array would appear. For example, if you burn the above shown qspi_image.bin which contains as its first element the _bg1_alpha_480x272_map into the flash, and you initialize the QSPI peripheral correctly in memorry-mapped mode, the data from the flash will just show up at 0x90000000, which is the start of the _bg1_alpha_480x272_map array. And so, data = _bg1_alpha_480x272_map, can be replaced with just .data = QSPI_BASE,. With multiple images, you then have to add an offset to it, which is the number of bytes coming before the image data you’re interested in. For example, if two images are stored in the QSPI, both 500 bytes long, then you would reference the first picture data with QSPI_BASE and the second one as QSPI_BASE + 500. This is the beauty of memory mapping – no special read functions needed, the microcontroller will internally handle reads to the special memory address by doing the QSPI read operation and returning the read data. Just as if you would read from RAM or internal flash.

wow, ok thanks a lot, with this detailed tutorial I think I should get it done.

Your main blocker will be writing / obtaining the correct QSPI configuration for your flash from the datasheet – if you use N25Q128A it’s directly supported through the existing driver and shown above, but even for other flash chips, as linked above but in a different branch, there are drivers stm32-external-loader/QSPI_Drivers at contrib · STMicroelectronics/stm32-external-loader · GitHub.

Thank you very much again :-D.
With your very detailed help, I got it running. I have integrated the following 3 files to activate the QSPI hardware:

…/lvgl/lv_port_stm32f746_disco/blob/master/Utilities/Components/n25q128a/n25q128a.h
…/lvgl/lv_port_stm32f746_disco/blob/master/Utilities/STM32746G-Discovery/stm32746g_discovery_qspi.h
…/lvgl/lv_port_stm32f746_disco/blob/master/Utilities/STM32746G-Discovery/stm32746g_discovery_qspi.c

1 Like