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!
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.