Developing OpenOCD FLASH drivers for External Memories
This tutorial shows how to develop FLASH drivers for use with the Sysprogs fork of OpenOCD and VisualGDB. FLASH drivers are responsible for automatically programming any external FLASH memories that your board is using. If you want to place some code or data in your project into an external FLASH memory and access it from your code as if it was located in the normal FLASH memory, you need to do the following:
- Add the external memory via External Memories page of VisualGDB Project Properties
- Actually setup the memory mapping from your startup code so that the memory contents is accessible to your program
- Develop a FLASH driver that will automatically program the external memory when you start debugging your project
This tutorial focuses on developing the FLASH driver; more information on configuring the project can be found in this tutorial.
We will show how to develop a FLASH driver based on the QSPI FLASH of the STM32F7-Discovery board.
- Follow this tutorial up to the step where a FLASH Driver is configured. Instead of configuring the driver, add the following lines to the main source file and start debugging the project as is:
1234567int QSPI_DATA g_ArrayInQSPI[] = { 1, 2, 3, 4, 5 };int main(){//...volatile int x = g_ArrayInQSPI[0];} - Use the debugger to double-check that the address of g_ArrayInQSPI is actually inside the QSPI address space (>=0x90000000) and that it contains garbage:
The values are incorrect because OpenOCD only knows how to program the internal FLASH memory of the STM32F7 chip, but knows nothing about QSPI. After we are done developing and configuring the driver, the array will be automatically programmed at the beginning of the debug session and will contain expected values.
- Before you begin creating your driver project, download the code from the Sysprogs FLASH driver framework repository somewhere on your computer.
- Start another instance of Visual Studio and open the VisualGDB Embedded Project Wizard:
- The driver is just an ELF file with certain mandatory functions defined inside it, so proceed with the default “Embedded Binary” selection:
- Select your device from the list (in this tutorial we select STM32F746NG that is installed in the STM32F7-Discovery board). The FLASH driver should be able to run from RAM (to avoid reprogramming the internal device FLASH to load it) and should be as small as possible to minimize the load time. Select “Execute from: SRAM” and “C library type: Newlib-nano” to achieve that:
- Proceed with the default “LEDBlink (HAL) sample”:
- On the Debug Method page select OpenOCD and click “Detect” to detect your programming interface. Then click “Finish” to generate the driver project.
- Open VisualGDB Project Properties and add the “common” directory from the FLASH driver framework to the Include Directories:
- Go to the Embedded Frameworks page and add a reference to “STM32746G-Discovery drivers”. Ensure that the QSPI driver is selected below:
- Finally add the FLASHPluginCommon.cpp file from the common directory in the FLASH driver framework to your project and create a new file called FLASHPluginConfig.h in your project directory with the following contents:
1234567#pragma once#include <stm32f7xx_hal.h>#include <stm32746g_discovery_qspi.h>#define MINIMUM_PROGRAMMED_BLOCK_SIZE N25Q128A_PAGE_SIZE#define FLASH_PLUGIN_SUPPORT_ASYNC_PROGRAMMING 1
Now you should be able to build the project. Once the build succeeds, you have all the necessary components to begin developing the actual driver.
- The FLASH driver is a normal ELF file compiled for your target device that must have several important functions that OpenOCD will use:
- First of all, it should have the FLASHPlugin_Probe() function that will be called to query the size of the FLASH memory, its base address and the amount of independently erasable sectors.
- The FLASHPlugin_FindWorkArea() function should locate the area in the SRAM that can be used to store the FLASH contents while it is being programmed.
- The FLASHPlugin_EraseSectors() function should erase a given range of sectors
- The FLASHPlugin_DoProgramSync() function will be responsible for actually programming the FLASH memory
- The FLASHPlugin_Unload() will be called by OpenOCD when the programming is complete and should undo any initialization done by the driver
The FLASH driver does not have a separate initialization function. Instead it should do all initialization in main() as usual and then call FLASHPlugin_InitDone() to signalize that the initialization is complete. OpenOCD will intercept this call and begin calling functions like FLASHPlugin_EraseSectors() once the initialization completes.
- Add the following implementation for the FLASHPlugin_Probe() function:
123456789101112FLASHBankInfo FLASHPlugin_Probe(unsigned base, unsigned size, unsigned width1, unsigned width2){InterruptEnabler enabler;FLASHBankInfo result = {.BaseAddress = base,.BlockCount = N25Q128A_FLASH_SIZE / N25Q128A_SUBSECTOR_SIZE,.BlockSize = N25Q128A_SUBSECTOR_SIZE,.WriteBlockSize = MINIMUM_PROGRAMMED_BLOCK_SIZE};return result;}
It will simply use the constants defined in the QSPI driver to provide the information about the FLASH memory to OpenOCD. Note that the base address comes from the base argument and will be specified when configuring the project. - The FLASHPlugin_FindWorkArea() should simply return 4 kilobytes after the end of stack (the stack will be managed by OpenOCD and won’t be set to the end of RAM as usual):
1234567WorkAreaInfo FLASHPlugin_FindWorkArea(void *endOfStack){InterruptEnabler enabler;WorkAreaInfo info = { .Address = endOfStack, .Size = 4096 };return info;} - The FLASHPlugin_EraseSectors() function should simply call BSP_QSPI_Erase_Block() for each erased sector:
123456789101112int FLASHPlugin_EraseSectors(unsigned firstSector, unsigned sectorCount){InterruptEnabler enabler;for (unsigned i = 0; i < sectorCount; i++){uint8_t error = BSP_QSPI_Erase_Block((firstSector + i) * N25Q128A_SUBSECTOR_SIZE);if (error != QSPI_OK)return -1;}return sectorCount;} - FLASHPlugin_DoProgramSync() should be just a wrapper to BSP_QSPI_Write():
1234567int FLASHPlugin_DoProgramSync(unsigned startOffset, const void *pData, int bytesToWrite){uint8_t result = BSP_QSPI_Write((uint8_t *)pData, startOffset, bytesToWrite);if (result != QSPI_OK)return 0;return bytesToWrite;} - FLASHPlugin_Unload() will call the regular uninitialization functions and will explicitly disable all interrupts including the SysTick interrupt so that no interrupt handler gets called until the actual program that is being loaded is ready:
1234567891011int FLASHPlugin_Unload(){BSP_QSPI_DeInit();HAL_DeInit();SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;for (int i = 0; i < sizeof(NVIC->ICER) / sizeof(NVIC->ICER[0]); i++)NVIC->ICER[0] = -1;return 0;} - Finally the main() function will do the necessary initialization and call FLASHPlugin_InitDone() when ready:
123456789101112int main(void){extern void *g_pfnVectors;SCB->VTOR = (uint32_t)&g_pfnVectors;HAL_Init();BSP_QSPI_Init();FLASHPlugin_InitDone();for (;;);}
Note that FLASHPlugin_InitDone() will actually never get called as OpenOCD will intercept the call. The default implementation simply makes sure that all other plugin functions appear referenced by the code and hence won’t be discarded by the linker. - Before trying the driver with the actual project, you can quickly step through its code in the debugger to see how it will work. The framework provides a function called TestFLASHProgramming() that simplifies that task. Simply call it from main as follows:
1TestFLASHProgramming(0x90000000, 0); - Last thing that needs to be done before we can try out the driver is manually set the initial stack pointer via VisualGDB Project Properties. Add “set $sp=&_estack” to gdb startup commands:
- Now you can press F5 to start debugging and step through TestFLASHProgramming() to ensure that your functions return the expected values:
- TestFLASHProgramming() will program the first page of your flash with a repeating value of 0x55. You can verify that by running your first project again and seeing that the array placed in QSPI is now initialized with 0x55555555:
- Go back to your main project. As VisualGDB 5.1 does not yet support automatic specification of the OpenOCD FLASH drivers, so open VisualGDB Project Properties, switch OpenOCD to manual mode and add the following line to additional command-line arguments:
1-c "flash bank qspi plugin 0x90000000 0 0 0 0 C:/projects/QSPIDriver/Debug/QSPIDriver.elf" -c "init" -c "reset init"
The “-c” part tells OpenOCD to load the QSPIDriver.elf plugin and call its probe() function with base=0x90000000 to find out the location and size of the QSPI memory. The “init” commands ensure that the initialization is performed in the correct order. Ensure you uncheck the “Issue a reset explicitly” checkbox as otherwise the init/reset init will be called before configuring the FLASH bank. - Set a breakpoint at the call to FunctionInQSPIFLASH() and press F5 to start debugging. Observe how the contents of the g_ArrayInQSPI now matches the expected values:
- You can now press F5 to continue into FunctionInQSPIFLASH() and observe how the LED blinking code contained in it will start running causing the LED to blink.