rust-embedded / wg

Coordination repository of the embedded devices Working Group
1.86k stars 95 forks source link

Request for Feedback: embedded-platform #412

Closed dflemstr closed 3 weeks ago

dflemstr commented 4 years ago

I recently started down a rabbit hole of creating my own abstraction over embedded-hal that makes it easy to mix and match various dev boards. My initial use-case has been to do experiments with Adafruit Feather boards, where I want to plug two devices together and expect the code to "just work" without having to figure out the specific pin/peripheral mappings between them. On the other hand, I didn't want to go all in and rewrite all crates in existence; instead, I wanted to rely on existing -hal and driver crates as much as possible.

As a result, I created https://github.com/dflemstr/embedded-platform (see docs at https://docs.rs/embedded-platform, in particular the example Feather spec). I realize that the scope of that library is huge and hence I would like to get feedback on the approach. I'm happy to share control of the repository in case others think this is a good idea, or yank the crate if not :)

Since I'm not super familiar with this WG, I don't know which exact process to follow here. Please give me input on what to do!

A teaser example:

async fn run() -> Result<(), nrf52840_platform::error::Error> {
    use embedded_platform::platform::PlatformExt;

    let platform = nrf52840_platform::ParticleArgon::initialize().await?;

    feather_blink(platform).await?;

    Ok(())
}

async fn feather_blink<P>(mut feather: P) -> Result<(), P::Error>
where
    P: embedded_platform::specs::feather::Feather,
{
    use embedded_platform::gpio::IntoPushPullOutputPin;
    use embedded_platform::gpio::OutputPinExt;

    let mut main_led = feather.take_main_led().into_push_pull_output_pin(false)?;
    let mut on = false;
    let timer = todo!(); // TODO: didn't have time to implement timer API yet

    while let _ = timer.next().await? {
        on = !on;
        main_led.set(on).await?;
    }
}

Quoting the README:

This is defines the embedded-platform set of crates. The idea is to add device and peripheral support to complement embedded-hal-based crates. This makes it possible to plug-and-play and mix-and-match different crates that adhere to common specs. For example, if you have a nrf52840-based MCU as well as a ili9341-based device, and both adhere to the Adafruit Feather spec (pin layout, voltage levels, ...), you can connect them up and all the wiring will be done for you.

The ambition is that embedded-platform should be to embedded-hal what tokio is to mio.

Some design trade-offs that have been made:

  • #![forbid(unsafe_code)]; that belongs in -pac or -hal crates.
  • Don't require alloc.
  • Do some compatibility checks at runtime during startup instead of at compile time, for example to check that a pin is used only once. It turns out to be super tricky to do granular ownership mapping of device registers at compile time (this has been done in drone-os), and instead we opt to do some checks at runtime (e.g. Option::take). This wastes a dozen or so instructions at startup, which is a one-time cost.
  • All APIs are async-first, so that code won't have to block and we can be power efficient. This does require an executor, and one can be made that doesn't require alloc, yet to be written.
  • The crate uses its own HAL-like traits for e.g. OutputPin or I2cRead to enable async APIs as well as smooth over any incompatibilities between embedded_hal::gpio::v1 and embedded_hal::gpio::v2 etc.
  • All platform crates should be maintained in this repository so that changes like the last bullet point can be made in lock-step.
  • Don't expose interrupts to the user. mypin.changes() should return an async futures::Stream when the pin changes. In the background, we stash away a Waker that gets called from the interrupt handler.

You can think about the intended stack like this:

┌─────────────────────────────────────────┐
│         Peripheral Access Crate         │
│            e.g. nrf52840-pac            │
├─────────────────────────────────────────┤
│        Hardware Abstraction Layer       │
│            e.g. nrf52840-hal            │
├─────────────────────────────────────────┤
│         Platform Implementation         │
│          e.g. nrf52840-platform         │
│ ┌─────────────────────────────────────┐ │
│ │          Specific Product           │ │
│ │         e.g. Particle Argon         │ │
│ ├─────────────────────────────────────┤ │
│ │            Common Spec              │ │
│ │        e.g. Adafruit Feather        │ │
│ │          or Arduino Shield          │ │
│ ├─────────────────────────────────────┤ │
│ │              Adapter                │ │
│ │        e.g. "Main SPI bus" on       │ │
│ │        specific Feather pins        │ │
│ └─────────────────────────────────────┘ │
├─────────────────────────────────────────┤
│              Device Driver              │
│              e.g. ili9341               │
└─────────────────────────────────────────┘
thejpster commented 4 years ago

I like the idea. It is to a BSP, what embedded-hal is to an SPI peripheral driver.

dflemstr commented 4 years ago

@thejpster okay, I will continue working on it for a while longer then and see where it ends up! (possibly adding support for more boards etc)

BartMassey commented 3 weeks ago

Closing this as part of 2024 triage. Haven't seen any subsequent activity. Happy to reopen if there's news or new feedback to give.

dflemstr commented 1 week ago

To close the loop here, I ended up finding the embassy ecosystem to solve the same problem and felt it didn't make sense to try to create something that competes with it.

BartMassey commented 1 week ago

Thanks much for letting us know!