How the STM32 handles interrupts while writing to flash memory
Normally, code (firmware) is stored in flash memory - simple right? But there's a catch. When you want to write data into flash memory, read access to the flash memory is "blocked". During "blocking", the CPU cannot read firmware instructions or data from the flash memory. So the CPU is forced to wait for the blocking to end. By "wait", I mean the CPU is literally forced to "pause" until the flash write completes. On top of that, if and when the CPU receives an interrupt (from the MIDI UART for example, but it could be any interrupt) it cannot jump to the corresponding interrupt service routine because the address of that routine is stored in the interrupt vector table which (by default) is also stored in flash memory (which is blocked!). The solution (put simply) is to place the ISR functions, the ISR vector table, and all functions that are called by the ISR functions, into SRAM. Doing that requires some real work, as follows:
Step 1 - Store the Required Functions in Static RAM
First, we need the CPU to be able call the functions that write to flash and functions (and all sub functions) that are called by interrupts (ISR's) while flash writing is underway. To do that, the called functions need to be stored in static RAM memory (because the flash memory is locked and any attempts to read from flash will result in the CPU being placed into a wait state until the blocking ends). In other words, since the essential functions are stored in static RAM (instead of flash memory), the CPU will be able to execute those functions even while the flash write is underway.
Step 1) Determine which interrupts are non essential (can be disabled temporarily). We will disable those before writing to flash and reenable them after the flash write completes. For the HA-832 project, we can get away without the sound generation interrupt (TIM2) and the LFO delay timer interrupt (TIM3).
Step 2) Determine which interrupts are essential (cannot be disabled at all). For the HA-832, the MIDI/sequencer interrupt cannot be disabled and neither can SysTick.
Step 3) Mark the essential ISR functions as being stored in static RAM. This is done by setting an attribute on the function that tells the compiler and linker to store the function in static RAM instead of flash memory.
Step 4) Make sure that
any and all sub functions that are called by the essential ISR functions are also marked as being stored in static RAM. In some cases, it might be possible to reduce the number of functions calls by collapsing code into the ISR's themselves.
Step 5) Mark the flash write functions so that they also will be stored in static RAM. Also ensure that any subroutines called by those flash write functions are also stored in static RAM.
Step 6) Modify the linker script to place the marked functions into static RAM during the linking process.
Step 2 - Relocate the Interrupt Vector Table into Static RAM
Second, when the CPU receives an interrupt, it needs to be able to jump to the corresphonding interrupt service (ISR) routine. In other words when an interrupt is received, the CPU needs to look up the address of the ISR function and then jump to that function address. The ISR jump addresses are stored in the interrupt vector lookup table, which is (by default) stored in flash memory. So we need to relocate the interrupt vector table into static RAM too.
Step 1) Define an array that will be used to store the vector table in static RAM.
Step 2) At start up, copy the contents of the vector table into the new vector table.
Step 3) Also at start up, switch the STM32 vector table pointer to use the new vector table. Since the new vector table is stored in static RAM it can be accessed while flash writes are underway.
Step 4) Disable the non essential interrupts before carrying out flash write functions. And reenable them after the flash write functions have completed.
Step 5) Modify the linker script to place the new interrupt vector table array into static RAM during the linking process.
Where to Begin?
It's important to keep in mind that all of the above was new learning for me. I was not aware of any of the details before I started working on this problem. The starting point for this was that I noticed that MIDI data reception simply stopped when a flash write was performed. At that point, the only thing I suspected was that the MIDI hardware interrupt was being blocked somehow, for reasons unknown.
I had experienced flash write blocking before while working with the HAWK-800 kit and the AT28C256 flash ROM's. So I had some sense of what the basic problem could be but I had no inkling of the steps required to fix the issue on the STM32. So I did many, many, many google searches and found the following two articles to be the most useful:
Writing to flash blocks all interrupts This article outlines the basic steps involved in correcting the problem. It is not dealing with the STM32F3 series CPUs specifically but it turns out that the methods are all very similar across the ARM CPU variants. The article also mentions the SysTick timer interrupt which was helpful because I could have easily missed that during my remediation work. This article also shows how there is a define in the SystemInit function that allows for pointing the vector table at static RAM. So with that articile, I had some steps to begin to work through in order to solve the problem.
Executing code from RAM on STM32 CPUs This article shows how to instruct the C compiler to relocate functions into static RAM. It also explains how the linker script is used to change the compilation process to place 'marked' C functions into the right places in memory. The article also explains how the startup *.s file contains assembly that copies that different code and data sections into memory during start up including the way in which code is copied from flash into static RAM.
But we are just getting started! TBD - more to come.