étale-cohomology
étale-cohomology

Reputation: 1861

Writing a program that updates itself at runtime for STM32

I have a program running on an STM32F103C8 that uses a bootloader for USB DFU updates.

The program and the bootloader/updates work nicely, but I wonder how can you a program that updates itself completely over USB?

Because with a bootloader, as far as I know, the bootloader lives from (say) 0x08000000 to 0x08001000 in Flash and then updates another program (another part of flash), in this case, eg., starting at 0x08001000.

One option I thought of would be to run the code not from flash but from RAM/SRAM, like in this question and then update the flash memory, reset, and boot from flash. However, the answers there involve booting from SRAM, which I think I may not want to do. (One of the reasons being that this must run in production, without a debugger.)

Rather, what makes more sense to me is to flash the program to the STM32 flash, as usual, at 0x08000000, run the code from flash, and then, at runtime, upon receiving the update command from the user, move the entire program (or perhaps just the strictly necessary parts) to RAM/SRAM, continue execution from SRAM, and then update the entire flash memory and reset the MCU and boot from flash, again, as usual. (In particular, you never boot from SRAM, and you only run code from SRAM when you want to update.)

So, can this (or something logically similar) be done, in order to have a program that updates itself completely?

Now, I know this is possible if you only want to update a subset of flash memory (eg. I think VIA does a partial update of flash memory at runtime in order to update keyboard mappings without reflashing the keyboard and without as much as reconnecting your keyboard), but I wonder how it can be done if you want to update the entire contents of flash memory.

Upvotes: 2

Views: 236

Answers (2)

pmacfarlane
pmacfarlane

Reputation: 4317

One way of doing it, which I have done successfully, and which I believe is quite common, is as follows:

Partition the flash into three areas. Let's imagine you have 32KB of flash. Partition like so:

  • Bootloader - 2 KB
  • Application - 15 KB
  • Upgrade image - 15 KB

Each of your Application and Upgrade images should contain within them some data (at a known offset) which contains at the least, the size of the image, version information for the image, and a CRC of the image. And maybe a "magic number" that indicates if the slot contains an image at all.

When your MCU comes out of reset, the Bootloader will run. It can check the CRCs and versions of both the Application image and the Upgrade image. It would then have logic something like:

  1. If the Upgrade image has a valid CRC and magic number, and if the Upgrade image is a newer version than the Application image, or the Application image does not have a valid CRC or magic number, then copy the Upgrade image over the Application image. (Then reset.)
  2. Otherwise boot the Application image.

Initially your Upgrade slot wouldn't contain an image at all. You'd just start off with a Bootloader and Application.

Your Application image is responsible for receiving an Upgrade image over USB (or from some other source) and writing it into flash in the Upgrade image area.

Once the Upgrade image has been written to flash, all you need to do is reset the MCU. The Bootloader will then run, and do the actual update. The MCU can be reset in software by writing a register, or if you are using the STM's HAL library, like so:

HAL_NVIC_SystemReset ( );

This is resilient to unexpected power failures because:

  • If power is lost while receiving a new Upgrade image and writing it to flash, then the CRC in the upgrade image will be incorrect, and on booting, the bootloader will not copy it into the Application image slot.
  • If power is lost while the bootloader is copying the Upgrade image into the Application image slot, then the CRC in the Application image will be incorrect. On next boot, the update (copying from Upgrade to Application) will be started again.

Note that the Bootloader never gets updated, and so should always be present, to either run the Application, or update the Application.

The disadvantage of this method is that you need to use (waste?) half of your flash memory for the Upgrade slot. But as far as I know this is the only way to make a fail-safe update method that can't be bricked by a power-failure.

I've used this method on a system where we send out updates over multicast UDP to hundreds of devices on a network. It works great, and it is idempotent, in so much as you can just send out the same update multiple times, and devices only update if they need to. (Assuming you set the version in the binary correctly.)

Upvotes: 2

jokn
jokn

Reputation: 98

I don't really understand why you don't want to use the USB booter. Or is it more that you want to know how it works. The USB bootloader is not located in the flash but in the ROM memory and can therefore easily flash the area from 0x8000.0000.

Using a bootloader in RAM is not a good idea because you will have to erase the flash and then reprogram it. Remember that if there is a power failure after erasing the flash, you will not have any firmware or your bootloader in RAM.

Maybe your problem is that you don't want to use a boot switch on your controller board. In this case it would also be possible to call the USB loader from your application. This only works if at least the first 4 bytes in the flash are deleted. The USB loader checks these 4 bytes and jumps to the application if it finds a valid vector.

Maybe this article has some tips for you:
https://community.st.com/t5/stm32-mcus/how-to-jump-to-system-bootloader-from-application-code-on-stm32/ta-p/49424

Upvotes: 0

Related Questions