cancel
Showing results for 
Search instead for 
Did you mean: 

How to add your spi flash into the STM32CubeProgrammer's External Loader Part 2

B.Montanari
ST Employee

How to add your SPI flash into the STM32CubeProgrammer’s external loader?

Welcome back with the part 2 of our article series that explains how to add a custom SPI flash memory in the External Loader portion of the STM32CubeProgrammer and in the STM32CubeIDE.

In the first part we covered the pre-requisites and linker script needed code edit - the full file edited is presented at the end of this article.
The next steps will show how to create the necessary files of the External Loader.
First, create the file that will receive all the necessary External Loader functions. In Core -> Src click with right mouse button, go to New ->  Source File and create a file named “Loader_Src.c”.
 
281.png
282.png

This file needs to receive the following functions in these specific format (the same names, types, parameters and return values). These functions will be used by STM32CubeProgrammer to manage the external memory and you should place your memory driver inside that, according to the function purpose. These are the functions will need to populate:
 
int Write(uint32_t Address, uint32_t Size, uint8_t *buffer);
int Read(uint32_t Address, uint32_t Size, uint8_t *Buffer);
int SectorErase (uint32_t EraseStartAddress ,uint32_t EraseEndAddress);
int MassErase(void);
You can add these following functions too, but they are optional:
uint32_t CheckSum(uint32_t StartAddress, uint32_t Size, uint32_t InitVal);
int Verify (uint32_t MemoryAddr, uint32_t RAMBufferAddr, uint32_t Size);
The return values of the functions need to be “0” to represent an operation failed or “1” when it succeeded, and you can create these defines to help you to manage that:
#define LOADER_OK	0x1
#define LOADER_FAIL	0x0
We need to include the HAL drivers and some initialization functions to manage the peripherals, so add the following lines to the code:
#include "main.h"
#include "gpio.h"
extern void SystemClock_Config(void);
extern void MX_SPI2_Init(void);
extern void MX_GPIO_Init(void);
The remaining function that we need to create in this file is the Init function that will be called by the Entry Point mentioned in the linker script step by step:
int Init(void) {

	SystemInit();

	SCB->VTOR = 0x20000000 | 0x200;

	HAL_DeInit();
	HAL_Init();

	/* Configure the system clock */
	SystemClock_Config();

	/* Initialize all configured peripherals */
	MX_GPIO_Init();
	MX_SPI2_Init();
	
	__set_PRIMASK(0);	//enable interrupts
	W25qxx_Init();	 	//External memory function initialization
	__set_PRIMASK(1);	//disable interrupts
	
	return LOADER_OK;
}
The other two files that we need to create are “Dev_Info.c” and “Dev_Info.h”. These files will manage the header memory information used by STM32CubeProgrammer to read, program and erase the device – there are examples available in the STM32CubeProgrammer installation folder and also referred in the UM2237. So, let’s start creating these files from scratch. Create “Dev_Inf.h” in Core - > Inc folder, and “Dev_Inf.c” in Core -> Src.
283.png
Starting by the header code, we need to create a specific struct that will receive all the necessary information about the memory and communication type.  So, add the following code on the file.
#define 	MCU_FLASH 	1
#define 	NAND_FLASH  2
#define 	NOR_FLASH   3
#define 	SRAM        4
#define 	PSRAM       5
#define 	PC_CARD     6
#define 	SPI_FLASH   7
#define 	I2C_FLASH   8
#define 	SDRAM       9
#define 	I2C_EEPROM  10

#define SECTOR_NUM 10				// Max Number of Sector types

struct DeviceSectors
{
  unsigned long		SectorNum;     // Number of Sectors
  unsigned long		SectorSize;    // Sector Size in Bytes
};

struct StorageInfo
{
   char       	  DeviceName[100];				// Device Name and Description
   unsigned short DeviceType;					// Device Type: ONCHIP, EXT8BIT, EXT16BIT, ...
   unsigned long  DeviceStartAddress;			// Default Device Start Address
   unsigned long  DeviceSize;					// Total Size of Device
   unsigned long  PageSize;						// Programming Page Size
   unsigned char  EraseValue;					// Content of Erased Memory
   struct 	      DeviceSectors	 sectors[SECTOR_NUM];
};
In “Dev_Inf.c” we only need to declare and initialize that struct and store it on the .Dev_info section of the RAM (for that, an __attribute__(section(“.Dev_Info”)) is placed in the struct declaration). The variable data of the struct should be changed to be compatible with the memory.
/*
 * Dev_Inf.c
 *
 */
