Library / SDK
rust-embedded/embedded-hal avatar
rust-embedded/embedded-hal

embedded-hal: the trait boundary that lets one Rust driver run on any microcontroller

A Hardware Abstraction Layer (HAL) for embedded systems

2,655 stars282 forksRustApache-2.0

At a glance

What is it?
The rust-embedded workspace that defines blocking, async, polling and CAN traits so sensor and transceiver drivers can be written once and used on Cortex-M, AVR or embedded Linux.
Who is it for?
The value of embedded-hal is not that any trait in it is clever, it is that thousands of driver crates have agreed to implement the same signatures, so a BME280 sensor library written once runs on a Cortex-M target and on embedded Linux without a fork. The workspace split tells you where to look: `embedded-hal` for blocking, `embedded-hal-async` for futures, `embedded-hal-nb` for polling, and `embedded-hal-bus` when two devices share one bus.
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 137 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 September 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One repository, eight crates, four execution models

The headline in the README is that this is a hardware abstraction layer for embedded systems, maintained by the HAL team under the rust-embedded working group. Underneath, the repository is a Cargo workspace whose member list is the whole story:

toml
members = [
    "embedded-hal",
    "embedded-hal-async",
    "embedded-hal-nb",
    "embedded-hal-bus",
    "embedded-can",
    "embedded-io",
    "embedded-io-async",
    "embedded-io-adapters",
]

The file tree matches that list exactly, with a directory per crate plus a `docs/` folder. The README is explicit that the main project is not tied to a specific execution model like blocking or non-blocking, which is why the split exists rather than a single crate with feature flags. When you pull in a driver crate you are picking an execution model at the same time.

The repository language is Rust, it is licensed under Apache-2.0 or MIT at your option, and the last push was on 2026-05-26. There are 2,655 stars, 282 forks and 157 open issues, which is a high issue count for a project of this size and mostly reflects how many HAL and driver crates are in flight at once.

The argument the scope section actually makes

The README spends its first real section on scope, and the argument is two-sided. A driver, defined in the text as a library crate that lets a target platform interface an external device like a digital sensor or a wireless transceiver, written as a generic library on top of embedded-hal can support any number of target platforms, with Cortex-M microcontrollers, AVR microcontrollers and embedded Linux named as examples. The other side is the application developer, who adopts embedded-hal and unlocks every driver written against it.

That second-order effect is the real product. One trait set in exchange for access to a large shared driver ecosystem, which is a different bargain from picking a vendor HAL that ships with the chip you happen to be using this quarter.

The scope is also bounded, and the README says so rather than implying otherwise. For functionality beyond what the crate provides, users are pointed at the target platform directly. New abstractions can be proposed for inclusion through the guide in `docs/how-to-add-a-new-trait.md`, which is the document to read if you find yourself wanting a capability that is not there yet. The design goals themselves live on the docs.rs page for the crate rather than in the README.

What the crate table covers and what it leaves out

The crate table in the README is the fastest orientation document, and each row names a distinct job:

text
embedded-hal            Core traits, blocking version
embedded-hal-async      Core traits, async version
embedded-hal-nb         Polling version using the nb crate
embedded-hal-bus        Utilities for sharing SPI and I2C buses
embedded-can            Controller Area Network (CAN) traits
embedded-io             I/O traits (read, write, seek, etc.), blocking and nonblocking version.
embedded-io-async       I/O traits, async version
embedded-io-adapters    Adapters between the embedded-io and embedded-io-async traits

Two of these deserve a second look. `embedded-hal-bus` exists because two devices on one SPI or I2C bus need mutual exclusion, and solving that once in the ecosystem is better than every driver inventing its own lock. `embedded-io-adapters` bridges the I/O traits to `std`, `tokio` and `futures` types, which is what lets the same I/O code move between a microcontroller and a hosted test harness.

What the table does not cover is the trait inventory itself. Which methods `SpiBus` exposes, what the error types look like, and which associated types a platform must supply are documented on docs.rs, not in this repository's README.

