Computing stuff tied to the physical world

Bootstrapping a boot loader

Let’s upload some code onto a new STM32 µC board (or one with unknown / incorrect code on it). As should be clear by now, there’s no other way with STM32F103 chips than to either use STM’s serial ROM boot, or SWD. Our blank chip is too dumb for anything else.

The purpose of this exercise is to prepare our board for use – not to go through these steps every time. Once done, it will be running the secondary boot loader we just installed on it. Since this boot loader in stored in flash memory, it will then run whenever the µC is reset.

If you think this is tedious: watch this 3-min video. It explains the steps needed, every time a PDP-8 computer is turned on! That’s right: toggling switches to enter the boot loader…

Serial upload

Here is an example using an FTDI interface, as commonly used with ATmega’s / Arduinos:

DSC 5230

It uses 4 pins, from top to bottom on the FTDI board: RX, TX, +5V, GND. Connect these to the target as needed (those boards all differ), but make sure to cross the RX and TX lines, i.e. what the FTDI sends must be received by the target, and vice versa.

Then, you need to put the target board into programming mode, i.e. have it start up from its internal ROM, running the serial boot loader. To do this, set jumpers on the target:

  • BOOT1 must be set to “0”, i.e. “pulled down” (this setting can be left permanently)
  • BOOT0 must be set to “1”, i.e. “pulled up”
  • finally, press the RESET button

Now you need a utility on the PC side. Here is an example using

$ -p /dev/tty.usbserial-A1014IN2 -ewv myboot.bin 
Reading data from myboot.bin
Bootloader version 0x22
Chip id 0x410, STM32F1, performance, medium-density
Writing 580 bytes to start address 0x8000000
Write 256 bytes at 0x8000000
Write 256 bytes at 0x8000100
Write 256 bytes at 0x8000200
Read 256 bytes at 0x8000000
Read 256 bytes at 0x8000100
Read 256 bytes at 0x8000200
Verification OK

This example uploads a small dummy file to keep the output short.

Last step: change the BOOT0 jumper back to “1”, and press RESET once more. That’s it!


With ST-Link, the steps look similar, although this approach relies on JTAG / SWD, and the code transfer mechanism will be completely different. Here’s an example setup:

DSC 5231

As it so happens, the pinout on this board turned out to be just right for the ST-Link clone used in this setup. Usually, you’ll need wires. The wires are, top to bottom: GND, SWCLK, SWDIO, +3.3V. Note that this example connects 3.3V, not 5V to the board’s regulator.

With SWD, we usually don’t have to adjust the BOOOT0/BOOT1 jumpers. The exception is if the currently running code disables SWD. In that case, again: set BOOT0 to “0”, BOOT1 to “1”, and press RESET. For SWD, we’ll need to use the st-flash utility from GitHub:

$ st-flash write myboot.bin 0x8000000
… Loading device parameters....
… Device connected is: F1 High-density device, id 0x10036414
… SRAM size: 0x10000 bytes (64 KiB), Flash: 0x40000 bytes
  (256 KiB) in pages of 2048 bytes
… Attempting to write 7036 (0x1b7c) bytes to stm32 address:
  134217728 (0x8000000)
Flash page at addr: 0x08001800 erased
… Finished erasing 4 pages of 2048 (0x800) bytes
… Starting Flash write for VL/F0/F3 core id
… Successfully loaded flash loader in sram
  3/3 pages written
… Starting verification of write complete
… Flash written and verified! jolly good!

Friendly chap, eh? As you can see, this is a somewhat larger µC variant of the STM32F103.

For other SWD-capable JTAG programmers, there’s usually a solution via openocd.

Black Magic Probe

With a BMP, we also use SWD to talk to the µC, very much like with an ST-Link, but with a different protocol. Here is an example setup, programming an Olimexino-STM32:

DSC 5232

