The “Blue Pill” is a small development board with an STM32F103 32-bit ARM µC on it, plus essential supporting logic. It’s widely available and very low-cost. See this wiki page on the STM32duino website for a wealth of useful information.

The tricky part is getting over the initial hurdle of uploading firmware to this little board. There are two ways to do it, but both of them require some care and tinkering:

  1. place the board in “boot mode” and upload via the serial pins (PA9 + PA10)
  2. upload via SWD (PA13 + PA14), using the 4-pin header on the side of the board

The other decision is what development setup to use, and which framework. There are several - nowadays, I always use PlatformIO, in combination with my own JeeH library.

This article describes how to use a Black Magic Probe (BMP), which can handle both serial and SWD, to upload a little demo application. The demo will blink the on-board LED and periodically send out a message over the serial port. This makes it very effective as a first “bring-up” sanity check for any new board.

I’ll be using SWD to upload, by the way. It’s faster, and requires no boot jumper fiddling.

Hardware setup

Here is the breadboard configuration I’m using to try this all out:

The 6-pin header on the left has the following (“HyTiny style”) pinout:

# BMP Blue Pill Pin
1 +3.3V +3.3V +3.3V
3 TXD RX1 PA10

The 4-pin header on the Blue Pill has: +3.3V, SWDIO, SWCLK, GND. Pins 2 and 3 on the 6-pin header are soldered to wire jumpers, which cross over (!) to USART1’s RX/TX pins.

Software setup

The PlatformIO software is open source and is available for Windows, MacOS, and Linux. It can be installed either from inside the VSCode IDE, or as command-line tool. I prefer the command-line, and use the latter, i.e. a “pio” command in the shell and “vim” as editor.

It all really doesn’t matter that much, and comes down to the same whatever combination you choose. Just be sure to stick with it and become really familiar with your tools.

The next step is to set up a PlatformIO project. This is very simple, requiring just two files:

First, we start with an empty directory for this project:

mkdir bluepill-blink
cd bluepill-blink

The PIO configuration file is platformio.ini and contains the following:

platform = ststm32
board = bluepill_f103c8
framework = stm32cube
upload_protocol = blackmagic
upload_port = /dev/cu.usbmodemE0C2C5A7
lib_deps = jeeh

The application code is in main.cpp, which needs to be placed in a new src/ directory:

mkdir src

The code uses some JeeH library definitions, here is src/main.cpp in full:

#include <jee.h>

UartBufDev< PinA<9>, PinA<10> > console;

int printf(const char* fmt, ...) {
    va_list ap; va_start(ap, fmt); veprintf(console.putc, fmt, ap); va_end(ap);
    return 0;

PinC<13> led;

int main() {

    while (true) {
        printf("%d\n", ticks);
        led = 0;
        led = 1;

Compile and upload

This is where the fun starts. Now we can compile and upload the resulting code in one go. On the command line, the magic incantation is:

pio run -t upload

I use this command so often, that I’ve created a bash alias for it in my ~/.bashrc:

alias pru='pio run -t upload'

Here is a complete transcript of the compile-and-upload process:

$ pru
Processing bluepill (framework: stm32cube; platform: ststm32; board: bluepill_f103c8)
Verbose mode can be enabled via `-v, --verbose` option
PLATFORM: ST STM32 > BluePill F103C8
HARDWARE: STM32F103C8T6 72MHz 20KB RAM (64KB Flash)
DEBUG: CURRENT(stlink) EXTERNAL(blackmagic, jlink, stlink)
Warning! Cannot find a linker script for the required board! Firmware will be linked with a default linker script!
Library Dependency Finder ->
Collected 3 compatible libraries
Scanning dependencies...
Dependency Graph
|-- <JeeH> 1.5
Checking size .pioenvs/bluepill/firmware.elf
Memory Usage ->
DATA:    [          ]   4.2% (used 852 bytes from 20480 bytes)
PROGRAM: [          ]   2.3% (used 1476 bytes from 65536 bytes)
Configuring upload protocol...
AVAILABLE: blackmagic, dfu, jlink, mbed, stlink
CURRENT: upload_protocol = blackmagic
Looking for BlackMagic port...
Use manually specified: /dev/cu.usbmodemE0C2C5A7
Uploading .pioenvs/bluepill/firmware.elf
Target voltage: Not Implemented!
Available Targets:
No. Att Driver
1      STM32F1 medium density
0x080003d0 in enableSysTick(unsigned long)::{lambda()#1}::_FUN() ()
Loading section .isr_vector, size 0x10c lma 0x8000000
Loading section .text, size 0x5ac lma 0x800010c
Loading section .rodata, size 0x18 lma 0x80006b8
Loading section .init_array, size 0x8 lma 0x80006d0
Loading section .fini_array, size 0x4 lma 0x80006d8
Start address 0x80005a8, load size 1756
Transfer rate: 12 KB/sec, 292 bytes/write.
Section .isr_vector, range 0x8000000 -- 0x800010c: matched.
Section .text, range 0x800010c -- 0x80006b8: matched.
Section .rodata, range 0x80006b8 -- 0x80006d0: matched.
Section .init_array, range 0x80006d0 -- 0x80006d8: matched.
Section .fini_array, range 0x80006d8 -- 0x80006dc: matched.
Kill the program being debugged? (y or n) [answered Y; input not from terminal]
Target voltage: Not Implemented!
Available Targets:
No. Att Driver
1      STM32F1 medium density
========================== [SUCCESS] Took 1.22 seconds =========================

This may all seem a bit daunting, but the key points to take note of are:

  1. The LED is blinking - it actually works!
  2. Compiling and uploading is near-instant, this took just over one second.
  3. The code size for a simple app is only a few percent of the BP’s available memory.
  4. PlatformIO took care of downloading and compiling the JeeH library for us.
  5. In fact, PlatformIO took care of downloading all the toochains + frameworks as well.

Hold on a second …

The above is what works for me, which – howdy, Mr. Murphy! – will not necessarily be what works for you. Here are a few details to be aware of:

Serial port connection

Since the above demo code also sends out messages to the Blue Pill’s serial console, and since we went through the trouble of hooking that port up to the BMP as well, it should be possible to view this debug output stream.

The way to do this, is to either use PIO itself, or to start some other serial port terminal application. I will describe both approaches here:

PIO Device Monitor

To connect to the serial port, we need to know its name. This may vary over time, and is bound to depend on the Black Magic Probe adapter. In my case, right now, that serial port is called /dev/cu.usbmodem469, so then I can connect with:

$ pio device monitor --quiet -b 115200 -p /dev/cu.usbmodem469

The code was already running, but as you can see, the current millisecond tick count is sent every 500 ms, i.e. once in every LED blink cycle. Control-C ends the connection.


Another approach is to launch a terminal emulator. I like the simple-and-small picocom, which can be installed with one command (brew install picocom on MacOS, or apt install picocom on Linux). There are many others, e.g. TeraTerm for Windows.

Here is the same connection, using picocom:

$ picocom -q -b 115200 --imap lfcrlf /dev/cu.usbmodem469

Very similar results, as you can see. The --imap lfcrlf option is needed to properly display each line. The -q option suppresses a 25-line startup report with all the settings. In picocom, Control-A followed by Control-X terminates the connection.

And that is all there is to it. Let the real coding and tinkering begin!


For your convenience, a copy of the above demo project can be found here. It’s part of a new Retro project, for reasons which will become clear in the next episode.