bluepill-from-scratch: 00-setup
00-setup
tl;dr
full source here.
this repository uses the STM32F103C8T6 “Blue Pill”, but you can use any STM32F103 board.
the following programs are all IDE-agnostic. all you need is
arm-none-eabi-gcc and a way to flash/debug the target
(j-link, openocd, st-link, etc.)
personally, i used vscode with the Cortex-Debug
extension and a J-Link EDU Mini. everything works out of the box with
the provided .vscode folder should you wish to go that
route.
this is the first of the write-ups, introducing the hardware, and detailing how to compile/flash our first program.
from scratch:
hardware
We will be using the ubiquitous “Blue Pill” dev board, which is
essentially a breakout board for the STM32F103C8T6
microcontroller. That is, it simply “breaks out” all of the tiny pins of
the microcontroller into larger standard-pitch pin headers for easy
connections. A microcontroller (MCU) is an entire mini-computer
contained in a single chip, in that it not only contains a CPU for
processing, but also numerous “peripherals” for communicating with the
outside world, including UART, I2C, SPI, GPIOs, USB, etc. It also
contains volatile memory (RAM) for storing dynamic values like the
global variables, stack, and heap of a program, and usually non-volatile
memory (FLASH) for storing the actual program code and constant
data.
Microcontrollers like the STM32F103C8T6 are generally
designed to be low-cost, low-power, and space-efficient. Having a
tiny/slow CPU and as little RAM and FLASH as possible greatly benefits
all three of those categories. And so, the STM32F103C8T6
only has a 72MHz CPU, 20KB of RAM and 64KB of FLASH, far too little to
run even the most lightweight Linux-based OS you can think of. This
means we have two options: use a Real-Time Operating System (RTOS) which
provides us with OS features like task scheduling, synchronization
primitives, etc. while being fast enough to respond to events in
“real-time”, or, go the “bare-metal” route, and use loops and interrupts
to accomplish whatever we need.
“bare-metal”?
Bare-metal is an ambiguous term, the most official/common definition is probably just “No-OS”, though it might also refer to particularly “direct” hardware access. The following programs are bare-metal by both definitions of the term, so I’ll just use it ambiguously.
why bare-metal?
I figured that starting out the bare-metal route would later allow me to better appreciate the use of RTOS’s and/or hardware abstraction layers. Plus, my end-goal project as I was starting out was a simple USB mouse. Does a mouse really need its own operating system?
We could have saved time using pre-existing libraries, HALs, or RTOS’s. In our approach though, nothing is left to the imagination, everything is done from scratch, and I found that really fun and interesting.
Unfortunately it isn’t enough to just plug the bluepill into your PC through USB and start flashing and debugging your own programs – that would require an “on-board debugger”, which the bluepill lacks. So, you’ll also need a debug probe, which is just another microcontroller that bridges between the target (the bluepill) via the debug pins (SWD/JTAG), and the host (your PC) via USB. I used the J-Link EDU Mini, which can be found on mouser/digikey for $60, and sometimes cheaper on eBay.
You can of course use any debug probe and software combination you want, but I can only go into the setup details of what I personally used.
If you use the J-Link EDU Mini, you’ll also need a breakout board for the 2x5 1.27mm IDC cable in order to connect it to the bluepill’s 2.54mm SWD pins.
The power also needs to be provided externally, either through USB or a 5v/3.3v pin on the pin header.
software
Now we need a way to compile our code into a binary suitable for the
ARM Cortex-M3 CPU found in our F103. This
means we need a cross compiler. We’ll use the tried and true
arm-none-eabi-gcc. We have arm, because our
target uses an ARM core, none, because our target has no
OS, and eabi because it complies with the
Embedded Application Binary Interface, which is just some
set of standards for object files targeting embedded systems.
pacman -S arm-none-eabi-gccWe’ll also need the host-side software for the J-Link debug probe,
yay -S jlink-software-and-documentationand with just two packages, we can now simply compile,
arm-none-eabi-gcc -mcpu=cortex-m3 -nostdlib -ffreestanding -g3 -Og -T link.ld blink.c -o blink.elfand flash:
$ JLinkExe -device STM32F103C8 -if SWD -speed 4000
J-Link>connect
J-Link>reset
J-Link>loadfile blink.elf
J-Link>reset
J-Link>exit[ add bluepill blinking GIF ]
If we want to debug, we simply install gdb for
arm-none-eabi targets,
pacman -S arm-none-eabi-gdbstart JLinkGDBServer,
$ JLinkGDBServer -device STM32F103C8 -if SWD -speed 4000 -port 2331
Connected to target
Waiting for GDB connection...and in a separate terminal, connect to the GDB server which was opened on port 2331 and start debugging.
$ arm-none-eabi-gdb blink.elf
(gdb) target remote localhost:2331
Remote debugging using localhost:2331
0x08000032 in delay (n=1000) at blink.c:10
10 for (uint32_t j = 0; j < n; j++) {
(gdb) mon reset
Resetting target
(gdb) load
Loading section .isr_vector, size 0x10 lma 0x8000000
Loading section .text, size 0x114 lma 0x8000010
Start address 0x080000b0, load size 292
Transfer rate: 142 KB/sec, 146 bytes/write.
(gdb) mon reset
Resetting target
(gdb) b main
Breakpoint 1 at 0x8000054: file blink.c, line 18.
(gdb) continue
Continuing.
Breakpoint 1, main () at blink.c:18
18 RCC_APB2ENR = RCC_APB2ENR | (1<<4);In the above example,
mon resetalso halts the CPU, allowing us to stop atmain.
We send special J-Link-specific commands through to the GDB server by
prepending them with monitor/mon, e.g.,
mon reset to reset the target. The full list of
mon commands is available on this page.
That’s all there is to it, we’ve flashed and debugged an MCU with no IDE.
I guess doing all that manually every time is a bit of work, and debugging through the GDB CLI is a little archaic, which is why we’ll use Makefiles and attach the J-Link GDB server to a graphical debugger.
If you’re interested in the Visual Studio Code + Cortex-Debug route, see the following tab:
vscode
Simply install vscode and the C/C++ and Cortex-Debug extensions.
There are three related extensions which Cortex-Debug relies on as front-ends. VScode hopefully prompts you to install these. If not, search for and install Peripheral Viewer, MemoryView, and debug-tracker-vscode.
Clone this
bluepill-from-scratchrepository and open it with vscode, or just copy the.vscodefolder over to your own folder and do your own thing.Finally, you can edit the contents of the
.vscodefolder to reflect your own setup. For example, you might modifylaunch.jsonby settingservertypetoopenocd, or choose a different target withdevice. There’s a CMSIS SVD file for the STM32F103 included in the repo, which you might replace with any other device’s SVD. The SVD provides bitfield definitions for every peripheral register, which lets you see their contents in real-time as you debug.You should also edit
.vscode/c_cpp_properties.jsonto reflect whichever program you are currently working on as you go, so that intellisense can keep up."includePath": [ "${workspaceFolder}/01-blink/**" ],That’s it!
You can now just press
Run and Debugon a program’s main source file and vscode will build, flash, and debug the program.
Don’t worry about any of the code or commands used, the next programs will explain everything in detail.