#include "Dev_Inf.h"

/* This structure contains information used by ST-LINK Utility to program and erase the device */
#if defined (__ICCARM__)
__root struct StorageInfo const StorageInfo  =  {
#else
struct StorageInfo __attribute__((section(".Dev_info"))) /*const*/ StorageInfo  = {
#endif
		"W25Q64_STM32G070R", 	 	         // Device Name + version number
		SPI_FLASH,                  		 // Device Type
		0x00000000,                			 // Device Start Address
		MEMORY_FLASH_SIZE,                 	 // Device Size in Bytes
		MEMORY_PAGE_SIZE,                    // Programming Page Size
		0xFF,                                // Initial Content of Erased Memory

		// Specify Size and Address of Sectors (view example below)
		{
				{ 0x80,  			// Sector Numbers,
				  0x1000 },        	//Sector Size

				{ 0x00000000, 0x00000000 }
		}
};
The struct data is shown by Cube Programmer when the External Loader is present on that.
284.png
Building the project, the functions (including the memory driver functions) should be inside the .text section that are in the RAM_D1 memory area and a section named .Dev_Info with the StorageInfo struct was created.
285.png 286.png
 
The final creation step of the external loader is to set the STM32 Project settings to generate the .stld (ST Loader) file after build the files. For that, click with right mouse button on the project and go to Properties.
287.png
In the settings, go to C/C++ Build -> Build Steps and add the following text in Command text box of Post-build steps:
cp "${BuildArtifactFileBaseName}.elf" "../STM32G070_W25Q64.stldr"
288.pngBuild the application and check in the project files if the STM32G070_W25Q64.stldr file was created (you can change the name if you want in the command text).
289.png
Now, the driver is ready to be used and we need to import that in the STM32CubeProgrammer. Copy the STM32G070_W25Q64.stldr file (that’s located in the root project folder) to the External Loader folder of the STM32CubeProgrammer. A fast way to locate this file is clicking with right mouse button on that and going to Show In -> System Explorer, so the file explorer should be open in the file located folder.
290.png
Then, copy and paste the *.stldr file to External Loader folder of the STM32CubeProgrammer, located in:
“C:\Program Files\STMicroelectronics\STM32Cube\STM32CubeProgrammer\bin\ExternalLoader”
(this path can change depending of your installation path, another way to reach on that is clicking with right mouse button on the STM32CubeProgrammer desktop shortcut and going to “Open file location” and search for the ExternalLoader folder on the opened location).
291.pngFinally, open the STM32CubeProgrammer and go to EL (External Loader) tab, search for the created loader and active that checking the current box.
292.png
Going back to “Memory & File edition” and searching for an address of the external memory, if the process done well, you will see the content stored on that. In this case there are a sequence stored on the memory that starts from the 0x00000000 address.
293.png
If all the steps were properly made, the External Loader is running properly and now you can directly read, program and erase external memories. Congratulations!
294.png
References:
Linker file code:
/* Entry Point */
ENTRY(Init)

/* Generate 2 segment for Loader code and device info */
PHDRS {Loader PT_LOAD ; SgInfo PT_LOAD ; }

/* Highest address of the user mode stack */
_estack = ORIGIN(RAM_D1) + LENGTH(RAM_D1); /* end of "RAM" Ram type memory */

_Min_Heap_Size = 0x200 ; /* required amount of heap */
_Min_Stack_Size = 0x400 ; /* required amount of stack */

/* Memories definition */
MEMORY
{
  RAM_D1    (xrw)    : ORIGIN = 0x20000004,   LENGTH = 36K-4
}

/* Sections */
SECTIONS
{

  .isr_vector :
  {
  	. = . + 0x1FC; /* ISR vector offset */
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >RAM_D1 :Loader

  .ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*)  } >RAM_D1

  .ARM : {
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
  } >RAM_D1 :Loader

  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  }  >RAM_D1 :Loader

  .init_array :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
  }  >RAM_D1 :Loader

  .fini_array :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
  }  >RAM_D1 :Loader

  /* Used by the startup to initialize data */
  _sidata = LOADADDR(.data);

  /* Initialized data sections into "RAM" Ram type memory */
  .data :
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */

  }  >RAM_D1 :Loader

  /* Uninitialized data section */
  . = ALIGN(4);
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss section */
    _sbss = .;         /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)

    . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
  } >RAM_D1 :Loader
  
  /* The program code and other data into RAM type memory */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))
	KEEP (*Loader_Src.o ( .text* ))
    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >RAM_D1 : Loader
  
  .Dev_Info :
  {
  	__Dev_info_START = .;
  	*(.Dev_info*)
  	KEEP(*(.Dev_info))
  	__Dev_info_END = .;
  } >RAM_D1 :SgInfo
  
  /* Constant data into "FLASH" Rom type memory */
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >RAM_D1 :Loader
  
  /* User_heap_stack section, used to check that there is enough "RAM" Ram  type memory left */
  ._user_heap_stack :
  {
    . = ALIGN(8);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
  } >RAM_D1 :Loader

  /* Remove information from the compiler libraries */
  /DISCARD/ :
  {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
  }

  .ARM.attributes 0 : { *(.ARM.attributes) }
}

 
Comments
AZyma.1
Associate II

