Scaffolding the crate
We are going to build an ordinary Rust library crate. No board support package, no cross toolchain, no custom linker setup.
Start with a fresh library:
$ cargo new --lib tmp102
Creating library `tmp102` package
$ cd tmp102
Now add the dependencies we know we will need:
$ cargo add embedded-hal
$ cargo add embedded-hal-async
$ cargo add device-driver --no-default-features -F toml
$ cargo add --dev pico-de-gallo-hal --git https://github.com/OpenDevicePartnership/pico-de-gallo
$ cargo add --dev tokio -F rt-multi-thread,time,macros
That gives us three layers:
embedded-halfor the blocking driver surfaceembedded-hal-asyncfor the async siblingdevice-driverfor generated register accessors
And in dev-dependencies:
pico-de-gallo-halso tests and examples can talk to real hardwaretokiofor async examples and hardware-in-the-loop tests
Tip
Keep
pico-de-gallo-halin[dev-dependencies], not in normal[dependencies]. Your end users should depend on your driver crate, not on the host-side test harness you used while writing it.
Your Cargo.toml should look roughly like this:
[package]
name = "tmp102"
version = "0.1.0"
edition = "2024"
[dependencies]
device-driver = { version = "1.0.7", default-features = false, features = ["toml"] }
embedded-hal = "1.0.0"
embedded-hal-async = "1.0.0"
[dev-dependencies]
pico-de-gallo-hal = { git = "https://github.com/OpenDevicePartnership/pico-de-gallo" }
tokio = { version = "1.47.1", features = ["rt-multi-thread", "time", "macros"] }
We also want the code generator that turns a register description into a Rust interface:
$ cargo install device-driver-cli
That binary reads a TOML description of the device and emits the boring part of the driver for us: register wrappers, field accessors, and the plumbing around them.
At this point the crate is still empty, but we have already set the shape of the project:
- library-first
embedded-hal-based- async-friendly
- hardware-testable on the host
Next we describe TMP102’s registers in a form the generator can digest.