This uses a 0.05″ header and 10-pin ribbon cable, as common with some of the newer ARM boards. The 4-pin serial port pass-through is not used here. Since the official BMP does not power the target board (it only measures its voltage), a second USB is needed for the +5V.

The same notes apply as for the ST-Link: usually, there is no need to adjust the BOOT0 / BOOT1 pins – unless the current running code disables the SWD interface. Note that another way to force control with SWD, is to keep the RESET button pressed during the whole session. SWD, and uploading will work in this mode – while the µC is kept in reset.

To upload using the BMP, we need to have the ARM build of gdb installed. Also, this works best not with a myboot.bin file but with the original myboot.elf file generated by gcc:

$ arm-none-eabi-gdb myboot.elf
GNU gdb (GNU Tools for ARM [...]
Reading symbols from myboot.elf...done.
(gdb) tar ext /dev/cu.usbmodemDDE7C6C1
Remote debugging using /dev/cu.usbmodemDDE7C6C1
(gdb) mon swdp
Target voltage: 3.3V
Available Targets:
No. Att Driver
 1      STM32F1 medium density
(gdb) at 1
Attaching to program: /Users/jcw/myboot/myboot.elf, Remote target
0x1ffff3b6 in ?? ()
(gdb) load
Loading section .text, size 0x19d8 lma 0x8000000
Loading section .data, size 0x14 lma 0x80019d8
Start address 0x80016ac, load size 6636
Transfer rate: 14 KB/sec, 829 bytes/write.
(gdb) det
Detaching from program: /Users/jcw/myboot/myboot.elf, Remote target
(gdb) q

Tip: you can use this conversion trick to convert a .bin to a .elf file which gdb will accept.


MBED enabled boards have an ST-Link built-in – use the same code as mentioned earlier.

But it’s also possible to re-use these boards as ST-Link programmer for external targets. Here’s the top section of a Nucleo board, cut off to act as stand-alone ST-Link (or leave it on, if you prefer). This also supports drag-and drop uploads, since it’s an ST-Link V2.1:

DSC 5206

What you see here is an extra board on top, with a header matching the HY-TinySTM103T board. Doing this for boards which you intend to use a lot is highly recommended – it’s just one less detail to keep checking whenever you grab this tool to fix a broken setup.

Poor-man’s boot loader upload

Note that to get something loaded onto a blank STM32 µC, you always need something like one of the setups described so far. It’s the chicken vs the egg problem in its full glory.

But what if you don’t have any of the above, properly working and ready to go?

Well, there is also another option if you have a working Arduino, JeeNode, or other IDE-compatible board. You can turn one of those boards into a temporary STM32 uploader!

Here’s an example of an old Arduino Diecimila programming a blank HY-TinySTM103T:

DSC 5221

A 2005-era board updating one from 2015 – how’s that for a “flash from the past”, eh?

The basic idea is to run stm32f1init on that Arduino. It knows about STM’s serial upload protocol, and contains a complete copy of the boot loader to send to the ARM µC. Check the source code for more information. An example of the output after a successful run:

  Connecting? ..... OK
Boot version: 0x22
   Chip type: 0x410
Unprotecting: OK
    Resuming? ... OK
     Erasing: OK
     Writing: +++++++++++++++++++++++++++ OK
        Done: 6661 bytes uploaded.

Via OpenOCD

With openocd, there are many other ways to set up a JTAG / SWD programmer. For an interesting approach, see this page, which uses 6 pins on a Raspberry Pi’s (or compatible) expansion header. The gdb debugger can then connect to it via the network. Neat, huh?

Or, the more traditional approach: build OpenOCD on the Raspberry Pi and plug in one of the many supported ready-made JTAG adapters via USB. Again, reachable via the network.

This concludes another essential summary of things needed to get going with STM32 µCs. We still need to decide what boot loader to install using any of the above methods, but that’s beyond the scope of this article. The key is that with a boot loader of our choosing installed, we can finally start to use these STM32 µC boards.

[Back to article index]