For the on­go­ing pro­ject to make my­self a cus­tom mech­an­ical key­board, with an in­ten­tion to get more fa­mil­iar with em­bed­ded pro­gram­ming and sol­der­ing, I bought some blue pills (ST­M32F103C8T6 based mi­cro­con­trol­ler board­s). At un­der €1.50 each these ex­tremely pop­u­lar mi­cro­con­trol­lers seemed like a great enough deal to get 5 of them at once.

The in­ten­tion is to even­tu­ally have a cheap Rust-­based key­board hard­ware-firm­ware combo for cus­tom key­board makers. Cur­rent status quo is Teensy, which will set you back for €13.5 plus ship­ping. For a less cap­able AVR board. Seems a shame to spend so much for what is tech­nic­ally less cap­able hard­ware. What Teensy has and the Blue Pill has­n’t, however, is the con­veni­ence and tools.

Most not­ably, a Blue Pill mi­cro­con­trol­ler does­n’t come abil­ity to be pro­grammed via USB by de­fault. It is ne­ces­sary to pro­gram these via a Serial or JTAG SWD in­ter­face in­stead. Do­ing either re­quires ex­tra hard­ware. While some re­cent PC mother­boards still in­clude a serial port, laptops cer­tainly won’t have one; so a USB to USART con­verter may be ne­ces­sary.

JTAG SWD is a much more con­veni­ent in­ter­face com­pared to Serial – it has ex­tra cap­ab­il­it­ies, such as be­ing able to de­bug a con­trol­ler as it is run­ning. Alas, not un­like Seri­al, a “dongle” of sorts is ne­ces­sary – for STM32 in par­tic­u­lar the keyword is ST-LINK – and these go any­where from €1.7 for Chinese-made dongles to €60 for a of­fi­cial ST-LINK with di­gital isol­a­tion.

Turns out it is pos­sible to con­vert one of those Blue Pills into a ST-LINK too. Ideal op­tion for me, as I hap­pen to have mul­tiple of these mi­cro­con­trol­lers already. Ex­cept… to make a Blue Pill into a ST-LINK, it is ne­ces­sary to flash it, which gets us back to square one…

For the tech­nique de­scribed in the post, the ne­ces­sary hard­ware is:


The soft­ware I used for the JTAG SWD de­bug­ger is called Black Ma­gic Probe. Be­cause of its sim­pli­city I ended up choos­ing stm32­loader to do the flash­ing over the UART in­ter­face. Fi­nally, a tool­chain (gcc, gdb1, …) for arm-none-e­abi tar­get is ne­ces­sary. In­stall the com­piler and clone the pro­jects:

arm-none-eabi-gcc --version # should print something
git clone
git clone

The usual build of Black Ma­gic Probe con­sists of two bin­ar­ies, which I could­n’t eas­ily pro­gram with stm32­load­er, so some ex­tra pre­par­a­tion was ne­ces­sary after the build:

cd blackmagic
make -j PROBE_HOST=stlink
cp src/blackmagic_dfu.bin ./
truncate --size=8192 blackmagic_dfu.bin
cat blackmagic_dfu.bin src/blackmagic.bin > blackmagic_full.bin

This res­ults in a single bin­ary firm­ware file blackmagic_full.bin which can then be eas­ily flashed into the Probe-to-be.

Flash­ing the Black Ma­gic Probe

Now that the Black Ma­gic Probe firm­ware is ready, it is time to con­nect to the Blue Pill and flash it! I’m us­ing a CH340G-­based US­B-to-R­S232 con­verter for my Serial com­mu­nic­a­tion needs. Mak­ing sure no com­pon­ents are con­nec­ted to any­thing else, this is how I con­nec­ted the con­verter to the Blue Pill.

Pin cor­res­pond­ence between a US­B-to-R­S232 con­verter and a Blue Pill for pro­gram­ming via Seri­al.
Dongle Blue Pill
5V or 3V3 5V or 3.3