Reading the 1.0 release notes as a changelog of API taste

v1.0.0 landed on 2024-01-09, and the release notes are short because the interesting decisions happened in the release candidates. Reading them in order is a good way to see what the maintainers considered important enough to spend a breaking change on.

The delay trait got the most attention. `DelayUs` was renamed to `DelayNs`, `delay_ns()` was added, and default implementations of `delay_ms` and `delay_us` were derived on top of it, with the millisecond default made more efficient so it issues fewer calls into the underlying `delay_ns`. SPI followed suit: `Operation::DelayUs` became `Operation::DelayNs` with nanosecond precision. PWM lost `get_max_duty_cycle` in favour of `max_duty_cycle`.

The GPIO story is the clearest statement of intent. The rc.3 notes require `&mut self` in `InputPin` and `StatefulOutputPin`, and the final release removes `ToggleableOutputPin` outright, moving `toggle()` onto `StatefulOutputPin`. Requiring exclusive access for a pin read is a deliberate choice: it makes the API impossible to use in a way that silently races on a register, at the cost of some ergonomics in single-threaded code.

MSRV, docs and where the README stops

The README commits to stable Rust 1.83 and up, and adds the honest caveat that older versions might compile today but that this can change in any patch release. The mechanics of an MSRV bump are documented separately in `docs/msrv.md`, which is where to look if your pinned toolchain suddenly stops building.

The rest of the navigation is short and worth reading in order. There is a migration guide for 0.2 to 1.0, a how-to for proposing a new trait, and the MSRV note. For discovering what already exists, the README points at awesome-embedded-rust as a non-exhaustive list, and at three crates.io keywords: `embedded-hal-impl` for HAL implementations, `embedded-hal-driver` for drivers, and `embedded-hal` for the core crate.

So the division of labour is clear. The repository defines the traits and governs how they change; docs.rs renders the API; the awesome list and the crates.io keywords are where the ecosystem lives. None of those three documents describe how to choose between `embedded-hal` and `embedded-hal-async` for your own project, which is the question the ecosystem answers in issues and blog posts rather than in a README.

Editorial conclusion

The value of embedded-hal is not that any trait in it is clever, it is that thousands of driver crates have agreed to implement the same signatures, so a BME280 sensor library written once runs on a Cortex-M target and on embedded Linux without a fork. The workspace split tells you where to look: `embedded-hal` for blocking, `embedded-hal-async` for futures, `embedded-hal-nb` for polling, and `embedded-hal-bus` when two devices share one bus. The design goals page on docs.rs and the guide for proposing a new trait are the documents to read before you write a driver, and the migration guide from 0.2 to 1.0 is the one to read if you are porting older code. Start at the trait signatures in `embedded-hal/src`, then pick the execution model your runtime already forces on you.

Frequently asked questions

What is a HAL in embedded systems?

In embedded systems a hardware abstraction layer is the interface between portable code and a specific piece of silicon. The rust-embedded README defines it the same way and adds the term it cares about most: a driver is a library crate that lets a target platform interface an external device such as a digital sensor or a wireless transceiver. Writing that driver against `embedded-hal` traits rather than a vendor SDK is what lets it run on more than one target.

What is the difference between HAL and BSP?

A HAL defines the traits a portable driver programs against, so the same driver code can target several chips. A board support package is the other side of the coin: the concrete package that wires one microcontroller to one board, and implements those traits for that hardware. The embedded-hal README names the HAL side and its scope limits, and points readers at the target platform for everything else rather than trying to be a BSP.

Should I use embedded-hal or embedded-hal-async in a new driver?

It depends on the execution model your runtime already imposes. The README states that the main project is not tied to a specific model like blocking or non-blocking, which is why both crates exist with the same trait set, and `embedded-hal-nb` exists as a polling variant using the `nb` crate. If your application is already async, writing the async version avoids wrapping every call in a blocking future.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. Releases
  5. rust-embedded/embedded-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/rust-embedded-embedded-hal.svg)](https://hysenlabs.com/projects/rust-embedded-embedded-hal)