Coding the STM32 Blue Pill with Rust and Visual Studio Code
[UPDATE — I have written a new tutorial on STM32 Blue Pill coding with C++, libopencm3 and Visual Studio Code, click here to read]
But many of the products there look very similar to this blue gadget…
The USB standard requires a 1.5 kΩ pullup resistor on D+, but this board is known to have a wrong value (R10 on the…wiki.stm32duino.com
You have discovered the viral ultra-cheap (US$ 2) gadget that’s quickly spreading around the world: the STM32 Blue Pill (flashback to The Matrix). The microcontroller development board runs on an ARM Cortex-M3 32-bit processor, a close cousin of the processors embedded in our phones, tablets, TVs and many other office and household gadgets.
In spite of its viral popularity, the Blue Pill seems rather difficult to program for hardware novices like me. In this article I have consolidated the best tools and techniques that others have used to program this board. To make the Blue Pill even more accessible, I have weaved the command-line tools into the friendly Visual Studio Code development environment.
But slapping a modern friendly façade on top of archaic tedious C++ code sounds so wrong. So I’m using Rust instead — a modern systems programming language that promotes safe, concurrent low-level coding. All the code in this article may be found in GitHub:
👜 Getting the Blue Pill and the ST-Link USB Debugger
If you’re doing development for the Blue Pill, you need to get the ST-Link V2 USB Debugger. The ST-Link Debugger connects your computer (via USB) to the Blue Pill’s debugging port. In this article we’ll be using the ST-Link to load our compiled program into the Blue Pill and to debug the program.
I bought the Blue Pill and the ST-Link Debugger from Lazada (I chose Hong Kong merchants rather than China for quicker delivery). You should be able to find similar products on Asian shopping sites like AliExpress. Just search for the keywords:
- stm32f103c8t6 development board
- st-link v2
🔌 Connecting the Blue Pill to the ST-Link USB Debugger
The Blue Pill has many, many ports for input and output: GPIO, SPI, I2C, USART, CAN, USB, …
For the sample program in this article, we don’t need to solder the headers onto the Blue Pill to access the ports. However we need to connect the Blue Pill to the ST-Link USB Debugger using the provided jumper cables:
Note: For the ST-Link, only the bottom row of pins is used. The Micro USB Port of the Blue Pill doesn’t need power — the program in this article runs fine on the Blue Pill powered by the ST-Link.
Ensure that both yellow jumpers on the Blue Pill (BOOT0 and BOOT1 near the Micro USB Port) are set to the “0” Positions (closer to the Micro USB Port). This configures the Blue Pill to operate in Flash Mode so that it’s ready to receive and run new programs.
Don’t connect the ST-Link to your computer yet, we need to install the Windows drivers for ST-Link, as described below.
📂 Installing The Command-Line Tools
We’ll now install the Blue Pill and Rust command-line tools that were recommended by Jorge Aparicio in his Discovery course for embedded Rust and his Cortex-M Quickstart guide. We’ll also be using Jorge’s HAL (Hardware Abstraction Layer) for Blue Pill. Nerijus Arlauskas’s Embedded Rust blog was very helpful for filling in the details.
The installation instructions in this article are for Windows only. For Ubuntu, refer to this article’s GitHub repository.
- Install ARM Cross-Compiler and Linker from the
► ARM Developer Website. Look for:
Windows 32-bit File: gcc-arm-none-eabi-…-win32.exe
- Select this option at the last install step:
"Add path to environment variable"
- In Windows Explorer, browse to
C:\Program Files (x86)\GNU Tools Arm Embedded\7 2018-q2-update\bin
"7 2018-q2-update"part may be different for your installation
- Copy the file
"ar.exe"workaround is temporary until we find a fix for the Windows Rust build
- Open a new Windows command prompt (not Windows Bash) and enter
- You should see something like
"version 5.4.1 20160919 (release)"
If there are no errors, close the command prompt.
If there is an error, update your
PATHenvironment variable so that it includes the folder for the ARM
- Download OpenOCD from the
► Unofficial OpenOCD release website. Look for:
- Unzip the downloaded file and copy the OpenOCD files into
openocd.exeis located in the folder
- Download the ST-Link USB driver from the
► ST-Link Driver Website (email registration required)
- Unzip the downloaded file. Double-click the driver installer:
- Install Build Tools for Visual Studio 2017 from the
► Visual Studio Downloads Website
"Visual C++ Build Tools"
Warning: The download is 1.1 GB and you need 4.8 GB of free disk space.
rustup(the Rust toolchain installer) from the
► Rustup Website
- If you see a message about
"Windows Defender SmartScreen",
Select the default installation option for
- Switch to the Nightly Rust Toolchain (instead of Stable or Beta):
Open a new Windows command prompt (not Windows Bash) and enter:
rustup default nightly
- Install the
thumbv7-none-eabiRust component to cross-compile Rust programs for ARM Cortex-M3:
rustup target add thumbv7m-none-eabi
- Download the
stm32-blue-pill-rustsource files for this article:
(You may install
gitfrom Git For Windows)
git clone https://github.com/lupyuen/stm32-blue-pill-rust.git
What have we just installed?
- ARM Cross-Compiler and Linker lets us compile programs for the Blue Pill’s ARM Cortex-M3 processor while running on Windows. Also includes the GDB debugger.
- OpenOCD (Open On-Chip Debugger) is the software that transfers compiled programs to the Blue Pill (via the ST-Link Debugger). OpenOCD also enables debugging of programs by Blue Pill. The GDB debugger sends commands to OpenOCD (via a local TCP socket) to control the Blue Pill during debugging.
- ST-Link USB Driver is needed for OpenOCD to communicate with the ST-Link Debugger via the USB port on Windows.
- rustup is the universal installer for Rust components (similar to npm for Node.js). Instead of the Stable Build, we use the Nightly Build of the Rust compiler and libraries because we are using some experimental features.
- We have configured the Rust compiler to generate machine code for the thumbv7m-none-eabi target, i.e. Blue Pill’s ARM Cortex-M3 processor. The Rust compiler calls the ARM Cross-Compiler to generate ARM machine code.
- stm32-blue-pill-rust contains a sample Rust program for the Blue Pill. It also includes Visual Studio Code settings for building and debugging Rust programs with the Rust compiler, ARM Cross-Compiler, GDB and OpenOCD.
📂 Installing Visual Studio Code
The command-line tools above work great for compiling Rust programs for the Blue Pill. But embedded developers today are already using graphical development environments. And new developers would quickly get lost at the command line. So kindly follow my instructions to hide the command-line complexity behind the friendly Visual Studio Code development tool:
- Download and install Visual Studio Code from
- Launch Visual Studio Code and install the following extensions (just click the links below followed by the “Install” button and
“Open Visual Studio Code”):
- Better TOML (bungcip)
- C/C++ (Microsoft)
- Native Debug (WebFreak)
- Rust (kalitaalexey)
- Rust (rls) (rust-lang)
- In Visual Studio Code, click “Install” when prompted to install the above extensions
- Restart Visual Studio Code
- Click File → Open Workspace
- Browse to the “stm32-blue-pill-rust” folder.
- In the Explorer → Workspace pane at left, browse to the source folder “src” and double-click the Rust source file “main.rs”
When prompted, install the Rust Language Service (RLS), which provides Autocomplete and “Go To Definition” features for Rust.
We have now installed everything we need to write, compile, load and debug Rust programs for the Blue Pill. Without using the command line! Let’s try out the graphical tools now…
🔨 Compiling the Rust program in Visual Studio Code
Look closely at the lower right corner of the Blue Pill. There’s an LED marked PC13.
PC13 happens to be a typical GPIO (General Purpose Input / Output) Pin for the Blue Pill — just that it’s also connected to an LED on the board.
We’ll examine a Rust program that blinks the PC13 LED every second, by alternating the voltage on Pin PC13 between 3.3 volts and 0 volts.
In Visual Studio Code, browse to the source folder “src” and double-click the Rust source file “main.rs”
The main body of the code prepares Pin PC13 to be used for GPIO.
After that, the program loops forever, setting the pin to 3.3 volts and then to 0 volts and back to 3.3 volts again (with one-second delays in between).
Let’s compile this Rust program into ARM machine code that the Blue Pill can understand.
In Visual Studio Code, click Tasks → Run Build Task.
Wait a while for the Rust program to be compiled.
Check the log in the Terminal window at the bottom of the Visual Studio Code screen.
When you see “Finished released [optimized + debuginfo] target(s)”, that means the Rust program has been compiled successfully.
We’ll proceed to the next step to run the program.
But if you see an error like this, you’ll have to fix the error and recompile the program.
Just mouse over the filename and line number in the log, and press Ctrl-click to jump to the offending line of code.
🔗 Running the Rust program in Visual Studio Code
Now we may connect the ST-Link to the USB port, as shown above.
The ST-Link device needs a software tool called OpenOCD (On-Chip Debugger) to transfer programs from your computer to the Blue Pill. Let’s run it…
Click Tasks → Run Task
Select “🔗 Connect To STM32 Blue Pill”
Check the messages from OpenOCD in the Terminal window at the bottom of Visual Studio Code.
When you see “Listening on port 3333 for gdb connections”, our program is ready to be started on the Blue Pill.
Click Debug → Start Debugging.
The GDB debugger sends the compiled program to OpenOCD, which connects via ST-Link to transfer the program to the Blue Pill. And the program begins running on the Blue Pill. Keep the “Connect To STM32 Blue Pill” (OpenOCD) session running when debugging our program.
🐛 Debugging the Rust program in Visual Studio Code
When our Rust program starts, the debugger pauses at the first line of code in our program. Now we may choose to step into each line of code (and into the functions they call), or step over each line of code, or let the program continue running.
The Debugger Toolbar at the top right lets us choose what to do next. For more details, check the Visual Studio Code documentation.
At the left of the screen we may inspect the state of the running program:
Variables shows the values of Rust variables that are currently in scope.
Watch lets us key in a Rust variable or expression that will be displayed whenever the debugger pauses.
Call Stack shows how we got to this state, through nested Rust function calls.
Recall that the program is supposed to show “Hello, world!” when it starts. You can find debug messages like these in the Terminal window at the bottom. Make sure that the box at the right shows “Task — Connect to STM32 Blue Pill”.
Click the Continue button in the Debugger Toolbar (i.e.the first button) and let the program run forever. Check out our Blue Pill — the green LED on PC13 is now blinking, just as we had expected!
🔍 Understanding the Rust program
The Rust program we just ran was derived from the sample program created by Jorge Aparicio. I’m still new to Rust programming, but here’s what I observed:
So Much Unwrapping (like Christmas): Rust is a safe language, so it’s natural to use layers of protection to wrap and unwrap our precious objects in Blue Pill’s memory. The unwrap() function checks to ensure we’re not trying to unwrap an empty object — equivalent to dereferencing a null pointer in C.
On some platforms, C will happily allow us to dereference null pointers and other invalid pointers — until our C program crashes. Rust stops us immediately when we’re doing shady business with our objects. Rust even refuses to compile programs with bad intentions. FYI: Jorge explains the
take(), freeze() and
constrain() concepts in this article.
STM32 Programming Is Highly Complex:Coming from the sugar-coated Arduino world, I find the sample program needlessly complicated. Just to flip the output level of a GPIO Pin, we had to declare the push/pull mode, select the GPIO port, enable the APB2 clock, which we fetched from the RCC registers. And why is flash memory relevant to our delay timer?
But compare the Rust code to a the equivalent code in C. I might have to teach Blue Pill programming to my IoT students at Temasek Polytechnic since it’s so viral — I’ll definitely choose the Rust version because I can actually read the code aloud and make some sense (unlike the C gibberish).
🔮 What’s Next?
Concurrency, of course. It’s scary to see my students stack up so many sensors into a single Arduino board — and expect everything to work! Jorge Aparicio has created an ingenious Real Time For the Masses (RTFM) framework for Rust that juggles multiple concurrent tasks with different priorities in real time.
The RTFM framework even simplifies the sample Rust code that we have been running. It uses Rust Macros to eliminate the redundant coding and it wraps everything neatly into either Resources or Tasks. Check out this sleek RTFM program that’s equivalent to our sample Rust program. And see how it scales up gracefully to handle concurrent tasks with different priorities.
So now that we have…
- A development board that’s ultra-affordable, feature-packed and yet realistically complex
— STM32 Blue Pill
- Modern tools and techniques for coding safe, concurrent programs
— Rust and RTFM
- A friendly development environment for embedded coding and debugging
— Visual Studio Code
… Perhaps it’s time to migrate my IoT students away from legacy Arduino and C++?
I have written a new article on STM32 Blue Pill programming with C++, libopencm3 and Visual Studio Code (PlatformIO). Read it here…
► Check this GitHub repository for the code in this article and for updates…