Hi @Laura C.​ , thank you for such a perfect article!

Yet the *.stldr file I obtained worked perfectly with CubeProgrammer v2.4.0, but stopped working with newer versions.

Is there any reason for that?

The issue is posted here, could you please check it?

LauraCx
ST Employee

Hello @AZyma.1​ ,

The article was made using the STM32CubeProg 2.8 or 2.9 (newer than 2.4), so we are reviewing your case internally.

RMurt.3
Associate III

Great Knowledge article and excellent examples.

I am using a STM32F030C8 with an external flash W25Q128 connected via SPI port, this processor does not have a VTOR so can't run code from RAM.

Is it possible to create an external loader for my CPU with this code running from FLASH ?

RMurt.3
Associate III

So if im not using interrupts I suspect I don't need VTOR, so Let me re-phrase that question.

To use the STM32cubeprogrammer for extern SPI flash loading, does the STMcubeprogrammer ONLY allow RAM based code to run on CPU. Or can the STMcubeprog load in a cpu FLASH based program to do the SPI writing and then replace the cpu flash code with the real cpu program later in the cycle ?

Nikita91
Lead II

Here you specify the loader may provide the function Verify:

int Verify (uint32_t MemoryAddr, uint32_t RAMBufferAddr, uint32_t Size);

But in the example provided by ST on ttps://github.com/STMicroelectronics/stm32-external-loader

The function is:

uint64_t Verify (uint32_t MemoryAddr, uint32_t RAMBufferAddr, uint32_t Size, uint32_t missalignement)

There is one more parameter: missalignement,

and the return type is not the same: int vs uint64_t

1) Which is the right function?

2) Can you give me some information on the value and the usage of the parameter missalignement?

3 Where can I get this project source? The linker scrip was very beneficial, but I would like to see a working Loader_Src.c.

Thank you.

GFien.2
Associate

Hello,

I get the code downloaded using the steps, but after power up microcontroller nucleo board does nothing, any way to test it via stm32cubeIDE?? I try to compile and download and get error every time.

Thanks.

Nikita91
Lead II

It should be noted that the value of SECTOR_NUM must be 10: otherwise the external loader is rejected by the STM32CubeProgrammer >= V1.12

Adding this precision in the code would avoid many errors...

https://community.st.com/t5/stm32cubeprogrammer-mcus/external-loader-parsing-error-v2-12-0/td-p/120021 

Kutlu
Associate II

Hi,

Assuming we have an external loader and we have programmed the external flash properly.

Are we able to debug the code residing in external flash by using the loader that we have used to program the external flash?

In my case I have flashed an external QSPI memory by using an external loader.(The main application resides in external QSPI Memory)

Another application runs on the internal flash inits clocks, QSPI and some GPIOs and then it brings the controller into memory mapped mode and jumps to main application which resides on external QSPI flash.

After the jump the debugger looses its connection with the new location.

I can verify that that the code in the external flash is running by blinking some LEDs, toggling them etc.

But when I start to debug in only debugs the code in the internal flash. As soon as it jumps to external code I can not debug.

Can you suggest a way of debugging the code in external flash.

PS:I have an external RAM on the custom board as well. Is it feasible to load the code in external flash into external RAM for debugging purposes or should the external loader written for loading the application to the flash memory may also be used for debugging.

Version history
Last update:
‎2022-06-30 12:43 AM
Updated by: