Open-source project
stm32-rs/stm32f1xx-hal avatar
stm32-rs/stm32f1xx-hal

stm32f1xx-hal gives Rust a hardware abstraction layer for the STM32F1

A Rust embedded-hal HAL impl for the STM32F1 family based on japarics stm32f103xx-hal

664 stars192 forksRustApache-2.0

At a glance

What is it?
stm32f1xx-hal is a Rust embedded-hal implementation for the STM32F1 family, based on japaric's stm32f103xx-hal, where the microcontroller and its memory density are compile-time features and the documented workflow runs through rustup, openocd and gdb.
Who is it for?
stm32f1xx-hal fits a Rust developer who wants to work on an STM32F1 board with the embedded ecosystem's abstractions rather than direct register writes, and who is prepared to install a cross toolchain, a debug probe and the arm target. It is the wrong choice if your board is not an F1, and it is the wrong choice if you want a zero-setup workflow, because the linking, memory layout and debugger configuration are steps you own rather than defaults you inherit.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 88 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the crate is, and where it came from

stm32f1xx-hal is a HAL for the STM32F1 family of microcontrollers, and the project description is explicit about its lineage: it is an embedded-hal implementation based on japaric's stm32f103xx-hal. The crate is published on crates.io as stm32f1xx-hal, with API documentation on docs.rs, and the repository sits under the stm32-rs organisation, which is the Rust-embedded community's home for this family of vendor HALs.

What a HAL means here is the standard embedded-rust meaning. Instead of poking at memory-mapped registers yourself, you get typed peripherals and traits, and the traits are the embedded-hal ones, so a driver written against embedded-hal works on this chip and on any other chip with a HAL that implements the same trait. That is the whole argument for a HAL crate, and it is why the crate carries several embedded-hal versions as dependencies rather than just one.

The scope is deliberately one family. The supported microcontrollers are stm32f100, stm32f101, stm32f103, stm32f105 and stm32f107, and the crate is written in Rust for the Cortex-M targets the STM32F1 uses. The examples directory gives a sense of the breadth, with ADC, DAC, CAN, CRC, delay, dynamic GPIO, external interrupt, I2C scanner and BME280 examples, and blinky variants including ones built on the real time clock and on timers with interrupts.

The audience is therefore a specific person: someone with an F1 board, an ST-LINK or another debug probe, and a preference for Rust over a vendor SDK.

The device and the density are both compile-time features

The most important operational detail in the crate is that the target chip is not a runtime choice but a feature selection, and the density of the device is a second one.

The microcontroller is specified with a feature, for example stm32f103, and the README is blunt about what happens if you do not: if no microcontroller is specified, the crate will not compile. That is a deliberate design. The peripheral set and the register layout differ across the family, so the crate is generated per device rather than exposing a union of every peripheral.

The density feature enables certain peripherals, and it is specified as medium, high or xl. The README explains how to work it out: generally the density is determined by the second character after the number in the device name, and for an STM32F103C6U the 6 indicates a low-density device, though it says to check the datasheet or CubeMX to be sure. The mapping it gives is that 4 and 6 are low density and need no feature, 8 and B need medium, C, D and E need high, and F and G need xl.

For the connectivity line parts, stm32f105 and stm32f107, no density feature must be specified. That exception is easy to miss and produces a confusing error if you follow the general rule anyway.

The practical consequence is that a Cargo manifest has to name the device and the density, and getting the density wrong produces a build that succeeds but lacks the peripherals you expected. This is worth checking against the part number on the board before anything else.

The toolchain the guide assumes

The README is candid that embedded Rust development requires a bit more setup than ordinary development, and then walks through what it needs.

The board used in the guide is the stm32 blue pill, and the README says another F1 microcontroller should be adaptable to the same instructions. You also need a debug probe, and the example given is an ST-LINK V3 mini for programming and debugging. The guide notes that there are many different ST-LINK probes and that all of them should work with the instructions, and that other JTAG or SWD probes will work as well but will need different software or configuration.

On the software side, programming the microcontroller requires openocd and gdb-multiarch, with a note that on some platforms you may need gdb-arm-none-eabi instead and that .cargo/config must be updated to reflect the change. Finally the compiler needs arm target support, installed with:

bash
rustup target install thumbv7m-none-eabi

That last line is the whole of the Rust-side installation, and it is worth noticing what is absent. There is no vendor SDK to install, no device tree, no configuration tool, and no licence to obtain. A blue pill board and a cheap ST-LINK clone are enough, which is the main practical argument for the Rust embedded ecosystem on this class of board.

The crate's own metadata adds one more constraint. The package is edition 2021 with rust-version 1.95, so the toolchain has to be a recent one, and the dependencies, cortex-m at 0.7.7 and cortex-m-rt at 0.7.5, are current but not bleeding edge.

Linking and the memory layout you have to copy

After the project is created with cargo init, the README walks through the parts that differ from ordinary Rust development, and the first one is a link error that looks like a compiler bug.

The dependencies for a new project are a short list, with the HAL pinned and its features set:

toml
[dependencies]
embedded-hal = "1.0"
nb = "1"
cortex-m = "0.7.7"
cortex-m-rt = "0.7.5"
# Panic behaviour, see https://crates.io/keywords/panic-impl for alternatives
panic-halt = "1.0.0"

[dependencies.stm32f1xx-hal]
version = "0.11.0"
features = ["stm32f103", "medium"]

The panic handler choice is called out, since on a no-std target there is no unwinding by default, and panic-halt is one of several options listed in the keywords reference.

If you build at this point, the README says you should get a single error, `error: language item required, but not found: eh_personality`, and it explains that this unhelpful message is fixed by compiling for the right target. So the error is expected rather than a mistake.

The second part is the linking and the memory layout. Rust needs to be told how to link the executable and how to lay out the result in memory, and the instruction is to copy .cargo/config.toml and memory.x from the stm32f1xx-hal repository into your project. After that, the build is a plain cargo build:

bash
cargo build

The fact that the crate ships its own memory.x is a small but significant convenience. The linker script describes the flash and RAM regions of the target, and a version that matches the part you selected is the difference between a binary that boots and one that faults.

Flashing and debugging, and the gdb safe-path step

The programming half of the guide starts with starting openocd against the board, with the interface configuration named for the probe in use:

bash
openocd -f interface/stlink-v3.cfg -f target/stm32f1x.cfg

If the probe is not an ST-LINK V3, the interface file changes accordingly. On success openocd detects the microcontroller and reports what the debug hardware offers, and the README quotes the expected line as saying the cpu has 6 breakpoints and 4 watchpoints. The server is left running in the background.

Uploading and debugging is done with gdb, and cargo starts gdb automatically because of the .cargo/config that was copied earlier. gdb also has to be told how to reach openocd, and that is done by copying .gdbinit to the root of the project.

Then comes the step that catches people out, and the README explains why it exists. gdb may need to be told that it is safe to load .gdbinit from the working directory:

bash
echo "set auto-load safe-path $(pwd)" >> ~/.gdbinit

On Windows the equivalent writes to the .gdbinit in the user profile, and the README notes you may need to restart your computer for it to take effect. The instruction appears twice, once in the programming section and again in the section on trying the examples, which is a fair signal of how often it is needed.

Once that is in place, cargo compiles the project, starts gdb, loads the program and gives a prompt, and typing continue in the gdb prompt starts the program, which on a blue pill means the green LED starts blinking. The blinky example is the reference point throughout the guide, and the more general route through the examples directory is given as a cargo run with a feature matching your hardware:

bash
$ cargo run --features stm32f103 --example hello

The interface configuration is parameterised by an environment variable in that form, with a note that an ST-LINK V2 needs stlink-v2.cfg.

The dependency set explains the compatibility story

The Cargo manifest is a good place to read the crate's compatibility policy, and it is more interesting than the version number alone suggests.

There are two embedded-hal versions in play. The current embedded-hal at 1.0 is a direct dependency, and there are separate entries for embedded-hal-nb at 1.0, the variant that carries the nb::Result style of returning would-block, and for embedded-hal 0.2.7 under the dependency name embedded-hal-02, with the unproven feature enabled. The 0.2 entry is there so that drivers written against the older trait set still compile.

The 1.0 traits are also present in a few narrower forms, embedded-hal-io at 0.7 for the IO traits, and embedded-hal-async at 1.0 for the async trait set, both optional. The async support is tied to the RTIC story, and there are two generations of it in the manifest. rtic-monotonic and systick-monotonic with cortex-m-rtic are the RTIC 1 line, while rtic-time, the rtic crate at 2.2 with the thumbv7 backend, and atomic-polyfill are the RTIC 2 line. Both sets are optional, so the choice of real time framework is left to the project.

The hardware side comes from the stm32f1 PAC at version 0.16.0 with the atomics feature, and bxcan for the CAN peripheral at 0.8.0, along with the usual support crates: fugit and fugit-timer for time types, bitflags and vcell for register access patterns, void and nb for the result types, and defmt as an optional logging format.

The docs.rs metadata is a small detail with a real effect. The published documentation is built with the features stm32f103, rtic1 and high, which means the API documentation on docs.rs shows one particular combination rather than every permutation of device, density and framework, and the default target is x86_64-unknown-linux-gnu so the build does not need a cross toolchain.

Licence, versions and the state of the crate

The licence is the usual dual arrangement for Rust crates, licensed under either of the Apache License, Version 2.0 or the MIT licence, and the repository holds both LICENSE-APACHE and LICENSE-MIT files. The Cargo manifest records it as MIT OR Apache-2.0. Either choice is permissive, and the dual form means a downstream project can pick the one its own policy prefers. As always, that is a description of the terms rather than legal advice, and a company with a licence policy should read the files.

The version history is short and slow, which is normal for a vendor HAL. The published releases are 0.11 on 2025-09-10, 0.10.0 on 2023-07-17 and 0.9.0 on 2022-03-03. The last push to the repository was on 2026-07-27, and the repository is not archived, so the code is being worked on between tags.

The crate is used as a dependency with the device chosen in the manifest rather than in the build command:

toml
[dependencies.stm32f1xx-hal]
version = "0.11.0"
features = ["stm32f100"]

The repository layout supports the workflow described above. The .cargo directory holds the configuration that makes cargo invoke gdb and the right target, .gdbinit is the debugger script that has to be copied into a user project, memory.x is the linker script, build.rs is the build script that generates the per-device peripheral code, and there are directories for examples, tests and tools.

The examples are the documentation in practice, and the README points at them twice, once for the blinky starting point and once for everything else, with the invitation to open an issue if something is unclear. Given how much of this crate's configuration lives outside the Rust source, in the linker script and the debugger setup, that is the right place to look before writing a line of driver code.

Editorial conclusion

stm32f1xx-hal fits a Rust developer who wants to work on an STM32F1 board with the embedded ecosystem's abstractions rather than direct register writes, and who is prepared to install a cross toolchain, a debug probe and the arm target. It is the wrong choice if your board is not an F1, and it is the wrong choice if you want a zero-setup workflow, because the linking, memory layout and debugger configuration are steps you own rather than defaults you inherit. Before adopting it, confirm your exact part number against the density table, since a wrong density feature fails to enable the peripherals you need, check that your Rust toolchain meets the rust-version 1.95 floor, and read the gdb safe-path step carefully, because that is where a surprising number of first runs stop.

Frequently asked questions

How do I set up a Rust project with stm32f1xx-hal?

Install the arm target with rustup target install thumbv7m-none-eabi, create the project with cargo init, add the dependencies including stm32f1xx-hal with the device and density features, and copy .cargo/config.toml and memory.x from the stm32f1xx-hal repository into the project so the linker and the memory layout are right.

Which microcontrollers does stm32f1xx-hal support?

The supported parts are stm32f100, stm32f101, stm32f103, stm32f105 and stm32f107. The specific device is chosen with a feature such as stm32f103, and the crate will not compile if no microcontroller feature is given.

What do the medium, high and xl features mean?

They are the memory density features that enable certain peripherals. The density is generally the second character after the number in the device name, with 4 and 6 low density needing no feature, 8 and B needing medium, C, D and E needing high, and F and G needing xl. The connectivity line parts stm32f105 and stm32f107 need no density feature.

How do I flash and debug with stm32f1xx-hal?

Start openocd with the interface configuration for your probe and the stm32f1x target, copy .gdbinit to your project root so gdb can reach openocd, and let cargo start gdb through the .cargo/config you copied. The README also shows running an example directly with cargo run --features stm32f103 --example hello.

Which embedded-hal versions does stm32f1xx-hal support?

The manifest depends on embedded-hal 1.0 and 1.0, plus embedded-hal 0.2.7 under the name embedded-hal-02 with the unproven feature enabled, so drivers written against the older trait set still compile. Optional entries cover embedded-hal-io 0.7 and embedded-hal-async 1.0, the latter tied to the RTIC support.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. Releases
  5. stm32-rs/stm32f1xx-hal on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/stm32-rs-stm32f1xx-hal.svg)](https://hysenlabs.com/projects/stm32-rs-stm32f1xx-hal)

Was this useful?