cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F427 Flash memory bank swapping

kaspars.laizans
Associate II

I would like to implement in-firmware firmware upgrades. E.g. I send the binary data to the running device, it checks it and saves it into flash bank not currently used. After saving the firmware itself, I save to flash a byte with info on which firmware should be loaded.

So, my idea of rough flash layout:

Bank1 sector 0 contains bootloader, which checks a byte in flash to decide, which firmware to load - bank1 sector 5 or bank2 sector 17. Sectors 1:4 and 13:17 are reserved for internal data storage.

I would prefer not having to release 2 update images (1 for bank1 and other for bank2), since that tends to confuse users. As I understand, the memory locations are fixed at compile time (linked), so I can't just compile and link single image which would by default work on whichever bank, right?

So, I wrote a simple bootloader, which is able to correctly jump to location on bank 1 and that works fine. To avoid the linking issue, I found out that there is a feature of Flash memory bank swapping for double-bank devices (example from HAL library):

void HAL_EnableMemorySwappingBank(void)
{
  *(__IO uint32_t *)UFB_MODE_BB = (uint32_t)ENABLE;
}

According to RM0090:

FB_MODE: Flash Bank mode selection
Set and cleared by software. This bit controls the Flash Bank 1/2 mapping.
0: Flash Bank 1 is mapped at 0x0800 0000 (and aliased at 0x0000 0000) and
Flash Bank 2 is mapped at 0x0810 0000 (and aliased at 0x0010 0000)
1: Flash Bank 2 is mapped at 0x0800 0000 (and aliased at 0x0000 0000) and
Flash Bank 1 is mapped at 0x0810 0000 (and aliased at 0x0010 0000)

So, theoretically by using this mechanism, I could produce single binary which could work on either of memory banks, right?

However, when I try to execute the enabling of said bit, my software hangs. Maybe because I'm actually running off of flash I'm attempting to swap? Should I be running from SRAM to do this? I could not find any preconditions or what actually happens after the call. Documentation is kind of lacking, and so are examples.

Any suggestions are welcome

6 REPLIES 6

I would keep the same loader code at sectors at 0x8000000 and 0x08100000 so the fork would occur transparently, and there wouldn't be an issue with the vector table.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

>>Documentation is kind of lacking, and so are examples.

Silicon eschews complexity, the implementation here will be simplistic, and the behaviour dependent on the CM4. Assume they have simply installed an XOR gate in the address bus that accesses the array/subsystem (ie ART+FLASH), but not the peripheral. The banks don't physically move, the peripheral perspective doesn't change, but you read/write accesses will need to be remapped to access the specific bank, so you will need to account for that when updating bank 0 while bank 1 is banked low.

The processor isn't going to be able to execute code or jump to vectors if the memory in question isn't providing usable data. ie reading 0xFFFFFFFF is going to causing it to crash and die.

Moving and executing code in RAM is going to have exactly the same behaviour as one would see in other situation. This means you'll need to be conscious about what memory it touches, and what routines/code it branches to relatively.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
AndyJT
Associate III

I know this is an old post but the only one I found that was asking the same questions I had.

I also wanted a "simple" firmware updating method which made use of Flash bank swapping and each bank held code linked to the same address.

As a starting point for anyone else, I got bank swapping to work by putting this at the top of my main()

// test jumping to other flash bank - by pressing a button on power up
	if(HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_8) == GPIO_PIN_SET)
	{
		// disable interrupts
		__disable_irq();
		// disable ART
		CLEAR_BIT(FLASH->ACR, FLASH_ACR_PRFTEN);
		CLEAR_BIT(FLASH->ACR, FLASH_ACR_ICEN | FLASH_ACR_DCEN);
		// clear cache
		SET_BIT(FLASH->ACR, FLASH_ACR_ICRST | FLASH_ACR_DCRST);
		// Don't need to switch vector table as BANK2 effectively becomes BANK1 (so both programs are linked as though they live in BANK1)
		//				SCB->VTOR = (uint32_t)vector2;
		SET_BIT(SYSCFG->MEMRMP, SYSCFG_MEMRMP_UFB_MODE);
		// at this point, the instruction pointer will continue at the same address BUT on the other bank
		// so this initial bit of code HAS to be in the same location (ie offset) on both Banks
		
		// re-enable ART
		SET_BIT(FLASH->ACR, FLASH_ACR_PRFTEN);
		SET_BIT(FLASH->ACR, FLASH_ACR_ICEN | FLASH_ACR_DCEN);
		// restore interrupt capability
		__enable_irq();
	}

I compiled the same code twice, just changing the speed of a flashing LED to indicate which code was running.

Then downloaded one .bin file to bank1 (0x0800000 in my case) and the other into bank2 (0x08100000) using the STLINK utility.

Hey presto, the 1st bank code ran normally but the 2nd bank would be executed if I held down the GPIO button on power up.

If anyone can see any issues with this please let me know as I plan to implement firmware update from the host PC and some way of remembering which Bank holds the latest binary on power up.

kaspars.laizans
Associate II

Hi.

If memory serves me well, I ended up implementing without bootloader or runtime code swapping. I would receive new FW binary, check the current bank, write it to the other bank. Then toggle dual-bank bit in flash option bytes and reboot. Internal system bootloader then aliases either 0x0800000 (bank1) or 0x0810000 (bank2) to 0x00000000 (depending on dual bank enable bit) and continues from there. This allows for 2 things - no need to recompile for specific banks and system would also boot into previous bank, if new firmware gets corrupted (system bootloader checks for at least vector table in the bank before booting).

There were issues with the fact, that for R/W access banks are mapped correctly, but for erasing you need to figure out correct sectors, since those either remain as-is (for 2MB devices) or get remapped in weird ways (1MB devices).

Another 2 very important notes:

  1. Do firmware checks before and after flashing (I used inbuilt CRC32 peripheral). Sometimes you might get some bitflips which mess things up
  2. Be very very careful with what you write to flash in general and option bytes in particular. One of my devboards had protection lvl3 set for some reason and I couldn't clear it in any way I tried. Considered it bricked.
AndyJT
Associate III

Good advice thanks.

I was hoping that swapping banks in the initialisation code (SYSCFG_MEMRMP_UFB_MODE) means I don't need to worry about which bank I'm erasing. I'll always presume I'm running from 0x08000000 and writing new firmware to 0x08100000. Maybe this assumption is wrong but not too hard to get round if I know whether the bank is swapped or not.

I didn't want to use the BFB bit as started to get messy in my earlier testing, the manual was somewhat vague about it all and looks like you still need to remap the vector table anyway...

0693W00000GXLfUQAX.png

kaspars.laizans
Associate II

Bank swapping just selects which memory bank is aliased at the address 0x00, it does not remap physical memory sectors which you have to specify before performing flash erase. That's why your update code has to be aware of the bank in use. Otherwise you are correct, code is always running from 0x0800 0000, so flashing 0x0810 0000 should work, but when erasing you are not dealing with addresses, but sectors. You'll understand when you try it out 🙂