Then, re­place the BOOT0 jumper so it is ad­ja­cent to the “1” mark­ing (see the im­age above) and plug the con­verter into a com­puter. Verify that the OS can see the con­verter; Flash the firm­ware pre­pared in the pre­vi­ous sec­tion:

$ dmesg
[...] usb 3-1: new full-speed USB device number 4 using xhci_hcd
[...] usbcore: registered new interface driver usbserial
[...] usbcore: registered new interface driver usbserial_generic
[...] usbserial: USB Serial support registered for generic
[...] usbcore: registered new interface driver ch341
[...] usbserial: USB Serial support registered for ch341-uart
[...] ch341 3-1:1.0: ch341-uart converter detected
[...] usb 3-1: ch341-uart converter now attached to ttyUSB0
$ sudo python2 -p /dev/ttyUSB0
Bootloader version 22
Chip id: 0x410 (STM32 Medium-density)
$ sudo python2 -p /dev/ttyUSB0 -e -w -v blackmagic_full.bin

Note that ttyUSB0 part of the com­mand above might dif­fer de­pend­ing on your sys­tem. Once the last com­mand shows signs of suc­cess, the con­nec­ted mi­cro­con­trol­ler is a full fledged Black Ma­gic Probe. Dis­con­nect the newly min­ted Black Ma­gic Probe from the con­verter and re­set the BOOT0 jumper to its ori­ginal po­s­i­tion (so it is ad­ja­cent to “0” mark­ing). The con­verter won’t be ne­ces­sary any­more, un­less you want to make an­other probe, or some­thing goes wrong and you need to (re-)­flash your cur­rent one. Verify that the Black Ma­gic Probe is work­ing cor­rectly by con­nect­ing it to the com­puter via USB cable and check­ing the OS logs:

$ dmesg
[...] usb 3-1: new full-speed USB device number 5 using xhci_hcd
[...] cdc_acm 3-1:1.0: ttyACM0: USB ACM device
[...] cdc_acm 3-1:1.2: ttyACM1: USB ACM device
[...] usbcore: registered new interface driver cdc_acm
[...] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters

Us­ing the Black Ma­gic

With a work­ing Black Ma­gic Probe in hand, we can now get in­volved in the ac­tual de­vel­op­ment of firm­ware for Blue Pills. Con­nect the Probe with an­other Blue Pill mi­cro­con­trol­ler like so:

Pin cor­res­pond­ence between a Probe and the tar­get for de­vel­op­ment via JTAG SWD.
Probe Tar­get
TOP: Probe; BOT­TOM: Tar­get. La­bels for SWD pins are on the other side of the board!

I re­com­mend hav­ing BOOT0 set to “1” on the tar­get for de­vel­op­ment pur­poses (as shown in the pic­ture above). In case of bug in the tar­get firm­ware, it makes re­turn­ing to a work­ing and re­spons­ive state as easy as click­ing the RST but­ton.

With everything con­nec­ted to­geth­er, use gdb to flash and run some firm­ware on the tar­get:

arm-none-eabi-gdb -ex "target extended-remote /dev/ttyACM0" \
-ex "monitor swdp_scan" \
-ex "attach 1" \
-ex "load target/thumbv7m-none-eabi/release/firmware" \
-ex "file target/thumbv7m-none-eabi/release/firmware" \
-ex "compare-sections" \
-ex "c"

Al­tern­at­ively, you can main­tain a .gdbinit script which would ex­ecute these com­mands dur­ing gdb start-up. This be­ing GDB the full power of the de­bug­ger is avail­able. Break­points, dis­as­sem­bler, step­ping, data watch­ers and so on.

Hav­ing spent some time us­ing these probes for de­vel­op­ment, I really ap­pre­ci­ate be­ing able to use full power of GDB to fig­ure out prob­lems in my code. At a low price of pos­sibly the cheapest ARM mi­cro­con­trol­ler and 10 minutes needed to flash the probe, this makes em­bed­ded de­vel­op­ment very ap­proach­able.

  1. clang and lldb could also be used, but us­ing them is out of scope for this post.↩︎