The year is 2029. At Moon Base One, two Moon Base Operators nearly wiped out their entire crop of beautiful red tomatoes… All because of a firmware bug in their IoT monitoring device that slipped past Embedded Unit Testing.
Operator 1: Are we all set to flash our STM32-BLUEST-PILL monitoring devices with the new firmware? The one that passed Embedded Unit Testing?
Operator 2: Yes! Lemme launch MakeCode version 2029… Hitting the DOWNLOAD button… Now regenerating the new UF2 firmware image… Transmitting firmware image to all STM32-BLUEST-PILL Bootloaders… And Firmware version 2029 has been flashed to all 1,000 devices! Wait we got 1,000 errors… “Critical system library libopencm3 version 2029 not found”…
Operator 1: WHAT’S HAPPENING TO OUR SENSORS ??? OUR IOT DASHBOARDS ALL TURNED BLANK !!! We’re running Bootloader version 2028, which contains libopencm3 version 2028! We don’t use version 2029 here!
Operator 2: So we just upgrade the Bootloader to version 2029. Piece of cake right? Oh wait… There’s no button for MakeCode Bootloader Upgrade. We haven’t implemented the Bootloader Upgrade yet. How often do we upgrade the Bootloader right? Heh heh…
Operator 1: The sprinklers have stopped !!! Our juicy tomatoes are drying up and turning into… SUN-DRIED TOMATOES !!! NOOOOOOO…
In the previous article we learned how our MakeCode Bootloader flashed Applications to Blue Pill via WebUSB. The chronicles of Moon Base One have taught us that we need a way to upgrade the Bootloader as well.
But how do we upgrade the Bootloader when it’s always running in the background, waiting for flashing requests? This article explains a special technique I used to upgrade the MakeCode Bootloader over WebUSB… I call it “Baseloading”. We’ll learn…
- How to write to Blue Pill’s flash memory without calling any libraries
- How we used this method of flashing to update the Bootloader code
- How to use Backup Registers to remember the state of the Blue Pill after restarting
Flashing with Zero Dependencies
Baseloader code for flashing the Bootloader. From https://github.com/lupyuen/codal-libopencm3/blob/master/stm32/baseloader/baseloader.c#L256-L362
Looks like typical Blue Pill
baseloader_start() calls some functions to lock, unlock, erase and update Blue Pill’s flash memory. Now look at the Arm Cortex-M3 assembly code
that the C compiler generated for
Compiled Baseloader code. From https://github.com/lupyuen/codal-libopencm3/blob/master/logs/firmware.dump#L875-L1650
Do you see the magic here?
There are no calls to other functions! It’s as though all the lock / unlock / erase / update functions are done within this
baseloader_start() has Zero
How is that possible? That’s because Blue Pill uses Memory-Mapped I/O. By reading/writing some special memory location, we can control the Blue Pill peripherals, including the flash memory. The lock / unlock / erase / update functions above are actually C macros that read/write the special memory addresses that control the Blue Pill flash memory.
Also note that
baseloader_start() uses Relative
Addresses (instead of Absolute Addresses) when branching conditionally to other parts
of the function. Just look at the above branch instructions like
bls.n, b.n, bcs.n.
Zero Dependencies + Relative
Addressing… That means the code is perfect for Relocation anywhere in
Blue Pill memory! We can copy the
baseloader_start() function into
any chunk of Blue Pill memory and it will still work! Assuming of course we don’t mess around
with the global variables accessed by
That’s the key reason why our Bootloader code is able to update itself… It’s able to clone itself into a new chunk of ROM memory AND execute the cloned copy of Bootloader to update the old ROM copy of Bootloader!
From now on we’ll call this “code that updates the Bootloader” as the Baseloader.
Now if the Baseloader can’t fetch the new Bootloader code from the USB port (remember that the Baseloader only uses flash memory), how can the Baseloader update the Bootloader?
By carefully juggling flash memory, as we shall soon see…
Upgrading the Bootloader, step by step
How does our Blue Pill WebUSB Bootloader upgrade its own code? Let’s watch how it’s done with the Baseloader…
1️⃣ We have partitioned Blue Pill’s ROM with this layout: Bootloader in the lower ROM region, Application in the upper ROM region
4️⃣ Blue Pill Bootloader receives the new Bootloader code and writes temporarily into the old Application region
6️⃣ Baseloader moves the new Bootloader code into the Bootloader ROM region, overwriting the old Bootloader. The Bootloader is not running now, so it’s safe to overwrite.
7️⃣ The Baseloader code is
Relocatable, so it may be executed anywhere in
ROM. That’s why the Baseloader can run outside the Bootloader ROM region. Unlike the
Bootloader, which may only be executed at a fixed address in the Bootloader ROM region (
8️⃣ The Baseloader code is Relocatable because it doesn’t call any other functions (it uses only C macros that access I/O memory). And because it branches to other parts of its own code through Relative Addresses, not Absolute Addresses.
9️⃣ Baseloader reboots the Blue Pill and starts the new Bootloader
🔟 Bootloader continues to receive the firmware from MakeCode via USB. Bootloader writes the updated Application code into the Application ROM region, overwriting the old Application code.
After flashing, Bootloader reboots the Blue Pill and starts the new Application
As we can see, the Bootloader upgrade is made possible by the Baseloader, a special tiny program that has zero dependencies and performs only one job — overwrite the old Bootloader in ROM by the new Bootloader (also in ROM).
Once again, watch how the Bootloader works together with the Baseloader to update its own code…
Upgrading the WebUSB Bootloader
Remembering the Blue Pill state after restarting
In some of the above steps we had to restart Blue Pill in either the Bootloader, Baseloader or Application Modes. How does Blue Pill remember which mode it should switch to after restarting?
Before we answer that, here’s the libopencm3 function that we should call to restart the Blue Pill…
After restarting, all contents of the RAM are wiped out. ROM is non-volatile, so we could flash a value into ROM to indicate the restart mode. But on Blue Pill there’s an easier, safer way to remember the restart mode: Backup Registers.
The Blue Pill’s Backup Registers are ten 16-bit registers that we may use to store 20 bytes of data. The contents of the registers will be preserved across restarts, as long as there is power supplied to Blue Pill.
In the Bootloader code above we use
RTC_BKP_DR() to read and write the contents of the Backup
Registers. Note that the
RTC_BKP_DR() macro is called twice, so
that we can store a 32-bit value into two 16-bit registers.
Also note the calls to libopencm3
pwr_enable_backup_domain_write_protect(). The Backup Registers are
write-protected against any possible data corruption, in case the Blue Pill is restarted and the power
is unstable. We need to call
pwr_disable_backup_domain_write_protect() to disable the
write-protection before writing to the Backup Registers, and call
pwr_enable_backup_domain_write_protect() when we’re done.
So here’s what happens when our Blue Pill restarts…
1️⃣ Before restarting, the
Bootloader sets the restart mode to Bootloader, Baseloader or Application Mode by calling
These three functions store the restart mode into Backup Registers 0 and 1.
End of the Journey?
The WebUSB Bootloader for MakeCode on Blue Pill is incredibly complex — it required five articles (plus this article) to explain the topics that we have covered today…
1️⃣ We implemented the WebUSB and WinUSB interfaces on Blue Pill…
2️⃣ We applied memory optimisation tools and techniques to squeeze the Bootloader into Blue Pill…
3️⃣ We created nano-float, an optimised version of the standard math library…
4️⃣ And we verified the accuracy of the nano-float library through Unit Testing…
5️⃣ Finally we combined all the topics from the above articles into a complex Bootloader…
We have done so many incredible things with Blue Pill… Is there a limit? Nope we haven’t hit the limit yet. So why don’t we create better development tools to let everyone enjoy the power of Blue Pill?
A good Bootloader is essential for a great programming experience on Blue Pill, and we have come a long way since the first Blue Pill Bootloader…
1️⃣ Blue Pill Factory-Installed Bootloader: Needs the UART pins to be connected before flashing. No USB support.
2️⃣ Blue Pill Arduino Bootloader: Works with USB. Flashing works only with Arduino IDE client.
3️⃣ Blue Pill MakeCode Bootloader: Works with Chrome and other WebUSB-compatible web browsers. Allows Bootloader to be reflashed via WebUSB. Probably the best Blue Pill Bootloader to date!
It’s been a long journey — join me if you’re keen to complete the Blue Pill port of MakeCode! So that we’ll finally have the perfect platform to build and test IoT prototypes.
There are too many topics to cover, so I kept a log of all things that I have tried (even the failed ones)…
Look forward to having you onboard the Blue Pill MakeCode journey! :-)