diff --git a/.github/bors.toml b/.github/bors.toml deleted file mode 100644 index cad44688f..000000000 --- a/.github/bors.toml +++ /dev/null @@ -1,16 +0,0 @@ -block_labels = ["needs-decision"] -delete_merged_branches = true -required_approvals = 1 -status = [ - "test (stable, x86_64-unknown-linux-gnu)", - "test (stable, thumbv6m-none-eabi)", - "test (stable, thumbv7m-none-eabi)", - "test (1.54.0, x86_64-unknown-linux-gnu)", - "test (1.54.0, thumbv6m-none-eabi)", - "test (1.54.0, thumbv7m-none-eabi)", - "test (nightly, x86_64-unknown-linux-gnu)", - "test (nightly, thumbv6m-none-eabi)", - "test (nightly, thumbv7m-none-eabi)", - "clippy", - "fmt", -] diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 19c7acbac..422eab62f 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -1,20 +1,17 @@ on: - push: - branches: [ staging, trying, master ] - pull_request: + push: # Run CI for all branches except GitHub merge queue tmp branches + branches-ignore: + - "gh-readonly-queue/**" + pull_request: # Run CI for PRs on any branch + merge_group: # Run CI for the GitHub merge queue name: Clippy check jobs: clippy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - profile: minimal - # embedded-hal-async needs nightly. - # Use a pinned version to avoid spontaneous breakages (new clippy lints are added often) - toolchain: nightly-2022-11-22 - override: true components: clippy - - run: cargo clippy -- --deny=warnings \ No newline at end of file + - run: cargo clippy --all-features -- --deny=warnings diff --git a/.github/workflows/rustdoc.yml b/.github/workflows/rustdoc.yml new file mode 100644 index 000000000..1a99f8d92 --- /dev/null +++ b/.github/workflows/rustdoc.yml @@ -0,0 +1,18 @@ +on: + push: # Run CI for all branches except GitHub merge queue tmp branches + branches-ignore: + - "gh-readonly-queue/**" + pull_request: # Run CI for PRs on any branch + merge_group: # Run CI for the GitHub merge queue + +name: Rustdoc check +jobs: + rustdoc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-07-26 + # tokio/net required to workaround https://github.com/tokio-rs/tokio/issues/6165 + - run: RUSTDOCFLAGS="--deny=warnings --cfg=docsrs" cargo doc --all-features --features tokio/net diff --git a/.github/workflows/rustfmt.yml b/.github/workflows/rustfmt.yml index 7ffd30a99..129eb2285 100644 --- a/.github/workflows/rustfmt.yml +++ b/.github/workflows/rustfmt.yml @@ -1,7 +1,9 @@ on: - push: - branches: [ staging, trying, master ] - pull_request: + push: # Run CI for all branches except GitHub merge queue tmp branches + branches-ignore: + - "gh-readonly-queue/**" + pull_request: # Run CI for PRs on any branch + merge_group: # Run CI for the GitHub merge queue name: Code formatting check @@ -9,11 +11,8 @@ jobs: fmt: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - profile: minimal - toolchain: nightly - override: true components: rustfmt - run: cargo fmt --check diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 98650cdd6..586d42513 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,7 +1,9 @@ on: - push: - branches: [ staging, trying, master ] - pull_request: + push: # Run CI for all branches except GitHub merge queue tmp branches + branches-ignore: + - "gh-readonly-queue/**" + pull_request: # Run CI for PRs on any branch + merge_group: # Run CI for the GitHub merge queue name: Continuous integration @@ -11,33 +13,34 @@ env: jobs: test: runs-on: ubuntu-latest - strategy: - matrix: - # All generated code should be running on stable now - rust: - - stable - - 1.54.0 # MSRV - - nightly - - # The default target we're compiling on and for - target: - - x86_64-unknown-linux-gnu - - thumbv6m-none-eabi - - thumbv7m-none-eabi + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo test --workspace + + test-all-features: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo test --workspace --all-features + build-nostd: + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - profile: minimal - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - - run: sed -i '/nightly-only/d' Cargo.toml - if: matrix.rust != 'nightly' - - - run: cargo check --target=${{ matrix.target }} - - - run: cargo test --target=${{ matrix.target }} - if: contains(matrix.target, 'linux') + target: thumbv7m-none-eabi + - run: > + cargo build + --workspace + --target thumbv7m-none-eabi + --features async,defmt-03 + + msrv-1-81: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@1.81 + - run: cargo test --workspace --all-features diff --git a/Cargo.toml b/Cargo.toml index 4322dde4e..eb2832174 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,13 @@ [workspace] +resolver = "2" -# CI removes lines containing 'nightly-only' when not building with nightly. members = [ "embedded-hal", - "embedded-hal-async", # nightly-only + "embedded-hal-async", "embedded-hal-nb", "embedded-hal-bus", "embedded-can", + "embedded-io", + "embedded-io-async", + "embedded-io-adapters", ] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 000000000..16fe87b06 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 000000000..52cb453f2 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2017-2018 Jorge Aparicio + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 46754d22c..63c347628 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,12 @@ This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team). +> [!IMPORTANT] +> 📣 `embedded-hal` v1.0 is now released! Check out the [announcement blog post](https://blog.rust-embedded.org/embedded-hal-v1/), the [API documentation](https://docs.rs/embedded-hal) and the [migration guide](docs/migrating-from-0.2-to-1.0.md). + ## Scope -`embedded-hal` serves as a foundation for building an ecosystem of platform agnostic drivers. +`embedded-hal` serves as a foundation for building an ecosystem of platform-agnostic drivers. (driver meaning library crates that let a target platform interface an external device like a digital sensor or a wireless transceiver). @@ -34,25 +37,14 @@ The main `embedded-hal` project is not tied to a specific execution model like | [embedded-hal-nb](./embedded-hal-nb) | [![crates.io](https://img.shields.io/crates/v/embedded-hal-nb.svg)](https://crates.io/crates/embedded-hal-nb) | [![Documentation](https://docs.rs/embedded-hal-nb/badge.svg)](https://docs.rs/embedded-hal-nb) | Core traits, polling version using the `nb` crate | | [embedded-hal-bus](./embedded-hal-bus) | [![crates.io](https://img.shields.io/crates/v/embedded-hal-bus.svg)](https://crates.io/crates/embedded-hal-bus) | [![Documentation](https://docs.rs/embedded-hal-bus/badge.svg)](https://docs.rs/embedded-hal-bus) | Utilities for sharing SPI and I2C buses | | [embedded-can](./embedded-can) | [![crates.io](https://img.shields.io/crates/v/embedded-can.svg)](https://crates.io/crates/embedded-can) | [![Documentation](https://docs.rs/embedded-can/badge.svg)](https://docs.rs/embedded-can) | Controller Area Network (CAN) traits | - -## Releases - -At the moment we are working towards a `1.0.0` release (see [#177]). During this process we will -release alpha versions like `1.0.0-alpha.1` and `1.0.0-alpha.2`. -Alpha releases are **not guaranteed** to be compatible with each other. -They are provided as early previews for community testing and preparation for the final release. -If you use an alpha release, we recommend you choose an exact version specification in your -`Cargo.toml` like: `embedded-hal = "=1.0.0-alpha.9"` - -See [this guide](docs/version-policy.md) for a way to implement both an `embedded-hal` `0.2.x` -version and an `-alpha` version side by side in a HAL. - -[#177]: https://github.com/rust-embedded/embedded-hal/issues/177 +| [embedded-io](./embedded-io) | [![crates.io](https://img.shields.io/crates/v/embedded-io.svg)](https://crates.io/crates/embedded-io) | [![Documentation](https://docs.rs/embedded-io/badge.svg)](https://docs.rs/embedded-io) | I/O traits (read, write, seek, etc.), blocking and nonblocking version. | +| [embedded-io-async](./embedded-io-async) | [![crates.io](https://img.shields.io/crates/v/embedded-io-async.svg)](https://crates.io/crates/embedded-io-async) | [![Documentation](https://docs.rs/embedded-io-async/badge.svg)](https://docs.rs/embedded-io-async) | I/O traits, async version | +| [embedded-io-adapters](./embedded-io-adapters) | [![crates.io](https://img.shields.io/crates/v/embedded-io-adapters.svg)](https://crates.io/crates/embedded-io-adapters) | [![Documentation](https://docs.rs/embedded-io-adapters/badge.svg)](https://docs.rs/embedded-io-adapters) | Adapters between the [`embedded-io`](https://crates.io/crates/embedded-io) and [`embedded-io-async`](https://crates.io/crates/embedded-io-async) traits and other IO traits (`std`, `tokio`, `futures`...) | ## Documents +- [Migrating from v0.2 to v1.0](docs/migrating-from-0.2-to-1.0.md). - [How-to: add a new trait](docs/how-to-add-a-new-trait.md) -- [Version policy](docs/version-policy.md) - [MSRV](docs/msrv.md) ## Implementations and drivers @@ -72,7 +64,7 @@ on crates.io. ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.54 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.81 and up. It *might* compile with older versions but that may change in any new patch release. See [here](docs/msrv.md) for details on how the MSRV may be upgraded. diff --git a/docs/migrating-from-0.2-to-1.0.md b/docs/migrating-from-0.2-to-1.0.md new file mode 100644 index 000000000..b35a7a2d4 --- /dev/null +++ b/docs/migrating-from-0.2-to-1.0.md @@ -0,0 +1,526 @@ +# Migrating from embedded-hal 0.2.x to 1.0.0 + +## Table of contents + +- [Overview and reasoning](#overview-and-reasoning) +- [Trait organization](#trait-organization) +- [Trait unification](#trait-unification) +- [Removed traits](#removed-traits) + - [Unconstrained associated types](#unconstrained-associated-types) + - [Impractical traits](#impractical-traits) + - [Serial traits](#serial-traits) + - [RNG traits](#rng-traits) + - [CAN traits](#can-traits) +- [SPI Bus/device separation](#spi-busdevice-separation) +- [Fallibility](#fallibility) +- [SPI transfer return type](#spi-transfer-return-type) +- [Error type bounds](#error-type-bounds) +- [GPIO traits now require `&mut self`](#gpio-traits-now-require-mut-self) +- [Prelude](#prelude) +- [Removed blanket implementations](#removed-blanket-implementations) +- [Cargo Features](#cargo-features) +- [Companion crates](#companion-crates) +- [Supporting both 0.2 and 1.0 in the same HAL](#supporting-both-02-and-10-in-the-same-hal) +- [`embedded-hal-compat`](#embedded-hal-compat) + +## Overview and reasoning + +There have been _a lot_ of changes in `embedded_hal` between versions 0.2.x and 1.0.0. +We understand the significance of `embedded-hal` in the Rust embedded +ecosystem and thus intend to release a version that stays compatible for a long time. + +The main difference between `embedded-hal` 0.2 and 1.0 is the project is now focused +on a single goal: traits for writing drivers that work on any HAL. + +In `embedded-hal` 0.2, the traits had dual goals: +- Standardize the API of HAL crates, so HAL crate authors get guidance on how to design their APIs and + end users writing code directly against one HAL get a familiar API. +- Allowing writing generic drivers using the traits, so they work on top of any HAL crate. + +For `embedded-hal` 1.0, we decided to drop the first goal, targeting only the second. The reasons are: + +- Standardizing HAL APIs is difficult, because hardware out there has wildly different sets of capabilities. Modeling all capabilities required many different variants of the traits, and required "customization points" like associated types, significantly increasing complexity. +- There is a tension between both goals. "Customization points" like associated types make the traits hard to use in generic HAL-independent drivers. +- The second goal delivers much more value. Being able to use any driver together with any HAL crate, out of the box, and across the entire Rust Embedded ecosystem, is just plain awesome. + +This refocusing on drivers is the root cause of many of the changes between `embedded-hal` 0.2 and 1.0: +- [Associated type compatibility](#removed-traits) +- [Trait fragmentation](#trait-organization) +- [Bus/device separation](#bus-device-separation) +- [Fallibility](#fallibility) +- [Execution model support](#trait-organization) + +## Trait organization + +All traits have been organized in modules for each peripheral. For example `embedded_hal::spi` and `embedded_hal::i2c`. +We only foresee having blocking traits in `embedded-hal`. We have put the traits for different execution models +into separate crates. Notably `embedded-hal-async` and `embedded-hal-nb`. See [companion crates](#companion-crates). +This allows for a separate and more tailored evolution. + +Execution-model-independent definitions have been moved into the peripheral's module. For example, SPI `Phase` is now defined in `embedded_hal::spi::Phase`. + +## Trait unification + +Previously, there were multiple traits for the same peripheral, for different sets of capabilities. The reasoning +was different hardware supports a different set of features, so by making the traits granular each HAL implementation +can implement only the features supported by the hardware. + +However, this has proven to be troublesome for generic drivers, in cases where a driver expects to use one +trait, but the HAL crate implements only other traits. To avoid this fragmentation and ensure +interoperability for generic code, these have now been unified. + +- I2C: `Read`, `Write`, `WriteIter`, `WriteIterRead`, `WriteRead`, `Transactional`, `TransactionalIter` have now been unified into a single `I2c` trait. +- SPI: `Write` `WriteIter`, `Transfer`, `Transactional` have been unified into `SpiBus`. +- GPIO: `ToggleableOutputPin` has been merged into `StatefulOutputPin`. +- Delays: `DelayMs`, `DelayUs` has been unified into `DelayNs` (and precision extended to nanoseconds). + +HAL implementation crates should implement the full functionality of the traits. If a feature is not supported natively by the hardware, it should be polyfilled/emulated in software. In no case should "not supported" errors be returned. This ensures maximum compatibility. + +## Removed traits + +These traits have been removed in the 1.0.0 release, with no replacement for now: + +- [`adc::OneShot`][adc] +- [`adc::Channel`][adc] +- [`capture::Capture`][capture] +- [`digital::IoPin`][iopin] +- [`pwm::Pwm`][pwm] +- [`qei::Qei`][qei] +- [`timer::Cancel`][timer] +- [`timer::CountDown`][timer] +- [`timer::Periodic`][timer] +- [`watchdog::Disable`][watchdog] +- [`watchdog::Enable`][watchdog] +- [`watchdog::Watchdog`][watchdog] + +Please find a general [roadmap with further guidance here][roadmap-rm-traits] about +whether and how to get these traits back in a future release. + +If you are a generic driver author and need one of them, we would like to hear from you. Please add your use case to the appropriate issue for the trait affected. + +HAL implementation crates are encouraged to provide their own APIs for functionality for the removed traits, and not implement any traits. This will allow the APIs to more closely match the hardware capabilities, and allow users to continue to use them. + +[roadmap-rm-traits]: https://github.com/rust-embedded/embedded-hal/issues/357 +[adc]: https://github.com/rust-embedded/embedded-hal/issues/377 +[iopin]: https://github.com/rust-embedded/embedded-hal/issues/397 +[capture]: https://github.com/rust-embedded/embedded-hal/issues/361 +[pwm]: https://github.com/rust-embedded/embedded-hal/issues/358 +[qei]: https://github.com/rust-embedded/embedded-hal/issues/362 +[timer]: https://github.com/rust-embedded/embedded-hal/issues/359 +[watchdog]: https://github.com/rust-embedded/embedded-hal/issues/360 + +### Unconstrained associated types + +Traits defined in `embedded-hal` pursue creating an interface for interoperability between generic code (be it generic user code, generic application code, generic device drivers, etc.). +When a trait has an unconstrained associated type (for example `type Time;`), it is not possible to write generic code around it. Each side (implementer and user) need to specify which type the associated type will be. If the types match, the both parts can work together, however, this is not truly generic code. + +For example, if somebody creates a device driver that receives a `CountDown` struct, it needs to specify what its `Time` type should be. If they choose a type coming from `fugit`, somebody else cannot use this driver if the HAL implementation for the MCU they are using only provides `CountDown` with `Time` types defined in `embedded-time`. It is also not possible for the user to implement `CountDown` for `Time` types defined by `fugit` in a straight-forward way due to the orphan rule. +In summary, it is not possible for anybody to start a countdown for a certain duration in a generic way, without it being tied to a particular time implementation and thus forcing everybody to use that one. This means all these traits don't fulfill the "allow writing generic drivers" goal. + +At the moment no solution for this has been found so we have decided to remove such traits hoping that a solution may be found +and we can add them back in a future 1.x release. + +### Impractical traits + +The [`digital::IoPin` trait][iopin] and the [`adc` traits][adc] have been deemed impractical for use and have thus been removed. +Please feel free to comment on the appropriate issue if you need any of these traits and propose a solution. + +### Serial traits + +The `blocking::serial::Write` trait has been removed in favor of the [`embedded-io`] traits, also maintained within the `embedded-hal` repository. + +[`embedded-io`]: https://crates.io/crates/embedded-io + +### RNG traits + +The `rng` module and its traits have been removed in favor of the [`rand_core`] traits. + +[`rand_core`]: https://crates.io/crates/rand_core + +### CAN traits + +The `can` module and its traits have been removed in favor of the [`embedded-can`] traits. + +[`embedded-can`]: https://crates.io/crates/embedded-can + +## SPI Bus/device separation + +The SPI traits have been unified into a single `SpiBus` trait. However, to allow sharing an SPI bus, and hardware control of the CS pin, 1.0 adds the `SpiDevice` trait. + +The short summary is: +- `SpiBus` represents an entire SPI bus (with SCK, MOSI, MISO) pins +- `SpiDevice` represents a single device on an SPI bus, selected by a CS pin. + +See the [SPI documentation](https://docs.rs/embedded-hal/1.0.0/embedded_hal/spi/index.html) for more details. + +When upgrading code to `embedded-hal` 1.0, it is critical to implement/use the right trait depending on the underlying situation. + +### For HAL implementation crates + +- If you previously implemented the SPI traits, and did *not* manage a CS pin automatically, you should now implement `SpiBus`, which is the equivalent in 1.0. +- Optionally, if the API *does* manage a CS pin automatically, you may implement `SpiDevice`. + - This is required if the underlying API requires it to manage the CS pin, like `spidev` on Linux. + +Do not implement `SpiBus` and `SpiDevice` on the same struct, since this is never correct. When there's no CS pin being controlled you must implement only `SpiBus`, and when there is, implement only `SpiDevice`. If you want to offer both APIs, implement them on separate structs so the user has to choose one or the other. + +### For driver crates + +- If your device has SCK, MOSI, MISO, CS pins: use `SpiDevice`. + - Do NOT take the CS pin as a separate `OutputPin`, the `SpiDevice` will manage it for you. Taking the CS pin separately will make your driver not work on shared buses. +- If your device only has SCK, MOSI, MISO: use `SpiBus`. + - This means bus sharing won't be supported, but there's no way to share without a CS pin anyway. +- If you're using SPI to bitbang non-SPI protocols (for example, WS2812 smart LEDs), use `SpiBus`. + +### For end users + +You will most likely find the HAL crate you're using implements `SpiBus`, and the driver you want to use +requires `SpiDevice`. To convert from `SpiBus` to `SpiDevice`, wrap it with a [`embedded_hal_bus::spi::ExclusiveDevice`](https://docs.rs/embedded-hal-bus/0.1.0/embedded_hal_bus/spi/struct.ExclusiveDevice.html), together with the CS pin: + +```rust +use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay}; + +// Create the SPI from the HAL. This implements SpiBus, not SpiDevice! +let spi_bus = my_hal::spi::Spi::new(...); +// Create the CS. This must implement OutputPin. +let cs = my_hal::gpio::Output::new(...); + +// Combine the SPI bus and the CS pin into a SPI device. This now does implement SpiDevice! +let spi_device = ExclusiveDevice::new(spi_bus, cs, NoDelay); + +// Now you can create your driver with it! +let driver = my_driver::Driver::new(spi_device, ...); +``` + +If you want multiple drivers to share the same SPI bus, [`embedded_hal_bus::spi`](https://docs.rs/embedded-hal-bus/0.1.0/embedded_hal_bus/spi/index.html) +has a few options depending on the kind of mutex you want to use. This is now built-in to `embedded-hal`, using external crates like `shared-bus` is discouraged. + +For example, you can use `RefCellDevice` when you don't need drivers to be `Send`. + +```rust +use core::cell::RefCell; +use embedded_hal_bus::spi::{RefCellDevice, NoDelay}; + +// Create the SPI bus and CS pins. +let spi_bus = my_hal::spi::Spi::new(...); +let cs1 = my_hal::gpio::Output::new(...); +let cs2 = my_hal::gpio::Output::new(...); + +// Wrap the bus with a RefCell. +let spi_bus = RefCell::new(spi_bus); + +// Combine references to the SPI bus with a CS pin to get a SpiDevice for one device on the bus. +let device1 = RefCellDevice::new(&spi_bus, cs1, NoDelay); +let device2 = RefCellDevice::new(&spi_bus, cs2, NoDelay); + +// Now you can create drivers. They will transparently talk each to its own device, sharing the same bus. +let driver1 = my_driver::Driver::new(device1, ...); +let driver2 = my_driver::Driver::new(device2, ...); +``` + +## Fallibility + +All trait methods are now fallible so that they can be used in any possible situation. +However, HAL implementations can also provide infallible versions of the methods. + +For example, an implementation similar to the one below would allow to use the GPIO pins as `OutputPin`s +in any generic driver or implementation-agnostic code (by importing the `OutputPin` trait), +as well as using the infallible methods in non-generic code. +This avoids the need to use `unwrap()` the results in many cases and results in more succinct code. + +It should be noted that given this implementation, importing the `OutputPin` trait can result in +ambiguous calls, so please remove the trait imports if you do not need them. + +```rust +use core::convert::Infallible; +use embedded_hal::digital::blocking::OutputPin; + +struct HalImplGpioPin; + +impl OutputPin for HalImplGpioPin { + type Error = Infallible; + + fn set_high(&mut self) -> Result<(), Self::Error> { + // ... + Ok(()) + } + + fn set_low(&mut self) -> Result<(), Self::Error> { + // ... + Ok(()) + } +} + +impl HalImplGpioPin { + fn set_high(&mut self) { + // ... + } + + fn set_low(&mut self) { + // ... + } +} +``` + +## SPI transfer return type + +Previously the `transfer()` method in SPI returned a slice of the output data. +This slice is the same as the output buffer which is passed to the method, though, thus redundant and potentially confusing. +The `transfer()` method now returns `Result<(), Self::Error>`. +If you were using this return value, adapting the code should be straight forward by simply using the reception buffer which is passed. + +See an example: + +```rust +let tx_data = [1, 2, 3, 4]; +let mut rx_data = [0; 4]; +let data = spi.transfer(&tx_data, &mut rx_data)?; +println!("{:?}", data); +// There is no need to do `let data = `, since we already have the data in `rx_data`. +// Do this instead: +spi.transfer(&tx_data, &mut rx_data)?; +println!("{:?}", rx_data); +``` + +## Error type bounds + +All associated error types are now required to implement `core::fmt::Debug`. +Usually it is enough to add a `#[derive(Debug)]` clause to your error types. For example: + +```diff ++ #[derive(Debug)] +pub enum MyError { + InvalidInputData, + // ... +} +``` + +Additionally, for the I2C and SPI communication interfaces we have added a dedicated mechanism +which allows for two crucial requirements: +1. Generic code like drivers can interpret and act on errors if they want to. +2. HAL implementations can have arbitrarily-precise error types. + +This works in the following way: + +For each interface, `embedded-hal` defines an `ErrorKind` `enum` type with all sensible error variants as well +as an `Error` trait featuring a method that converts the type into that `ErrorKind`. + +`embedded-hal` still allows for implementation-defined error types associated to each trait, but requires these to +implement the appropriate `Error` trait, thus providing a mapping to a defined set of error variants. + +With this mechanism, HAL implementations can continue to define their own error types which can carry as much +information as they want. On the other hand it is now possible for generic code to inspect those errors +and act on common errors like I2Cs NACK. + +Furthermore, implementation-specific code can access the original error type and retrieve any information contained. + +An example of a driver which looks for I2C NACK errors and returns its own `DeviceBusy` or `Comm` error +wrapping the original one could be as follows: + +```rust +const address = 0x1D; + +fn set_some_parameter(&mut self) -> Result<(), Self::Error> { + const data = [0, 1]; + match self.i2c.write(address, &data) { + Err(e) => match e.kind() { + ErrorKind::NoAcknowledge(_) => Err(Self::Error::DeviceBusy(e)), + _ => Err(Self::Error::Comm(e)) // wrap and return any other error + }, + Ok(_) => Ok(()) + } +} +``` + +## GPIO traits now require `&mut self` + +Methods on `InputPin` and `State` now take `&mut self` instead of `&self`, to allow implementations to +have mutable state or access exclusive resources. + +**For HAL implementors**: You should not need to do any changes, since `&mut self` is strictly more permissive +for implementations. + +For ease of use, you might want to provide inherent methods that take `&self` if the hardware permits it. In this case, +you might need to do `*self` to call them from the trait methods. Otherwise Rust will resolve the +method call to the trait method, causing infinite recursion. + +```rust +struct HalPin; + +impl HalPin { + fn is_high(&self) -> bool { + true + } + + fn is_low(&self) -> bool { + true + } +} + +impl InputPin for HalPin { + fn is_high(&mut self) -> Result { + // Needs `*self` so that the inherent `is_high` is picked. + Ok((*self).is_high()) + } + + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} +``` + +**For driver authors**: If your driver does not need sharing input pins, you should be able to upgrade without any changes. +If you do need to share input pins, the recommended solution is wrapping them with a `RefCell`. + +Note that if you need to share multiple objects, you should prefer using a single `RefCell` wherever possible to reduce RAM +usage. Make an "inner" struct with all the objects that need sharing, and wrap it in a single `RefCell`. Below is an example +skeleton of a keypad driver using row/column multiplexing, sharing multiple `InputPin`s and `OutputPin`s with a single `RefCell`: + +```rust +use core::cell::RefCell; + +use embedded_hal::digital::{ErrorType, InputPin, OutputPin}; + +pub struct Keypad { + inner: RefCell>, +} + +struct KeypadInner { + cols: [O; NCOLS], + rows: [I; NROWS], +} + +pub struct KeypadInput<'a, O: OutputPin, I: InputPin, const NCOLS: usize, const NROWS: usize> { + inner: &'a RefCell>, + row: usize, + col: usize, +} + +impl<'a, O: OutputPin, I: InputPin, const NCOLS: usize, const NROWS: usize> ErrorType for KeypadInput<'a, O, I, NCOLS, NROWS> { + type Error = core::convert::Infallible; +} + +impl<'a, O: OutputPin, I: InputPin, const NCOLS: usize, const NROWS: usize> InputPin for KeypadInput<'a, O, I, NCOLS, NROWS> { + fn is_high(&mut self) -> Result { + Ok(!self.is_low()?) + } + + fn is_low(&mut self) -> Result { + let inner = &mut *self.inner.borrow_mut(); + let row = &mut inner.rows[self.row]; + let col = &mut inner.cols[self.col]; + + // using unwrap for demo purposes, you should propagate errors up instead. + col.set_low().unwrap(); + let out = row.is_low().unwrap(); + col.set_high().unwrap(); + + Ok(out) + } +} +``` + + +## Prelude + +The prelude has been removed because it could make method calls ambiguous, since the method names are now +the same across traits. +To overcome this, please import the traits you wish to use individually. + +If you run into ambiguous method calls, you can disambiguate using the fully-qualified syntax (the error message +from the compiler should already tell you how it should look in your case) or tweak your trait imports or code +to limit the scope of the trait imports and thus avoid the ambiguity. +Please note that it is also possible to import traits *inside a function*. + +## Removed blanket implementations + +There were several blanket implementations of blocking traits using the non-blocking +traits as a base. + +Since the non-blocking traits have been extracted into the separate crate `embedded-hal-nb`, +these have been removed. + +## Cargo features + +The `unproven` feature has been removed and the traits have been marked as proven. +In the past, managing unproven features, and having "sort of breaking" changes has been a struggling point. +Also, people tended to adopt `unproven` features quickly, but the features would take a very +long time to stabilize. + +Instead, we would like to push experimentation OUT of the `embedded-hal` crate, allowing people to +experiment externally, and merge when some kind of feasibility had been proven. + +## Companion crates + +The `embedded-hal` project now spans several crates, where some functionality has been moved out from the main `embedded-hal` crate to separate crates as detailed above. + +Different crates are released independently. The main `embedded-hal-*` trait crates have reached 1.0 maturity, others will become 1.0 as time passes. + +Here is the full listing of crates: + +| Crate | crates.io | Docs | | +|-|-|-|-| +| [embedded-hal](../embedded-hal) | [![crates.io](https://img.shields.io/crates/v/embedded-hal.svg)](https://crates.io/crates/embedded-hal) | [![Documentation](https://docs.rs/embedded-hal/badge.svg)](https://docs.rs/embedded-hal) | Core traits, blocking version | +| [embedded-hal-async](../embedded-hal-async) | [![crates.io](https://img.shields.io/crates/v/embedded-hal-async.svg)](https://crates.io/crates/embedded-hal-async) | [![Documentation](https://docs.rs/embedded-hal-async/badge.svg)](https://docs.rs/embedded-hal-async) | Core traits, async version | +| [embedded-hal-nb](../embedded-hal-nb) | [![crates.io](https://img.shields.io/crates/v/embedded-hal-nb.svg)](https://crates.io/crates/embedded-hal-nb) | [![Documentation](https://docs.rs/embedded-hal-nb/badge.svg)](https://docs.rs/embedded-hal-nb) | Core traits, polling version using the `nb` crate | +| [embedded-hal-bus](../embedded-hal-bus) | [![crates.io](https://img.shields.io/crates/v/embedded-hal-bus.svg)](https://crates.io/crates/embedded-hal-bus) | [![Documentation](https://docs.rs/embedded-hal-bus/badge.svg)](https://docs.rs/embedded-hal-bus) | Utilities for sharing SPI and I2C buses | +| [embedded-can](../embedded-can) | [![crates.io](https://img.shields.io/crates/v/embedded-can.svg)](https://crates.io/crates/embedded-can) | [![Documentation](https://docs.rs/embedded-can/badge.svg)](https://docs.rs/embedded-can) | Controller Area Network (CAN) traits | +| [embedded-io](../embedded-io) | [![crates.io](https://img.shields.io/crates/v/embedded-io.svg)](https://crates.io/crates/embedded-io) | [![Documentation](https://docs.rs/embedded-io/badge.svg)](https://docs.rs/embedded-io) | I/O traits (read, write, seek, etc.), blocking and nonblocking version. | +| [embedded-io-async](../embedded-io-async) | [![crates.io](https://img.shields.io/crates/v/embedded-io-async.svg)](https://crates.io/crates/embedded-io-async) | [![Documentation](https://docs.rs/embedded-io-async/badge.svg)](https://docs.rs/embedded-io-async) | I/O traits, async version | +| [embedded-io-adapters](../embedded-io-adapters) | [![crates.io](https://img.shields.io/crates/v/embedded-io-adapters.svg)](https://crates.io/crates/embedded-io-adapters) | [![Documentation](https://docs.rs/embedded-io-adapters/badge.svg)](https://docs.rs/embedded-io-adapters) | Adapters between the [`embedded-io`](https://crates.io/crates/embedded-io) and [`embedded-io-async`](https://crates.io/crates/embedded-io-async) traits and other IO traits (`std`, `tokio`, `futures`...) | + +## Supporting both 0.2 and 1.0 in the same HAL + +It is strongly recommended that HAL implementation crates provide implementations for both the `embedded-hal` v0.2 and v1.0 traits. +This allows users to use drivers using either version seamlessly. + +The way you do it is adding a dependency on both versions in `Cargo.toml` like this: + +```toml +[dependencies] +embedded-hal-02 = { package = "embedded-hal", version = "0.2.7", features = ["unproven"] } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +``` + +This allows you to refer to the v0.2 traits under the `embedded_hal_02` name, and the v1.0 traits under +`embedded_hal_1`. Implement both versions on the same struct. For example, for an input pin: + +```rust +/// The HAL's input pin struct +struct Input {...} + +/// Implement the v0.2 traits on the struct. +impl embedded_hal_02::digital::v2::InputPin for Input { + type Error = Infallible; + + fn is_high(&self) -> Result { + ... + } + + fn is_low(&self) -> Result { + ... + } +} + +/// ... and implement the v1.0 traits on the *same* struct. +impl embedded_hal_1::digital::ErrorType for Input { + type Error = Infallible; +} + +impl embedded_hal_1::digital::InputPin for Input { + fn is_high(&mut self) -> Result { + ... + } + + fn is_low(&mut self) -> Result { + ... + } +} +``` + +## `embedded-hal-compat` + +For HAL implementation crates that haven't been updated yet, [embedded-hal-compat](https://github.com/ryankurte/embedded-hal-compat) +provides shims to support interoperability between `embedded-hal` v0.2 and v1.0. + +This allows using a driver requiring v1.0 with a HAL crate implementing only v0.2 or vice-versa, (generally) without alteration. +See the [docs](https://docs.rs/embedded-hal-compat/) for examples. diff --git a/docs/version-policy.md b/docs/version-policy.md deleted file mode 100644 index 2f4f0e75e..000000000 --- a/docs/version-policy.md +++ /dev/null @@ -1,26 +0,0 @@ -# Version policy - -At the moment we are working towards a `1.0.0` release (see [#177]). During this process we will -release alpha versions like `1.0.0-alpha.1` and `1.0.0-alpha.2`. -Alpha releases are **not guaranteed** to be compatible with each other. -They are provided as early previews for community testing and preparation for the final release. -If you use an alpha release, we recommend you choose an exact version specification in your -`Cargo.toml` like: `embedded-hal = "=1.0.0-alpha.2"` - -See below for a way to implement both an `embedded-hal` `0.2.x` version and an `-alpha` version -side by side in a HAL. - -[#177]: https://github.com/rust-embedded/embedded-hal/issues/177 - -## Supporting different (alpha and non-alpha) HALs - -[embedded-hal-compat](https://github.com/ryankurte/embedded-hal-compat) provides shims -to support interoperability between the latest `0.2.x` and `1.0.0-alpha.N` HALs, allowing one to use -incompatible HAL components (generally) without alteration. -See the [docs](https://docs.rs/embedded-hal-compat/) for examples. - -It is also possible for HAL implementations to support both the latest `0.2.x` and `1.0.0-alpha.N` versions -side by side, for an example see [LPC8xx HAL](https://github.com/lpc-rs/lpc8xx-hal). - -Note that `embedded-hal` `-alpha` versions are a moving target and _not guaranteed_ to be compatible. -Because of this we only aim to support the latest `-alpha`. diff --git a/embedded-can/CHANGELOG.md b/embedded-can/CHANGELOG.md index 302c85a09..f8719d632 100644 --- a/embedded-can/CHANGELOG.md +++ b/embedded-can/CHANGELOG.md @@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] -... +- Added `core::error::Error` implementations for every custom `impl Error` +- Increased MSRV to 1.81 due to `core::error::Error` ## [v0.4.1] - 2022-09-28 diff --git a/embedded-can/Cargo.toml b/embedded-can/Cargo.toml index 4ed2ede3c..105f286b0 100644 --- a/embedded-can/Cargo.toml +++ b/embedded-can/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "embedded-can" version = "0.4.1" -edition = "2018" +edition = "2021" +rust-version = "1.81" description = "HAL traits for Controller Area Network (CAN) devices." categories = ["embedded", "hardware-support", "no-std"] @@ -13,3 +14,7 @@ repository = "https://github.com/rust-embedded/embedded-hal" [dependencies] nb = "1" +defmt = { version = "0.3", optional = true } + +[features] +defmt-03 = ["dep:defmt"] diff --git a/embedded-can/README.md b/embedded-can/README.md index 7c68b024c..1beaf39cd 100644 --- a/embedded-can/README.md +++ b/embedded-can/README.md @@ -1,7 +1,7 @@ [![crates.io](https://img.shields.io/crates/d/embedded-can.svg)](https://crates.io/crates/embedded-can) [![crates.io](https://img.shields.io/crates/v/embedded-can.svg)](https://crates.io/crates/embedded-can) [![Documentation](https://docs.rs/embedded-can/badge.svg)](https://docs.rs/embedded-can) -![Minimum Supported Rust Version](https://img.shields.io/badge/rustc-1.54+-blue.svg) +![Minimum Supported Rust Version](https://img.shields.io/badge/rustc-1.60+-blue.svg) # `embedded-can` @@ -13,9 +13,13 @@ This project is developed and maintained by the [HAL team](https://github.com/ru [API reference]: https://docs.rs/embedded-can +## Optional features + +- **`defmt-03`**: Derive `defmt::Format` from `defmt` 0.3 for enums and structs. + ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.54 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.60 and up. It *might* compile with older versions but that may change in any new patch release. See [here](../docs/msrv.md) for details on how the MSRV may be upgraded. diff --git a/embedded-can/src/id.rs b/embedded-can/src/id.rs index 88ddaa0a2..071e7b933 100644 --- a/embedded-can/src/id.rs +++ b/embedded-can/src/id.rs @@ -2,6 +2,7 @@ /// Standard 11-bit CAN Identifier (`0..=0x7FF`). #[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] pub struct StandardId(u16); impl StandardId { @@ -9,14 +10,18 @@ impl StandardId { pub const ZERO: Self = Self(0); /// CAN ID `0x7FF`, the lowest priority. - pub const MAX: Self = Self(0x7FF); + pub const MAX: Self = Self(Self::MAX_RAW); + + /// Raw CAN ID `0x7FF`, the lowest priority. + pub const MAX_RAW: u16 = 0x7FF; /// Tries to create a `StandardId` from a raw 16-bit integer. /// /// This will return `None` if `raw` is out of range of an 11-bit integer (`> 0x7FF`). #[inline] + #[must_use] pub const fn new(raw: u16) -> Option { - if raw <= 0x7FF { + if raw <= Self::MAX_RAW { Some(Self(raw)) } else { None @@ -28,19 +33,22 @@ impl StandardId { /// # Safety /// Using this method can create an invalid ID and is thus marked as unsafe. #[inline] + #[must_use] pub const unsafe fn new_unchecked(raw: u16) -> Self { Self(raw) } /// Returns this CAN Identifier as a raw 16-bit integer. #[inline] - pub fn as_raw(&self) -> u16 { + #[must_use] + pub const fn as_raw(&self) -> u16 { self.0 } } /// Extended 29-bit CAN Identifier (`0..=1FFF_FFFF`). #[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] pub struct ExtendedId(u32); impl ExtendedId { @@ -48,14 +56,18 @@ impl ExtendedId { pub const ZERO: Self = Self(0); /// CAN ID `0x1FFFFFFF`, the lowest priority. - pub const MAX: Self = Self(0x1FFF_FFFF); + pub const MAX: Self = Self(Self::MAX_RAW); + + /// Raw CAN ID `0x1FFFFFFF`, the lowest priority. + pub const MAX_RAW: u32 = 0x1FFF_FFFF; /// Tries to create a `ExtendedId` from a raw 32-bit integer. /// /// This will return `None` if `raw` is out of range of an 29-bit integer (`> 0x1FFF_FFFF`). #[inline] + #[must_use] pub const fn new(raw: u32) -> Option { - if raw <= 0x1FFF_FFFF { + if raw <= Self::MAX_RAW { Some(Self(raw)) } else { None @@ -67,17 +79,20 @@ impl ExtendedId { /// # Safety /// Using this method can create an invalid ID and is thus marked as unsafe. #[inline] + #[must_use] pub const unsafe fn new_unchecked(raw: u32) -> Self { Self(raw) } /// Returns this CAN Identifier as a raw 32-bit integer. #[inline] - pub fn as_raw(&self) -> u32 { + #[must_use] + pub const fn as_raw(&self) -> u32 { self.0 } /// Returns the Base ID part of this extended identifier. + #[must_use] pub fn standard_id(&self) -> StandardId { // ID-28 to ID-18 StandardId((self.0 >> 18) as u16) @@ -86,6 +101,7 @@ impl ExtendedId { /// A CAN Identifier (standard or extended). #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] pub enum Id { /// Standard 11-bit Identifier (`0..=0x7FF`). Standard(StandardId), @@ -94,6 +110,23 @@ pub enum Id { Extended(ExtendedId), } +impl Id { + /// Creates a CAN identifier as a standard ID. + pub fn new_standard_id(raw: u16) -> Option { + Some(Id::from(StandardId::new(raw)?)) + } + + /// Creates a CAN identifier as an extended ID. + pub fn new_extended_id(raw: u32) -> Option { + Some(Id::from(ExtendedId::new(raw)?)) + } + + /// Determines if the value is an extended identifier. + pub fn is_extended(&self) -> bool { + matches!(self, Id::Extended(_)) + } +} + /// Implement `Ord` according to the CAN arbitration rules /// /// When performing arbitration, frames are looked at bit for bit starting @@ -153,19 +186,19 @@ mod tests { #[test] fn standard_id_new() { assert_eq!( - StandardId::new(StandardId::MAX.as_raw()), + StandardId::new(StandardId::MAX_RAW), Some(StandardId::MAX) ); } #[test] fn standard_id_new_out_of_range() { - assert_eq!(StandardId::new(StandardId::MAX.as_raw() + 1), None); + assert_eq!(StandardId::new(StandardId::MAX_RAW + 1), None); } #[test] fn standard_id_new_unchecked_out_of_range() { - let id = StandardId::MAX.as_raw() + 1; + let id = StandardId::MAX_RAW + 1; assert_eq!(unsafe { StandardId::new_unchecked(id) }, StandardId(id)); } @@ -179,12 +212,12 @@ mod tests { #[test] fn extended_id_new_out_of_range() { - assert_eq!(ExtendedId::new(ExtendedId::MAX.as_raw() + 1), None); + assert_eq!(ExtendedId::new(ExtendedId::MAX_RAW + 1), None); } #[test] fn extended_id_new_unchecked_out_of_range() { - let id = ExtendedId::MAX.as_raw() + 1; + let id = ExtendedId::MAX_RAW + 1; assert_eq!(unsafe { ExtendedId::new_unchecked(id) }, ExtendedId(id)); } @@ -206,4 +239,28 @@ mod tests { assert!(Id::Extended(ExtendedId((1 << 11) - 1)) < Id::Standard(StandardId(1))); assert!(Id::Standard(StandardId(1)) < Id::Extended(ExtendedId::MAX)); } + + #[test] + fn id_new() { + let id = Id::new_standard_id(StandardId::MAX_RAW); + match id { + Some(Id::Standard(id)) => assert_eq!(StandardId::MAX, id), + _ => assert!(false), + } + + let id = Id::new_extended_id(ExtendedId::MAX_RAW); + match id { + Some(Id::Extended(id)) => assert_eq!(ExtendedId::MAX, id), + _ => assert!(false), + } + } + + #[test] + fn id_raw() { + let id = StandardId::new(StandardId::MAX_RAW).unwrap(); + assert_eq!(StandardId::MAX_RAW, id.as_raw()); + + let id = ExtendedId::new(ExtendedId::MAX_RAW).unwrap(); + assert_eq!(ExtendedId::MAX_RAW, id.as_raw()); + } } diff --git a/embedded-can/src/lib.rs b/embedded-can/src/lib.rs index db80c455b..bb010b965 100644 --- a/embedded-can/src/lib.rs +++ b/embedded-can/src/lib.rs @@ -22,7 +22,7 @@ pub trait Frame: Sized { /// This will return `None` if the data length code (DLC) is not valid. fn new_remote(id: impl Into, dlc: usize) -> Option; - /// Returns true if this frame is a extended frame. + /// Returns true if this frame is an extended frame. fn is_extended(&self) -> bool; /// Returns true if this frame is a standard frame. @@ -73,6 +73,7 @@ impl Error for core::convert::Infallible { /// free to define more specific or additional error types. However, by providing /// a mapping to these common CAN errors, generic code can still react to them. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] #[non_exhaustive] pub enum ErrorKind { /// The peripheral receive buffer was overrun. @@ -109,6 +110,8 @@ impl Error for ErrorKind { } } +impl core::error::Error for ErrorKind {} + impl core::fmt::Display for ErrorKind { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { diff --git a/embedded-hal-async/CHANGELOG.md b/embedded-hal-async/CHANGELOG.md index dbed8a393..cfd5ad051 100644 --- a/embedded-hal-async/CHANGELOG.md +++ b/embedded-hal-async/CHANGELOG.md @@ -7,6 +7,61 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +No unreleased changes yet. + +## [v1.0.0] - 2023-12-28 + +Check out the [announcement blog post](https://blog.rust-embedded.org/embedded-hal-v1/) and the [migration guide](../docs/migrating-from-0.2-to-1.0.md) for help with migrating from v0.2 to v1.0. + +- Updated `embedded-hal` to version `1.0.0`. + +## [v1.0.0-rc.3] - 2023-12-14 + +- Updated `embedded-hal` to version `1.0.0-rc.3`. + +## [v1.0.0-rc.2] - 2023-11-28 + +- Updated `embedded-hal` to version `1.0.0-rc.2`. +- Minor document fixes. +- Add #[inline] hints to most of `embedded-hal-async` functions. +- delay: Rename `DelayUs` to `DelayNs` +- delay: Add `DelayNs::delay_ns()` +- delay: Add default impls of `delay_ms` and `delay_us` based on `delay_ns`. +- spi: Rename `Operation::DelayUs` to `Operation::DelayNs`, with nanosecond precision. +- Use `feature()` on nightly toolchains only. This adds support for 1.75 beta and stable. + +## [v1.0.0-rc.1] - 2023-08-15 + +- Updated `embedded-hal` to version `1.0.0-rc.1`. +- Add optional `defmt` 0.3 support. +- Remove serial traits, the replacement is the `embedded-io` crate. +- Added `+ ?Sized` to all blanket impls. +- Moved `ExclusiveDevice` to `embedded-hal-bus`. + +## [v0.2.0-alpha.2] - 2023-07-04 + +### Added +- spi: added `Operation::DelayUs(u32)`. + +### Changed +- Updated `embedded-hal` to version `1.0.0-alpha.11`. +- spi: removed redundant lifetime annotations. Note that recent nightlies care about them and require impls to match, so you might have to adjust them. + +### Removed +- spi: removed read-only and write-only traits. + +## [v0.2.0-alpha.1] - 2023-04-04 + +### Added +- Added a `serial::Write` trait. + +### Changed +- Updated `embedded-hal` to version `1.0.0-alpha.10`. +- delay: make infallible. +- i2c: remove `_iter()` methods. +- i2c: add default implementations for all methods based on `transaction()`. +- spi: SpiDevice transaction now takes an operation slice instead of a closure + ## [v0.2.0-alpha.0] - 2022-11-23 - Switch all traits to use [`async_fn_in_trait`](https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html) (AFIT). Requires `nightly-2022-11-22` or newer. @@ -37,7 +92,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). First release to crates.io -[Unreleased]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-async-v0.2.0-alpha.0...HEAD +[Unreleased]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-async-v1.0.0...HEAD +[v1.0.0]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-async-v1.0.0-rc.3...embedded-hal-async-v1.0.0 +[v1.0.0-rc.3]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-async-v1.0.0-rc.2...embedded-hal-async-v1.0.0-rc.3 +[v1.0.0-rc.2]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-async-v1.0.0-rc.1...embedded-hal-async-v1.0.0-rc.2 +[v1.0.0-rc.1]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-async-v0.2.0-alpha.2...embedded-hal-async-v1.0.0-rc.1 +[v0.2.0-alpha.2]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-async-v0.2.0-alpha.1...embedded-hal-async-v0.2.0-alpha.2 +[v0.2.0-alpha.1]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-async-v0.2.0-alpha.0...embedded-hal-async-v0.2.0-alpha.1 [v0.2.0-alpha.0]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-async-v0.1.0-alpha.3...embedded-hal-async-v0.2.0-alpha.0 [v0.1.0-alpha.3]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-async-v0.1.0-alpha.2...embedded-hal-async-v0.1.0-alpha.3 [v0.1.0-alpha.2]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-async-v0.1.0-alpha.1...embedded-hal-async-v0.1.0-alpha.2 diff --git a/embedded-hal-async/Cargo.toml b/embedded-hal-async/Cargo.toml index 7078beb6e..4d2679336 100644 --- a/embedded-hal-async/Cargo.toml +++ b/embedded-hal-async/Cargo.toml @@ -5,13 +5,18 @@ authors = [ categories = ["asynchronous", "embedded", "hardware-support", "no-std"] description = "An asynchronous Hardware Abstraction Layer (HAL) for embedded systems" documentation = "https://docs.rs/embedded-hal-async" -edition = "2018" +edition = "2021" keywords = ["hal", "IO"] license = "MIT OR Apache-2.0" name = "embedded-hal-async" readme = "README.md" repository = "https://github.com/rust-embedded/embedded-hal" -version = "0.2.0-alpha.0" +version = "1.0.0" +rust-version = "1.75" + +[features] +defmt-03 = ["dep:defmt-03", "embedded-hal/defmt-03"] [dependencies] -embedded-hal = { version = "=1.0.0-alpha.9", path = "../embedded-hal" } +embedded-hal = { version = "1.0.0", path = "../embedded-hal" } +defmt-03 = { package = "defmt", version = "0.3", optional = true } diff --git a/embedded-hal-async/README.md b/embedded-hal-async/README.md index 819c13488..39440d5c7 100644 --- a/embedded-hal-async/README.md +++ b/embedded-hal-async/README.md @@ -1,39 +1,40 @@ [![crates.io](https://img.shields.io/crates/d/embedded-hal-async.svg)](https://crates.io/crates/embedded-hal-async) [![crates.io](https://img.shields.io/crates/v/embedded-hal-async.svg)](https://crates.io/crates/embedded-hal-async) [![Documentation](https://docs.rs/embedded-hal-async/badge.svg)](https://docs.rs/embedded-hal-async) - # `embedded-hal-async` An asynchronous Hardware Abstraction Layer (HAL) for embedded systems. This crate contains asynchronous versions of the [`embedded-hal`](https://crates.io/crates/embedded-hal) traits and shares its scope and [design goals](https://docs.rs/embedded-hal/latest/embedded_hal/#design-goals). -The purpose of this crate is to iterate over these trait versions before integrating them into [`embedded-hal`](https://crates.io/crates/embedded-hal). - -**NOTE** These traits are still experimental. At least one breaking change to this crate is expected in the future (changing from GATs to `async fn`), but there might be more. This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team). -## [API reference] +## Serial/UART traits + +There is no serial traits in `embedded-hal-async`. Instead, use [`embedded-io-async`](https://crates.io/crates/embedded-io-async). +A serial port is essentially a byte-oriented stream, and that's what `embedded-io-async` models. Sharing the traits +with all byte streams has some advantages. For example, it allows generic code providing a command-line interface +or a console to operate either on hardware serial ports or on virtual ones like Telnet or USB CDC-ACM. -[API reference]: https://docs.rs/embedded-hal-async +## Optional Cargo features + +- **`defmt-03`**: Derive `defmt::Format` from `defmt` 0.3 for enums and structs. - + +See [here](../docs/msrv.md) for details on how the MSRV may be upgraded. ## License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. diff --git a/embedded-hal-async/src/delay.rs b/embedded-hal-async/src/delay.rs index 9106dc4d7..87c540d2d 100644 --- a/embedded-hal-async/src/delay.rs +++ b/embedded-hal-async/src/delay.rs @@ -1,30 +1,49 @@ -//! Delays +//! Delays. -/// Microsecond delay -pub trait DelayUs { - /// Enumeration of errors - type Error: core::fmt::Debug; +/// Delay with up to nanosecond precision. +pub trait DelayNs { + /// Pauses execution for at minimum `ns` nanoseconds. Pause can be longer + /// if the implementation requires it due to precision/timing issues. + async fn delay_ns(&mut self, ns: u32); /// Pauses execution for at minimum `us` microseconds. Pause can be longer /// if the implementation requires it due to precision/timing issues. - async fn delay_us(&mut self, us: u32) -> Result<(), Self::Error>; + async fn delay_us(&mut self, mut us: u32) { + while us > 4_294_967 { + us -= 4_294_967; + self.delay_ns(4_294_967_000).await; + } + self.delay_ns(us * 1_000).await; + } /// Pauses execution for at minimum `ms` milliseconds. Pause can be longer /// if the implementation requires it due to precision/timing issues. - async fn delay_ms(&mut self, ms: u32) -> Result<(), Self::Error>; + #[inline] + async fn delay_ms(&mut self, mut ms: u32) { + while ms > 4294 { + ms -= 4294; + self.delay_ns(4_294_000_000).await; + } + self.delay_ns(ms * 1_000_000).await; + } } -impl DelayUs for &mut T +impl DelayNs for &mut T where - T: DelayUs, + T: DelayNs + ?Sized, { - type Error = T::Error; + #[inline] + async fn delay_ns(&mut self, ns: u32) { + T::delay_ns(self, ns).await; + } - async fn delay_us(&mut self, us: u32) -> Result<(), Self::Error> { - T::delay_us(self, us).await + #[inline] + async fn delay_us(&mut self, us: u32) { + T::delay_us(self, us).await; } - async fn delay_ms(&mut self, ms: u32) -> Result<(), Self::Error> { - T::delay_ms(self, ms).await + #[inline] + async fn delay_ms(&mut self, ms: u32) { + T::delay_ms(self, ms).await; } } diff --git a/embedded-hal-async/src/digital.rs b/embedded-hal-async/src/digital.rs index 600876975..5efe3d180 100644 --- a/embedded-hal-async/src/digital.rs +++ b/embedded-hal-async/src/digital.rs @@ -1,4 +1,4 @@ -//! Asynchronous digital I/O +//! Asynchronous digital I/O. //! //! # Example //! @@ -15,9 +15,10 @@ //! .expect("failed to await input pin") //! } //! ``` +pub use embedded_hal::digital::{Error, ErrorKind, ErrorType}; /// Asynchronously wait for GPIO pin state. -pub trait Wait: embedded_hal::digital::ErrorType { +pub trait Wait: ErrorType { /// Wait until the pin is high. If it is already high, return immediately. /// /// # Note for implementers @@ -48,23 +49,28 @@ pub trait Wait: embedded_hal::digital::ErrorType { async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error>; } -impl Wait for &mut T { +impl Wait for &mut T { + #[inline] async fn wait_for_high(&mut self) -> Result<(), Self::Error> { T::wait_for_high(self).await } + #[inline] async fn wait_for_low(&mut self) -> Result<(), Self::Error> { T::wait_for_low(self).await } + #[inline] async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { T::wait_for_rising_edge(self).await } + #[inline] async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { T::wait_for_falling_edge(self).await } + #[inline] async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { T::wait_for_any_edge(self).await } diff --git a/embedded-hal-async/src/i2c.rs b/embedded-hal-async/src/i2c.rs index 4f84beb0d..86014eec0 100644 --- a/embedded-hal-async/src/i2c.rs +++ b/embedded-hal-async/src/i2c.rs @@ -1,4 +1,4 @@ -//! Async I2C API +//! Async I2C API. //! //! This API supports 7-bit and 10-bit addresses. Traits feature an `AddressMode` //! marker type parameter. Two implementation of the `AddressMode` exist: @@ -16,14 +16,14 @@ //! Since 7-bit addressing is the mode of the majority of I2C devices, //! `SevenBitAddress` has been set as default mode and thus can be omitted if desired. -pub use embedded_hal::i2c::Operation; pub use embedded_hal::i2c::{ - AddressMode, Error, ErrorKind, ErrorType, NoAcknowledgeSource, SevenBitAddress, TenBitAddress, + AddressMode, Error, ErrorKind, ErrorType, NoAcknowledgeSource, Operation, SevenBitAddress, + TenBitAddress, }; -/// Async i2c +/// Async I2c. pub trait I2c: ErrorType { - /// Reads enough bytes from slave with `address` to fill `buffer` + /// Reads enough bytes from slave with `address` to fill `buffer`. /// /// # I2C Events (contract) /// @@ -41,9 +41,13 @@ pub trait I2c: ErrorType { /// - `MAK` = master acknowledge /// - `NMAK` = master no acknowledge /// - `SP` = stop condition - async fn read<'a>(&'a mut self, address: A, read: &'a mut [u8]) -> Result<(), Self::Error>; + #[inline] + async fn read(&mut self, address: A, read: &mut [u8]) -> Result<(), Self::Error> { + self.transaction(address, &mut [Operation::Read(read)]) + .await + } - /// Writes bytes to slave with address `address` + /// Writes bytes to slave with address `address`. /// /// # I2C Events (contract) /// @@ -59,7 +63,11 @@ pub trait I2c: ErrorType { /// - `SAK` = slave acknowledge /// - `Bi` = ith byte of data /// - `SP` = stop condition - async fn write<'a>(&'a mut self, address: A, write: &'a [u8]) -> Result<(), Self::Error>; + #[inline] + async fn write(&mut self, address: A, write: &[u8]) -> Result<(), Self::Error> { + self.transaction(address, &mut [Operation::Write(write)]) + .await + } /// Writes bytes to slave with address `address` and then reads enough bytes to fill `read` *in a /// single transaction*. @@ -83,12 +91,19 @@ pub trait I2c: ErrorType { /// - `MAK` = master acknowledge /// - `NMAK` = master no acknowledge /// - `SP` = stop condition - async fn write_read<'a>( - &'a mut self, + #[inline] + async fn write_read( + &mut self, address: A, - write: &'a [u8], - read: &'a mut [u8], - ) -> Result<(), Self::Error>; + write: &[u8], + read: &mut [u8], + ) -> Result<(), Self::Error> { + self.transaction( + address, + &mut [Operation::Write(write), Operation::Read(read)], + ) + .await + } /// Execute the provided operations on the I2C bus as a single transaction. /// @@ -97,41 +112,45 @@ pub trait I2c: ErrorType { /// - Data from adjacent operations of the same type are sent after each other without an SP or SR. /// - Between adjacent operations of a different type an SR and SAD+R/W is sent. /// - After executing the last operation an SP is sent automatically. - /// - If the last operation is a `Read` the master does not send an acknowledge for the last byte. + /// - At the end of each read operation (before SP or SR), the master does not send an acknowledge for the last byte. /// /// - `ST` = start condition /// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 to indicate writing /// - `SR` = repeated start condition /// - `SP` = stop condition - async fn transaction<'a, 'b>( - &'a mut self, + async fn transaction( + &mut self, address: A, - operations: &'a mut [Operation<'b>], + operations: &mut [Operation<'_>], ) -> Result<(), Self::Error>; } -impl> I2c for &mut T { - async fn read<'a>(&'a mut self, address: A, buffer: &'a mut [u8]) -> Result<(), Self::Error> { - T::read(self, address, buffer).await +impl + ?Sized> I2c for &mut T { + #[inline] + async fn read(&mut self, address: A, read: &mut [u8]) -> Result<(), Self::Error> { + T::read(self, address, read).await } - async fn write<'a>(&'a mut self, address: A, bytes: &'a [u8]) -> Result<(), Self::Error> { - T::write(self, address, bytes).await + #[inline] + async fn write(&mut self, address: A, write: &[u8]) -> Result<(), Self::Error> { + T::write(self, address, write).await } - async fn write_read<'a>( - &'a mut self, + #[inline] + async fn write_read( + &mut self, address: A, - bytes: &'a [u8], - buffer: &'a mut [u8], + write: &[u8], + read: &mut [u8], ) -> Result<(), Self::Error> { - T::write_read(self, address, bytes, buffer).await + T::write_read(self, address, write, read).await } - async fn transaction<'a, 'b>( - &'a mut self, + #[inline] + async fn transaction( + &mut self, address: A, - operations: &'a mut [Operation<'b>], + operations: &mut [Operation<'_>], ) -> Result<(), Self::Error> { T::transaction(self, address, operations).await } diff --git a/embedded-hal-async/src/lib.rs b/embedded-hal-async/src/lib.rs index 14748349c..cbd74f5d4 100644 --- a/embedded-hal-async/src/lib.rs +++ b/embedded-hal-async/src/lib.rs @@ -1,16 +1,7 @@ -//! An asynchronous Hardware Abstraction Layer (HAL) for embedded systems -//! -//! **NOTE** These traits are still experimental. At least one breaking -//! change to this crate is expected in the future (changing from GATs to -//! `async fn`), but there might be more. -//! -//! **NOTE** The traits and modules in this crate should follow the same structure as in -//! `embedded-hal` to ease merging and migration. - +#![doc = include_str!("../README.md")] #![warn(missing_docs)] #![no_std] -#![allow(incomplete_features)] -#![feature(async_fn_in_trait, impl_trait_projections)] +#![allow(async_fn_in_trait)] pub mod delay; pub mod digital; diff --git a/embedded-hal-async/src/spi.rs b/embedded-hal-async/src/spi.rs index 2f563820c..69a46a468 100644 --- a/embedded-hal-async/src/spi.rs +++ b/embedded-hal-async/src/spi.rs @@ -1,293 +1,130 @@ //! SPI master mode traits. -use core::{fmt::Debug, future::Future}; - -use embedded_hal::digital::OutputPin; -use embedded_hal::spi as blocking; pub use embedded_hal::spi::{ - Error, ErrorKind, ErrorType, Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3, + Error, ErrorKind, ErrorType, Mode, Operation, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3, }; -#[macro_export] -/// Do an SPI transaction on a bus. -/// This is a safe wrapper for [SpiDevice::transaction], which handles dereferencing the raw pointer for you. -/// -/// # Examples -/// -/// ``` -/// use embedded_hal_async::spi::{transaction, SpiBus, SpiBusRead, SpiBusWrite, SpiDevice}; -/// -/// pub async fn transaction_example(mut device: SPI) -> Result -/// where -/// SPI: SpiDevice, -/// SPI::Bus: SpiBus, -/// { -/// transaction!(&mut device, move |bus| async move { -/// // Unlike `SpiDevice::transaction`, we don't need to -/// // manually dereference a pointer in order to use the bus. -/// bus.write(&[42]).await?; -/// let mut data = [0; 4]; -/// bus.read(&mut data).await?; -/// Ok(u32::from_be_bytes(data)) -/// }) -/// .await -/// } -/// ``` -/// -/// Note that the compiler will prevent you from moving the bus reference outside of the closure -/// ```compile_fail -/// # use embedded_hal_async::spi::{transaction, SpiBus, SpiBusRead, SpiBusWrite, SpiDevice}; -/// # -/// # pub async fn smuggle_test(mut device: SPI) -> Result<(), SPI::Error> -/// # where -/// # SPI: SpiDevice, -/// # SPI::Bus: SpiBus, -/// # { -/// let mut bus_smuggler: Option<&mut SPI::Bus> = None; -/// transaction!(&mut device, move |bus| async move { -/// bus_smuggler = Some(bus); -/// Ok(()) -/// }) -/// .await -/// # } -/// ``` -macro_rules! spi_transaction { - ($device:expr, move |$bus:ident| async move $closure_body:expr) => { - $crate::spi::SpiDevice::transaction($device, move |$bus| { - // Safety: Implementers of the `SpiDevice` trait guarantee that the pointer is - // valid and dereferencable for the entire duration of the closure. - let $bus = unsafe { &mut *$bus }; - async move { - let result = $closure_body; - let $bus = $bus; // Ensure that the bus reference was not moved out of the closure - let _ = $bus; // Silence the "unused variable" warning from prevous line - result - } - }) - }; - ($device:expr, move |$bus:ident| async $closure_body:expr) => { - $crate::spi::SpiDevice::transaction($device, move |$bus| { - // Safety: Implementers of the `SpiDevice` trait guarantee that the pointer is - // valid and dereferencable for the entire duration of the closure. - let $bus = unsafe { &mut *$bus }; - async { - let result = $closure_body; - let $bus = $bus; // Ensure that the bus reference was not moved out of the closure - let _ = $bus; // Silence the "unused variable" warning from prevous line - result - } - }) - }; - ($device:expr, |$bus:ident| async move $closure_body:expr) => { - $crate::spi::SpiDevice::transaction($device, |$bus| { - // Safety: Implementers of the `SpiDevice` trait guarantee that the pointer is - // valid and dereferencable for the entire duration of the closure. - let $bus = unsafe { &mut *$bus }; - async move { - let result = $closure_body; - let $bus = $bus; // Ensure that the bus reference was not moved out of the closure - let _ = $bus; // Silence the "unused variable" warning from prevous line - result - } - }) - }; - ($device:expr, |$bus:ident| async $closure_body:expr) => { - $crate::spi::SpiDevice::transaction($device, |$bus| { - // Safety: Implementers of the `SpiDevice` trait guarantee that the pointer is - // valid and dereferencable for the entire duration of the closure. - let $bus = unsafe { &mut *$bus }; - async { - let result = $closure_body; - let $bus = $bus; // Ensure that the bus reference was not moved out of the closure - let _ = $bus; // Silence the "unused variable" warning from prevous line - result - } - }) - }; -} - -#[doc(inline)] -pub use spi_transaction as transaction; - -/// SPI device trait +/// SPI device trait. /// /// `SpiDevice` represents ownership over a single SPI device on a (possibly shared) bus, selected /// with a CS (Chip Select) pin. /// -/// See (the docs on embedded-hal)[embedded_hal::spi::blocking] for important information on SPI Bus vs Device traits. -/// -/// # Safety -/// -/// See [`SpiDevice::transaction`] for details. -pub unsafe trait SpiDevice: ErrorType { - /// SPI Bus type for this device. - type Bus: ErrorType; - +/// See [the docs on embedded-hal](embedded_hal::spi) for important information on SPI Bus vs Device traits. +pub trait SpiDevice: ErrorType { /// Perform a transaction against the device. /// - /// **NOTE:** - /// It is not recommended to use this method directly, because it requires `unsafe` code to dereference the raw pointer. - /// Instead, the [`transaction!`] macro should be used, which handles this safely inside the macro. - /// /// - Locks the bus /// - Asserts the CS (Chip Select) pin. - /// - Calls `f` with an exclusive reference to the bus, which can then be used to do transfers against the device. - /// - [Flushes](SpiBusFlush::flush) the bus. + /// - Performs all the operations. + /// - [Flushes](SpiBus::flush) the bus. /// - Deasserts the CS pin. /// - Unlocks the bus. /// /// The locking mechanism is implementation-defined. The only requirement is it must prevent two /// transactions from executing concurrently against the same bus. Examples of implementations are: - /// critical sections, blocking mutexes, async mutexes, returning an error or panicking if the bus is already busy. + /// critical sections, blocking mutexes, returning an error or panicking if the bus is already busy. /// /// On bus errors the implementation should try to deassert CS. /// If an error occurs while deasserting CS the bus error should take priority as the return value. - /// - /// # Safety - /// - /// The current state of the Rust typechecker doesn't allow expressing the necessary lifetime constraints, so - /// the `f` closure receives a lifetime-less `*mut Bus` raw pointer instead. - /// - /// Implementers of the `SpiDevice` trait must guarantee that the pointer is valid and dereferencable - /// for the entire duration of the closure. - async fn transaction(&mut self, f: F) -> Result - where - F: FnOnce(*mut Self::Bus) -> Fut, - Fut: Future::Error>>; + async fn transaction( + &mut self, + operations: &mut [Operation<'_, Word>], + ) -> Result<(), Self::Error>; /// Do a read within a transaction. /// - /// This is a convenience method equivalent to `device.transaction(|bus| bus.read(buf))`. + /// This is a convenience method equivalent to `device.read_transaction(&mut [buf])`. /// - /// See also: [`SpiDevice::transaction`], [`SpiBusRead::read`] - async fn read<'a, Word>(&'a mut self, buf: &'a mut [Word]) -> Result<(), Self::Error> - where - Self::Bus: SpiBusRead, - Word: Copy + 'static, - { - transaction!(self, move |bus| async move { bus.read(buf).await }).await + /// See also: [`SpiDevice::transaction`], [`SpiDevice::read`] + #[inline] + async fn read(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + self.transaction(&mut [Operation::Read(buf)]).await } /// Do a write within a transaction. /// - /// This is a convenience method equivalent to `device.transaction(|bus| bus.write(buf))`. + /// This is a convenience method equivalent to `device.write_transaction(&mut [buf])`. /// - /// See also: [`SpiDevice::transaction`], [`SpiBusWrite::write`] - async fn write<'a, Word>(&'a mut self, buf: &'a [Word]) -> Result<(), Self::Error> - where - Self::Bus: SpiBusWrite, - Word: Copy + 'static, - { - transaction!(self, move |bus| async move { bus.write(buf).await }).await + /// See also: [`SpiDevice::transaction`], [`SpiDevice::write`] + #[inline] + async fn write(&mut self, buf: &[Word]) -> Result<(), Self::Error> { + self.transaction(&mut [Operation::Write(buf)]).await } /// Do a transfer within a transaction. /// - /// This is a convenience method equivalent to `device.transaction(|bus| bus.transfer(read, write))`. + /// This is a convenience method equivalent to `device.transaction(&mut [Operation::Transfer(read, write)])`. /// /// See also: [`SpiDevice::transaction`], [`SpiBus::transfer`] - async fn transfer<'a, Word>( - &'a mut self, - read: &'a mut [Word], - write: &'a [Word], - ) -> Result<(), Self::Error> - where - Self::Bus: SpiBus, - Word: Copy + 'static, - { - transaction!( - self, - move |bus| async move { bus.transfer(read, write).await } - ) - .await + #[inline] + async fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + self.transaction(&mut [Operation::Transfer(read, write)]) + .await } /// Do an in-place transfer within a transaction. /// - /// This is a convenience method equivalent to `device.transaction(|bus| bus.transfer_in_place(buf))`. + /// This is a convenience method equivalent to `device.transaction(&mut [Operation::TransferInPlace(buf)])`. /// /// See also: [`SpiDevice::transaction`], [`SpiBus::transfer_in_place`] - async fn transfer_in_place<'a, Word>( - &'a mut self, - buf: &'a mut [Word], - ) -> Result<(), Self::Error> - where - Self::Bus: SpiBus, - Word: Copy + 'static, - { - transaction!( - self, - move |bus| async move { bus.transfer_in_place(buf).await } - ) - .await + #[inline] + async fn transfer_in_place(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + self.transaction(&mut [Operation::TransferInPlace(buf)]) + .await } } -unsafe impl SpiDevice for &mut T { - type Bus = T::Bus; +impl + ?Sized> SpiDevice for &mut T { + #[inline] + async fn transaction( + &mut self, + operations: &mut [Operation<'_, Word>], + ) -> Result<(), Self::Error> { + T::transaction(self, operations).await + } - async fn transaction(&mut self, f: F) -> Result - where - F: FnOnce(*mut Self::Bus) -> Fut, - Fut: Future::Error>>, - { - T::transaction(self, f).await + #[inline] + async fn read(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + T::read(self, buf).await } -} -/// Flush support for SPI bus -pub trait SpiBusFlush: ErrorType { - /// Wait until all operations have completed and the bus is idle. - /// - /// See (the docs on embedded-hal)[embedded_hal::spi::blocking] for information on flushing. - async fn flush(&mut self) -> Result<(), Self::Error>; -} + #[inline] + async fn write(&mut self, buf: &[Word]) -> Result<(), Self::Error> { + T::write(self, buf).await + } -impl SpiBusFlush for &mut T { - async fn flush(&mut self) -> Result<(), Self::Error> { - T::flush(self).await + #[inline] + async fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + T::transfer(self, read, write).await + } + + #[inline] + async fn transfer_in_place(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + T::transfer_in_place(self, buf).await } } -/// Read-only SPI bus -pub trait SpiBusRead: SpiBusFlush { +/// SPI bus. +/// +/// `SpiBus` represents **exclusive ownership** over the whole SPI bus, with SCK, MOSI and MISO pins. +/// +/// See [the docs on embedded-hal][embedded_hal::spi] for important information on SPI Bus vs Device traits. +pub trait SpiBus: ErrorType { /// Read `words` from the slave. /// /// The word value sent on MOSI during reading is implementation-defined, /// typically `0x00`, `0xFF`, or configurable. /// /// Implementations are allowed to return before the operation is - /// complete. See (the docs on embedded-hal)[embedded_hal::spi::blocking] for details on flushing. + /// complete. See [the docs on embedded-hal][embedded_hal::spi] for details on flushing. async fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error>; -} - -impl, Word: 'static + Copy> SpiBusRead for &mut T { - async fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { - T::read(self, words).await - } -} -/// Write-only SPI -pub trait SpiBusWrite: SpiBusFlush { - /// Write `words` to the slave, ignoring all the incoming words + /// Write `words` to the slave, ignoring all the incoming words. /// /// Implementations are allowed to return before the operation is - /// complete. See (the docs on embedded-hal)[embedded_hal::spi::blocking] for details on flushing. + /// complete. See [the docs on embedded-hal][embedded_hal::spi] for details on flushing. async fn write(&mut self, words: &[Word]) -> Result<(), Self::Error>; -} -impl, Word: 'static + Copy> SpiBusWrite for &mut T { - async fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { - T::write(self, words).await - } -} - -/// Read-write SPI bus -/// -/// `SpiBus` represents **exclusive ownership** over the whole SPI bus, with SCK, MOSI and MISO pins. -/// -/// See (the docs on embedded-hal)[embedded_hal::spi::blocking] for important information on SPI Bus vs Device traits. -pub trait SpiBus: SpiBusRead + SpiBusWrite { /// Write and read simultaneously. `write` is written to the slave on MOSI and /// words received on MISO are stored in `read`. /// @@ -298,133 +135,46 @@ pub trait SpiBus: SpiBusRead + SpiBusWrite( - &'a mut self, - read: &'a mut [Word], - write: &'a [Word], - ) -> Result<(), Self::Error>; + /// complete. See [the docs on embedded-hal][embedded_hal::spi] for details on flushing. + async fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error>; /// Write and read simultaneously. The contents of `words` are /// written to the slave, and the received words are stored into the same /// `words` buffer, overwriting it. /// /// Implementations are allowed to return before the operation is - /// complete. See (the docs on embedded-hal)[embedded_hal::spi::blocking] for details on flushing. - async fn transfer_in_place<'a>(&'a mut self, words: &'a mut [Word]) -> Result<(), Self::Error>; -} - -impl, Word: 'static + Copy> SpiBus for &mut T { - async fn transfer<'a>( - &'a mut self, - read: &'a mut [Word], - write: &'a [Word], - ) -> Result<(), Self::Error> { - T::transfer(self, read, write).await - } - - async fn transfer_in_place<'a>(&'a mut self, words: &'a mut [Word]) -> Result<(), Self::Error> { - T::transfer_in_place(self, words).await - } -} + /// complete. See [the docs on embedded-hal][embedded_hal::spi] for details on flushing. + async fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error>; -/// Error type for [`ExclusiveDevice`] operations. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum ExclusiveDeviceError { - /// An inner SPI bus operation failed - Spi(BUS), - /// Asserting or deasserting CS failed - Cs(CS), + /// Wait until all operations have completed and the bus is idle. + /// + /// See [the docs on embedded-hal][embedded_hal::spi] for information on flushing. + async fn flush(&mut self) -> Result<(), Self::Error>; } -impl Error for ExclusiveDeviceError -where - BUS: Error + Debug, - CS: Debug, -{ - fn kind(&self) -> ErrorKind { - match self { - Self::Spi(e) => e.kind(), - Self::Cs(_) => ErrorKind::ChipSelectFault, - } +impl + ?Sized, Word: 'static + Copy> SpiBus for &mut T { + #[inline] + async fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + T::read(self, words).await } -} - -/// [`SpiDevice`] implementation with exclusive access to the bus (not shared). -/// -/// This is the most straightforward way of obtaining an [`SpiDevice`] from an [`SpiBus`], -/// ideal for when no sharing is required (only one SPI device is present on the bus). -pub struct ExclusiveDevice { - bus: BUS, - cs: CS, -} -impl ExclusiveDevice { - /// Create a new ExclusiveDevice - pub fn new(bus: BUS, cs: CS) -> Self { - Self { bus, cs } + #[inline] + async fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { + T::write(self, words).await } -} - -impl ErrorType for ExclusiveDevice -where - BUS: ErrorType, - CS: OutputPin, -{ - type Error = ExclusiveDeviceError; -} - -impl blocking::SpiDevice for ExclusiveDevice -where - BUS: blocking::SpiBusFlush, - CS: OutputPin, -{ - type Bus = BUS; - - fn transaction( - &mut self, - f: impl FnOnce(&mut Self::Bus) -> Result::Error>, - ) -> Result { - self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?; - - let f_res = f(&mut self.bus); - - // On failure, it's important to still flush and deassert CS. - let flush_res = self.bus.flush(); - let cs_res = self.cs.set_high(); - let f_res = f_res.map_err(ExclusiveDeviceError::Spi)?; - flush_res.map_err(ExclusiveDeviceError::Spi)?; - cs_res.map_err(ExclusiveDeviceError::Cs)?; - - Ok(f_res) + #[inline] + async fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + T::transfer(self, read, write).await } -} - -unsafe impl SpiDevice for ExclusiveDevice -where - BUS: SpiBusFlush, - CS: OutputPin, -{ - type Bus = BUS; - - async fn transaction(&mut self, f: F) -> Result - where - F: FnOnce(*mut Self::Bus) -> Fut, - Fut: Future::Error>>, - { - self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?; - - let f_res = f(&mut self.bus).await; - // On failure, it's important to still flush and deassert CS. - let flush_res = self.bus.flush().await; - let cs_res = self.cs.set_high(); - - let f_res = f_res.map_err(ExclusiveDeviceError::Spi)?; - flush_res.map_err(ExclusiveDeviceError::Spi)?; - cs_res.map_err(ExclusiveDeviceError::Cs)?; + #[inline] + async fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + T::transfer_in_place(self, words).await + } - Ok(f_res) + #[inline] + async fn flush(&mut self) -> Result<(), Self::Error> { + T::flush(self).await } } diff --git a/embedded-hal-bus/CHANGELOG.md b/embedded-hal-bus/CHANGELOG.md index c43d3898e..d9009d9c3 100644 --- a/embedded-hal-bus/CHANGELOG.md +++ b/embedded-hal-bus/CHANGELOG.md @@ -7,7 +7,53 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] -... +- Added the `alloc` feature. +- Added a new `RcDevice` for I2C and SPI, a reference-counting equivalent to `RefCellDevice`. +- Migrated `std` feature-gated `std::error::Error` implementations to `core::error::Error` +- Increased MSRV to 1.81 due to `core::error::Error` + +## [v0.2.0] - 2024-04-23 + +- Added a new `AtomicDevice` for I2C and SPI to enable bus sharing across multiple contexts. +- SPI shared bus constructors now set `CS` high, to prevent sharing issues if it was low. + +## [v0.1.0] - 2023-12-28 + +- Updated `embedded-hal` to version `1.0.0`. + +## [v0.1.0-rc.3] - 2023-12-14 + +- Updated `embedded-hal` to version `1.0.0-rc.3`. + +## [v0.1.0-rc.2] - 2023-11-28 + +- Updated `embedded-hal(-async)` to version `1.0.0-rc.2`. +- Minor document fixes. +- Add #[inline] hints to most of `embedded-hal-bus` functions. +- Use `feature()` on nightly toolchains only. This adds async support for 1.75 beta and stable. + +## [v0.1.0-rc.1] - 2023-08-15 + +- Updated `embedded-hal`, `embedded-hal-async` to version `1.0.0-rc.1`. +- The Minimum Supported Rust Version (MSRV) is now 1.60.0 +- Added `embedded-hal-async` support to SPI `ExclusiveDevice`. +- Added methods to access the inner bus to SPI `ExclusiveDevice`. +- Add optional `defmt` 0.3 support. + +## [v0.1.0-alpha.3] - 2023-07-04 + +### Changed +- Updated `embedded-hal` to version `1.0.0-alpha.11`. + + +## [v0.1.0-alpha.2] - 2023-04-04 + +### Changed +- Updated `embedded-hal` to version `1.0.0-alpha.10`. + +### Added +- i2c: add bus sharing implementations. +- spi: add bus sharing implementations. ## [v0.1.0-alpha.1] - 2022-09-28 @@ -18,6 +64,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). First release to crates.io -[Unreleased]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-bus-v0.1.0-alpha.1...HEAD +[Unreleased]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-bus-v0.2.0...HEAD +[v0.2.0]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-bus-v0.1.0...embedded-hal-bus-v0.2.0 +[v0.1.0]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-bus-v0.1.0-rc.3...embedded-hal-bus-v0.1.0 +[v0.1.0-rc.3]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-bus-v0.1.0-rc.2...embedded-hal-bus-v0.1.0-rc.3 +[v0.1.0-rc.2]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-bus-v0.1.0-rc.1...embedded-hal-bus-v0.1.0-rc.2 +[v0.1.0-rc.1]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-bus-v0.1.0-alpha.3...embedded-hal-bus-v0.1.0-rc.1 +[v0.1.0-alpha.3]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-bus-v0.1.0-alpha.2...embedded-hal-bus-v0.1.0-alpha.3 +[v0.1.0-alpha.2]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-bus-v0.1.0-alpha.1...embedded-hal-bus-v0.1.0-alpha.2 [v0.1.0-alpha.1]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-bus-v0.1.0-alpha.0...embedded-hal-bus-v0.1.0-alpha.1 [v0.1.0-alpha.0]: https://github.com/rust-embedded/embedded-hal/tree/embedded-hal-bus-v0.1.0-alpha.0 diff --git a/embedded-hal-bus/Cargo.toml b/embedded-hal-bus/Cargo.toml index b3f022fe9..605f01b85 100644 --- a/embedded-hal-bus/Cargo.toml +++ b/embedded-hal-bus/Cargo.toml @@ -5,13 +5,39 @@ authors = [ categories = ["embedded", "hardware-support", "no-std"] description = "Bus/Device connection mechanisms for embedded-hal, a Hardware Abstraction Layer (HAL) for embedded systems" documentation = "https://docs.rs/embedded-hal-bus" -edition = "2018" +edition = "2021" +rust-version = "1.81" keywords = ["hal", "IO"] license = "MIT OR Apache-2.0" name = "embedded-hal-bus" readme = "README.md" repository = "https://github.com/rust-embedded/embedded-hal" -version = "0.1.0-alpha.1" +version = "0.2.0" + +[features] +# Enable shared bus implementations using `std::sync::Mutex` +std = ["alloc"] +# Use `portable-atomic` to enable `atomic-device` on devices without native atomic CAS +# +# `portable-atomic` emulates atomic CAS functionality, allowing `embedded-hal-bus` to use `atomic-device` on hardware +# that does not natively support atomic CAS. If you enable this, you must also add `portable-atomic` to your crate with +# a feature flag such as `unsafe-assume-single-core` or `critical-section` to choose how atomic CAS is implemented. +# See https://docs.rs/portable-atomic/1.7.0/portable_atomic/#optional-features for more info. +portable-atomic = ["dep:portable-atomic"] +# Enable `embedded-hal-async` support. +async = ["dep:embedded-hal-async"] +# Derive `defmt::Format` from `defmt` 0.3 for enums and structs. See https://github.com/knurling-rs/defmt for more info +defmt-03 = ["dep:defmt-03", "embedded-hal/defmt-03", "embedded-hal-async?/defmt-03"] +# Enables additional utilities requiring a global allocator. +alloc = [] [dependencies] -embedded-hal = { version = "=1.0.0-alpha.9", path = "../embedded-hal" } +embedded-hal = { version = "1.0.0", path = "../embedded-hal" } +embedded-hal-async = { version = "1.0.0", path = "../embedded-hal-async", optional = true } +critical-section = { version = "1.0" } +defmt-03 = { package = "defmt", version = "0.3", optional = true } +portable-atomic = {version = "1.3", default-features = false, optional = true, features = ["require-cas"]} + +[package.metadata.docs.rs] +features = ["std", "async"] +rustdoc-args = ["--cfg", "docsrs"] diff --git a/embedded-hal-bus/README.md b/embedded-hal-bus/README.md index b41570516..6f767aae7 100644 --- a/embedded-hal-bus/README.md +++ b/embedded-hal-bus/README.md @@ -1,45 +1,62 @@ [![crates.io](https://img.shields.io/crates/d/embedded-hal-bus.svg)](https://crates.io/crates/embedded-hal-bus) [![crates.io](https://img.shields.io/crates/v/embedded-hal-bus.svg)](https://crates.io/crates/embedded-hal-bus) [![Documentation](https://docs.rs/embedded-hal-bus/badge.svg)](https://docs.rs/embedded-hal-bus) -![Minimum Supported Rust Version](https://img.shields.io/badge/rustc-1.54+-blue.svg) +![Minimum Supported Rust Version](https://img.shields.io/badge/rustc-1.60+-blue.svg) # `embedded-hal-bus` -Bus/Device connection mechanisms for [`embedded-hal`](https://crates.io/crates/embedded-hal), a Hardware Abstraction Layer (HAL) for embedded systems. +Bus sharing utilities for [`embedded-hal`](https://crates.io/crates/embedded-hal), a Hardware Abstraction Layer (HAL) for embedded systems. -It is possible to connect several peripherals to a bus like SPI or I2C. -To support this, `embedded-hal` provides the `SpiBus` and `SpiDevice` traits in the case of SPI, for example. +`embedded-hal` provides traits for SPI and I2C buses and devices. This crate provides hardware-independent adapters for sharing a single bus between multiple devices, compatible with the traits. -`embedded-hal` trait implementations for microcontrollers should implement the `...Bus` traits. -However, device drivers should use the `...Device` traits, _not the `...Bus` traits_ if at all possible +This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team). + +## SPI + +To support bus sharing, `embedded-hal` provides the `SpiBus` and `SpiDevice` traits. `SpiBus` represents an entire bus, +while `SpiDevice` represents a device on that bus. For further details on these traits, please consult the +[`embedded-hal` documentation](https://docs.rs/embedded-hal/latest/embedded_hal/spi/index.html). + +`embedded-hal` trait implementations for microcontrollers should implement the `SpiBus` trait. +However, device drivers should use the `SpiDevice` traits, _not the `SpiBus` traits_ if at all possible in order to allow for sharing of the bus they are connected to. -This crate provides mechanisms to connect a `...Bus` and a `...Device`. +This crate provides mechanisms to connect a `SpiBus` and a `SpiDevice`. -For further details on these traits, please consult the [`embedded-hal` documentation](https://docs.rs/embedded-hal). +## I2C -This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team). +In the case of I2C, the same `I2c` `embedded-hal` trait represents either an entire bus, or a device on a bus. This crate +provides mechanisms to obtain multiple `I2c` instances out of a single `I2c` instance, sharing the bus. -## [API reference] +## Optional Cargo features -[API reference]: https://docs.rs/embedded-hal-bus +- **`async`**: enable `embedded-hal-async` support. +- **`defmt-03`**: Derive `defmt::Format` from `defmt` 0.3 for enums and structs. +- **`alloc`**: enable implementations using `alloc` (for instance, `spi::RcDevice`, which makes use of `alloc::rc::Rc`) +- **`portable-atomic`**: Use `portable-atomic` to enable `atomic-device` on devices without native atomic CAS -## Minimum Supported Rust Version (MSRV) + `portable-atomic` emulates atomic CAS functionality, allowing `embedded-hal-bus` to use `atomic-device` on hardware + that does not natively support atomic CAS. If you enable this, you must also add `portable-atomic` to your crate with + a feature flag such as `unsafe-assume-single-core` or `critical-section` to choose how atomic CAS is implemented. + See for more info. +- **`std`**: enable shared bus implementations using `std::sync::Mutex`. +## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.54 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.60 and up. It *might* compile with older versions but that may change in any new patch release. See [here](../docs/msrv.md) for details on how the MSRV may be upgraded. +Enabling the `async` Cargo features requires Rust 1.75 or higher. ## License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. diff --git a/embedded-hal-bus/src/i2c/atomic.rs b/embedded-hal-bus/src/i2c/atomic.rs new file mode 100644 index 000000000..793e827b1 --- /dev/null +++ b/embedded-hal-bus/src/i2c/atomic.rs @@ -0,0 +1,179 @@ +use embedded_hal::i2c::{Error, ErrorKind, ErrorType, I2c}; + +use crate::util::AtomicCell; + +/// Atomics-based shared bus [`I2c`] implementation. +/// +/// Sharing is implemented with a [`AtomicDevice`], which consists of an `UnsafeCell` and an `AtomicBool` "locked" flag. +/// This means it has low overhead, like [`RefCellDevice`](crate::i2c::RefCellDevice). Aditionally, it is `Send`, +/// which allows sharing a single bus across multiple threads (interrupt priority level), like [`CriticalSectionDevice`](crate::i2c::CriticalSectionDevice), +/// while not using critical sections and therefore impacting real-time performance less. +/// +/// The downside is using a simple `AtomicBool` for locking doesn't prevent two threads from trying to lock it at once. +/// For example, the main thread can be doing an I2C transaction, and an interrupt fires and tries to do another. In this +/// case, a `Busy` error is returned that must be handled somehow, usually dropping the data or trying again later. +/// +/// Note that retrying in a loop on `Busy` errors usually leads to deadlocks. In the above example, it'll prevent the +/// interrupt handler from returning, which will starve the main thread and prevent it from releasing the lock. If +/// this is an issue for your use case, you most likely should use [`CriticalSectionDevice`](crate::i2c::CriticalSectionDevice) instead. +/// +/// This primitive is particularly well-suited for applications that have external arbitration +/// rules that prevent `Busy` errors in the first place, such as the RTIC framework. +/// +/// # Examples +/// +/// Assuming there is a pressure sensor with address `0x42` on the same bus as a temperature sensor +/// with address `0x20`; [`AtomicDevice`] can be used to give access to both of these sensors +/// from a single `i2c` instance. +/// +/// ``` +/// use embedded_hal_bus::i2c; +/// use embedded_hal_bus::util::AtomicCell; +/// # use embedded_hal::i2c::{self as hali2c, SevenBitAddress, TenBitAddress, I2c, Operation, ErrorKind}; +/// # pub struct Sensor { +/// # i2c: I2C, +/// # address: u8, +/// # } +/// # impl Sensor { +/// # pub fn new(i2c: I2C, address: u8) -> Self { +/// # Self { i2c, address } +/// # } +/// # } +/// # type PressureSensor = Sensor; +/// # type TemperatureSensor = Sensor; +/// # pub struct I2c0; +/// # #[derive(Debug, Copy, Clone, Eq, PartialEq)] +/// # pub enum Error { } +/// # impl hali2c::Error for Error { +/// # fn kind(&self) -> hali2c::ErrorKind { +/// # ErrorKind::Other +/// # } +/// # } +/// # impl hali2c::ErrorType for I2c0 { +/// # type Error = Error; +/// # } +/// # impl I2c for I2c0 { +/// # fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> { +/// # Ok(()) +/// # } +/// # } +/// # struct Hal; +/// # impl Hal { +/// # fn i2c(&self) -> I2c0 { +/// # I2c0 +/// # } +/// # } +/// # let hal = Hal; +/// +/// let i2c = hal.i2c(); +/// let i2c_cell = AtomicCell::new(i2c); +/// let mut temperature_sensor = TemperatureSensor::new( +/// i2c::AtomicDevice::new(&i2c_cell), +/// 0x20, +/// ); +/// let mut pressure_sensor = PressureSensor::new( +/// i2c::AtomicDevice::new(&i2c_cell), +/// 0x42, +/// ); +/// ``` +pub struct AtomicDevice<'a, T> { + bus: &'a AtomicCell, +} + +#[derive(Debug, Copy, Clone)] +/// Wrapper type for errors originating from the atomically-checked I2C bus manager. +pub enum AtomicError { + /// This error is returned if the I2C bus was already in use when an operation was attempted, + /// which indicates that the driver requirements are not being met with regard to + /// synchronization. + Busy, + + /// An I2C-related error occurred, and the internal error should be inspected. + Other(T), +} + +impl Error for AtomicError { + fn kind(&self) -> ErrorKind { + match self { + AtomicError::Other(e) => e.kind(), + _ => ErrorKind::Other, + } + } +} + +unsafe impl<'a, T> Send for AtomicDevice<'a, T> {} + +impl<'a, T> AtomicDevice<'a, T> +where + T: I2c, +{ + /// Create a new `AtomicDevice`. + #[inline] + pub fn new(bus: &'a AtomicCell) -> Self { + Self { bus } + } + + fn lock(&self, f: F) -> Result> + where + F: FnOnce(&mut T) -> Result::Error>, + { + self.bus + .busy + .compare_exchange( + false, + true, + core::sync::atomic::Ordering::SeqCst, + core::sync::atomic::Ordering::SeqCst, + ) + .map_err(|_| AtomicError::::Busy)?; + + let result = f(unsafe { &mut *self.bus.bus.get() }); + + self.bus + .busy + .store(false, core::sync::atomic::Ordering::SeqCst); + + result.map_err(AtomicError::Other) + } +} + +impl<'a, T> ErrorType for AtomicDevice<'a, T> +where + T: I2c, +{ + type Error = AtomicError; +} + +impl<'a, T> I2c for AtomicDevice<'a, T> +where + T: I2c, +{ + #[inline] + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.lock(|bus| bus.read(address, read)) + } + + #[inline] + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.lock(|bus| bus.write(address, write)) + } + + #[inline] + fn write_read( + &mut self, + address: u8, + write: &[u8], + read: &mut [u8], + ) -> Result<(), Self::Error> { + self.lock(|bus| bus.write_read(address, write, read)) + } + + #[inline] + fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + self.lock(|bus| bus.transaction(address, operations)) + } +} diff --git a/embedded-hal-bus/src/i2c/critical_section.rs b/embedded-hal-bus/src/i2c/critical_section.rs new file mode 100644 index 000000000..6830f284f --- /dev/null +++ b/embedded-hal-bus/src/i2c/critical_section.rs @@ -0,0 +1,75 @@ +use core::cell::RefCell; +use critical_section::Mutex; +use embedded_hal::i2c::{ErrorType, I2c}; + +/// `critical-section`-based shared bus [`I2c`] implementation. +/// +/// Sharing is implemented with a `critical-section` [`Mutex`]. A critical section is taken for +/// the entire duration of a transaction. This allows sharing a single bus across multiple threads (interrupt priority levels). +/// The downside is critical sections typically require globally disabling interrupts, so `CriticalSectionDevice` will likely +/// negatively impact real-time properties, such as interrupt latency. If you can, prefer using +/// [`RefCellDevice`](super::RefCellDevice) instead, which does not require taking critical sections. +pub struct CriticalSectionDevice<'a, T> { + bus: &'a Mutex>, +} + +impl<'a, T> CriticalSectionDevice<'a, T> { + /// Create a new `CriticalSectionDevice`. + #[inline] + pub fn new(bus: &'a Mutex>) -> Self { + Self { bus } + } +} + +impl<'a, T> ErrorType for CriticalSectionDevice<'a, T> +where + T: I2c, +{ + type Error = T::Error; +} + +impl<'a, T> I2c for CriticalSectionDevice<'a, T> +where + T: I2c, +{ + #[inline] + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + critical_section::with(|cs| { + let bus = &mut *self.bus.borrow_ref_mut(cs); + bus.read(address, read) + }) + } + + #[inline] + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + critical_section::with(|cs| { + let bus = &mut *self.bus.borrow_ref_mut(cs); + bus.write(address, write) + }) + } + + #[inline] + fn write_read( + &mut self, + address: u8, + write: &[u8], + read: &mut [u8], + ) -> Result<(), Self::Error> { + critical_section::with(|cs| { + let bus = &mut *self.bus.borrow_ref_mut(cs); + bus.write_read(address, write, read) + }) + } + + #[inline] + fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + critical_section::with(|cs| { + let bus = &mut *self.bus.borrow_ref_mut(cs); + bus.transaction(address, operations) + }) + } +} diff --git a/embedded-hal-bus/src/i2c/mod.rs b/embedded-hal-bus/src/i2c/mod.rs new file mode 100644 index 000000000..5f3226313 --- /dev/null +++ b/embedded-hal-bus/src/i2c/mod.rs @@ -0,0 +1,19 @@ +//! `I2c` shared bus implementations. + +mod refcell; +pub use refcell::*; +#[cfg(feature = "std")] +mod mutex; +#[cfg(feature = "std")] +pub use mutex::*; +mod critical_section; +pub use self::critical_section::*; +#[cfg(any(feature = "portable-atomic", target_has_atomic = "8"))] +mod atomic; +#[cfg(any(feature = "portable-atomic", target_has_atomic = "8"))] +pub use atomic::*; + +#[cfg(feature = "alloc")] +mod rc; +#[cfg(feature = "alloc")] +pub use rc::*; diff --git a/embedded-hal-bus/src/i2c/mutex.rs b/embedded-hal-bus/src/i2c/mutex.rs new file mode 100644 index 000000000..ef4a9f510 --- /dev/null +++ b/embedded-hal-bus/src/i2c/mutex.rs @@ -0,0 +1,65 @@ +use embedded_hal::i2c::{ErrorType, I2c}; +use std::sync::Mutex; + +/// `std` `Mutex`-based shared bus [`I2c`] implementation. +/// +/// Sharing is implemented with an `std` [`Mutex`]. It allows a single bus across multiple threads, +/// with finer-grained locking than [`CriticalSectionDevice`](super::CriticalSectionDevice). The downside is that +/// it is only available in `std` targets. +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub struct MutexDevice<'a, T> { + bus: &'a Mutex, +} + +impl<'a, T> MutexDevice<'a, T> { + /// Create a new `MutexDevice`. + #[inline] + pub fn new(bus: &'a Mutex) -> Self { + Self { bus } + } +} + +impl<'a, T> ErrorType for MutexDevice<'a, T> +where + T: I2c, +{ + type Error = T::Error; +} + +impl<'a, T> I2c for MutexDevice<'a, T> +where + T: I2c, +{ + #[inline] + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.lock().unwrap(); + bus.read(address, read) + } + + #[inline] + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.lock().unwrap(); + bus.write(address, write) + } + + #[inline] + fn write_read( + &mut self, + address: u8, + write: &[u8], + read: &mut [u8], + ) -> Result<(), Self::Error> { + let bus = &mut *self.bus.lock().unwrap(); + bus.write_read(address, write, read) + } + + #[inline] + fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + let bus = &mut *self.bus.lock().unwrap(); + bus.transaction(address, operations) + } +} diff --git a/embedded-hal-bus/src/i2c/rc.rs b/embedded-hal-bus/src/i2c/rc.rs new file mode 100644 index 000000000..2f1621687 --- /dev/null +++ b/embedded-hal-bus/src/i2c/rc.rs @@ -0,0 +1,75 @@ +extern crate alloc; +use alloc::rc::Rc; + +use core::cell::RefCell; +use embedded_hal::i2c::{ErrorType, I2c}; + +/// `Rc>`-based shared bus [`I2c`] implementation. +/// This is the reference-counting equivalent of [`RefCellDevice`](super::RefCellDevice). +/// +/// Sharing is implemented with a [`RefCell`] and ownership is managed by [`Rc`]. +/// Like [`RefCellDevice`](super::RefCellDevice), `RcDevice` instances are not [`Send`], +/// so they can only be shared within a single thread (interrupt priority level). +/// +/// When this `RcDevice` is dropped, the reference count of the I2C bus will be decremented. +/// Once that reference count hits zero, it will be cleaned up. +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +pub struct RcDevice { + bus: Rc>, +} + +impl RcDevice { + /// Creates a new `RcDevice`. + /// + /// This function does not increment the reference count for the bus: + /// you will need to call `Rc::clone(&bus)` if you only have a `&Rc>`. + #[inline] + pub fn new(bus: Rc>) -> Self { + Self { bus } + } +} + +impl ErrorType for RcDevice +where + Bus: ErrorType, +{ + type Error = Bus::Error; +} + +impl I2c for RcDevice +where + Bus: I2c, +{ + #[inline] + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + bus.read(address, read) + } + + #[inline] + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + bus.write(address, write) + } + + #[inline] + fn write_read( + &mut self, + address: u8, + write: &[u8], + read: &mut [u8], + ) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + bus.write_read(address, write, read) + } + + #[inline] + fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + bus.transaction(address, operations) + } +} diff --git a/embedded-hal-bus/src/i2c/refcell.rs b/embedded-hal-bus/src/i2c/refcell.rs new file mode 100644 index 000000000..1519a751b --- /dev/null +++ b/embedded-hal-bus/src/i2c/refcell.rs @@ -0,0 +1,121 @@ +use core::cell::RefCell; +use embedded_hal::i2c::{ErrorType, I2c}; + +/// `RefCell`-based shared bus [`I2c`] implementation. +/// +/// Sharing is implemented with a `RefCell`. This means it has low overhead, but `RefCellDevice` instances are not `Send`, +/// so it only allows sharing within a single thread (interrupt priority level). If you need to share a bus across several +/// threads, use [`CriticalSectionDevice`](super::CriticalSectionDevice) instead. +/// +/// # Examples +/// +/// Assuming there is a pressure sensor with address `0x42` on the same bus as a temperature sensor +/// with address `0x20`; [`RefCellDevice`] can be used to give access to both of these sensors +/// from a single `i2c` instance. +/// +/// ``` +/// use embedded_hal_bus::i2c; +/// use core::cell::RefCell; +/// # use embedded_hal::i2c::{self as hali2c, SevenBitAddress, TenBitAddress, I2c, Operation, ErrorKind}; +/// # pub struct Sensor { +/// # i2c: I2C, +/// # address: u8, +/// # } +/// # impl Sensor { +/// # pub fn new(i2c: I2C, address: u8) -> Self { +/// # Self { i2c, address } +/// # } +/// # } +/// # type PressureSensor = Sensor; +/// # type TemperatureSensor = Sensor; +/// # pub struct I2c0; +/// # #[derive(Debug, Copy, Clone, Eq, PartialEq)] +/// # pub enum Error { } +/// # impl hali2c::Error for Error { +/// # fn kind(&self) -> hali2c::ErrorKind { +/// # ErrorKind::Other +/// # } +/// # } +/// # impl hali2c::ErrorType for I2c0 { +/// # type Error = Error; +/// # } +/// # impl I2c for I2c0 { +/// # fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> { +/// # Ok(()) +/// # } +/// # } +/// # struct Hal; +/// # impl Hal { +/// # fn i2c(&self) -> I2c0 { +/// # I2c0 +/// # } +/// # } +/// # let hal = Hal; +/// +/// let i2c = hal.i2c(); +/// let i2c_ref_cell = RefCell::new(i2c); +/// let mut temperature_sensor = TemperatureSensor::new( +/// i2c::RefCellDevice::new(&i2c_ref_cell), +/// 0x20, +/// ); +/// let mut pressure_sensor = PressureSensor::new( +/// i2c::RefCellDevice::new(&i2c_ref_cell), +/// 0x42, +/// ); +/// ``` +pub struct RefCellDevice<'a, T> { + bus: &'a RefCell, +} + +impl<'a, T> RefCellDevice<'a, T> { + /// Create a new `RefCellDevice`. + #[inline] + pub fn new(bus: &'a RefCell) -> Self { + Self { bus } + } +} + +impl<'a, T> ErrorType for RefCellDevice<'a, T> +where + T: I2c, +{ + type Error = T::Error; +} + +impl<'a, T> I2c for RefCellDevice<'a, T> +where + T: I2c, +{ + #[inline] + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + bus.read(address, read) + } + + #[inline] + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + bus.write(address, write) + } + + #[inline] + fn write_read( + &mut self, + address: u8, + write: &[u8], + read: &mut [u8], + ) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + bus.write_read(address, write, read) + } + + #[inline] + fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + bus.transaction(address, operations) + } +} diff --git a/embedded-hal-bus/src/lib.rs b/embedded-hal-bus/src/lib.rs index 566e74b61..dfeca16e1 100644 --- a/embedded-hal-bus/src/lib.rs +++ b/embedded-hal-bus/src/lib.rs @@ -1,17 +1,12 @@ -//! Bus/Device connection mechanisms for [`embedded-hal`], a Hardware Abstraction Layer (HAL) for embedded systems. -//! -//! It is possible to connect several peripherals to a bus like SPI or I2C. -//! To support this, `embedded-hal` provides the `SpiBus` and `SpiDevice` traits in the case of SPI, for example. -//! -//! `embedded-hal` trait implementations for microcontrollers should implement the `...Bus` traits. -//! However, device drivers should use the `...Device` traits, _not the `...Bus` traits_ if at all possible -//! in order to allow for sharing of the bus they are connected to. -//! -//! This crate provides mechanisms to connect a `...Bus` and a `...Device`. -//! -//! For further details on these traits, please consult the [`embedded-hal` documentation](https://docs.rs/embedded-hal). - +#![doc = include_str!("../README.md")] #![warn(missing_docs)] -#![no_std] +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +// needed to prevent defmt macros from breaking, since they emit code that does `defmt::blahblah`. +#[cfg(feature = "defmt-03")] +use defmt_03 as defmt; +pub mod i2c; pub mod spi; +pub mod util; diff --git a/embedded-hal-bus/src/spi.rs b/embedded-hal-bus/src/spi.rs deleted file mode 100644 index 22ef300be..000000000 --- a/embedded-hal-bus/src/spi.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! SPI bus sharing mechanisms. - -use core::fmt::Debug; -use embedded_hal::digital::OutputPin; -use embedded_hal::spi::{Error, ErrorKind, ErrorType, SpiBusFlush, SpiDevice}; - -/// Error type for [`ExclusiveDevice`] operations. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum ExclusiveDeviceError { - /// An inner SPI bus operation failed - Spi(BUS), - /// Asserting or deasserting CS failed - Cs(CS), -} - -impl Error for ExclusiveDeviceError -where - BUS: Error + Debug, - CS: Debug, -{ - fn kind(&self) -> ErrorKind { - match self { - Self::Spi(e) => e.kind(), - Self::Cs(_) => ErrorKind::ChipSelectFault, - } - } -} - -/// [`SpiDevice`] implementation with exclusive access to the bus (not shared). -/// -/// This is the most straightforward way of obtaining an [`SpiDevice`] from an [`SpiBus`](embedded_hal::spi::blocking::SpiBus), -/// ideal for when no sharing is required (only one SPI device is present on the bus). -pub struct ExclusiveDevice { - bus: BUS, - cs: CS, -} - -impl ExclusiveDevice { - /// Create a new ExclusiveDevice - pub fn new(bus: BUS, cs: CS) -> Self { - Self { bus, cs } - } -} - -impl ErrorType for ExclusiveDevice -where - BUS: ErrorType, - CS: OutputPin, -{ - type Error = ExclusiveDeviceError; -} - -impl SpiDevice for ExclusiveDevice -where - BUS: SpiBusFlush, - CS: OutputPin, -{ - type Bus = BUS; - - fn transaction( - &mut self, - f: impl FnOnce(&mut Self::Bus) -> Result::Error>, - ) -> Result { - self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?; - - let f_res = f(&mut self.bus); - - // On failure, it's important to still flush and deassert CS. - let flush_res = self.bus.flush(); - let cs_res = self.cs.set_high(); - - let f_res = f_res.map_err(ExclusiveDeviceError::Spi)?; - flush_res.map_err(ExclusiveDeviceError::Spi)?; - cs_res.map_err(ExclusiveDeviceError::Cs)?; - - Ok(f_res) - } -} diff --git a/embedded-hal-bus/src/spi/atomic.rs b/embedded-hal-bus/src/spi/atomic.rs new file mode 100644 index 000000000..7d18e28c5 --- /dev/null +++ b/embedded-hal-bus/src/spi/atomic.rs @@ -0,0 +1,145 @@ +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; +use embedded_hal::spi::{Error, ErrorKind, ErrorType, Operation, SpiBus, SpiDevice}; + +use super::DeviceError; +use crate::spi::shared::transaction; +use crate::util::AtomicCell; + +/// Atomics-based shared bus [`SpiDevice`] implementation. +/// +/// This allows for sharing an [`SpiBus`], obtaining multiple [`SpiDevice`] instances, +/// each with its own `CS` pin. +/// +/// Sharing is implemented with a [`AtomicDevice`], which consists of an `UnsafeCell` and an `AtomicBool` "locked" flag. +/// This means it has low overhead, like [`RefCellDevice`](crate::spi::RefCellDevice). Aditionally, it is `Send`, +/// which allows sharing a single bus across multiple threads (interrupt priority level), like [`CriticalSectionDevice`](crate::spi::CriticalSectionDevice), +/// while not using critical sections and therefore impacting real-time performance less. +/// +/// The downside is using a simple `AtomicBool` for locking doesn't prevent two threads from trying to lock it at once. +/// For example, the main thread can be doing a SPI transaction, and an interrupt fires and tries to do another. In this +/// case, a `Busy` error is returned that must be handled somehow, usually dropping the data or trying again later. +/// +/// Note that retrying in a loop on `Busy` errors usually leads to deadlocks. In the above example, it'll prevent the +/// interrupt handler from returning, which will starve the main thread and prevent it from releasing the lock. If +/// this is an issue for your use case, you most likely should use [`CriticalSectionDevice`](crate::spi::CriticalSectionDevice) instead. +/// +/// This primitive is particularly well-suited for applications that have external arbitration +/// rules that prevent `Busy` errors in the first place, such as the RTIC framework. +pub struct AtomicDevice<'a, BUS, CS, D> { + bus: &'a AtomicCell, + cs: CS, + delay: D, +} + +#[derive(Debug, Copy, Clone)] +/// Wrapper type for errors returned by [`AtomicDevice`]. +pub enum AtomicError { + /// This error is returned if the SPI bus was already in use when an operation was attempted, + /// which indicates that the driver requirements are not being met with regard to + /// synchronization. + Busy, + + /// An SPI-related error occurred, and the internal error should be inspected. + Other(T), +} + +impl<'a, BUS, CS, D> AtomicDevice<'a, BUS, CS, D> { + /// Create a new [`AtomicDevice`]. + /// + /// This sets the `cs` pin high, and returns an error if that fails. It is recommended + /// to set the pin high the moment it's configured as an output, to avoid glitches. + #[inline] + pub fn new(bus: &'a AtomicCell, mut cs: CS, delay: D) -> Result + where + CS: OutputPin, + { + cs.set_high()?; + Ok(Self { bus, cs, delay }) + } +} + +impl<'a, BUS, CS> AtomicDevice<'a, BUS, CS, super::NoDelay> +where + BUS: ErrorType, + CS: OutputPin, +{ + /// Create a new [`AtomicDevice`] without support for in-transaction delays. + /// + /// This sets the `cs` pin high, and returns an error if that fails. It is recommended + /// to set the pin high the moment it's configured as an output, to avoid glitches. + /// + /// **Warning**: The returned instance *technically* doesn't comply with the `SpiDevice` + /// contract, which mandates delay support. It is relatively rare for drivers to use + /// in-transaction delays, so you might still want to use this method because it's more practical. + /// + /// Note that a future version of the driver might start using delays, causing your + /// code to panic. This wouldn't be considered a breaking change from the driver side, because + /// drivers are allowed to assume `SpiDevice` implementations comply with the contract. + /// If you feel this risk outweighs the convenience of having `cargo` automatically upgrade + /// the driver crate, you might want to pin the driver's version. + /// + /// # Panics + /// + /// The returned device will panic if you try to execute a transaction + /// that contains any operations of type [`Operation::DelayNs`]. + #[inline] + pub fn new_no_delay(bus: &'a AtomicCell, mut cs: CS) -> Result + where + CS: OutputPin, + { + cs.set_high()?; + Ok(Self { + bus, + cs, + delay: super::NoDelay, + }) + } +} + +impl Error for AtomicError { + fn kind(&self) -> ErrorKind { + match self { + AtomicError::Other(e) => e.kind(), + _ => ErrorKind::Other, + } + } +} + +impl<'a, BUS, CS, D> ErrorType for AtomicDevice<'a, BUS, CS, D> +where + BUS: ErrorType, + CS: OutputPin, +{ + type Error = AtomicError>; +} + +impl<'a, Word: Copy + 'static, BUS, CS, D> SpiDevice for AtomicDevice<'a, BUS, CS, D> +where + BUS: SpiBus, + CS: OutputPin, + D: DelayNs, +{ + #[inline] + fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { + self.bus + .busy + .compare_exchange( + false, + true, + core::sync::atomic::Ordering::SeqCst, + core::sync::atomic::Ordering::SeqCst, + ) + .map_err(|_| AtomicError::Busy)?; + + let bus = unsafe { &mut *self.bus.bus.get() }; + + let result = transaction(operations, bus, &mut self.delay, &mut self.cs); + + self.bus + .busy + .store(false, core::sync::atomic::Ordering::SeqCst); + + result.map_err(AtomicError::Other) + } +} diff --git a/embedded-hal-bus/src/spi/critical_section.rs b/embedded-hal-bus/src/spi/critical_section.rs new file mode 100644 index 000000000..4c3a46eb2 --- /dev/null +++ b/embedded-hal-bus/src/spi/critical_section.rs @@ -0,0 +1,97 @@ +use core::cell::RefCell; +use critical_section::Mutex; +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; +use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice}; + +use super::DeviceError; +use crate::spi::shared::transaction; + +/// `critical-section`-based shared bus [`SpiDevice`] implementation. +/// +/// This allows for sharing an [`SpiBus`], obtaining multiple [`SpiDevice`] instances, +/// each with its own `CS` pin. +/// +/// Sharing is implemented with a `critical-section` [`Mutex`]. A critical section is taken for +/// the entire duration of a transaction. This allows sharing a single bus across multiple threads (interrupt priority levels). +/// The downside is critical sections typically require globally disabling interrupts, so `CriticalSectionDevice` will likely +/// negatively impact real-time properties, such as interrupt latency. If you can, prefer using +/// [`RefCellDevice`](super::RefCellDevice) instead, which does not require taking critical sections. +pub struct CriticalSectionDevice<'a, BUS, CS, D> { + bus: &'a Mutex>, + cs: CS, + delay: D, +} + +impl<'a, BUS, CS, D> CriticalSectionDevice<'a, BUS, CS, D> { + /// Create a new [`CriticalSectionDevice`]. + /// + /// This sets the `cs` pin high, and returns an error if that fails. It is recommended + /// to set the pin high the moment it's configured as an output, to avoid glitches. + #[inline] + pub fn new(bus: &'a Mutex>, mut cs: CS, delay: D) -> Result + where + CS: OutputPin, + { + cs.set_high()?; + Ok(Self { bus, cs, delay }) + } +} + +impl<'a, BUS, CS> CriticalSectionDevice<'a, BUS, CS, super::NoDelay> { + /// Create a new [`CriticalSectionDevice`] without support for in-transaction delays. + /// + /// This sets the `cs` pin high, and returns an error if that fails. It is recommended + /// to set the pin high the moment it's configured as an output, to avoid glitches. + /// + /// **Warning**: The returned instance *technically* doesn't comply with the `SpiDevice` + /// contract, which mandates delay support. It is relatively rare for drivers to use + /// in-transaction delays, so you might still want to use this method because it's more practical. + /// + /// Note that a future version of the driver might start using delays, causing your + /// code to panic. This wouldn't be considered a breaking change from the driver side, because + /// drivers are allowed to assume `SpiDevice` implementations comply with the contract. + /// If you feel this risk outweighs the convenience of having `cargo` automatically upgrade + /// the driver crate, you might want to pin the driver's version. + /// + /// # Panics + /// + /// The returned device will panic if you try to execute a transaction + /// that contains any operations of type [`Operation::DelayNs`]. + #[inline] + pub fn new_no_delay(bus: &'a Mutex>, mut cs: CS) -> Result + where + CS: OutputPin, + { + cs.set_high()?; + Ok(Self { + bus, + cs, + delay: super::NoDelay, + }) + } +} + +impl<'a, BUS, CS, D> ErrorType for CriticalSectionDevice<'a, BUS, CS, D> +where + BUS: ErrorType, + CS: OutputPin, +{ + type Error = DeviceError; +} + +impl<'a, Word: Copy + 'static, BUS, CS, D> SpiDevice for CriticalSectionDevice<'a, BUS, CS, D> +where + BUS: SpiBus, + CS: OutputPin, + D: DelayNs, +{ + #[inline] + fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { + critical_section::with(|cs| { + let bus = &mut *self.bus.borrow_ref_mut(cs); + + transaction(operations, bus, &mut self.delay, &mut self.cs) + }) + } +} diff --git a/embedded-hal-bus/src/spi/exclusive.rs b/embedded-hal-bus/src/spi/exclusive.rs new file mode 100644 index 000000000..1599ae7ae --- /dev/null +++ b/embedded-hal-bus/src/spi/exclusive.rs @@ -0,0 +1,153 @@ +//! SPI bus sharing mechanisms. + +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; +use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice}; +#[cfg(feature = "async")] +use embedded_hal_async::{ + delay::DelayNs as AsyncDelayNs, + spi::{SpiBus as AsyncSpiBus, SpiDevice as AsyncSpiDevice}, +}; + +use super::shared::transaction; +use super::DeviceError; + +/// [`SpiDevice`] implementation with exclusive access to the bus (not shared). +/// +/// This is the most straightforward way of obtaining an [`SpiDevice`] from an [`SpiBus`], +/// ideal for when no sharing is required (only one SPI device is present on the bus). +pub struct ExclusiveDevice { + bus: BUS, + cs: CS, + delay: D, +} + +impl ExclusiveDevice { + /// Create a new [`ExclusiveDevice`]. + /// + /// This sets the `cs` pin high, and returns an error if that fails. It is recommended + /// to set the pin high the moment it's configured as an output, to avoid glitches. + #[inline] + pub fn new(bus: BUS, mut cs: CS, delay: D) -> Result + where + CS: OutputPin, + { + cs.set_high()?; + Ok(Self { bus, cs, delay }) + } + + /// Returns a reference to the underlying bus object. + #[inline] + pub fn bus(&self) -> &BUS { + &self.bus + } + + /// Returns a mutable reference to the underlying bus object. + #[inline] + pub fn bus_mut(&mut self) -> &mut BUS { + &mut self.bus + } +} + +impl ExclusiveDevice { + /// Create a new [`ExclusiveDevice`] without support for in-transaction delays. + /// + /// This sets the `cs` pin high, and returns an error if that fails. It is recommended + /// to set the pin high the moment it's configured as an output, to avoid glitches. + /// + /// **Warning**: The returned instance *technically* doesn't comply with the `SpiDevice` + /// contract, which mandates delay support. It is relatively rare for drivers to use + /// in-transaction delays, so you might still want to use this method because it's more practical. + /// + /// Note that a future version of the driver might start using delays, causing your + /// code to panic. This wouldn't be considered a breaking change from the driver side, because + /// drivers are allowed to assume `SpiDevice` implementations comply with the contract. + /// If you feel this risk outweighs the convenience of having `cargo` automatically upgrade + /// the driver crate, you might want to pin the driver's version. + /// + /// # Panics + /// + /// The returned device will panic if you try to execute a transaction + /// that contains any operations of type [`Operation::DelayNs`]. + #[inline] + pub fn new_no_delay(bus: BUS, mut cs: CS) -> Result + where + CS: OutputPin, + { + cs.set_high()?; + Ok(Self { + bus, + cs, + delay: super::NoDelay, + }) + } +} + +impl ErrorType for ExclusiveDevice +where + BUS: ErrorType, + CS: OutputPin, +{ + type Error = DeviceError; +} + +impl SpiDevice for ExclusiveDevice +where + BUS: SpiBus, + CS: OutputPin, + D: DelayNs, +{ + #[inline] + fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { + transaction(operations, &mut self.bus, &mut self.delay, &mut self.cs) + } +} + +#[cfg(feature = "async")] +#[cfg_attr(docsrs, doc(cfg(feature = "async")))] +impl AsyncSpiDevice for ExclusiveDevice +where + BUS: AsyncSpiBus, + CS: OutputPin, + D: AsyncDelayNs, +{ + #[inline] + async fn transaction( + &mut self, + operations: &mut [Operation<'_, Word>], + ) -> Result<(), Self::Error> { + self.cs.set_low().map_err(DeviceError::Cs)?; + + let op_res = 'ops: { + for op in operations { + let res = match op { + Operation::Read(buf) => self.bus.read(buf).await, + Operation::Write(buf) => self.bus.write(buf).await, + Operation::Transfer(read, write) => self.bus.transfer(read, write).await, + Operation::TransferInPlace(buf) => self.bus.transfer_in_place(buf).await, + Operation::DelayNs(ns) => match self.bus.flush().await { + Err(e) => Err(e), + Ok(()) => { + self.delay.delay_ns(*ns).await; + Ok(()) + } + }, + }; + if let Err(e) = res { + break 'ops Err(e); + } + } + Ok(()) + }; + + // On failure, it's important to still flush and deassert CS. + let flush_res = self.bus.flush().await; + let cs_res = self.cs.set_high(); + + op_res.map_err(DeviceError::Spi)?; + flush_res.map_err(DeviceError::Spi)?; + cs_res.map_err(DeviceError::Cs)?; + + Ok(()) + } +} diff --git a/embedded-hal-bus/src/spi/mod.rs b/embedded-hal-bus/src/spi/mod.rs new file mode 100644 index 000000000..c7a793f90 --- /dev/null +++ b/embedded-hal-bus/src/spi/mod.rs @@ -0,0 +1,90 @@ +//! `SpiDevice` implementations. + +use core::fmt::{self, Debug, Display, Formatter}; +use embedded_hal::spi::{Error, ErrorKind}; + +mod exclusive; +pub use exclusive::*; +mod refcell; +pub use refcell::*; +#[cfg(feature = "std")] +mod mutex; +#[cfg(feature = "std")] +pub use mutex::*; +#[cfg(any(feature = "portable-atomic", target_has_atomic = "8"))] +mod atomic; +mod critical_section; +mod shared; +#[cfg(any(feature = "portable-atomic", target_has_atomic = "8"))] +pub use atomic::*; + +#[cfg(feature = "alloc")] +mod rc; +#[cfg(feature = "alloc")] +pub use rc::*; + +pub use self::critical_section::*; + +#[cfg(feature = "defmt-03")] +use crate::defmt; + +/// Error type for [`ExclusiveDevice`] operations. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] +pub enum DeviceError { + /// An inner SPI bus operation failed. + Spi(BUS), + /// Asserting or deasserting CS failed. + Cs(CS), +} + +impl Display for DeviceError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Spi(bus) => write!(f, "SPI bus error: {}", bus), + Self::Cs(cs) => write!(f, "SPI CS error: {}", cs), + } + } +} + +impl core::error::Error for DeviceError {} + +impl Error for DeviceError +where + BUS: Error + Debug, + CS: Debug, +{ + #[inline] + fn kind(&self) -> ErrorKind { + match self { + Self::Spi(e) => e.kind(), + Self::Cs(_) => ErrorKind::ChipSelectFault, + } + } +} + +/// Dummy [`DelayNs`](embedded_hal::delay::DelayNs) implementation that panics on use. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] +pub struct NoDelay; + +#[cold] +fn no_delay_panic() { + panic!("You've tried to execute a SPI transaction containing a `Operation::DelayNs` in a `SpiDevice` created with `new_no_delay()`. Create it with `new()` instead, passing a `DelayNs` implementation."); +} + +impl embedded_hal::delay::DelayNs for NoDelay { + #[inline] + fn delay_ns(&mut self, _ns: u32) { + no_delay_panic(); + } +} + +#[cfg(feature = "async")] +#[cfg_attr(docsrs, doc(cfg(feature = "async")))] +impl embedded_hal_async::delay::DelayNs for NoDelay { + #[inline] + async fn delay_ns(&mut self, _ns: u32) { + no_delay_panic(); + } +} diff --git a/embedded-hal-bus/src/spi/mutex.rs b/embedded-hal-bus/src/spi/mutex.rs new file mode 100644 index 000000000..83fe85d88 --- /dev/null +++ b/embedded-hal-bus/src/spi/mutex.rs @@ -0,0 +1,93 @@ +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; +use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice}; +use std::sync::Mutex; + +use super::DeviceError; +use crate::spi::shared::transaction; + +/// `std` `Mutex`-based shared bus [`SpiDevice`] implementation. +/// +/// This allows for sharing an [`SpiBus`], obtaining multiple [`SpiDevice`] instances, +/// each with its own `CS` pin. +/// +/// Sharing is implemented with a `std` [`Mutex`]. It allows a single bus across multiple threads, +/// with finer-grained locking than [`CriticalSectionDevice`](super::CriticalSectionDevice). The downside is +/// it is only available in `std` targets. +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub struct MutexDevice<'a, BUS, CS, D> { + bus: &'a Mutex, + cs: CS, + delay: D, +} + +impl<'a, BUS, CS, D> MutexDevice<'a, BUS, CS, D> { + /// Create a new [`MutexDevice`]. + /// + /// This sets the `cs` pin high, and returns an error if that fails. It is recommended + /// to set the pin high the moment it's configured as an output, to avoid glitches. + #[inline] + pub fn new(bus: &'a Mutex, mut cs: CS, delay: D) -> Result + where + CS: OutputPin, + { + cs.set_high()?; + Ok(Self { bus, cs, delay }) + } +} + +impl<'a, BUS, CS> MutexDevice<'a, BUS, CS, super::NoDelay> { + /// Create a new [`MutexDevice`] without support for in-transaction delays. + /// + /// This sets the `cs` pin high, and returns an error if that fails. It is recommended + /// to set the pin high the moment it's configured as an output, to avoid glitches. + /// + /// **Warning**: The returned instance *technically* doesn't comply with the `SpiDevice` + /// contract, which mandates delay support. It is relatively rare for drivers to use + /// in-transaction delays, so you might still want to use this method because it's more practical. + /// + /// Note that a future version of the driver might start using delays, causing your + /// code to panic. This wouldn't be considered a breaking change from the driver side, because + /// drivers are allowed to assume `SpiDevice` implementations comply with the contract. + /// If you feel this risk outweighs the convenience of having `cargo` automatically upgrade + /// the driver crate, you might want to pin the driver's version. + /// + /// # Panics + /// + /// The returned device will panic if you try to execute a transaction + /// that contains any operations of type [`Operation::DelayNs`]. + #[inline] + pub fn new_no_delay(bus: &'a Mutex, mut cs: CS) -> Result + where + CS: OutputPin, + { + cs.set_high()?; + Ok(Self { + bus, + cs, + delay: super::NoDelay, + }) + } +} + +impl<'a, BUS, CS, D> ErrorType for MutexDevice<'a, BUS, CS, D> +where + BUS: ErrorType, + CS: OutputPin, +{ + type Error = DeviceError; +} + +impl<'a, Word: Copy + 'static, BUS, CS, D> SpiDevice for MutexDevice<'a, BUS, CS, D> +where + BUS: SpiBus, + CS: OutputPin, + D: DelayNs, +{ + #[inline] + fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.lock().unwrap(); + + transaction(operations, bus, &mut self.delay, &mut self.cs) + } +} diff --git a/embedded-hal-bus/src/spi/rc.rs b/embedded-hal-bus/src/spi/rc.rs new file mode 100644 index 000000000..5a1c558bd --- /dev/null +++ b/embedded-hal-bus/src/spi/rc.rs @@ -0,0 +1,90 @@ +extern crate alloc; +use alloc::rc::Rc; + +use core::cell::RefCell; +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; +use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice}; + +use super::DeviceError; +use crate::spi::shared::transaction; + +/// Implementation of [`SpiDevice`] around a bus shared with `Rc>`. +/// This is the reference-counting equivalent of [`RefCellDevice`](super::RefCellDevice), requiring allocation. +/// +/// A single [`SpiBus`] is shared via [`RefCell`], and its ownership is handled by [`Rc`]. +/// Both of these mechanisms only allow sharing within a single thread (or interrupt priority level). +/// For this reason, this does not implement [`Send`]. +/// +/// When this structure is dropped, the reference count of the `Bus` instance will be decremented, +/// and it will be cleaned up once the reference count reaches zero. +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +pub struct RcDevice { + bus: Rc>, + cs: Cs, + delay: Delay, +} + +impl RcDevice { + /// Creates a new [`RcDevice`]. + /// + /// This sets the `cs` pin high, and returns an error if that fails. + /// It is recommended to have already set that pin high the moment it has been configured as an output, to avoid glitches. + /// + /// This function does not increment the reference count: + /// you will need to call `Rc::clone(&bus)` if you only have a `&Rc>`. + #[inline] + pub fn new(bus: Rc>, mut cs: Cs, delay: Delay) -> Result + where + Cs: OutputPin, + { + cs.set_high()?; + + Ok(Self { bus, cs, delay }) + } +} + +impl RcDevice { + /// Creates a new [`RcDevice`] without support for in-transaction delays. + /// + /// **Warning**: It's advised to prefer [`RcDevice::new`], + /// as the contract of [`SpiDevice`] requests support for in-transaction delays. + /// + /// Refer to [`RefCellDevice::new_no_delay`](super::RefCellDevice::new_no_delay) for more information. + #[inline] + pub fn new_no_delay(bus: Rc>, mut cs: Cs) -> Result + where + Cs: OutputPin, + { + cs.set_high()?; + + Ok(Self { + bus, + cs, + delay: super::NoDelay, + }) + } +} + +impl ErrorType for RcDevice +where + Bus: ErrorType, + Cs: OutputPin, +{ + type Error = DeviceError; +} + +impl SpiDevice for RcDevice +where + Word: Copy + 'static, + Bus: SpiBus, + Cs: OutputPin, + Delay: DelayNs, +{ + #[inline] + fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + + transaction(operations, bus, &mut self.delay, &mut self.cs) + } +} diff --git a/embedded-hal-bus/src/spi/refcell.rs b/embedded-hal-bus/src/spi/refcell.rs new file mode 100644 index 000000000..35bea03a2 --- /dev/null +++ b/embedded-hal-bus/src/spi/refcell.rs @@ -0,0 +1,92 @@ +use core::cell::RefCell; +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; +use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice}; + +use super::DeviceError; +use crate::spi::shared::transaction; + +/// `RefCell`-based shared bus [`SpiDevice`] implementation. +/// +/// This allows for sharing an [`SpiBus`], obtaining multiple [`SpiDevice`] instances, +/// each with its own `CS` pin. +/// +/// Sharing is implemented with a `RefCell`. This means it has low overhead, but `RefCellDevice` instances are not `Send`, +/// so it only allows sharing within a single thread (interrupt priority level). If you need to share a bus across several +/// threads, use [`CriticalSectionDevice`](super::CriticalSectionDevice) instead. +pub struct RefCellDevice<'a, BUS, CS, D> { + bus: &'a RefCell, + cs: CS, + delay: D, +} + +impl<'a, BUS, CS, D> RefCellDevice<'a, BUS, CS, D> { + /// Create a new [`RefCellDevice`]. + /// + /// This sets the `cs` pin high, and returns an error if that fails. It is recommended + /// to set the pin high the moment it's configured as an output, to avoid glitches. + #[inline] + pub fn new(bus: &'a RefCell, mut cs: CS, delay: D) -> Result + where + CS: OutputPin, + { + cs.set_high()?; + Ok(Self { bus, cs, delay }) + } +} + +impl<'a, BUS, CS> RefCellDevice<'a, BUS, CS, super::NoDelay> { + /// Create a new [`RefCellDevice`] without support for in-transaction delays. + /// + /// This sets the `cs` pin high, and returns an error if that fails. It is recommended + /// to set the pin high the moment it's configured as an output, to avoid glitches. + /// + /// **Warning**: The returned instance *technically* doesn't comply with the `SpiDevice` + /// contract, which mandates delay support. It is relatively rare for drivers to use + /// in-transaction delays, so you might still want to use this method because it's more practical. + /// + /// Note that a future version of the driver might start using delays, causing your + /// code to panic. This wouldn't be considered a breaking change from the driver side, because + /// drivers are allowed to assume `SpiDevice` implementations comply with the contract. + /// If you feel this risk outweighs the convenience of having `cargo` automatically upgrade + /// the driver crate, you might want to pin the driver's version. + /// + /// # Panics + /// + /// The returned device will panic if you try to execute a transaction + /// that contains any operations of type [`Operation::DelayNs`]. + #[inline] + pub fn new_no_delay(bus: &'a RefCell, mut cs: CS) -> Result + where + CS: OutputPin, + { + cs.set_high()?; + Ok(Self { + bus, + cs, + delay: super::NoDelay, + }) + } +} + +impl<'a, BUS, CS, D> ErrorType for RefCellDevice<'a, BUS, CS, D> +where + BUS: ErrorType, + CS: OutputPin, +{ + type Error = DeviceError; +} + +impl<'a, Word: Copy + 'static, BUS, CS, D> SpiDevice for RefCellDevice<'a, BUS, CS, D> +where + BUS: SpiBus, + CS: OutputPin, + D: DelayNs, +{ + #[inline] + fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + + transaction(operations, bus, &mut self.delay, &mut self.cs) + } +} diff --git a/embedded-hal-bus/src/spi/shared.rs b/embedded-hal-bus/src/spi/shared.rs new file mode 100644 index 000000000..95730ba19 --- /dev/null +++ b/embedded-hal-bus/src/spi/shared.rs @@ -0,0 +1,44 @@ +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; +use embedded_hal::spi::{ErrorType, Operation, SpiBus}; + +use crate::spi::DeviceError; + +/// Common implementation to perform a transaction against the device. +#[inline] +pub fn transaction( + operations: &mut [Operation], + bus: &mut BUS, + delay: &mut D, + cs: &mut CS, +) -> Result<(), DeviceError> +where + BUS: SpiBus + ErrorType, + CS: OutputPin, + D: DelayNs, + Word: Copy, +{ + cs.set_low().map_err(DeviceError::Cs)?; + + let op_res = operations.iter_mut().try_for_each(|op| match op { + Operation::Read(buf) => bus.read(buf), + Operation::Write(buf) => bus.write(buf), + Operation::Transfer(read, write) => bus.transfer(read, write), + Operation::TransferInPlace(buf) => bus.transfer_in_place(buf), + Operation::DelayNs(ns) => { + bus.flush()?; + delay.delay_ns(*ns); + Ok(()) + } + }); + + // On failure, it's important to still flush and deassert CS. + let flush_res = bus.flush(); + let cs_res = cs.set_high(); + + op_res.map_err(DeviceError::Spi)?; + flush_res.map_err(DeviceError::Spi)?; + cs_res.map_err(DeviceError::Cs)?; + + Ok(()) +} diff --git a/embedded-hal-bus/src/util.rs b/embedded-hal-bus/src/util.rs new file mode 100644 index 000000000..bb16577c4 --- /dev/null +++ b/embedded-hal-bus/src/util.rs @@ -0,0 +1,34 @@ +//! Utilities shared by all bus types. + +#[allow(unused_imports)] +use core::cell::UnsafeCell; + +#[cfg(not(feature = "portable-atomic"))] +use core::sync::atomic::AtomicBool; +#[cfg(feature = "portable-atomic")] +use portable_atomic::AtomicBool; + +#[cfg(any(feature = "portable-atomic", target_has_atomic = "8"))] +/// Cell type used by [`spi::AtomicDevice`](crate::spi::AtomicDevice) and [`i2c::AtomicDevice`](crate::i2c::AtomicDevice). +/// +/// To use `AtomicDevice`, you must wrap the bus with this struct, and then +/// construct multiple `AtomicDevice` instances with references to it. +pub struct AtomicCell { + pub(crate) bus: UnsafeCell, + pub(crate) busy: AtomicBool, +} +#[cfg(any(feature = "portable-atomic", target_has_atomic = "8"))] +unsafe impl Send for AtomicCell {} +#[cfg(any(feature = "portable-atomic", target_has_atomic = "8"))] +unsafe impl Sync for AtomicCell {} + +#[cfg(any(feature = "portable-atomic", target_has_atomic = "8"))] +impl AtomicCell { + /// Create a new `AtomicCell` + pub fn new(bus: BUS) -> Self { + Self { + bus: UnsafeCell::new(bus), + busy: AtomicBool::from(false), + } + } +} diff --git a/embedded-hal-nb/CHANGELOG.md b/embedded-hal-nb/CHANGELOG.md index 5634771f3..63314c7cf 100644 --- a/embedded-hal-nb/CHANGELOG.md +++ b/embedded-hal-nb/CHANGELOG.md @@ -7,7 +7,40 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] -... +- Added `core::error::Error` implementations for every custom `impl Error` +- Increased MSRV to 1.81 due to `core::error::Error` + +## [v1.0.0] - 2023-12-28 + +Check out the [announcement blog post](https://blog.rust-embedded.org/embedded-hal-v1/) and the [migration guide](../docs/migrating-from-0.2-to-1.0.md) for help with migrating from v0.2 to v1.0. + +- Updated `embedded-hal` to version `1.0.0`. + +## [v1.0.0-rc.3] - 2023-12-14 + +- Updated `embedded-hal` to version `1.0.0-rc.3`. + +## [v1.0.0-rc.2] - 2023-11-28 + +- Updated `embedded-hal` to version `1.0.0-rc.2`. +- Minor document fixes. +- Add #[inline] hints to most of `embedded-hal-nb` functions. + +## [v1.0.0-rc.1] - 2023-08-15 + +- Updated `embedded-hal` to version `1.0.0-rc.1`. +- Added `+ ?Sized` to all blanket impls. + +## [v1.0.0-alpha.3] - 2023-07-04 + +### Changed +- Updated `embedded-hal` to version `1.0.0-alpha.11`. + + +## [v1.0.0-alpha.2] - 2023-04-04 + +### Changed +- Updated `embedded-hal` to version `1.0.0-alpha.10`. ## [v1.0.0-alpha.1] - 2022-09-28 @@ -18,6 +51,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). First release to crates.io -[Unreleased]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-nb-v1.0.0-alpha.1...HEAD +[Unreleased]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-nb-v1.0.0...HEAD +[v1.0.0]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-nb-v1.0.0-rc.3...embedded-hal-nb-v1.0.0 +[v1.0.0-rc.3]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-nb-v1.0.0-rc.2...embedded-hal-nb-v1.0.0-rc.3 +[v1.0.0-rc.2]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-nb-v1.0.0-rc.1...embedded-hal-nb-v1.0.0-rc.2 +[v1.0.0-rc.1]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-nb-v1.0.0-alpha.3...embedded-hal-nb-v1.0.0-rc.1 +[v1.0.0-alpha.3]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-nb-v1.0.0-alpha.2...embedded-hal-nb-v1.0.0-alpha.3 +[v1.0.0-alpha.2]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-nb-v1.0.0-alpha.1...embedded-hal-nb-v1.0.0-alpha.2 [v1.0.0-alpha.1]: https://github.com/rust-embedded/embedded-hal/compare/embedded-hal-nb-v1.0.0-alpha.0...embedded-hal-nb-v1.0.0-alpha.1 [v1.0.0-alpha.0]: https://github.com/rust-embedded/embedded-hal/tree/embedded-hal-nb-v1.0.0-alpha.0 diff --git a/embedded-hal-nb/Cargo.toml b/embedded-hal-nb/Cargo.toml index 6e8f49c71..43a6cd7be 100644 --- a/embedded-hal-nb/Cargo.toml +++ b/embedded-hal-nb/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "embedded-hal-nb" -version = "1.0.0-alpha.1" -edition = "2018" +version = "1.0.0" +edition = "2021" +rust-version = "1.81" categories = ["embedded", "hardware-support", "no-std"] description = "Non-blocking Hardware Abstraction Layer (HAL) for embedded systems using the `nb` crate." @@ -12,9 +13,9 @@ readme = "README.md" repository = "https://github.com/rust-embedded/embedded-hal" [dependencies] -embedded-hal = { version = "=1.0.0-alpha.9", path = "../embedded-hal" } +embedded-hal = { version = "1.0.0", path = "../embedded-hal" } nb = "1" [dev-dependencies] -cortex-m-rt = "=0.7.1" # 0.7.2 bumped its MSRV higher than embedded-hal's +cortex-m-rt = "0.7" stm32f1 = { version = "0.15", features = ["stm32f103", "rt"] } diff --git a/embedded-hal-nb/README.md b/embedded-hal-nb/README.md index b47a80dd8..aa72e7b1e 100644 --- a/embedded-hal-nb/README.md +++ b/embedded-hal-nb/README.md @@ -1,7 +1,7 @@ [![crates.io](https://img.shields.io/crates/d/embedded-hal-nb.svg)](https://crates.io/crates/embedded-hal-nb) [![crates.io](https://img.shields.io/crates/v/embedded-hal-nb.svg)](https://crates.io/crates/embedded-hal-nb) [![Documentation](https://docs.rs/embedded-hal-nb/badge.svg)](https://docs.rs/embedded-hal-nb) -![Minimum Supported Rust Version](https://img.shields.io/badge/rustc-1.54+-blue.svg) +![Minimum Supported Rust Version](https://img.shields.io/badge/rustc-1.60+-blue.svg) # `embedded-hal-nb` @@ -17,7 +17,7 @@ This project is developed and maintained by the [HAL team](https://github.com/ru ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.54 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.60 and up. It *might* compile with older versions but that may change in any new patch release. See [here](../docs/msrv.md) for details on how the MSRV may be upgraded. diff --git a/embedded-hal-nb/src/lib.rs b/embedded-hal-nb/src/lib.rs index d2089c5af..4cdf90dc1 100644 --- a/embedded-hal-nb/src/lib.rs +++ b/embedded-hal-nb/src/lib.rs @@ -57,7 +57,7 @@ //! [`svd2rust`]: https://crates.io/crates/svd2rust //! //! Shown below is an implementation of some of the HAL traits for the [`stm32f1xx-hal`] crate. This -//! single implementation will work for *any* microcontroller in the STM32F1xx family. +//! single implementation will work for *any* microcontroller in the `STM32F1xx` family. //! //! [`stm32f1`]: https://crates.io/crates/stm32f1 //! diff --git a/embedded-hal-nb/src/serial.rs b/embedded-hal-nb/src/serial.rs index e01803ab1..2b0d504ef 100644 --- a/embedded-hal-nb/src/serial.rs +++ b/embedded-hal-nb/src/serial.rs @@ -1,8 +1,84 @@ -//! Serial interface +//! Serial interface. -pub use embedded_hal::serial::{Error, ErrorKind, ErrorType}; +/// Serial error. +pub trait Error: core::fmt::Debug { + /// Convert error to a generic serial error kind + /// + /// By using this method, serial errors freely defined by HAL implementations + /// can be converted to a set of generic serial errors upon which generic + /// code can act. + fn kind(&self) -> ErrorKind; +} + +impl Error for core::convert::Infallible { + #[inline] + fn kind(&self) -> ErrorKind { + match *self {} + } +} + +/// Serial error kind. +/// +/// This represents a common set of serial operation errors. HAL implementations are +/// free to define more specific or additional error types. However, by providing +/// a mapping to these common serial errors, generic code can still react to them. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[non_exhaustive] +pub enum ErrorKind { + /// The peripheral receive buffer was overrun. + Overrun, + /// Received data does not conform to the peripheral configuration. + /// Can be caused by a misconfigured device on either end of the serial line. + FrameFormat, + /// Parity check failed. + Parity, + /// Serial line is too noisy to read valid data. + Noise, + /// A different error occurred. The original error may contain more information. + Other, +} + +impl Error for ErrorKind { + #[inline] + fn kind(&self) -> ErrorKind { + *self + } +} + +impl core::error::Error for ErrorKind {} + +impl core::fmt::Display for ErrorKind { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Overrun => write!(f, "The peripheral receive buffer was overrun"), + Self::Parity => write!(f, "Parity check failed"), + Self::Noise => write!(f, "Serial line is too noisy to read valid data"), + Self::FrameFormat => write!( + f, + "Received data does not conform to the peripheral configuration" + ), + Self::Other => write!( + f, + "A different error occurred. The original error may contain more information" + ), + } + } +} + +/// Serial error type trait. +/// +/// This just defines the error type, to be used by the other traits. +pub trait ErrorType { + /// Error type + type Error: Error; +} + +impl ErrorType for &mut T { + type Error = T::Error; +} -/// Read half of a serial interface +/// Read half of a serial interface. /// /// Some serial interfaces support different data sizes (8 bits, 9 bits, etc.); /// This can be encoded in this trait via the `Word` type parameter. @@ -11,26 +87,29 @@ pub trait Read: ErrorType { fn read(&mut self) -> nb::Result; } -impl, Word: Copy> Read for &mut T { +impl + ?Sized, Word: Copy> Read for &mut T { + #[inline] fn read(&mut self) -> nb::Result { T::read(self) } } -/// Write half of a serial interface +/// Write half of a serial interface. pub trait Write: ErrorType { - /// Writes a single word to the serial interface + /// Writes a single word to the serial interface. fn write(&mut self, word: Word) -> nb::Result<(), Self::Error>; - /// Ensures that none of the previously written words are still buffered + /// Ensures that none of the previously written words are still buffered. fn flush(&mut self) -> nb::Result<(), Self::Error>; } -impl, Word: Copy> Write for &mut T { +impl + ?Sized, Word: Copy> Write for &mut T { + #[inline] fn write(&mut self, word: Word) -> nb::Result<(), Self::Error> { T::write(self, word) } + #[inline] fn flush(&mut self) -> nb::Result<(), Self::Error> { T::flush(self) } @@ -40,11 +119,11 @@ impl, Word: Copy> Write for &mut T { /// /// TODO write example of usage -impl core::fmt::Write - for dyn Write + '_ +impl core::fmt::Write for dyn Write + '_ where Word: Copy + From, { + #[inline] fn write_str(&mut self, s: &str) -> core::fmt::Result { let _ = s .bytes() diff --git a/embedded-hal-nb/src/spi.rs b/embedded-hal-nb/src/spi.rs index a9e7c94f0..75553c78b 100644 --- a/embedded-hal-nb/src/spi.rs +++ b/embedded-hal-nb/src/spi.rs @@ -4,22 +4,22 @@ pub use embedded_hal::spi::{ Error, ErrorKind, ErrorType, Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3, }; -/// Full duplex SPI (master mode) +/// Full duplex SPI (master mode). /// /// # Notes /// -/// - It's the task of the user of this interface to manage the slave select lines +/// - It's the task of the user of this interface to manage the slave select lines. /// /// - Due to how full duplex SPI works each `read` call must be preceded by a `write` call. /// /// - `read` calls only return the data received with the last `write` call. -/// Previously received data is discarded +/// Previously received data is discarded /// /// - Data is only guaranteed to be clocked out when the `read` call succeeds. -/// The slave select line shouldn't be released before that. +/// The slave select line shouldn't be released before that. /// /// - Some SPIs can work with 8-bit *and* 16-bit words. You can overload this trait with different -/// `Word` types to allow operation in both modes. +/// `Word` types to allow operation in both modes. pub trait FullDuplex: ErrorType { /// Reads the word stored in the shift register /// @@ -31,11 +31,13 @@ pub trait FullDuplex: ErrorType { fn write(&mut self, word: Word) -> nb::Result<(), Self::Error>; } -impl, Word: Copy> FullDuplex for &mut T { +impl + ?Sized, Word: Copy> FullDuplex for &mut T { + #[inline] fn read(&mut self) -> nb::Result { T::read(self) } + #[inline] fn write(&mut self, word: Word) -> nb::Result<(), Self::Error> { T::write(self, word) } diff --git a/embedded-hal/CHANGELOG.md b/embedded-hal/CHANGELOG.md index 7682542d5..e919e25ad 100644 --- a/embedded-hal/CHANGELOG.md +++ b/embedded-hal/CHANGELOG.md @@ -7,6 +7,61 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +- Added `core::error::Error` implementations for every custom `impl Error` +- Increased MSRV to 1.81 due to `core::error::Error` + +## [v1.0.0] - 2023-12-28 + +Check out the [announcement blog post](https://blog.rust-embedded.org/embedded-hal-v1/) and the [migration guide](../docs/migrating-from-0.2-to-1.0.md) for help with migrating from v0.2 to v1.0. + +- gpio: remove `ToggleableOutputPin`, move `toggle()` to `StatefulOutputPin`. + +## [v1.0.0-rc.3] - 2023-12-14 + +- gpio: require `&mut self` in `InputPin` and `StatefulOutputPin`. + +## [v1.0.0-rc.2] - 2023-11-28 + +- Minor document fixes. +- Add #[inline] hints to most of `embedded-hal` functions. +- pwm: rename `get_max_duty_cycle` to `max_duty_cycle`. +- delay: Rename `DelayUs` to `DelayNs` +- delay: Add `DelayNs::delay_ns()` +- delay: Add default impls of `delay_ms` and `delay_us` based on `delay_ns`. +- delay: Make the default impl of `delay_ms` more efficient, it now does less calls to the underlying `delay_ns` (previously `delay_us`). +- spi: Rename `Operation::DelayUs` to `Operation::DelayNs`, with nanosecond precision. + +## [v1.0.0-rc.1] - 2023-08-15 + +- The Minimum Supported Rust Version (MSRV) is now 1.60.0 +- Add optional `defmt` 0.3 support. +- Remove serial traits, the replacement is the `embedded-io` crate. +- Added `+ ?Sized` to all blanket impls. + +## [v1.0.0-alpha.11] - 2023-07-04 + +*** This is (also) an alpha release with breaking changes (sorry) *** + +### Added +- spi: added `Operation::DelayUs(u32)`. + +### Removed +- spi: removed read-only and write-only traits. + +## [v1.0.0-alpha.10] - 2023-04-04 + +*** This is (also) an alpha release with breaking changes (sorry) *** + +### Added +- Added `pwm::SetDutyCycle` trait. + +### Changed +- gpio: add `ErrorKind` enum for consistency with other traits and for future extensibility. No kinds are defined for now. +- delay: make infallible. +- i2c: remove `_iter()` methods. +- i2c: add default implementations for all methods based on `transaction()`. +- i2c: document guidelines for shared bus usage. +- spi: SpiDevice transaction now takes an operation slice instead of a closure ## [v1.0.0-alpha.9] - 2022-09-28 @@ -40,7 +95,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). *** This is (also) an alpha release with breaking changes (sorry) *** ### Changed -- The Minimum Supported Rust Version (MSRV) is now 1.54.0 +- The Minimum Supported Rust Version (MSRV) is now 1.59.0 - `spi`: unify all traits into `SpiReadBus`, `SpiWriteBus` and `SpiBus` (read-write). - `spi`: Add `SpiDevice` trait to represent a single device in a (possibly shared) bus, with managed chip-select (CS) pin. - `spi`: Clarify that implementations are allowed to return before operations are finished, add `flush` to wait until finished. @@ -59,7 +114,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixed blanket impl of `DelayUs` not covering the `delay_ms` method. ### Changed -- `spi`: traits now enforce all impls on the same struct (eg `Transfer` and `Write`) have the same `Error` type. +- `spi`: traits now enforce all impls on the same struct (e.g. `Transfer` and `Write`) have the same `Error` type. - `digital`: traits now enforce all impls on the same struct have the same `Error` type. - `serial`: traits now enforce all impls on the same struct have the same `Error` type. - `i2c`: traits now enforce all impls on the same struct have the same `Error` type. @@ -116,7 +171,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Swap PWM channel arguments to references - All trait methods have been renamed to remove the `try_` prefix (i.e. `try_send` -> `send`) for consistency. -- Moved all traits into two sub modules for each feature depending on the execution model: `blocking` and `nb` (non-blocking). For example, the spi traits can now be found under `embedded_hal::spi::blocking` or `embedded_hal::spi::nb`. +- Moved all traits into two submodules for each feature depending on the execution model: `blocking` and `nb` (non-blocking). For example, the spi traits can now be found under `embedded_hal::spi::blocking` or `embedded_hal::spi::nb`. - Execution-model-independent definitions have been moved into the feature module. For example, SPI `Phase` is now defined in `embedded_hal::spi::Phase`. For convenience, these definitions are reexported in both of its blocking and non-blocking submodules. - Re-export `nb::{block!, Error, Result}` to avoid version mismatches. These should be used instead of importing the `nb` crate directly in dependent crates. @@ -247,7 +302,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- Re-export most / unchanged traits from embedded-hal v0.2.x to allow inter-operation between HAL +- Re-export most / unchanged traits from embedded-hal v0.2.x to allow interoperation between HAL implementations and drivers that are using different minor versions. ## [v0.1.2] - 2018-02-14 @@ -266,7 +321,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). Initial release -[Unreleased]: https://github.com/rust-embedded/embedded-hal/compare/v1.0.0-alpha.9...HEAD +[Unreleased]: https://github.com/rust-embedded/embedded-hal/compare/v1.0.0...HEAD +[v1.0.0]: https://github.com/rust-embedded/embedded-hal/compare/v1.0.0-rc.3...v1.0.0 +[v1.0.0-rc.3]: https://github.com/rust-embedded/embedded-hal/compare/v1.0.0-rc.2...v1.0.0-rc.3 +[v1.0.0-rc.2]: https://github.com/rust-embedded/embedded-hal/compare/v1.0.0-rc.1...v1.0.0-rc.2 +[v1.0.0-rc.1]: https://github.com/rust-embedded/embedded-hal/compare/v1.0.0-alpha.11...v1.0.0-rc.1 +[v1.0.0-alpha.11]: https://github.com/rust-embedded/embedded-hal/compare/v1.0.0-alpha.10...v1.0.0-alpha.11 +[v1.0.0-alpha.10]: https://github.com/rust-embedded/embedded-hal/compare/v1.0.0-alpha.9...v1.0.0-alpha.10 [v1.0.0-alpha.9]: https://github.com/rust-embedded/embedded-hal/compare/v1.0.0-alpha.8...v1.0.0-alpha.9 [v1.0.0-alpha.8]: https://github.com/rust-embedded/embedded-hal/compare/v1.0.0-alpha.7...v1.0.0-alpha.8 [v1.0.0-alpha.7]: https://github.com/rust-embedded/embedded-hal/compare/v1.0.0-alpha.6...v1.0.0-alpha.7 diff --git a/embedded-hal/Cargo.toml b/embedded-hal/Cargo.toml index 8f99da5db..011f3cc9d 100644 --- a/embedded-hal/Cargo.toml +++ b/embedded-hal/Cargo.toml @@ -2,15 +2,22 @@ authors = [ "The Embedded HAL Team ", "Jorge Aparicio ", - "Jonathan 'theJPster' Pallant " + "Jonathan 'theJPster' Pallant ", ] categories = ["asynchronous", "embedded", "hardware-support", "no-std"] description = " A Hardware Abstraction Layer (HAL) for embedded systems " documentation = "https://docs.rs/embedded-hal" -edition = "2018" +edition = "2021" +rust-version = "1.81" keywords = ["hal", "IO"] license = "MIT OR Apache-2.0" name = "embedded-hal" readme = "README.md" repository = "https://github.com/rust-embedded/embedded-hal" -version = "1.0.0-alpha.9" +version = "1.0.0" + +[features] +defmt-03 = ["dep:defmt-03"] + +[dependencies] +defmt-03 = { package = "defmt", version = "0.3", optional = true } diff --git a/embedded-hal/README.md b/embedded-hal/README.md index 478b68608..e47182b71 100644 --- a/embedded-hal/README.md +++ b/embedded-hal/README.md @@ -1,7 +1,7 @@ [![crates.io](https://img.shields.io/crates/d/embedded-hal.svg)](https://crates.io/crates/embedded-hal) [![crates.io](https://img.shields.io/crates/v/embedded-hal.svg)](https://crates.io/crates/embedded-hal) [![Documentation](https://docs.rs/embedded-hal/badge.svg)](https://docs.rs/embedded-hal) -![Minimum Supported Rust Version](https://img.shields.io/badge/rustc-1.54+-blue.svg) +![Minimum Supported Rust Version](https://img.shields.io/badge/rustc-1.60+-blue.svg) # `embedded-hal` @@ -9,13 +9,82 @@ This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team). -## [API reference] +## Companion crates -[API reference]: https://docs.rs/embedded-hal +The main `embedded-hal` crate contains only blocking traits, where the operation is done +synchronously before returning. Check out the following crates, which contain versions +of the traits for other execution models: + +- [`embedded-hal-async`](https://docs.rs/embedded-hal-async): async/await-based. +- [`embedded-hal-nb`](https://docs.rs/embedded-hal-nb): polling-based, using the `nb` crate. + +The [`embedded-hal-bus`](https://docs.rs/embedded-hal-bus) crate provides utilities for sharing +SPI and I2C buses. + +Additionally, more domain-specific traits are available in separate crates: +- [`embedded-can`](https://docs.rs/embedded-can): Controller Area Network (CAN) +- [`embedded-io`](https://docs.rs/embedded-io): I/O byte streams (like `std::io`, but `no-std`-compatible). + +## Serial/UART traits + +There is no serial traits in `embedded-hal`. Instead, use [`embedded-io`](https://crates.io/crates/embedded-io). +A serial port is essentially a byte-oriented stream, and that's what `embedded-io` models. Sharing the traits +with all byte streams has some advantages. For example, it allows generic code providing a command-line interface +or a console to operate either on hardware serial ports or on virtual ones like Telnet or USB CDC-ACM. + +## Design goals + +The HAL + +- Must *erase* device specific details. Neither register, register blocks, nor magic values should + appear in the API. + +- Must be generic *within* a device and *across* devices. The API to use a serial interface must + be the same regardless of whether the implementation uses the USART1 or UART4 peripheral of a + device or the UART0 peripheral of another device. + +- Where possible must *not* be tied to a specific asynchronous model. The API should be usable + in blocking mode, with the `futures` model, with an async/await model or with a callback model. + (cf. the [`nb`](https://docs.rs/nb) crate) + +- Must be minimal, and thus easy to implement and zero cost, yet highly composable. People that + want higher level abstraction should *prefer to use this HAL* rather than *re-implement* + register manipulation code. + +- Serve as a foundation for building an ecosystem of platform-agnostic drivers. Here driver + means a library crate that lets a target platform interface an external device like a digital + sensor or a wireless transceiver. The advantage of this system is that by writing the driver as + a generic library on top of `embedded-hal` driver authors can support any number of target + platforms (e.g. Cortex-M microcontrollers, AVR microcontrollers, embedded Linux, etc.). The + advantage for application developers is that by adopting `embedded-hal` they can unlock all + these drivers for their platform. + +- Trait methods must be fallible so that they can be used in any possible situation. + Nevertheless, HAL implementations can additionally provide infallible versions of the same methods + if they can never fail in their platform. This way, generic code can use the fallible abstractions + provided here but platform-specific code can avoid fallibility-related boilerplate if possible. + +## Out of scope + +- Initialization and configuration stuff like "ensure this serial interface and that SPI + interface are not using the same pins". The HAL will focus on *doing I/O*. + +## Platform agnostic drivers + +You can find platform-agnostic drivers built on top of `embedded-hal` on crates.io by [searching +for the *embedded-hal* keyword](https://crates.io/keywords/embedded-hal). + +If you are writing a platform-agnostic driver yourself you are highly encouraged to [add the +embedded-hal keyword](https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata) +to your crate before publishing it! + +## Optional Cargo features + +- **`defmt-03`**: Derive `defmt::Format` from `defmt` 0.3 for enums and structs. ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.54 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.60 and up. It *might* compile with older versions but that may change in any new patch release. See [here](../docs/msrv.md) for details on how the MSRV may be upgraded. @@ -25,8 +94,8 @@ See [here](../docs/msrv.md) for details on how the MSRV may be upgraded. Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. diff --git a/embedded-hal/src/delay.rs b/embedded-hal/src/delay.rs index 52c1fdd5a..01e563750 100644 --- a/embedded-hal/src/delay.rs +++ b/embedded-hal/src/delay.rs @@ -1,37 +1,62 @@ -//! Delays +//! Delays. -/// Microsecond delay -/// -pub trait DelayUs { - /// Enumeration of `DelayUs` errors - type Error: core::fmt::Debug; +/// Nanoseconds per microsecond +const NANOS_PER_MICRO: u32 = 1_000; +/// Nanoseconds per millisecond +const NANOS_PER_MILLI: u32 = 1_000_000; + +/// Delay with up to nanosecond precision. +pub trait DelayNs { + /// Pauses execution for at minimum `ns` nanoseconds. Pause can be longer + /// if the implementation requires it due to precision/timing issues. + fn delay_ns(&mut self, ns: u32); /// Pauses execution for at minimum `us` microseconds. Pause can be longer /// if the implementation requires it due to precision/timing issues. - fn delay_us(&mut self, us: u32) -> Result<(), Self::Error>; + fn delay_us(&mut self, mut us: u32) { + const MAX_MICROS: u32 = u32::MAX / NANOS_PER_MICRO; + + // Avoid potential overflow if micro -> nano conversion is too large + while us > MAX_MICROS { + us -= MAX_MICROS; + self.delay_ns(MAX_MICROS * NANOS_PER_MICRO); + } + + self.delay_ns(us * NANOS_PER_MICRO); + } /// Pauses execution for at minimum `ms` milliseconds. Pause can be longer /// if the implementation requires it due to precision/timing issues. - fn delay_ms(&mut self, ms: u32) -> Result<(), Self::Error> { - for _ in 0..ms { - self.delay_us(1000)?; + #[inline] + fn delay_ms(&mut self, mut ms: u32) { + const MAX_MILLIS: u32 = u32::MAX / NANOS_PER_MILLI; + + // Avoid potential overflow if milli -> nano conversion is too large + while ms > MAX_MILLIS { + ms -= MAX_MILLIS; + self.delay_ns(MAX_MILLIS * NANOS_PER_MILLI); } - Ok(()) + self.delay_ns(ms * NANOS_PER_MILLI); } } -impl DelayUs for &mut T +impl DelayNs for &mut T where - T: DelayUs, + T: DelayNs + ?Sized, { - type Error = T::Error; + #[inline] + fn delay_ns(&mut self, ns: u32) { + T::delay_ns(self, ns); + } - fn delay_us(&mut self, us: u32) -> Result<(), Self::Error> { - T::delay_us(self, us) + #[inline] + fn delay_us(&mut self, us: u32) { + T::delay_us(self, us); } - fn delay_ms(&mut self, ms: u32) -> Result<(), Self::Error> { - T::delay_ms(self, ms) + #[inline] + fn delay_ms(&mut self, ms: u32) { + T::delay_ms(self, ms); } } diff --git a/embedded-hal/src/digital.rs b/embedded-hal/src/digital.rs index c1fb82fe6..a38040c0c 100644 --- a/embedded-hal/src/digital.rs +++ b/embedded-hal/src/digital.rs @@ -1,23 +1,77 @@ -//! Digital I/O +//! Digital I/O. -use core::{convert::From, ops::Not}; +use core::ops::Not; -/// GPIO error type trait +#[cfg(feature = "defmt-03")] +use crate::defmt; + +/// Error. +pub trait Error: core::fmt::Debug { + /// Convert error to a generic error kind + /// + /// By using this method, errors freely defined by HAL implementations + /// can be converted to a set of generic errors upon which generic + /// code can act. + fn kind(&self) -> ErrorKind; +} + +impl Error for core::convert::Infallible { + fn kind(&self) -> ErrorKind { + match *self {} + } +} + +/// Error kind. +/// +/// This represents a common set of operation errors. HAL implementations are +/// free to define more specific or additional error types. However, by providing +/// a mapping to these common errors, generic code can still react to them. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] +#[non_exhaustive] +pub enum ErrorKind { + /// A different error occurred. The original error may contain more information. + Other, +} + +impl Error for ErrorKind { + #[inline] + fn kind(&self) -> ErrorKind { + *self + } +} + +impl core::error::Error for ErrorKind {} + +impl core::fmt::Display for ErrorKind { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Other => write!( + f, + "A different error occurred. The original error may contain more information" + ), + } + } +} + +/// Error type trait. /// /// This just defines the error type, to be used by the other traits. pub trait ErrorType { /// Error type - type Error: core::fmt::Debug; + type Error: Error; } -impl ErrorType for &T { +impl ErrorType for &T { type Error = T::Error; } -impl ErrorType for &mut T { + +impl ErrorType for &mut T { type Error = T::Error; } -/// Digital output pin state +/// Digital output pin state. /// /// Conversion from `bool` and logical negation are also implemented /// for this type. @@ -28,14 +82,16 @@ impl ErrorType for &mut T { /// assert_eq!(!state, PinState::High); /// ``` #[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] pub enum PinState { - /// Low pin state + /// Low pin state. Low, - /// High pin state + /// High pin state. High, } impl From for PinState { + #[inline] fn from(value: bool) -> Self { match value { false => PinState::Low, @@ -47,6 +103,7 @@ impl From for PinState { impl Not for PinState { type Output = PinState; + #[inline] fn not(self) -> Self::Output { match self { PinState::High => PinState::Low, @@ -55,24 +112,35 @@ impl Not for PinState { } } -/// Single digital push-pull output pin +impl From for bool { + #[inline] + fn from(value: PinState) -> bool { + match value { + PinState::Low => false, + PinState::High => true, + } + } +} + +/// Single digital push-pull output pin. pub trait OutputPin: ErrorType { - /// Drives the pin low + /// Drives the pin low. /// /// *NOTE* the actual electrical state of the pin may not actually be low, e.g. due to external - /// electrical sources + /// electrical sources. fn set_low(&mut self) -> Result<(), Self::Error>; - /// Drives the pin high + /// Drives the pin high. /// /// *NOTE* the actual electrical state of the pin may not actually be high, e.g. due to external - /// electrical sources + /// electrical sources. fn set_high(&mut self) -> Result<(), Self::Error>; - /// Drives the pin high or low depending on the provided value + /// Drives the pin high or low depending on the provided value. /// /// *NOTE* the actual electrical state of the pin may not actually be high or low, e.g. due to external - /// electrical sources + /// electrical sources. + #[inline] fn set_state(&mut self, state: PinState) -> Result<(), Self::Error> { match state { PinState::Low => self.set_low(), @@ -81,70 +149,76 @@ pub trait OutputPin: ErrorType { } } -impl OutputPin for &mut T { +impl OutputPin for &mut T { + #[inline] fn set_low(&mut self) -> Result<(), Self::Error> { T::set_low(self) } + #[inline] fn set_high(&mut self) -> Result<(), Self::Error> { T::set_high(self) } + #[inline] fn set_state(&mut self, state: PinState) -> Result<(), Self::Error> { T::set_state(self, state) } } -/// Push-pull output pin that can read its output state +/// Push-pull output pin that can read its output state. pub trait StatefulOutputPin: OutputPin { /// Is the pin in drive high mode? /// - /// *NOTE* this does *not* read the electrical state of the pin - fn is_set_high(&self) -> Result; + /// *NOTE* this does *not* read the electrical state of the pin. + fn is_set_high(&mut self) -> Result; /// Is the pin in drive low mode? /// - /// *NOTE* this does *not* read the electrical state of the pin - fn is_set_low(&self) -> Result; + /// *NOTE* this does *not* read the electrical state of the pin. + fn is_set_low(&mut self) -> Result; + + /// Toggle pin output. + fn toggle(&mut self) -> Result<(), Self::Error> { + let was_low: bool = self.is_set_low()?; + self.set_state(PinState::from(was_low)) + } } -impl StatefulOutputPin for &mut T { - fn is_set_high(&self) -> Result { +impl StatefulOutputPin for &mut T { + #[inline] + fn is_set_high(&mut self) -> Result { T::is_set_high(self) } - fn is_set_low(&self) -> Result { + #[inline] + fn is_set_low(&mut self) -> Result { T::is_set_low(self) } -} - -/// Output pin that can be toggled -pub trait ToggleableOutputPin: ErrorType { - /// Toggle pin output. - fn toggle(&mut self) -> Result<(), Self::Error>; -} -impl ToggleableOutputPin for &mut T { + #[inline] fn toggle(&mut self) -> Result<(), Self::Error> { T::toggle(self) } } -/// Single digital input pin +/// Single digital input pin. pub trait InputPin: ErrorType { /// Is the input pin high? - fn is_high(&self) -> Result; + fn is_high(&mut self) -> Result; /// Is the input pin low? - fn is_low(&self) -> Result; + fn is_low(&mut self) -> Result; } -impl InputPin for &T { - fn is_high(&self) -> Result { +impl InputPin for &mut T { + #[inline] + fn is_high(&mut self) -> Result { T::is_high(self) } - fn is_low(&self) -> Result { + #[inline] + fn is_low(&mut self) -> Result { T::is_low(self) } } diff --git a/embedded-hal/src/i2c-shared-bus.svg b/embedded-hal/src/i2c-shared-bus.svg new file mode 100644 index 000000000..fad8cee6c --- /dev/null +++ b/embedded-hal/src/i2c-shared-bus.svg @@ -0,0 +1,4 @@ + + + +
SDA
SDA
SCL
SCL
MCU
MCU
SDA
SDA
SCL
SCL
I2C DEVICE 1
I2C DEVICE 1
SDA
SDA
SCL
SCL
I2C DEVICE 2
I2C DEVICE 2
Text is not SVG - cannot display
\ No newline at end of file diff --git a/embedded-hal/src/i2c.rs b/embedded-hal/src/i2c.rs index 67097ab2f..8b99197ef 100644 --- a/embedded-hal/src/i2c.rs +++ b/embedded-hal/src/i2c.rs @@ -1,8 +1,8 @@ -//! Blocking I2C API +//! Blocking I2C API. //! -//! This API supports 7-bit and 10-bit addresses. Traits feature an `AddressMode` -//! marker type parameter. Two implementation of the `AddressMode` exist: -//! `SevenBitAddress` and `TenBitAddress`. +//! This API supports 7-bit and 10-bit addresses. Traits feature an [`AddressMode`] +//! marker type parameter. Two implementation of the [`AddressMode`] exist: +//! [`SevenBitAddress`] and [`TenBitAddress`]. //! //! Through this marker types it is possible to implement each address mode for //! the traits independently in `embedded-hal` implementations and device drivers @@ -14,137 +14,161 @@ //! is not supported by the hardware. //! //! Since 7-bit addressing is the mode of the majority of I2C devices, -//! `SevenBitAddress` has been set as default mode and thus can be omitted if desired. +//! [`SevenBitAddress`] has been set as default mode and thus can be omitted if desired. //! -//! ## Examples +//! # Bus sharing //! -//! ### `embedded-hal` implementation for an MCU -//! Here is an example of an embedded-hal implementation of the `Write` trait -//! for both modes: -//! ``` -//! # use embedded_hal::i2c::{ErrorKind, ErrorType, SevenBitAddress, TenBitAddress, I2c, Operation}; -//! /// I2C0 hardware peripheral which supports both 7-bit and 10-bit addressing. -//! pub struct I2c0; +//! I2C allows sharing a single bus between many I2C devices. The SDA and SCL lines are +//! wired in parallel to all devices. When starting a transfer an "address" is sent +//! so that the addressed device can respond and all the others can ignore the transfer. //! -//! # impl ErrorType for I2c0 { type Error = ErrorKind; } -//! impl I2c for I2c0 -//! { -//! fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { -//! // ... -//! # Ok(()) -//! } -//! fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> { -//! // ... -//! # Ok(()) -//! } -//! fn write_iter>(&mut self, addr: u8, bytes: B) -> Result<(), Self::Error> { -//! // ... -//! # Ok(()) -//! } -//! fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> { -//! // ... -//! # Ok(()) -//! } -//! fn write_iter_read>(&mut self, addr: u8, bytes: B, buffer: &mut [u8]) -> Result<(), Self::Error> { -//! // ... -//! # Ok(()) -//! } -//! fn transaction<'a>(&mut self, address: u8, operations: &mut [Operation<'a>]) -> Result<(), Self::Error> { -//! // ... -//! # Ok(()) -//! } -//! fn transaction_iter<'a, O: IntoIterator>>(&mut self, address: u8, operations: O) -> Result<(), Self::Error> { -//! // ... -//! # Ok(()) -//! } -//! } +#![doc = include_str!("i2c-shared-bus.svg")] //! -//! impl I2c for I2c0 -//! { -//! fn read(&mut self, addr: u16, buffer: &mut [u8]) -> Result<(), Self::Error> { -//! // ... -//! # Ok(()) -//! } -//! fn write(&mut self, addr: u16, bytes: &[u8]) -> Result<(), Self::Error> { -//! // ... -//! # Ok(()) -//! } -//! fn write_iter>(&mut self, addr: u16, bytes: B) -> Result<(), Self::Error> { -//! // ... -//! # Ok(()) -//! } -//! fn write_read(&mut self, addr: u16, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> { -//! // ... -//! # Ok(()) -//! } -//! fn write_iter_read>(&mut self, addr: u16, bytes: B, buffer: &mut [u8]) -> Result<(), Self::Error> { -//! // ... -//! # Ok(()) -//! } -//! fn transaction<'a>(&mut self, address: u16, operations: &mut [Operation<'a>]) -> Result<(), Self::Error> { -//! // ... -//! # Ok(()) -//! } -//! fn transaction_iter<'a, O: IntoIterator>>(&mut self, address: u16, operations: O) -> Result<(), Self::Error> { -//! // ... -//! # Ok(()) -//! } -//! } -//! ``` +//! This bus sharing is common when having multiple I2C devices in the same board, since it uses fewer MCU +//! pins (`2` instead of `2*n`), and fewer MCU I2C peripherals (`1` instead of `n`). +//! +//! This API supports bus sharing natively. Types implementing [`I2c`] are allowed +//! to represent either exclusive or shared access to an I2C bus. HALs typically +//! provide exclusive access implementations. Drivers shouldn't care which +//! kind they receive, they just do transactions on it and let the +//! underlying implementation share or not. +//! +//! The [`embedded-hal-bus`](https://docs.rs/embedded-hal-bus) crate provides several +//! implementations for sharing I2C buses. You can use them to take an exclusive instance +//! you've received from a HAL and "split" it into multiple shared ones, to instantiate +//! several drivers on the same bus. +//! +//! # Flushing //! -//! ### Device driver compatible only with 7-bit addresses +//! Implementations must flush the transfer, ensuring the bus has returned to an idle state before returning. +//! No pipelining is allowed. Users must be able to shut down the I2C peripheral immediately after a transfer +//! returns, without any risk of e.g. cutting short a stop condition. +//! +//! (Implementations must wait until the last ACK bit to report it as an error anyway. Therefore pipelining would only +//! yield very small time savings, not worth the complexity) +//! +//! # For driver authors +//! +//! Drivers can select the adequate address length with `I2c` or `I2c` depending +//! on the target device. If it can use either, the driver can +//! be generic over the address kind as well, though this is rare. +//! +//! Drivers should take the `I2c` instance as an argument to `new()`, and store it in their +//! struct. They **should not** take `&mut I2c`, the trait has a blanket impl for all `&mut T` +//! so taking just `I2c` ensures the user can still pass a `&mut`, but is not forced to. +//! +//! Drivers **should not** try to enable bus sharing by taking `&mut I2c` at every method. +//! This is much less ergonomic than owning the `I2c`, which still allows the user to pass an +//! implementation that does sharing behind the scenes +//! (from [`embedded-hal-bus`](https://docs.rs/embedded-hal-bus), or others). +//! +//! ## Device driver compatible only with 7-bit addresses //! //! For demonstration purposes the address mode parameter has been omitted in this example. //! //! ``` -//! # use embedded_hal::i2c::{I2c, Error}; -//! const ADDR: u8 = 0x15; +//! use embedded_hal::i2c::{I2c, Error}; +//! +//! const ADDR: u8 = 0x15; //! # const TEMP_REGISTER: u8 = 0x1; //! pub struct TemperatureSensorDriver { //! i2c: I2C, //! } //! -//! impl TemperatureSensorDriver -//! where -//! I2C: I2c, -//! { -//! pub fn read_temperature(&mut self) -> Result { +//! impl TemperatureSensorDriver { +//! pub fn new(i2c: I2C) -> Self { +//! Self { i2c } +//! } +//! +//! pub fn read_temperature(&mut self) -> Result { //! let mut temp = [0]; -//! self.i2c -//! .write_read(ADDR, &[TEMP_REGISTER], &mut temp) -//! .and(Ok(temp[0])) +//! self.i2c.write_read(ADDR, &[TEMP_REGISTER], &mut temp)?; +//! Ok(temp[0]) //! } //! } //! ``` //! -//! ### Device driver compatible only with 10-bit addresses +//! ## Device driver compatible only with 10-bit addresses //! //! ``` -//! # use embedded_hal::i2c::{Error, TenBitAddress, I2c}; -//! const ADDR: u16 = 0x158; +//! use embedded_hal::i2c::{Error, TenBitAddress, I2c}; +//! +//! const ADDR: u16 = 0x158; //! # const TEMP_REGISTER: u8 = 0x1; //! pub struct TemperatureSensorDriver { //! i2c: I2C, //! } //! -//! impl TemperatureSensorDriver -//! where -//! I2C: I2c, -//! { -//! pub fn read_temperature(&mut self) -> Result { +//! impl> TemperatureSensorDriver { +//! pub fn new(i2c: I2C) -> Self { +//! Self { i2c } +//! } +//! +//! pub fn read_temperature(&mut self) -> Result { //! let mut temp = [0]; -//! self.i2c -//! .write_read(ADDR, &[TEMP_REGISTER], &mut temp) -//! .and(Ok(temp[0])) +//! self.i2c.write_read(ADDR, &[TEMP_REGISTER], &mut temp)?; +//! Ok(temp[0]) +//! } +//! } +//! ``` +//! +//! # For HAL authors +//! +//! HALs **should not** include bus sharing mechanisms. They should expose a single type representing +//! exclusive ownership over the bus, and let the user use [`embedded-hal-bus`](https://docs.rs/embedded-hal-bus) +//! if they want to share it. (One exception is if the underlying platform already +//! supports sharing, such as Linux or some RTOSs.) +//! +//! Here is an example of an embedded-hal implementation of the `I2C` trait +//! for both addressing modes. All trait methods have have default implementations in terms of `transaction`. +//! As such, that is the only method that requires implementation in the HAL. +//! +//! ``` +//! use embedded_hal::i2c::{self, SevenBitAddress, TenBitAddress, I2c, Operation}; +//! +//! /// I2C0 hardware peripheral which supports both 7-bit and 10-bit addressing. +//! pub struct I2c0; +//! +//! #[derive(Debug, Copy, Clone, Eq, PartialEq)] +//! pub enum Error { +//! // ... +//! } +//! +//! impl i2c::Error for Error { +//! fn kind(&self) -> i2c::ErrorKind { +//! match *self { +//! // ... +//! } +//! } +//! } +//! +//! impl i2c::ErrorType for I2c0 { +//! type Error = Error; +//! } +//! +//! impl I2c for I2c0 { +//! fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> { +//! // ... +//! # Ok(()) +//! } +//! } +//! +//! impl I2c for I2c0 { +//! fn transaction(&mut self, address: u16, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> { +//! // ... +//! # Ok(()) //! } //! } //! ``` use crate::private; -/// I2C error +#[cfg(feature = "defmt-03")] +use crate::defmt; + +/// I2C error. pub trait Error: core::fmt::Debug { - /// Convert error to a generic I2C error kind + /// Convert error to a generic I2C error kind. /// /// By using this method, I2C errors freely defined by HAL implementations /// can be converted to a set of generic I2C errors upon which generic @@ -153,40 +177,43 @@ pub trait Error: core::fmt::Debug { } impl Error for core::convert::Infallible { + #[inline] fn kind(&self) -> ErrorKind { match *self {} } } -/// I2C error kind +/// I2C error kind. /// /// This represents a common set of I2C operation errors. HAL implementations are /// free to define more specific or additional error types. However, by providing /// a mapping to these common I2C errors, generic code can still react to them. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] #[non_exhaustive] pub enum ErrorKind { /// Bus error occurred. e.g. A START or a STOP condition is detected and is not /// located after a multiple of 9 SCL clock pulses. Bus, - /// The arbitration was lost, e.g. electrical problems with the clock signal + /// The arbitration was lost, e.g. electrical problems with the clock signal. ArbitrationLoss, /// A bus operation was not acknowledged, e.g. due to the addressed device not /// being available on the bus or the device not being ready to process requests - /// at the moment + /// at the moment. NoAcknowledge(NoAcknowledgeSource), - /// The peripheral receive buffer was overrun + /// The peripheral receive buffer was overrun. Overrun, /// A different error occurred. The original error may contain more information. Other, } -/// I2C no acknowledge error source +/// I2C no acknowledge error source. /// /// In cases where it is possible, a device should indicate if a no acknowledge /// response was received to an address versus a no acknowledge to a data byte. /// Where it is not possible to differentiate, `Unknown` should be indicated. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] pub enum NoAcknowledgeSource { /// The device did not acknowledge its address. The device may be missing. Address, @@ -199,12 +226,16 @@ pub enum NoAcknowledgeSource { } impl Error for ErrorKind { + #[inline] fn kind(&self) -> ErrorKind { *self } } +impl core::error::Error for ErrorKind {} + impl core::fmt::Display for ErrorKind { + #[inline] fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::Bus => write!(f, "Bus error occurred"), @@ -220,6 +251,7 @@ impl core::fmt::Display for ErrorKind { } impl core::fmt::Display for NoAcknowledgeSource { + #[inline] fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::Address => write!(f, "The device did not acknowledge its address"), @@ -229,7 +261,7 @@ impl core::fmt::Display for NoAcknowledgeSource { } } -/// I2C error type trait +/// I2C error type trait. /// /// This just defines the error type, to be used by the other traits. pub trait ErrorType { @@ -237,39 +269,51 @@ pub trait ErrorType { type Error: Error; } -impl ErrorType for &mut T { +impl ErrorType for &mut T { type Error = T::Error; } -/// Address mode (7-bit / 10-bit) +/// Address mode (7-bit / 10-bit). /// /// Note: This trait is sealed and should not be implemented outside of this crate. -pub trait AddressMode: private::Sealed + 'static {} +pub trait AddressMode: Copy + 'static + private::Sealed {} -/// 7-bit address mode type +/// 7-bit address mode type. +/// +/// Note that 7-bit addresses defined by drivers should be specified in **right-aligned** form, +/// e.g. in the range `0x00..=0x7F`. +/// +/// For example, a device that has the seven bit address of `0b011_0010`, and therefore is addressed on the wire using: +/// +/// * `0b0110010_0` or `0x64` for *writes* +/// * `0b0110010_1` or `0x65` for *reads* +/// +/// Should be specified as `0b0011_0010` or `0x32`, NOT `0x64` or `0x65`. Care should be taken by both HAL and driver +/// crate writers to use this scheme consistently. pub type SevenBitAddress = u8; -/// 10-bit address mode type +/// 10-bit address mode type. pub type TenBitAddress = u16; impl AddressMode for SevenBitAddress {} impl AddressMode for TenBitAddress {} -/// Transactional I2C operation. +/// I2C operation. /// /// Several operations can be combined as part of a transaction. #[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] pub enum Operation<'a> { - /// Read data into the provided buffer + /// Read data into the provided buffer. Read(&'a mut [u8]), - /// Write data from the provided buffer + /// Write data from the provided buffer. Write(&'a [u8]), } -/// Blocking I2C +/// Blocking I2C. pub trait I2c: ErrorType { - /// Reads enough bytes from slave with `address` to fill `buffer` + /// Reads enough bytes from slave with `address` to fill `read`. /// /// # I2C Events (contract) /// @@ -287,9 +331,12 @@ pub trait I2c: ErrorType { /// - `MAK` = master acknowledge /// - `NMAK` = master no acknowledge /// - `SP` = stop condition - fn read(&mut self, address: A, buffer: &mut [u8]) -> Result<(), Self::Error>; + #[inline] + fn read(&mut self, address: A, read: &mut [u8]) -> Result<(), Self::Error> { + self.transaction(address, &mut [Operation::Read(read)]) + } - /// Writes bytes to slave with address `address` + /// Writes bytes to slave with address `address`. /// /// # I2C Events (contract) /// @@ -305,19 +352,13 @@ pub trait I2c: ErrorType { /// - `SAK` = slave acknowledge /// - `Bi` = ith byte of data /// - `SP` = stop condition - fn write(&mut self, address: A, bytes: &[u8]) -> Result<(), Self::Error>; - - /// Writes bytes to slave with address `address` - /// - /// # I2C Events (contract) - /// - /// Same as the `write` method - fn write_iter(&mut self, address: A, bytes: B) -> Result<(), Self::Error> - where - B: IntoIterator; + #[inline] + fn write(&mut self, address: A, write: &[u8]) -> Result<(), Self::Error> { + self.transaction(address, &mut [Operation::Write(write)]) + } - /// Writes bytes to slave with address `address` and then reads enough bytes to fill `buffer` *in a - /// single transaction* + /// Writes bytes to slave with address `address` and then reads enough bytes to fill `read` *in a + /// single transaction*. /// /// # I2C Events (contract) /// @@ -338,27 +379,13 @@ pub trait I2c: ErrorType { /// - `MAK` = master acknowledge /// - `NMAK` = master no acknowledge /// - `SP` = stop condition - fn write_read( - &mut self, - address: A, - bytes: &[u8], - buffer: &mut [u8], - ) -> Result<(), Self::Error>; - - /// Writes bytes to slave with address `address` and then reads enough bytes to fill `buffer` *in a - /// single transaction* - /// - /// # I2C Events (contract) - /// - /// Same as the `write_read` method - fn write_iter_read( - &mut self, - address: A, - bytes: B, - buffer: &mut [u8], - ) -> Result<(), Self::Error> - where - B: IntoIterator; + #[inline] + fn write_read(&mut self, address: A, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.transaction( + address, + &mut [Operation::Write(write), Operation::Read(read)], + ) + } /// Execute the provided operations on the I2C bus. /// @@ -367,85 +394,41 @@ pub trait I2c: ErrorType { /// - Data from adjacent operations of the same type are sent after each other without an SP or SR. /// - Between adjacent operations of a different type an SR and SAD+R/W is sent. /// - After executing the last operation an SP is sent automatically. - /// - If the last operation is a `Read` the master does not send an acknowledge for the last byte. + /// - At the end of each read operation (before SP or SR), the master does not send an acknowledge for the last byte. /// /// - `ST` = start condition /// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 to indicate writing /// - `SR` = repeated start condition /// - `SP` = stop condition - fn transaction<'a>( + fn transaction( &mut self, address: A, - operations: &mut [Operation<'a>], + operations: &mut [Operation<'_>], ) -> Result<(), Self::Error>; - - /// Execute the provided operations on the I2C bus (iterator version). - /// - /// Transaction contract: - /// - Before executing the first operation an ST is sent automatically. This is followed by SAD+R/W as appropriate. - /// - Data from adjacent operations of the same type are sent after each other without an SP or SR. - /// - Between adjacent operations of a different type an SR and SAD+R/W is sent. - /// - After executing the last operation an SP is sent automatically. - /// - If the last operation is a `Read` the master does not send an acknowledge for the last byte. - /// - /// - `ST` = start condition - /// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 to indicate writing - /// - `SR` = repeated start condition - /// - `SP` = stop condition - fn transaction_iter<'a, O>(&mut self, address: A, operations: O) -> Result<(), Self::Error> - where - O: IntoIterator>; } -impl> I2c
for &mut T { - fn read(&mut self, address: A, buffer: &mut [u8]) -> Result<(), Self::Error> { - T::read(self, address, buffer) - } - - fn write(&mut self, address: A, bytes: &[u8]) -> Result<(), Self::Error> { - T::write(self, address, bytes) - } - - fn write_iter(&mut self, address: A, bytes: B) -> Result<(), Self::Error> - where - B: IntoIterator, - { - T::write_iter(self, address, bytes) +impl + ?Sized> I2c for &mut T { + #[inline] + fn read(&mut self, address: A, read: &mut [u8]) -> Result<(), Self::Error> { + T::read(self, address, read) } - fn write_read( - &mut self, - address: A, - bytes: &[u8], - buffer: &mut [u8], - ) -> Result<(), Self::Error> { - T::write_read(self, address, bytes, buffer) + #[inline] + fn write(&mut self, address: A, write: &[u8]) -> Result<(), Self::Error> { + T::write(self, address, write) } - fn write_iter_read( - &mut self, - address: A, - bytes: B, - buffer: &mut [u8], - ) -> Result<(), Self::Error> - where - B: IntoIterator, - { - T::write_iter_read(self, address, bytes, buffer) + #[inline] + fn write_read(&mut self, address: A, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + T::write_read(self, address, write, read) } - fn transaction<'a>( + #[inline] + fn transaction( &mut self, address: A, - operations: &mut [Operation<'a>], + operations: &mut [Operation<'_>], ) -> Result<(), Self::Error> { T::transaction(self, address, operations) } - - fn transaction_iter<'a, O>(&mut self, address: A, operations: O) -> Result<(), Self::Error> - where - O: IntoIterator>, - { - T::transaction_iter(self, address, operations) - } } diff --git a/embedded-hal/src/lib.rs b/embedded-hal/src/lib.rs index 8b65ded47..f5eb76c32 100644 --- a/embedded-hal/src/lib.rs +++ b/embedded-hal/src/lib.rs @@ -1,86 +1,11 @@ -//! A Hardware Abstraction Layer (HAL) for embedded systems -//! -//! **NOTE** This HAL is still is active development. Expect the traits presented here to be -//! tweaked, split or be replaced wholesale before being stabilized, i.e. before hitting the 1.0.0 -//! release. -//! -//! **NOTE** If you want to use an alpha release of the 1.0.0 version, use an exact version -//! specifier in your `Cargo.toml` like: `embedded-hal = "=1.0.0-alpha.2"`. -//! -//! # Companion crates -//! -//! The main `embedded-hal` crate contains only blocking traits, where the operation is done -//! synchronously before returning. Check out the following crates, which contain versions -//! of the traits for other execution models: -//! -//! - [`embedded-hal-async`](https://docs.rs/embedded-hal-async): async/await-based. -//! - [`embedded-hal-nb`](https://docs.rs/embedded-hal-nb): polling-based, using the `nb` crate. -//! -//! The [`embedded-hal-bus`](https://docs.rs/embedded-hal-bus) crate provides utilities for sharing -//! SPI and I2C buses. -//! -//! Additionally, more domain-specific traits are available in separate crates: -//! - [`embedded-can`](https://docs.rs/embedded-can): Controller Area Network (CAN) -//! -//! # Design goals -//! -//! The HAL -//! -//! - Must *erase* device specific details. Neither register, register blocks or magic values should -//! appear in the API. -//! -//! - Must be generic *within* a device and *across* devices. The API to use a serial interface must -//! be the same regardless of whether the implementation uses the USART1 or UART4 peripheral of a -//! device or the UART0 peripheral of another device. -//! -//! - Where possible must *not* be tied to a specific asynchronous model. The API should be usable -//! in blocking mode, with the `futures` model, with an async/await model or with a callback model. -//! (cf. the [`nb`] crate) -//! -//! - Must be minimal, and thus easy to implement and zero cost, yet highly composable. People that -//! want higher level abstraction should *prefer to use this HAL* rather than *re-implement* -//! register manipulation code. -//! -//! - Serve as a foundation for building an ecosystem of platform agnostic drivers. Here driver -//! means a library crate that lets a target platform interface an external device like a digital -//! sensor or a wireless transceiver. The advantage of this system is that by writing the driver as -//! a generic library on top of `embedded-hal` driver authors can support any number of target -//! platforms (e.g. Cortex-M microcontrollers, AVR microcontrollers, embedded Linux, etc.). The -//! advantage for application developers is that by adopting `embedded-hal` they can unlock all -//! these drivers for their platform. -//! -//! - Trait methods must be fallible so that they can be used in any possible situation. -//! Nevertheless, HAL implementations can additionally provide infallible versions of the same methods -//! if they can never fail in their platform. This way, generic code can use the fallible abstractions -//! provided here but platform-specific code can avoid fallibility-related boilerplate if possible. -//! -//! # Out of scope -//! -//! - Initialization and configuration stuff like "ensure this serial interface and that SPI -//! interface are not using the same pins". The HAL will focus on *doing I/O*. -//! -//! # Reference implementation -//! -//! The [`stm32f1xx-hal`] crate contains a reference implementation of this HAL. -//! -//! [`stm32f1xx-hal`]: https://crates.io/crates/stm32f1xx-hal -//! -//! # Platform agnostic drivers -//! -//! You can find platform agnostic drivers built on top of `embedded-hal` on crates.io by [searching -//! for the *embedded-hal* keyword](https://crates.io/keywords/embedded-hal). -//! -//! If you are writing a platform agnostic driver yourself you are highly encouraged to [add the -//! embedded-hal keyword](https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata) -//! to your crate before publishing it! - +#![doc = include_str!("../README.md")] #![warn(missing_docs)] #![no_std] pub mod delay; pub mod digital; pub mod i2c; -pub mod serial; +pub mod pwm; pub mod spi; mod private { @@ -90,3 +15,7 @@ mod private { impl Sealed for SevenBitAddress {} impl Sealed for TenBitAddress {} } + +// needed to prevent defmt macros from breaking, since they emit code that does `defmt::blahblah`. +#[cfg(feature = "defmt-03")] +use defmt_03 as defmt; diff --git a/embedded-hal/src/pwm.rs b/embedded-hal/src/pwm.rs new file mode 100644 index 000000000..4d4ff1508 --- /dev/null +++ b/embedded-hal/src/pwm.rs @@ -0,0 +1,152 @@ +//! Pulse Width Modulation (PWM) traits. + +#[cfg(feature = "defmt-03")] +use crate::defmt; + +/// Error +pub trait Error: core::fmt::Debug { + /// Convert error to a generic error kind. + /// + /// By using this method, errors freely defined by HAL implementations + /// can be converted to a set of generic errors upon which generic + /// code can act. + fn kind(&self) -> ErrorKind; +} + +impl Error for core::convert::Infallible { + #[inline] + fn kind(&self) -> ErrorKind { + match *self {} + } +} + +/// Error kind. +/// +/// This represents a common set of operation errors. HAL implementations are +/// free to define more specific or additional error types. However, by providing +/// a mapping to these common errors, generic code can still react to them. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] +#[non_exhaustive] +pub enum ErrorKind { + /// A different error occurred. The original error may contain more information. + Other, +} + +impl Error for ErrorKind { + #[inline] + fn kind(&self) -> ErrorKind { + *self + } +} + +impl core::error::Error for ErrorKind {} + +impl core::fmt::Display for ErrorKind { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Other => write!( + f, + "A different error occurred. The original error may contain more information" + ), + } + } +} + +/// Error type trait. +/// +/// This just defines the error type, to be used by the other traits. +pub trait ErrorType { + /// Error type + type Error: Error; +} + +impl ErrorType for &mut T { + type Error = T::Error; +} + +/// Single PWM channel / pin. +pub trait SetDutyCycle: ErrorType { + /// Get the maximum duty cycle value. + /// + /// This value corresponds to a 100% duty cycle. + fn max_duty_cycle(&self) -> u16; + + /// Set the duty cycle to `duty / max_duty`. + /// + /// The caller is responsible for ensuring that the duty cycle value is less than or equal to the maximum duty cycle value, + /// as reported by [`max_duty_cycle`]. + /// + /// [`max_duty_cycle`]: SetDutyCycle::max_duty_cycle + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error>; + + /// Set the duty cycle to 0%, or always inactive. + #[inline] + fn set_duty_cycle_fully_off(&mut self) -> Result<(), Self::Error> { + self.set_duty_cycle(0) + } + + /// Set the duty cycle to 100%, or always active. + #[inline] + fn set_duty_cycle_fully_on(&mut self) -> Result<(), Self::Error> { + self.set_duty_cycle(self.max_duty_cycle()) + } + + /// Set the duty cycle to `num / denom`. + /// + /// The caller is responsible for ensuring that `num` is less than or equal to `denom`, + /// and that `denom` is not zero. + #[inline] + fn set_duty_cycle_fraction(&mut self, num: u16, denom: u16) -> Result<(), Self::Error> { + debug_assert!(denom != 0); + debug_assert!(num <= denom); + let duty = u32::from(num) * u32::from(self.max_duty_cycle()) / u32::from(denom); + + // This is safe because we know that `num <= denom`, so `duty <= self.max_duty_cycle()` (u16) + #[allow(clippy::cast_possible_truncation)] + { + self.set_duty_cycle(duty as u16) + } + } + + /// Set the duty cycle to `percent / 100` + /// + /// The caller is responsible for ensuring that `percent` is less than or equal to 100. + #[inline] + fn set_duty_cycle_percent(&mut self, percent: u8) -> Result<(), Self::Error> { + self.set_duty_cycle_fraction(u16::from(percent), 100) + } +} + +impl SetDutyCycle for &mut T { + #[inline] + fn max_duty_cycle(&self) -> u16 { + T::max_duty_cycle(self) + } + + #[inline] + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + T::set_duty_cycle(self, duty) + } + + #[inline] + fn set_duty_cycle_fully_off(&mut self) -> Result<(), Self::Error> { + T::set_duty_cycle_fully_off(self) + } + + #[inline] + fn set_duty_cycle_fully_on(&mut self) -> Result<(), Self::Error> { + T::set_duty_cycle_fully_on(self) + } + + #[inline] + fn set_duty_cycle_fraction(&mut self, num: u16, denom: u16) -> Result<(), Self::Error> { + T::set_duty_cycle_fraction(self, num, denom) + } + + #[inline] + fn set_duty_cycle_percent(&mut self, percent: u8) -> Result<(), Self::Error> { + T::set_duty_cycle_percent(self, percent) + } +} diff --git a/embedded-hal/src/serial.rs b/embedded-hal/src/serial.rs deleted file mode 100644 index f0c05f98b..000000000 --- a/embedded-hal/src/serial.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! Serial traits - -/// Serial error -pub trait Error: core::fmt::Debug { - /// Convert error to a generic serial error kind - /// - /// By using this method, serial errors freely defined by HAL implementations - /// can be converted to a set of generic serial errors upon which generic - /// code can act. - fn kind(&self) -> ErrorKind; -} - -impl Error for core::convert::Infallible { - fn kind(&self) -> ErrorKind { - match *self {} - } -} - -/// Serial error kind -/// -/// This represents a common set of serial operation errors. HAL implementations are -/// free to define more specific or additional error types. However, by providing -/// a mapping to these common serial errors, generic code can still react to them. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[non_exhaustive] -pub enum ErrorKind { - /// The peripheral receive buffer was overrun. - Overrun, - /// Received data does not conform to the peripheral configuration. - /// Can be caused by a misconfigured device on either end of the serial line. - FrameFormat, - /// Parity check failed. - Parity, - /// Serial line is too noisy to read valid data. - Noise, - /// A different error occurred. The original error may contain more information. - Other, -} - -impl Error for ErrorKind { - fn kind(&self) -> ErrorKind { - *self - } -} - -impl core::fmt::Display for ErrorKind { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Overrun => write!(f, "The peripheral receive buffer was overrun"), - Self::Parity => write!(f, "Parity check failed"), - Self::Noise => write!(f, "Serial line is too noisy to read valid data"), - Self::FrameFormat => write!( - f, - "Received data does not conform to the peripheral configuration" - ), - Self::Other => write!( - f, - "A different error occurred. The original error may contain more information" - ), - } - } -} - -/// Serial error type trait -/// -/// This just defines the error type, to be used by the other traits. -pub trait ErrorType { - /// Error type - type Error: Error; -} - -impl ErrorType for &mut T { - type Error = T::Error; -} - -/// Write half of a serial interface (blocking variant) -pub trait Write: ErrorType { - /// Writes a slice, blocking until everything has been written - /// - /// An implementation can choose to buffer the write, returning `Ok(())` - /// after the complete slice has been written to a buffer, but before all - /// words have been sent via the serial interface. To make sure that - /// everything has been sent, call [`flush`] after this function returns. - /// - /// [`flush`]: #tymethod.flush - fn write(&mut self, buffer: &[Word]) -> Result<(), Self::Error>; - - /// Block until the serial interface has sent all buffered words - fn flush(&mut self) -> Result<(), Self::Error>; -} - -impl, Word: Copy> Write for &mut T { - fn write(&mut self, buffer: &[Word]) -> Result<(), Self::Error> { - T::write(self, buffer) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - T::flush(self) - } -} diff --git a/embedded-hal/src/spi.rs b/embedded-hal/src/spi.rs index e65022fe1..55fc7e9fb 100644 --- a/embedded-hal/src/spi.rs +++ b/embedded-hal/src/spi.rs @@ -23,21 +23,15 @@ //! //! ## Bus //! -//! SPI bus traits represent **exclusive ownership** over the whole SPI bus. This is usually the entire +//! The [`SpiBus`] trait represents **exclusive ownership** over the whole SPI bus. This is usually the entire //! SPI MCU peripheral, plus the SCK, MOSI and MISO pins. //! //! Owning an instance of an SPI bus guarantees exclusive access, this is, we have the guarantee no other //! piece of code will try to use the bus while we own it. //! -//! There's 3 bus traits, depending on the bus capabilities. -//! -//! - [`SpiBus`]: Read-write access. This is the most commonly used. -//! - [`SpiBusRead`]: Read-only access, for example a bus with a MISO pin but no MOSI pin. -//! - [`SpiBusWrite`]: Write-only access, for example a bus with a MOSI pin but no MISO pin. -//! //! ## Device //! -//! [`SpiDevice`] represents **ownership over a single SPI device selected by a CS pin** in a (possibly shared) bus. This is typically: +//! The [`SpiDevice`] trait represents **ownership over a single SPI device selected by a CS pin** in a (possibly shared) bus. This is typically: //! //! - Exclusive ownership of the **CS pin**. //! - Access to the **underlying SPI bus**. If shared, it'll be behind some kind of lock/mutex. @@ -46,20 +40,17 @@ //! consists of asserting CS, then doing one or more transfers, then deasserting CS. For the entire duration of the transaction, the [`SpiDevice`] //! implementation will ensure no other transaction can be opened on the same bus. This is the key that allows correct sharing of the bus. //! -//! The capabilities of the bus (read-write, read-only or write-only) are determined by which of the [`SpiBus`], [`SpiBusRead`] [`SpiBusWrite`] traits -//! are implemented for the [`Bus`](SpiDevice::Bus) associated type. -//! //! # For driver authors //! //! When implementing a driver, it's crucial to pick the right trait, to ensure correct operation //! with maximum interoperability. Here are some guidelines depending on the device you're implementing a driver for: //! -//! If your device **has a CS pin**, use [`SpiDevice`]. Do not manually manage the CS pin, the [`SpiDevice`] implementation will do it for you. -//! Add bounds like `where T::Bus: SpiBus`, `where T::Bus: SpiBusRead`, `where T::Bus: SpiBusWrite` to specify the kind of access you need. +//! If your device **has a CS pin**, use [`SpiDevice`]. Do not manually +//! manage the CS pin, the [`SpiDevice`] implementation will do it for you. //! By using [`SpiDevice`], your driver will cooperate nicely with other drivers for other devices in the same shared SPI bus. //! //! ``` -//! # use embedded_hal::spi::{SpiBus, SpiBusRead, SpiBusWrite, SpiDevice}; +//! # use embedded_hal::spi::{SpiBus, SpiDevice, Operation}; //! pub struct MyDriver { //! spi: SPI, //! } @@ -67,7 +58,6 @@ //! impl MyDriver //! where //! SPI: SpiDevice, -//! SPI::Bus: SpiBus, // or SpiBusRead/SpiBusWrite if you only need to read or only write. //! { //! pub fn new(spi: SPI) -> Self { //! Self { spi } @@ -77,10 +67,10 @@ //! let mut buf = [0; 2]; //! //! // `transaction` asserts and deasserts CS for us. No need to do it manually! -//! self.spi.transaction(|bus| { -//! bus.write(&[0x90])?; -//! bus.read(&mut buf) -//! }).map_err(MyError::Spi)?; +//! self.spi.transaction(&mut [ +//! Operation::Write(&[0x90]), +//! Operation::Read(&mut buf), +//! ]).map_err(MyError::Spi)?; //! //! Ok(buf) //! } @@ -93,19 +83,19 @@ //! } //! ``` //! -//! If your device **does not have a CS pin**, use [`SpiBus`] (or [`SpiBusRead`], [`SpiBusWrite`]). This will ensure +//! If your device **does not have a CS pin**, use [`SpiBus`]. This will ensure //! your driver has exclusive access to the bus, so no other drivers can interfere. It's not possible to safely share //! a bus without CS pins. By requiring [`SpiBus`] you disallow sharing, ensuring correct operation. //! //! ``` -//! # use embedded_hal::spi::{SpiBus, SpiBusRead, SpiBusWrite}; +//! # use embedded_hal::spi::SpiBus; //! pub struct MyDriver { //! spi: SPI, //! } //! //! impl MyDriver //! where -//! SPI: SpiBus, // or SpiBusRead/SpiBusWrite if you only need to read or only write. +//! SPI: SpiBus, //! { //! pub fn new(spi: SPI) -> Self { //! Self { spi } @@ -131,8 +121,8 @@ //! //! # For HAL authors //! -//! HALs **must** implement [`SpiBus`], [`SpiBusRead`] and [`SpiBusWrite`]. Users can combine the bus together with the CS pin (which should -//! implement [`OutputPin`](crate::digital::blocking::OutputPin)) using HAL-independent [`SpiDevice`] implementations such as the ones in [`embedded-hal-bus`](https://crates.io/crates/embedded-hal-bus). +//! HALs **must** implement [`SpiBus`]. Users can combine the bus together with the CS pin (which should +//! implement [`OutputPin`](crate::digital::OutputPin)) using HAL-independent [`SpiDevice`] implementations such as the ones in [`embedded-hal-bus`](https://crates.io/crates/embedded-hal-bus). //! //! HALs may additionally implement [`SpiDevice`] to **take advantage of hardware CS management**, which may provide some performance //! benefits. (There's no point in a HAL implementing [`SpiDevice`] if the CS management is software-only, this task is better left to @@ -144,85 +134,105 @@ //! # Flushing //! //! To improve performance, [`SpiBus`] implementations are allowed to return before the operation is finished, i.e. when the bus is still not -//! idle. +//! idle. This allows pipelining SPI transfers with CPU work. //! -//! When using a [`SpiBus`], call [`flush`](SpiBusFlush::flush) to wait for operations to actually finish. Examples of situations +//! When calling another method when a previous operation is still in progress, implementations can either wait for the previous operation +//! to finish, or enqueue the new one, but they must not return a "busy" error. Users must be able to do multiple method calls in a row +//! and have them executed "as if" they were done sequentially, without having to check for "busy" errors. +//! +//! When using a [`SpiBus`], call [`flush`](SpiBus::flush) to wait for operations to actually finish. Examples of situations //! where this is needed are: //! - To synchronize SPI activity and GPIO activity, for example before deasserting a CS pin. //! - Before deinitializing the hardware SPI peripheral. //! -//! When using a [`SpiDevice`], you can still call [`flush`](SpiBusFlush::flush) on the bus within a transaction. +//! When using a [`SpiDevice`], you can still call [`flush`](SpiBus::flush) on the bus within a transaction. //! It's very rarely needed, because [`transaction`](SpiDevice::transaction) already flushes for you //! before deasserting CS. For example, you may need it to synchronize with GPIOs other than CS, such as DCX pins //! sometimes found in SPI displays. //! -//! For example, for [`write`](SpiBusWrite::write) operations, it is common for hardware SPI peripherals to have a small +//! For example, for [`write`](SpiBus::write) operations, it is common for hardware SPI peripherals to have a small //! FIFO buffer, usually 1-4 bytes. Software writes data to the FIFO, and the peripheral sends it on MOSI at its own pace, -//! at the specified SPI frequency. It is allowed for an implementation of [`write`](SpiBusWrite::write) to return as soon -//! as all the data has been written to the FIFO, before it is actually sent. Calling [`flush`](SpiBusFlush::flush) would +//! at the specified SPI frequency. It is allowed for an implementation of [`write`](SpiBus::write) to return as soon +//! as all the data has been written to the FIFO, before it is actually sent. Calling [`flush`](SpiBus::flush) would //! wait until all the bits have actually been sent, the FIFO is empty, and the bus is idle. //! -//! This still applies to other operations such as [`read`](SpiBusRead::read) or [`transfer`](SpiBus::transfer). It is less obvious +//! This still applies to other operations such as [`read`](SpiBus::read) or [`transfer`](SpiBus::transfer). It is less obvious //! why, because these methods can't return before receiving all the read data. However it's still technically possible //! for them to return before the bus is idle. For example, assuming SPI mode 0, the last bit is sampled on the first (rising) edge //! of SCK, at which point a method could return, but the second (falling) SCK edge still has to happen before the bus is idle. +//! +//! # CS-to-clock delays +//! +//! Many chips require a minimum delay between asserting CS and the first SCK edge, and the last SCK edge and deasserting CS. +//! Drivers should *NOT* use [`Operation::DelayNs`] for this, they should instead document that the user should configure the +//! delays when creating the `SpiDevice` instance, same as they have to configure the SPI frequency and mode. This has a few advantages: +//! +//! - Allows implementations that use hardware-managed CS to program the delay in hardware +//! - Allows the end user more flexibility. For example, they can choose to not configure any delay if their MCU is slow +//! enough to "naturally" do the delay (very common if the delay is in the order of nanoseconds). use core::fmt::Debug; -/// Clock polarity +#[cfg(feature = "defmt-03")] +use crate::defmt; + +/// Clock polarity. #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] pub enum Polarity { - /// Clock signal low when idle + /// Clock signal low when idle. IdleLow, - /// Clock signal high when idle + /// Clock signal high when idle. IdleHigh, } -/// Clock phase +/// Clock phase. #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] pub enum Phase { - /// Data in "captured" on the first clock transition + /// Data in "captured" on the first clock transition. CaptureOnFirstTransition, - /// Data in "captured" on the second clock transition + /// Data in "captured" on the second clock transition. CaptureOnSecondTransition, } -/// SPI mode +/// SPI mode. #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] pub struct Mode { - /// Clock polarity + /// Clock polarity. pub polarity: Polarity, - /// Clock phase + /// Clock phase. pub phase: Phase, } -/// Helper for CPOL = 0, CPHA = 0 +/// Helper for CPOL = 0, CPHA = 0. pub const MODE_0: Mode = Mode { polarity: Polarity::IdleLow, phase: Phase::CaptureOnFirstTransition, }; -/// Helper for CPOL = 0, CPHA = 1 +/// Helper for CPOL = 0, CPHA = 1. pub const MODE_1: Mode = Mode { polarity: Polarity::IdleLow, phase: Phase::CaptureOnSecondTransition, }; -/// Helper for CPOL = 1, CPHA = 0 +/// Helper for CPOL = 1, CPHA = 0. pub const MODE_2: Mode = Mode { polarity: Polarity::IdleHigh, phase: Phase::CaptureOnFirstTransition, }; -/// Helper for CPOL = 1, CPHA = 1 +/// Helper for CPOL = 1, CPHA = 1. pub const MODE_3: Mode = Mode { polarity: Polarity::IdleHigh, phase: Phase::CaptureOnSecondTransition, }; -/// SPI error -pub trait Error: core::fmt::Debug { - /// Convert error to a generic SPI error kind +/// SPI error. +pub trait Error: Debug { + /// Convert error to a generic SPI error kind. /// /// By using this method, SPI errors freely defined by HAL implementations /// can be converted to a set of generic SPI errors upon which generic @@ -231,24 +241,26 @@ pub trait Error: core::fmt::Debug { } impl Error for core::convert::Infallible { + #[inline] fn kind(&self) -> ErrorKind { match *self {} } } -/// SPI error kind +/// SPI error kind. /// /// This represents a common set of SPI operation errors. HAL implementations are /// free to define more specific or additional error types. However, by providing /// a mapping to these common SPI errors, generic code can still react to them. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] #[non_exhaustive] pub enum ErrorKind { - /// The peripheral receive buffer was overrun + /// The peripheral receive buffer was overrun. Overrun, - /// Multiple devices on the SPI bus are trying to drive the slave select pin, e.g. in a multi-master setup + /// Multiple devices on the SPI bus are trying to drive the slave select pin, e.g. in a multi-master setup. ModeFault, - /// Received data does not conform to the peripheral configuration + /// Received data does not conform to the peripheral configuration. FrameFormat, /// An error occurred while asserting or deasserting the Chip Select pin. ChipSelectFault, @@ -257,12 +269,16 @@ pub enum ErrorKind { } impl Error for ErrorKind { + #[inline] fn kind(&self) -> ErrorKind { *self } } +impl core::error::Error for ErrorKind {} + impl core::fmt::Display for ErrorKind { + #[inline] fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::Overrun => write!(f, "The peripheral receive buffer was overrun"), @@ -286,34 +302,57 @@ impl core::fmt::Display for ErrorKind { } } -/// SPI error type trait +/// SPI error type trait. /// /// This just defines the error type, to be used by the other SPI traits. pub trait ErrorType { - /// Error type + /// Error type. type Error: Error; } -impl ErrorType for &mut T { +impl ErrorType for &mut T { type Error = T::Error; } -/// SPI device trait +/// SPI transaction operation. +/// +/// This allows composition of SPI operations into a single bus transaction. +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] +pub enum Operation<'a, Word: 'static> { + /// Read data into the provided buffer. + /// + /// Equivalent to [`SpiBus::read`]. + Read(&'a mut [Word]), + /// Write data from the provided buffer, discarding read data. + /// + /// Equivalent to [`SpiBus::write`]. + Write(&'a [Word]), + /// Read data into the first buffer, while writing data from the second buffer. + /// + /// Equivalent to [`SpiBus::transfer`]. + Transfer(&'a mut [Word], &'a [Word]), + /// Write data out while reading data into the provided buffer. + /// + /// Equivalent to [`SpiBus::transfer_in_place`]. + TransferInPlace(&'a mut [Word]), + /// Delay for at least the specified number of nanoseconds. + DelayNs(u32), +} + +/// SPI device trait. /// /// `SpiDevice` represents ownership over a single SPI device on a (possibly shared) bus, selected /// with a CS (Chip Select) pin. /// /// See the [module-level documentation](self) for important usage information. -pub trait SpiDevice: ErrorType { - /// SPI Bus type for this device. - type Bus: ErrorType; - +pub trait SpiDevice: ErrorType { /// Perform a transaction against the device. /// /// - Locks the bus /// - Asserts the CS (Chip Select) pin. - /// - Calls `f` with an exclusive reference to the bus, which can then be used to do transfers against the device. - /// - [Flushes](SpiBusFlush::flush) the bus. + /// - Performs all the operations. + /// - [Flushes](SpiBus::flush) the bus. /// - Deasserts the CS pin. /// - Unlocks the bus. /// @@ -323,90 +362,82 @@ pub trait SpiDevice: ErrorType { /// /// On bus errors the implementation should try to deassert CS. /// If an error occurs while deasserting CS the bus error should take priority as the return value. - fn transaction( - &mut self, - f: impl FnOnce(&mut Self::Bus) -> Result::Error>, - ) -> Result; + fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error>; - /// Do a write within a transaction. + /// Do a read within a transaction. /// - /// This is a convenience method equivalent to `device.transaction(|bus| bus.write(buf))`. + /// This is a convenience method equivalent to `device.transaction(&mut [Operation::Read(buf)])`. /// - /// See also: [`SpiDevice::transaction`], [`SpiBusWrite::write`] - fn write(&mut self, buf: &[Word]) -> Result<(), Self::Error> - where - Self::Bus: SpiBusWrite, - Word: Copy, - { - self.transaction(|bus| bus.write(buf)) + /// See also: [`SpiDevice::transaction`], [`SpiBus::read`] + #[inline] + fn read(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + self.transaction(&mut [Operation::Read(buf)]) } - /// Do a read within a transaction. + /// Do a write within a transaction. /// - /// This is a convenience method equivalent to `device.transaction(|bus| bus.read(buf))`. + /// This is a convenience method equivalent to `device.transaction(&mut [Operation::Write(buf)])`. /// - /// See also: [`SpiDevice::transaction`], [`SpiBusRead::read`] - fn read(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> - where - Self::Bus: SpiBusRead, - Word: Copy, - { - self.transaction(|bus| bus.read(buf)) + /// See also: [`SpiDevice::transaction`], [`SpiBus::write`] + #[inline] + fn write(&mut self, buf: &[Word]) -> Result<(), Self::Error> { + self.transaction(&mut [Operation::Write(buf)]) } /// Do a transfer within a transaction. /// - /// This is a convenience method equivalent to `device.transaction(|bus| bus.transfer(read, write))`. + /// This is a convenience method equivalent to `device.transaction(&mut [Operation::Transfer(read, write)]`. /// /// See also: [`SpiDevice::transaction`], [`SpiBus::transfer`] - fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> - where - Self::Bus: SpiBus, - Word: Copy, - { - self.transaction(|bus| bus.transfer(read, write)) + #[inline] + fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + self.transaction(&mut [Operation::Transfer(read, write)]) } /// Do an in-place transfer within a transaction. /// - /// This is a convenience method equivalent to `device.transaction(|bus| bus.transfer_in_place(buf))`. + /// This is a convenience method equivalent to `device.transaction(&mut [Operation::TransferInPlace(buf)]`. /// /// See also: [`SpiDevice::transaction`], [`SpiBus::transfer_in_place`] - fn transfer_in_place(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> - where - Self::Bus: SpiBus, - Word: Copy, - { - self.transaction(|bus| bus.transfer_in_place(buf)) + #[inline] + fn transfer_in_place(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + self.transaction(&mut [Operation::TransferInPlace(buf)]) } } -impl SpiDevice for &mut T { - type Bus = T::Bus; - fn transaction( - &mut self, - f: impl FnOnce(&mut Self::Bus) -> Result::Error>, - ) -> Result { - T::transaction(self, f) +impl + ?Sized> SpiDevice for &mut T { + #[inline] + fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { + T::transaction(self, operations) } -} -/// Flush support for SPI bus -pub trait SpiBusFlush: ErrorType { - /// Wait until all operations have completed and the bus is idle. - /// - /// See the [module-level documentation](self) for important usage information. - fn flush(&mut self) -> Result<(), Self::Error>; -} + #[inline] + fn read(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + T::read(self, buf) + } -impl SpiBusFlush for &mut T { - fn flush(&mut self) -> Result<(), Self::Error> { - T::flush(self) + #[inline] + fn write(&mut self, buf: &[Word]) -> Result<(), Self::Error> { + T::write(self, buf) + } + + #[inline] + fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + T::transfer(self, read, write) + } + + #[inline] + fn transfer_in_place(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + T::transfer_in_place(self, buf) } } -/// Read-only SPI bus -pub trait SpiBusRead: SpiBusFlush { +/// SPI bus. +/// +/// `SpiBus` represents **exclusive ownership** over the whole SPI bus, with SCK, MOSI and MISO pins. +/// +/// See the [module-level documentation](self) for important information on SPI Bus vs Device traits. +pub trait SpiBus: ErrorType { /// Read `words` from the slave. /// /// The word value sent on MOSI during reading is implementation-defined, @@ -415,35 +446,13 @@ pub trait SpiBusRead: SpiBusFlush { /// Implementations are allowed to return before the operation is /// complete. See the [module-level documentation](self) for details. fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error>; -} - -impl, Word: Copy> SpiBusRead for &mut T { - fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { - T::read(self, words) - } -} -/// Write-only SPI bus -pub trait SpiBusWrite: SpiBusFlush { - /// Write `words` to the slave, ignoring all the incoming words + /// Write `words` to the slave, ignoring all the incoming words. /// /// Implementations are allowed to return before the operation is /// complete. See the [module-level documentation](self) for details. fn write(&mut self, words: &[Word]) -> Result<(), Self::Error>; -} -impl, Word: Copy> SpiBusWrite for &mut T { - fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { - T::write(self, words) - } -} - -/// Read-write SPI bus -/// -/// `SpiBus` represents **exclusive ownership** over the whole SPI bus, with SCK, MOSI and MISO pins. -/// -/// See the [module-level documentation](self) for important information on SPI Bus vs Device traits. -pub trait SpiBus: SpiBusRead + SpiBusWrite { /// Write and read simultaneously. `write` is written to the slave on MOSI and /// words received on MISO are stored in `read`. /// @@ -464,14 +473,36 @@ pub trait SpiBus: SpiBusRead + SpiBusWrite { /// Implementations are allowed to return before the operation is /// complete. See the [module-level documentation](self) for details. fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error>; + + /// Wait until all operations have completed and the bus is idle. + /// + /// See the [module-level documentation](self) for important usage information. + fn flush(&mut self) -> Result<(), Self::Error>; } -impl, Word: Copy> SpiBus for &mut T { +impl + ?Sized, Word: Copy + 'static> SpiBus for &mut T { + #[inline] + fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + T::read(self, words) + } + + #[inline] + fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { + T::write(self, words) + } + + #[inline] fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { T::transfer(self, read, write) } + #[inline] fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { T::transfer_in_place(self, words) } + + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + T::flush(self) + } } diff --git a/embedded-io-adapters/CHANGELOG.md b/embedded-io-adapters/CHANGELOG.md new file mode 100644 index 000000000..f427e0aa1 --- /dev/null +++ b/embedded-io-adapters/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +- Added `ToFmt` adapter for `core::fmt::Write`. + +## 0.6.1 - 2023-11-28 + +- Handle reading from `FromTokio` with empty buffer, ensuring `Ok(0)` is always returned. +- Use `feature()` on nightly toolchains only. This adds async support for 1.75 beta and stable. + +## 0.6.0 - 2023-10-02 + +- Add support for adapting `BufRead` from `futures` and `tokio`. +- Return an error when a wrapped `std`/`futures`/`tokio` `write()` call returns + `Ok(0)` to comply with `embedded_io::Write` requirements. + +## 0.5.0 - 2023-08-06 + +- First release diff --git a/embedded-io-adapters/Cargo.toml b/embedded-io-adapters/Cargo.toml new file mode 100644 index 000000000..89721f1bb --- /dev/null +++ b/embedded-io-adapters/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "embedded-io-adapters" +version = "0.6.1" +edition = "2021" +rust-version = "1.60" +description = "Adapters between the `embedded-io` traits and other I/O traits" +repository = "https://github.com/rust-embedded/embedded-hal" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", +] + +[features] +std = ["embedded-io/std"] +tokio-1 = ["std", "dep:tokio", "dep:embedded-io-async", "embedded-io-async?/std"] +futures-03 = ["std", "dep:futures", "dep:embedded-io-async", "embedded-io-async?/std"] + +[dependencies] +embedded-io = { version = "0.6", path = "../embedded-io" } +embedded-io-async = { version = "0.6.1", path = "../embedded-io-async", optional = true } + +futures = { version = "0.3.21", features = ["std"], default-features = false, optional = true } +tokio = { version = "1", features = ["io-util"], default-features = false, optional = true } + +[package.metadata.docs.rs] +features = ["std", "tokio-1", "futures-03"] +rustdoc-args = ["--cfg", "docsrs"] diff --git a/embedded-io-adapters/LICENSE-APACHE b/embedded-io-adapters/LICENSE-APACHE new file mode 100644 index 000000000..16fe87b06 --- /dev/null +++ b/embedded-io-adapters/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/embedded-io-adapters/LICENSE-MIT b/embedded-io-adapters/LICENSE-MIT new file mode 100644 index 000000000..e00608fbd --- /dev/null +++ b/embedded-io-adapters/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2023 The embedded-io authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/embedded-io-adapters/README.md b/embedded-io-adapters/README.md new file mode 100644 index 000000000..a069d1fac --- /dev/null +++ b/embedded-io-adapters/README.md @@ -0,0 +1,53 @@ +[![crates.io](https://img.shields.io/crates/d/embedded-io-adapters.svg)](https://crates.io/crates/embedded-io-adapters) +[![crates.io](https://img.shields.io/crates/v/embedded-io-adapters.svg)](https://crates.io/crates/embedded-io-adapters) +[![Documentation](https://docs.rs/embedded-io-adapters/badge.svg)](https://docs.rs/embedded-io-adapters) + +# `embedded-io-adapters` + +This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team). + +Adapters between the [`embedded-io`](https://crates.io/crates/embedded-io) and [`embedded-io-async`](https://crates.io/crates/embedded-io-async) traits and other IO traits. + +The adapters are structs that wrap an I/O stream and implement another family of I/O traits +based on the wrapped streams. This allows "converting" from an `embedded_io::Read` +to a `std::io::Read` or vice versa, for example. + +There are no separate adapters for `Read`/`ReadBuf`/`Write` traits. Instead, a single +adapter implements the right traits based on what the inner type implements. +This allows using these adapters when using combinations of traits, like `Read+Write`. + +## Supported traits + +For `embedded-io`: + +- [`std::io`](https://doc.rust-lang.org/stable/std/io/index.html) traits. Needs the `std` feature. + +For `embedded-io-async`: + +- [`futures` 0.3](https://crates.io/crates/futures) traits. Needs the `futures-03` feature. +- [`tokio` 1.x](https://crates.io/crates/tokio) traits. Needs the `tokio-1` feature. + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.60 and up. It *might* +compile with older versions but that may change in any new patch release. + +See [here](../docs/msrv.md) for details on how the MSRV may be upgraded. + +Enabling any of the `tokio-*` or `futures-*` Cargo features requires Rust 1.75 or higher. + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/embedded-io-adapters/src/fmt.rs b/embedded-io-adapters/src/fmt.rs new file mode 100644 index 000000000..7a057668d --- /dev/null +++ b/embedded-io-adapters/src/fmt.rs @@ -0,0 +1,42 @@ +//! Adapters to the `core::fmt::Write`. + +/// Adapter to the `core::fmt::Write` trait. +#[derive(Clone, Default, PartialEq, Debug)] +pub struct ToFmt { + inner: T, +} + +impl ToFmt { + /// Create a new adapter. + pub fn new(inner: T) -> Self { + Self { inner } + } + + /// Consume the adapter, returning the inner object. + pub fn into_inner(self) -> T { + self.inner + } +} + +impl ToFmt { + /// Borrow the inner object. + pub fn inner(&self) -> &T { + &self.inner + } + + /// Mutably borrow the inner object. + pub fn inner_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl core::fmt::Write for ToFmt { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.inner.write_all(s.as_bytes()).or(Err(core::fmt::Error)) + } + + // Use fmt::Write default impls for + // * write_fmt(): better here than e-io::Write::write_fmt + // since we don't need to bother with saving the Error + // * write_char(): would be the same +} diff --git a/embedded-io-adapters/src/futures_03.rs b/embedded-io-adapters/src/futures_03.rs new file mode 100644 index 000000000..d1127a8c6 --- /dev/null +++ b/embedded-io-adapters/src/futures_03.rs @@ -0,0 +1,87 @@ +//! Adapters to/from `futures::io` traits. + +// MSRV is 1.60 if you don't enable async, 1.80 if you do. +// Cargo.toml has 1.60, which makes Clippy complain that `poll_fn` was introduced +// in 1.64. So, just silence it for this file. +#![allow(clippy::incompatible_msrv)] + +use core::future::poll_fn; +use core::pin::Pin; + +use futures::AsyncBufReadExt; + +/// Adapter from `futures::io` traits. +#[derive(Clone)] +pub struct FromFutures { + inner: T, +} + +impl FromFutures { + /// Create a new adapter. + pub fn new(inner: T) -> Self { + Self { inner } + } + + /// Consume the adapter, returning the inner object. + pub fn into_inner(self) -> T { + self.inner + } +} + +impl FromFutures { + /// Borrow the inner object. + pub fn inner(&self) -> &T { + &self.inner + } + + /// Mutably borrow the inner object. + pub fn inner_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl embedded_io::ErrorType for FromFutures { + type Error = std::io::Error; +} + +impl embedded_io_async::Read for FromFutures { + async fn read(&mut self, buf: &mut [u8]) -> Result { + poll_fn(|cx| Pin::new(&mut self.inner).poll_read(cx, buf)).await + } +} + +impl embedded_io_async::BufRead for FromFutures { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.inner.fill_buf().await + } + + fn consume(&mut self, amt: usize) { + Pin::new(&mut self.inner).consume(amt); + } +} + +impl embedded_io_async::Write for FromFutures { + async fn write(&mut self, buf: &[u8]) -> Result { + match poll_fn(|cx| Pin::new(&mut self.inner).poll_write(cx, buf)).await { + Ok(0) if !buf.is_empty() => Err(std::io::ErrorKind::WriteZero.into()), + Ok(n) => Ok(n), + Err(e) => Err(e), + } + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + poll_fn(|cx| Pin::new(&mut self.inner).poll_flush(cx)).await + } +} + +impl embedded_io_async::Seek for FromFutures { + async fn seek(&mut self, pos: embedded_io::SeekFrom) -> Result { + poll_fn(move |cx| Pin::new(&mut self.inner).poll_seek(cx, pos.into())).await + } +} + +// TODO: ToFutures. +// It's a bit tricky because futures::io is "stateless", while we're "stateful" (we +// return futures that borrow Self and get polled for the duration of the operation.) +// It can probably done by storing the futures in Self, with unsafe Pin hacks because +// we're a self-referential struct diff --git a/embedded-io-adapters/src/lib.rs b/embedded-io-adapters/src/lib.rs new file mode 100644 index 000000000..c5e6710a0 --- /dev/null +++ b/embedded-io-adapters/src/lib.rs @@ -0,0 +1,18 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +pub mod fmt; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod std; + +#[cfg(feature = "futures-03")] +#[cfg_attr(docsrs, doc(cfg(feature = "futures-03")))] +pub mod futures_03; + +#[cfg(feature = "tokio-1")] +#[cfg_attr(docsrs, doc(cfg(feature = "tokio-1")))] +pub mod tokio_1; diff --git a/embedded-io-adapters/src/std.rs b/embedded-io-adapters/src/std.rs new file mode 100644 index 000000000..90a2f8525 --- /dev/null +++ b/embedded-io-adapters/src/std.rs @@ -0,0 +1,132 @@ +//! Adapters to/from `std::io` traits. + +use embedded_io::Error as _; + +/// Adapter from `std::io` traits. +#[derive(Clone)] +pub struct FromStd { + inner: T, +} + +impl FromStd { + /// Create a new adapter. + pub fn new(inner: T) -> Self { + Self { inner } + } + + /// Consume the adapter, returning the inner object. + pub fn into_inner(self) -> T { + self.inner + } +} + +impl FromStd { + /// Borrow the inner object. + pub fn inner(&self) -> &T { + &self.inner + } + + /// Mutably borrow the inner object. + pub fn inner_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl embedded_io::ErrorType for FromStd { + type Error = std::io::Error; +} + +impl embedded_io::Read for FromStd { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner.read(buf) + } +} + +impl embedded_io::BufRead for FromStd { + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.inner.fill_buf() + } + + fn consume(&mut self, amt: usize) { + self.inner.consume(amt); + } +} + +impl embedded_io::Write for FromStd { + fn write(&mut self, buf: &[u8]) -> Result { + match self.inner.write(buf) { + Ok(0) if !buf.is_empty() => Err(std::io::ErrorKind::WriteZero.into()), + Ok(n) => Ok(n), + Err(e) => Err(e), + } + } + fn flush(&mut self) -> Result<(), Self::Error> { + self.inner.flush() + } +} + +impl embedded_io::Seek for FromStd { + fn seek(&mut self, pos: embedded_io::SeekFrom) -> Result { + self.inner.seek(pos.into()) + } +} + +/// Adapter to `std::io` traits. +#[derive(Clone)] +pub struct ToStd { + inner: T, +} + +impl ToStd { + /// Create a new adapter. + pub fn new(inner: T) -> Self { + Self { inner } + } + + /// Consume the adapter, returning the inner object. + pub fn into_inner(self) -> T { + self.inner + } +} + +impl ToStd { + /// Borrow the inner object. + pub fn inner(&self) -> &T { + &self.inner + } + + /// Mutably borrow the inner object. + pub fn inner_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl std::io::Read for ToStd { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner.read(buf).map_err(to_std_error) + } +} + +impl std::io::Write for ToStd { + fn write(&mut self, buf: &[u8]) -> Result { + match self.inner.write(buf) { + Ok(n) => Ok(n), + Err(e) if e.kind() == embedded_io::ErrorKind::WriteZero => Ok(0), + Err(e) => Err(to_std_error(e)), + } + } + fn flush(&mut self) -> Result<(), std::io::Error> { + self.inner.flush().map_err(to_std_error) + } +} + +impl std::io::Seek for ToStd { + fn seek(&mut self, pos: std::io::SeekFrom) -> Result { + self.inner.seek(pos.into()).map_err(to_std_error) + } +} + +/// Convert a embedded-io error to a [`std::io::Error`] +pub fn to_std_error(err: T) -> std::io::Error { + std::io::Error::new(err.kind().into(), format!("{err:?}")) +} diff --git a/embedded-io-adapters/src/tokio_1.rs b/embedded-io-adapters/src/tokio_1.rs new file mode 100644 index 000000000..0dd4d265b --- /dev/null +++ b/embedded-io-adapters/src/tokio_1.rs @@ -0,0 +1,110 @@ +//! Adapters to/from `tokio::io` traits. + +// MSRV is 1.60 if you don't enable async, 1.80 if you do. +// Cargo.toml has 1.60, which makes Clippy complain that `poll_fn` was introduced +// in 1.64. So, just silence it for this file. +#![allow(clippy::incompatible_msrv)] + +use core::future::poll_fn; +use core::pin::Pin; +use core::task::Poll; + +use tokio::io::AsyncBufReadExt; + +/// Adapter from `tokio::io` traits. +#[derive(Clone)] +pub struct FromTokio { + inner: T, +} + +impl FromTokio { + /// Create a new adapter. + pub fn new(inner: T) -> Self { + Self { inner } + } + + /// Consume the adapter, returning the inner object. + pub fn into_inner(self) -> T { + self.inner + } +} + +impl FromTokio { + /// Borrow the inner object. + pub fn inner(&self) -> &T { + &self.inner + } + + /// Mutably borrow the inner object. + pub fn inner_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl embedded_io::ErrorType for FromTokio { + type Error = std::io::Error; +} + +impl embedded_io_async::Read for FromTokio { + async fn read(&mut self, buf: &mut [u8]) -> Result { + // The current tokio implementation (https://github.com/tokio-rs/tokio/blob/tokio-1.33.0/tokio/src/io/poll_evented.rs#L165) + // does not consider the case of buf.is_empty() as a special case, + // which can cause Poll::Pending to be returned at the end of the stream when called with an empty buffer. + // This poll will, however, never become ready, as no more bytes will be received. + if buf.is_empty() { + return Ok(0); + } + + poll_fn(|cx| { + let mut buf = tokio::io::ReadBuf::new(buf); + match Pin::new(&mut self.inner).poll_read(cx, &mut buf) { + Poll::Ready(r) => match r { + Ok(()) => Poll::Ready(Ok(buf.filled().len())), + Err(e) => Poll::Ready(Err(e)), + }, + Poll::Pending => Poll::Pending, + } + }) + .await + } +} + +impl embedded_io_async::BufRead for FromTokio { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.inner.fill_buf().await + } + + fn consume(&mut self, amt: usize) { + Pin::new(&mut self.inner).consume(amt); + } +} + +impl embedded_io_async::Write for FromTokio { + async fn write(&mut self, buf: &[u8]) -> Result { + match poll_fn(|cx| Pin::new(&mut self.inner).poll_write(cx, buf)).await { + Ok(0) if !buf.is_empty() => Err(std::io::ErrorKind::WriteZero.into()), + Ok(n) => Ok(n), + Err(e) => Err(e), + } + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + poll_fn(|cx| Pin::new(&mut self.inner).poll_flush(cx)).await + } +} + +impl embedded_io_async::Seek for FromTokio { + async fn seek(&mut self, pos: embedded_io::SeekFrom) -> Result { + // Note: `start_seek` can return an error if there is another seek in progress. + // Therefor it is recommended to call `poll_complete` before any call to `start_seek`. + poll_fn(|cx| Pin::new(&mut self.inner).poll_complete(cx)).await?; + Pin::new(&mut self.inner).start_seek(pos.into())?; + poll_fn(|cx| Pin::new(&mut self.inner).poll_complete(cx)).await + } +} + +// TODO: ToTokio. +// It's a bit tricky because tokio::io is "stateless", while we're "stateful" (we +// return futures that borrow Self and get polled for the duration of the operation.) +// It can probably done by storing the futures in Self, with unsafe Pin hacks because +// we're a self-referential struct diff --git a/embedded-io-async/CHANGELOG.md b/embedded-io-async/CHANGELOG.md new file mode 100644 index 000000000..5f421e351 --- /dev/null +++ b/embedded-io-async/CHANGELOG.md @@ -0,0 +1,28 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +Add unreleased changes here + +## 0.6.1 - 2023-11-28 + +- Use `feature()` on nightly toolchains only. This adds support for 1.75 beta and stable. + +## 0.6.0 - 2023-10-02 + +- Prohibit `Write::write` implementations returning `Ok(0)` unless there is no + data to write; consequently remove `WriteAllError`. + Update the `&mut [u8]` impl to possibly return + a new `SliceWriteError` if the slice is full instead of `Ok(0)`. +- Add `WriteZero` variant to `ErrorKind` for implementations that previously + may have returned `Ok(0)` to indicate no further data could be written. +- `Write::write_all` now panics if the `write()` implementation returns `Ok(0)`. + +## 0.5.0 - 2023-08-06 + +- First release diff --git a/embedded-io-async/Cargo.toml b/embedded-io-async/Cargo.toml new file mode 100644 index 000000000..1cf2e0aad --- /dev/null +++ b/embedded-io-async/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "embedded-io-async" +version = "0.6.1" +edition = "2021" +rust-version = "1.75" +description = "Async embedded IO traits" +repository = "https://github.com/rust-embedded/embedded-hal" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", +] + +[features] +std = ["alloc", "embedded-io/std"] +alloc = ["embedded-io/alloc"] +defmt-03 = ["dep:defmt-03", "embedded-io/defmt-03"] + +[dependencies] +embedded-io = { version = "0.6.1", path = "../embedded-io" } +defmt-03 = { package = "defmt", version = "0.3", optional = true } + +[package.metadata.docs.rs] +features = ["std"] +rustdoc-args = ["--cfg", "docsrs"] diff --git a/embedded-io-async/LICENSE-APACHE b/embedded-io-async/LICENSE-APACHE new file mode 100644 index 000000000..16fe87b06 --- /dev/null +++ b/embedded-io-async/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/embedded-io-async/LICENSE-MIT b/embedded-io-async/LICENSE-MIT new file mode 100644 index 000000000..3f5b8019c --- /dev/null +++ b/embedded-io-async/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2023 The embedded-io-async authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/embedded-io-async/README.md b/embedded-io-async/README.md new file mode 100644 index 000000000..8c5043593 --- /dev/null +++ b/embedded-io-async/README.md @@ -0,0 +1,40 @@ +[![crates.io](https://img.shields.io/crates/d/embedded-io-async.svg)](https://crates.io/crates/embedded-io-async) +[![crates.io](https://img.shields.io/crates/v/embedded-io-async.svg)](https://crates.io/crates/embedded-io-async) +[![Documentation](https://docs.rs/embedded-io-async/badge.svg)](https://docs.rs/embedded-io-async) + +# `embedded-io-async` + +Async IO traits for embedded systems. + +This crate contains asynchronous versions of the [`embedded-io`](https://crates.io/crates/embedded-io) traits and shares its scope and design goals. + +This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team). + +## Optional Cargo features + +- **`std`**: Adds `From` impls to convert to/from `std::io` structs. +- **`alloc`**: Adds blanket impls for `Box`, adds `Write` impl to `Vec`. +- **`defmt-03`**: Derive `defmt::Format` from `defmt` 0.3 for enums and structs. + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.75 and up. It *might* +compile with older versions but that may change in any new patch release. + +See [here](../docs/msrv.md) for details on how the MSRV may be upgraded. + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/embedded-io-async/src/impls/boxx.rs b/embedded-io-async/src/impls/boxx.rs new file mode 100644 index 000000000..d6186b0c7 --- /dev/null +++ b/embedded-io-async/src/impls/boxx.rs @@ -0,0 +1,44 @@ +use crate::{BufRead, Read, Seek, SeekFrom, Write}; +use alloc::boxed::Box; + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Read for Box { + #[inline] + async fn read(&mut self, buf: &mut [u8]) -> Result { + T::read(self, buf).await + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl BufRead for Box { + #[inline] + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + T::fill_buf(self).await + } + + #[inline] + fn consume(&mut self, amt: usize) { + T::consume(self, amt); + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Write for Box { + #[inline] + async fn write(&mut self, buf: &[u8]) -> Result { + T::write(self, buf).await + } + + #[inline] + async fn flush(&mut self) -> Result<(), Self::Error> { + T::flush(self).await + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Seek for Box { + #[inline] + async fn seek(&mut self, pos: SeekFrom) -> Result { + T::seek(self, pos).await + } +} diff --git a/embedded-io-async/src/impls/mod.rs b/embedded-io-async/src/impls/mod.rs new file mode 100644 index 000000000..e79b9b8bf --- /dev/null +++ b/embedded-io-async/src/impls/mod.rs @@ -0,0 +1,7 @@ +mod slice_mut; +mod slice_ref; + +#[cfg(feature = "alloc")] +mod boxx; +#[cfg(feature = "alloc")] +mod vec; diff --git a/embedded-io-async/src/impls/slice_mut.rs b/embedded-io-async/src/impls/slice_mut.rs new file mode 100644 index 000000000..bd64d1320 --- /dev/null +++ b/embedded-io-async/src/impls/slice_mut.rs @@ -0,0 +1,27 @@ +use core::mem; +use embedded_io::SliceWriteError; + +use crate::Write; + +/// Write is implemented for `&mut [u8]` by copying into the slice, overwriting +/// its data. +/// +/// Note that writing updates the slice to point to the yet unwritten part. +/// The slice will be empty when it has been completely overwritten. +/// +/// If the number of bytes to be written exceeds the size of the slice, write operations will +/// return short writes: ultimately, `Ok(0)`; in this situation, `write_all` returns an error of +/// kind `ErrorKind::WriteZero`. +impl Write for &mut [u8] { + #[inline] + async fn write(&mut self, buf: &[u8]) -> Result { + let amt = core::cmp::min(buf.len(), self.len()); + if !buf.is_empty() && amt == 0 { + return Err(SliceWriteError::Full); + } + let (a, b) = mem::take(self).split_at_mut(amt); + a.copy_from_slice(&buf[..amt]); + *self = b; + Ok(amt) + } +} diff --git a/embedded-io-async/src/impls/slice_ref.rs b/embedded-io-async/src/impls/slice_ref.rs new file mode 100644 index 000000000..a6a4ba807 --- /dev/null +++ b/embedded-io-async/src/impls/slice_ref.rs @@ -0,0 +1,37 @@ +use crate::{BufRead, Read}; + +/// Read is implemented for `&[u8]` by copying from the slice. +/// +/// Note that reading updates the slice to point to the yet unread part. +/// The slice will be empty when EOF is reached. +impl Read for &[u8] { + #[inline] + async fn read(&mut self, buf: &mut [u8]) -> Result { + let amt = core::cmp::min(buf.len(), self.len()); + let (a, b) = self.split_at(amt); + + // First check if the amount of bytes we want to read is small: + // `copy_from_slice` will generally expand to a call to `memcpy`, and + // for a single byte the overhead is significant. + if amt == 1 { + buf[0] = a[0]; + } else { + buf[..amt].copy_from_slice(a); + } + + *self = b; + Ok(amt) + } +} + +impl BufRead for &[u8] { + #[inline] + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Ok(*self) + } + + #[inline] + fn consume(&mut self, amt: usize) { + *self = &self[amt..]; + } +} diff --git a/embedded-io-async/src/impls/vec.rs b/embedded-io-async/src/impls/vec.rs new file mode 100644 index 000000000..c24405e39 --- /dev/null +++ b/embedded-io-async/src/impls/vec.rs @@ -0,0 +1,12 @@ +use alloc::vec::Vec; + +use crate::Write; + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Write for Vec { + #[inline] + async fn write(&mut self, buf: &[u8]) -> Result { + self.extend_from_slice(buf); + Ok(buf.len()) + } +} diff --git a/embedded-io-async/src/lib.rs b/embedded-io-async/src/lib.rs new file mode 100644 index 000000000..eef50ebcd --- /dev/null +++ b/embedded-io-async/src/lib.rs @@ -0,0 +1,207 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +#![allow(async_fn_in_trait)] + +#[cfg(feature = "alloc")] +extern crate alloc; + +mod impls; + +pub use embedded_io::{ + Error, ErrorKind, ErrorType, ReadExactError, ReadReady, SeekFrom, WriteReady, +}; + +/// Async reader. +/// +/// This trait is the `embedded-io-async` equivalent of [`std::io::Read`]. +pub trait Read: ErrorType { + /// Read some bytes from this source into the specified buffer, returning how many bytes were read. + /// + /// If no bytes are currently available to read: + /// - The method waits until at least one byte becomes available; + /// - Once at least one (or more) bytes become available, a non-zero amount of those is copied to the + /// beginning of `buf`, and the amount is returned, *without waiting any further for more bytes to + /// become available*. + /// + /// If bytes are available to read: + /// - A non-zero amount of bytes is read to the beginning of `buf`, and the amount is returned immediately, + /// *without waiting for more bytes to become available*; + /// + /// Note that once some bytes are available to read, it is *not* guaranteed that all available bytes are returned. + /// It is possible for the implementation to read an amount of bytes less than `buf.len()` while there are more + /// bytes immediately available. + /// + /// This waiting behavior is important for the cases where `Read` represents the "read" leg of a pipe-like + /// protocol (a socket, a pipe, a serial line etc.). The semantics is that the caller - by passing a non-empty + /// buffer - does expect _some_ data (one or more bytes) - but _not necessarily `buf.len()` or more bytes_ - + /// to become available, before the peer represented by `Read` would stop sending bytes due to + /// application-specific reasons (as in the peer waiting for a response to the data it had sent so far). + /// + /// If the reader is at end-of-file (EOF), `Ok(0)` is returned. There is no guarantee that a reader at EOF + /// will always be so in the future, for example a reader can stop being at EOF if another process appends + /// more bytes to the underlying file. + /// + /// If `buf.len() == 0`, `read` returns without waiting, with either `Ok(0)` or an error. + /// The `Ok(0)` doesn't indicate EOF, unlike when called with a non-empty buffer. + /// + /// Implementations are encouraged to make this function side-effect-free on cancel (AKA "cancel-safe"), i.e. + /// guarantee that if you cancel (drop) a `read()` future that hasn't completed yet, the stream's + /// state hasn't changed (no bytes have been read). + /// + /// This is not a requirement to allow implementations that read into the user's buffer straight from + /// the hardware with e.g. DMA. + /// + /// Implementations should document whether they're actually side-effect-free on cancel or not. + async fn read(&mut self, buf: &mut [u8]) -> Result; + + /// Read the exact number of bytes required to fill `buf`. + /// + /// This function calls `read()` in a loop until exactly `buf.len()` bytes have + /// been read, waiting if needed. + /// + /// This function is not side-effect-free on cancel (AKA "cancel-safe"), i.e. if you cancel (drop) a returned + /// future that hasn't completed yet, some bytes might have already been read, which will get lost. + async fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), ReadExactError> { + while !buf.is_empty() { + match self.read(buf).await { + Ok(0) => break, + Ok(n) => buf = &mut buf[n..], + Err(e) => return Err(ReadExactError::Other(e)), + } + } + if buf.is_empty() { + Ok(()) + } else { + Err(ReadExactError::UnexpectedEof) + } + } +} + +/// Async buffered reader. +/// +/// This trait is the `embedded-io-async` equivalent of [`std::io::BufRead`]. +pub trait BufRead: ErrorType { + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + /// + /// If no bytes are currently available to read, this function waits until at least one byte is available. + /// + /// If the reader is at end-of-file (EOF), an empty slice is returned. There is no guarantee that a reader at EOF + /// will always be so in the future, for example a reader can stop being at EOF if another process appends + /// more bytes to the underlying file. + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error>; + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + fn consume(&mut self, amt: usize); +} + +/// Async writer. +/// +/// This trait is the `embedded-io-async` equivalent of [`std::io::Write`]. +pub trait Write: ErrorType { + /// Write a buffer into this writer, returning how many bytes were written. + /// + /// If the writer is not currently ready to accept more bytes (for example, its buffer is full), + /// this function waits until it is ready to accept least one byte. + /// + /// If it's ready to accept bytes, a non-zero amount of bytes is written from the beginning of `buf`, and the amount + /// is returned. It is not guaranteed that *all* available buffer space is filled, i.e. it is possible for the + /// implementation to write an amount of bytes less than `buf.len()` while the writer continues to be + /// ready to accept more bytes immediately. + /// + /// Implementations should never return `Ok(0)` when `buf.len() != 0`. Situations where the writer is not + /// able to accept more bytes and likely never will are better indicated with errors. + /// + /// If `buf.len() == 0`, `write` returns without waiting, with either `Ok(0)` or an error. + /// The `Ok(0)` doesn't indicate an error. + /// + /// Implementations are encouraged to make this function side-effect-free on cancel (AKA "cancel-safe"), i.e. + /// guarantee that if you cancel (drop) a `write()` future that hasn't completed yet, the stream's + /// state hasn't changed (no bytes have been written). + /// + /// This is not a requirement to allow implementations that write from the user's buffer straight to + /// the hardware with e.g. DMA. + /// + /// Implementations should document whether they're actually side-effect-free on cancel or not. + async fn write(&mut self, buf: &[u8]) -> Result; + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + /// Write an entire buffer into this writer. + /// + /// This function calls `write()` in a loop until exactly `buf.len()` bytes have + /// been written, waiting if needed. + /// + /// This function is not side-effect-free on cancel (AKA "cancel-safe"), i.e. if you cancel (drop) a returned + /// future that hasn't completed yet, some bytes might have already been written. + async fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { + let mut buf = buf; + while !buf.is_empty() { + match self.write(buf).await { + Ok(0) => panic!("write() returned Ok(0)"), + Ok(n) => buf = &buf[n..], + Err(e) => return Err(e), + } + } + Ok(()) + } +} + +/// Async seek within streams. +/// +/// This trait is the `embedded-io-async` equivalent of [`std::io::Seek`]. +pub trait Seek: ErrorType { + /// Seek to an offset, in bytes, in a stream. + async fn seek(&mut self, pos: SeekFrom) -> Result; + + /// Rewind to the beginning of a stream. + async fn rewind(&mut self) -> Result<(), Self::Error> { + self.seek(SeekFrom::Start(0)).await?; + Ok(()) + } + + /// Returns the current seek position from the start of the stream. + async fn stream_position(&mut self) -> Result { + self.seek(SeekFrom::Current(0)).await + } +} + +impl Read for &mut T { + #[inline] + async fn read(&mut self, buf: &mut [u8]) -> Result { + T::read(self, buf).await + } +} + +impl BufRead for &mut T { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + T::fill_buf(self).await + } + + fn consume(&mut self, amt: usize) { + T::consume(self, amt); + } +} + +impl Write for &mut T { + #[inline] + async fn write(&mut self, buf: &[u8]) -> Result { + T::write(self, buf).await + } + + #[inline] + async fn flush(&mut self) -> Result<(), Self::Error> { + T::flush(self).await + } +} + +impl Seek for &mut T { + #[inline] + async fn seek(&mut self, pos: SeekFrom) -> Result { + T::seek(self, pos).await + } +} diff --git a/embedded-io/CHANGELOG.md b/embedded-io/CHANGELOG.md new file mode 100644 index 000000000..1c8ea414c --- /dev/null +++ b/embedded-io/CHANGELOG.md @@ -0,0 +1,60 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +- Added `core::error::Error` implementations for every custom `impl Error` +- Migrated `std` feature-gated `std::error::Error` implementations to `core::error::Error` +- Increased MSRV to 1.81 due to `core::error::Error` + +## 0.6.1 - 2023-10-22 + +- Make `SliceWriteError` publicly available. + +## 0.6.0 - 2023-10-02 + +- Prohibit `Write::write` implementations returning `Ok(0)` unless there is no + data to write; consequently remove `WriteAllError` and the `WriteAllError` + variant of `WriteFmtError`. Update the `&mut [u8]` impl to possibly return + a new `SliceWriteError` if the slice is full instead of `Ok(0)`. +- Add `WriteZero` variant to `ErrorKind` for implementations that previously + may have returned `Ok(0)` to indicate no further data could be written. +- `Write::write_all` now panics if the `write()` implementation returns `Ok(0)`. + +## 0.5.0 - 2023-08-06 + +- Add `ReadReady`, `WriteReady` traits. They allow peeking whether the I/O handle is ready to read/write, so they allow using the traits in a non-blocking way. +- Add variants to `ErrorKind` mirroring `std::io::ErrorKind`. +- Add `From` impls to convert between `ErrorKind` and `std::io::ErrorKind`. +- Moved `embedded_io::blocking` to the crate root. +- Split async traits to the `embedded-io-async` crate. +- Split trait adapters to the `embedded-io-adapters` crate. +- Add `std::error` impls for `ReadExactError` & `WriteAllError`. +- Rename trait `Io` to `ErrorType`, for consistency with `embedded-hal`. +- Added optional `defmt` 0.3 support. + +## 0.4.0 - 2022-11-25 + +- Switch all traits to use [`async_fn_in_trait`](https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html) (AFIT). Requires `nightly-2022-11-22` or newer. + +## 0.3.1 - 2022-10-26 + +- Fix compilation on recent nightlies (#5) + +## 0.3.0 - 2022-05-19 + +- `FromFutures` adapter now requires `futures` Cargo feature. (breaking change) +- Add `FromTokio` adapter. +- Add blanket impls for `&mut T`, `Box`. +- Add impl `Read`, `BufRead` for `&[u8]` +- Add impl `Write` for `&mut [u8]` +- Add impl `Write` for `Vec` +- impl `std::error::Error` for `ReadExactError`, `WriteFmtError`. + +## 0.2.0 - 2022-05-07 + +- First release diff --git a/embedded-io/Cargo.toml b/embedded-io/Cargo.toml new file mode 100644 index 000000000..dc878f7b6 --- /dev/null +++ b/embedded-io/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "embedded-io" +version = "0.6.1" +edition = "2021" +rust-version = "1.81" +description = "Embedded IO traits" +repository = "https://github.com/rust-embedded/embedded-hal" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", +] + +[features] +std = ["alloc"] +alloc = [] +defmt-03 = ["dep:defmt-03"] + +[dependencies] +defmt-03 = { package = "defmt", version = "0.3", optional = true } + +[package.metadata.docs.rs] +features = ["std"] +rustdoc-args = ["--cfg", "docsrs"] diff --git a/embedded-io/LICENSE-APACHE b/embedded-io/LICENSE-APACHE new file mode 100644 index 000000000..16fe87b06 --- /dev/null +++ b/embedded-io/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/embedded-io/LICENSE-MIT b/embedded-io/LICENSE-MIT new file mode 100644 index 000000000..e00608fbd --- /dev/null +++ b/embedded-io/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2023 The embedded-io authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/embedded-io/README.md b/embedded-io/README.md new file mode 100644 index 000000000..6c228d8b4 --- /dev/null +++ b/embedded-io/README.md @@ -0,0 +1,49 @@ +[![crates.io](https://img.shields.io/crates/d/embedded-io.svg)](https://crates.io/crates/embedded-io) +[![crates.io](https://img.shields.io/crates/v/embedded-io.svg)](https://crates.io/crates/embedded-io) +[![Documentation](https://docs.rs/embedded-io/badge.svg)](https://docs.rs/embedded-io) + +# `embedded-io` + +This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team). + +Input/Output traits for embedded systems. + +Rust's `std::io` traits are not available in `no_std` targets, mainly because `std::io::Error` +requires allocation. This crate contains replacement equivalent traits, usable in `no_std` +targets. + +## Differences with `std::io` + +- `Error` is an associated type. This allows each implementor to return its own error type, + while avoiding `dyn` or `Box`. This is consistent with how errors are handled in [`embedded-hal`](https://github.com/rust-embedded/embedded-hal/). +- In `std::io`, the `Read`/`Write` traits might be blocking or non-blocking (i.e. returning `WouldBlock` errors) depending on the file descriptor's mode, which is only known at run-time. This allows passing a non-blocking stream to code that expects a blocking + stream, causing unexpected errors. To solve this, `embedded-io` specifies `Read`/`Write` are always blocking, and adds new `ReadReady`/`WriteReady` traits to allow using streams in a non-blocking way. + +## Optional Cargo features + +- **`std`**: Adds `From` impls to convert to/from `std::io` structs. +- **`alloc`**: Adds blanket impls for `Box`, adds `Write` impl to `Vec`. +- **`defmt-03`**: Derive `defmt::Format` from `defmt` 0.3 for enums and structs. + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.60 and up. It *might* +compile with older versions but that may change in any new patch release. + +See [here](../docs/msrv.md) for details on how the MSRV may be upgraded. + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/embedded-io/src/impls/boxx.rs b/embedded-io/src/impls/boxx.rs new file mode 100644 index 000000000..037a6be0c --- /dev/null +++ b/embedded-io/src/impls/boxx.rs @@ -0,0 +1,63 @@ +use crate::{BufRead, ErrorType, Read, ReadReady, Seek, Write, WriteReady}; +use alloc::boxed::Box; + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl ErrorType for Box { + type Error = T::Error; +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Read for Box { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> Result { + T::read(self, buf) + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl BufRead for Box { + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + T::fill_buf(self) + } + + fn consume(&mut self, amt: usize) { + T::consume(self, amt); + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Write for Box { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + T::write(self, buf) + } + + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + T::flush(self) + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Seek for Box { + #[inline] + fn seek(&mut self, pos: crate::SeekFrom) -> Result { + T::seek(self, pos) + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl ReadReady for Box { + #[inline] + fn read_ready(&mut self) -> Result { + T::read_ready(self) + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl WriteReady for Box { + #[inline] + fn write_ready(&mut self) -> Result { + T::write_ready(self) + } +} diff --git a/embedded-io/src/impls/mod.rs b/embedded-io/src/impls/mod.rs new file mode 100644 index 000000000..e79b9b8bf --- /dev/null +++ b/embedded-io/src/impls/mod.rs @@ -0,0 +1,7 @@ +mod slice_mut; +mod slice_ref; + +#[cfg(feature = "alloc")] +mod boxx; +#[cfg(feature = "alloc")] +mod vec; diff --git a/embedded-io/src/impls/slice_mut.rs b/embedded-io/src/impls/slice_mut.rs new file mode 100644 index 000000000..67606561b --- /dev/null +++ b/embedded-io/src/impls/slice_mut.rs @@ -0,0 +1,49 @@ +use crate::{Error, ErrorKind, ErrorType, SliceWriteError, Write}; +use core::mem; + +impl Error for SliceWriteError { + fn kind(&self) -> ErrorKind { + match self { + SliceWriteError::Full => ErrorKind::WriteZero, + } + } +} + +impl ErrorType for &mut [u8] { + type Error = SliceWriteError; +} + +impl core::fmt::Display for SliceWriteError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{self:?}") + } +} + +impl core::error::Error for SliceWriteError {} + +/// Write is implemented for `&mut [u8]` by copying into the slice, overwriting +/// its data. +/// +/// Note that writing updates the slice to point to the yet unwritten part. +/// The slice will be empty when it has been completely overwritten. +/// +/// If the number of bytes to be written exceeds the size of the slice, write operations will +/// return short writes: ultimately, a `SliceWriteError::Full`. +impl Write for &mut [u8] { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + let amt = core::cmp::min(buf.len(), self.len()); + if !buf.is_empty() && amt == 0 { + return Err(SliceWriteError::Full); + } + let (a, b) = mem::take(self).split_at_mut(amt); + a.copy_from_slice(&buf[..amt]); + *self = b; + Ok(amt) + } + + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/embedded-io/src/impls/slice_ref.rs b/embedded-io/src/impls/slice_ref.rs new file mode 100644 index 000000000..6332d70dd --- /dev/null +++ b/embedded-io/src/impls/slice_ref.rs @@ -0,0 +1,41 @@ +use crate::{BufRead, ErrorType, Read}; + +impl ErrorType for &[u8] { + type Error = core::convert::Infallible; +} + +/// Read is implemented for `&[u8]` by copying from the slice. +/// +/// Note that reading updates the slice to point to the yet unread part. +/// The slice will be empty when EOF is reached. +impl Read for &[u8] { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> Result { + let amt = core::cmp::min(buf.len(), self.len()); + let (a, b) = self.split_at(amt); + + // First check if the amount of bytes we want to read is small: + // `copy_from_slice` will generally expand to a call to `memcpy`, and + // for a single byte the overhead is significant. + if amt == 1 { + buf[0] = a[0]; + } else { + buf[..amt].copy_from_slice(a); + } + + *self = b; + Ok(amt) + } +} + +impl BufRead for &[u8] { + #[inline] + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Ok(*self) + } + + #[inline] + fn consume(&mut self, amt: usize) { + *self = &self[amt..]; + } +} diff --git a/embedded-io/src/impls/vec.rs b/embedded-io/src/impls/vec.rs new file mode 100644 index 000000000..3b279c564 --- /dev/null +++ b/embedded-io/src/impls/vec.rs @@ -0,0 +1,21 @@ +use crate::{ErrorType, Write}; +use alloc::vec::Vec; + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl ErrorType for Vec { + type Error = core::convert::Infallible; +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Write for Vec { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + self.extend_from_slice(buf); + Ok(buf.len()) + } + + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/embedded-io/src/lib.rs b/embedded-io/src/lib.rs new file mode 100644 index 000000000..ae5ff4853 --- /dev/null +++ b/embedded-io/src/lib.rs @@ -0,0 +1,570 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +use core::fmt; + +// needed to prevent defmt macros from breaking, since they emit code that does `defmt::blahblah`. +#[cfg(feature = "defmt-03")] +use defmt_03 as defmt; + +#[cfg(feature = "alloc")] +extern crate alloc; + +mod impls; + +/// Enumeration of possible methods to seek within an I/O object. +/// +/// This is the `embedded-io` equivalent of [`std::io::SeekFrom`]. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] +pub enum SeekFrom { + /// Sets the offset to the provided number of bytes. + Start(u64), + /// Sets the offset to the size of this object plus the specified number of bytes. + End(i64), + /// Sets the offset to the current position plus the specified number of bytes. + Current(i64), +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl From for std::io::SeekFrom { + fn from(pos: SeekFrom) -> Self { + match pos { + SeekFrom::Start(n) => std::io::SeekFrom::Start(n), + SeekFrom::End(n) => std::io::SeekFrom::End(n), + SeekFrom::Current(n) => std::io::SeekFrom::Current(n), + } + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl From for SeekFrom { + fn from(pos: std::io::SeekFrom) -> SeekFrom { + match pos { + std::io::SeekFrom::Start(n) => SeekFrom::Start(n), + std::io::SeekFrom::End(n) => SeekFrom::End(n), + std::io::SeekFrom::Current(n) => SeekFrom::Current(n), + } + } +} + +/// Possible kinds of errors. +/// +/// This list is intended to grow over time and it is not recommended to +/// exhaustively match against it. In application code, use `match` for the `ErrorKind` +/// values you are expecting; use `_` to match "all other errors". +/// +/// This is the `embedded-io` equivalent of [`std::io::ErrorKind`], except with the following changes: +/// +/// - `WouldBlock` is removed, since `embedded-io` traits are always blocking. See the [crate-level documentation](crate) for details. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] +#[non_exhaustive] +pub enum ErrorKind { + /// Unspecified error kind. + Other, + + /// An entity was not found, often a file. + NotFound, + /// The operation lacked the necessary privileges to complete. + PermissionDenied, + /// The connection was refused by the remote server. + ConnectionRefused, + /// The connection was reset by the remote server. + ConnectionReset, + /// The connection was aborted (terminated) by the remote server. + ConnectionAborted, + /// The network operation failed because it was not connected yet. + NotConnected, + /// A socket address could not be bound because the address is already in + /// use elsewhere. + AddrInUse, + /// A nonexistent interface was requested or the requested address was not + /// local. + AddrNotAvailable, + /// The operation failed because a pipe was closed. + BrokenPipe, + /// An entity already exists, often a file. + AlreadyExists, + /// A parameter was incorrect. + InvalidInput, + /// Data not valid for the operation were encountered. + /// + /// Unlike [`InvalidInput`], this typically means that the operation + /// parameters were valid, however the error was caused by malformed + /// input data. + /// + /// For example, a function that reads a file into a string will error with + /// `InvalidData` if the file's contents are not valid UTF-8. + /// + /// [`InvalidInput`]: ErrorKind::InvalidInput + InvalidData, + /// The I/O operation's timeout expired, causing it to be canceled. + TimedOut, + /// This operation was interrupted. + /// + /// Interrupted operations can typically be retried. + Interrupted, + /// This operation is unsupported on this platform. + /// + /// This means that the operation can never succeed. + Unsupported, + /// An operation could not be completed, because it failed + /// to allocate enough memory. + OutOfMemory, + /// An attempted write could not write any data. + WriteZero, +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl From for std::io::ErrorKind { + fn from(value: ErrorKind) -> Self { + match value { + ErrorKind::NotFound => std::io::ErrorKind::NotFound, + ErrorKind::PermissionDenied => std::io::ErrorKind::PermissionDenied, + ErrorKind::ConnectionRefused => std::io::ErrorKind::ConnectionRefused, + ErrorKind::ConnectionReset => std::io::ErrorKind::ConnectionReset, + ErrorKind::ConnectionAborted => std::io::ErrorKind::ConnectionAborted, + ErrorKind::NotConnected => std::io::ErrorKind::NotConnected, + ErrorKind::AddrInUse => std::io::ErrorKind::AddrInUse, + ErrorKind::AddrNotAvailable => std::io::ErrorKind::AddrNotAvailable, + ErrorKind::BrokenPipe => std::io::ErrorKind::BrokenPipe, + ErrorKind::AlreadyExists => std::io::ErrorKind::AlreadyExists, + ErrorKind::InvalidInput => std::io::ErrorKind::InvalidInput, + ErrorKind::InvalidData => std::io::ErrorKind::InvalidData, + ErrorKind::TimedOut => std::io::ErrorKind::TimedOut, + ErrorKind::Interrupted => std::io::ErrorKind::Interrupted, + ErrorKind::Unsupported => std::io::ErrorKind::Unsupported, + ErrorKind::OutOfMemory => std::io::ErrorKind::OutOfMemory, + _ => std::io::ErrorKind::Other, + } + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl From for ErrorKind { + fn from(value: std::io::ErrorKind) -> Self { + match value { + std::io::ErrorKind::NotFound => ErrorKind::NotFound, + std::io::ErrorKind::PermissionDenied => ErrorKind::PermissionDenied, + std::io::ErrorKind::ConnectionRefused => ErrorKind::ConnectionRefused, + std::io::ErrorKind::ConnectionReset => ErrorKind::ConnectionReset, + std::io::ErrorKind::ConnectionAborted => ErrorKind::ConnectionAborted, + std::io::ErrorKind::NotConnected => ErrorKind::NotConnected, + std::io::ErrorKind::AddrInUse => ErrorKind::AddrInUse, + std::io::ErrorKind::AddrNotAvailable => ErrorKind::AddrNotAvailable, + std::io::ErrorKind::BrokenPipe => ErrorKind::BrokenPipe, + std::io::ErrorKind::AlreadyExists => ErrorKind::AlreadyExists, + std::io::ErrorKind::InvalidInput => ErrorKind::InvalidInput, + std::io::ErrorKind::InvalidData => ErrorKind::InvalidData, + std::io::ErrorKind::TimedOut => ErrorKind::TimedOut, + std::io::ErrorKind::Interrupted => ErrorKind::Interrupted, + std::io::ErrorKind::Unsupported => ErrorKind::Unsupported, + std::io::ErrorKind::OutOfMemory => ErrorKind::OutOfMemory, + _ => ErrorKind::Other, + } + } +} + +/// Error trait. +/// +/// This trait allows generic code to do limited inspecting of errors, +/// to react differently to different kinds. +pub trait Error: fmt::Debug { + /// Get the kind of this error. + fn kind(&self) -> ErrorKind; +} + +impl Error for core::convert::Infallible { + fn kind(&self) -> ErrorKind { + match *self {} + } +} + +impl Error for ErrorKind { + fn kind(&self) -> ErrorKind { + *self + } +} + +impl core::error::Error for ErrorKind {} + +impl fmt::Display for ErrorKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl Error for std::io::Error { + fn kind(&self) -> ErrorKind { + self.kind().into() + } +} + +/// Base trait for all IO traits, defining the error type. +/// +/// All IO operations of all traits return the error defined in this trait. +/// +/// Having a shared trait instead of having every trait define its own +/// `Error` associated type enforces all impls on the same type use the same error. +/// This is very convenient when writing generic code, it means you have to +/// handle a single error type `T::Error`, instead of `::Error` and `::Error` +/// which might be different types. +pub trait ErrorType { + /// Error type of all the IO operations on this type. + type Error: Error; +} + +impl ErrorType for &mut T { + type Error = T::Error; +} + +/// Error returned by [`Read::read_exact`] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] +pub enum ReadExactError { + /// An EOF error was encountered before reading the exact amount of requested bytes. + UnexpectedEof, + /// Error returned by the inner Read. + Other(E), +} + +impl From for ReadExactError { + fn from(err: E) -> Self { + Self::Other(err) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl From> for std::io::Error { + fn from(err: ReadExactError) -> Self { + match err { + ReadExactError::UnexpectedEof => std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "UnexpectedEof".to_owned(), + ), + ReadExactError::Other(e) => std::io::Error::new(e.kind(), format!("{e:?}")), + } + } +} + +impl fmt::Display for ReadExactError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self:?}") + } +} + +impl core::error::Error for ReadExactError {} + +/// Errors that could be returned by `Write` on `&mut [u8]`. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] +#[non_exhaustive] +pub enum SliceWriteError { + /// The target slice was full and so could not receive any new data. + Full, +} + +/// Error returned by [`Write::write_fmt`] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] +pub enum WriteFmtError { + /// An error was encountered while formatting. + FmtError, + /// Error returned by the inner Write. + Other(E), +} + +impl From for WriteFmtError { + fn from(err: E) -> Self { + Self::Other(err) + } +} + +impl fmt::Display for WriteFmtError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self:?}") + } +} + +impl core::error::Error for WriteFmtError {} + +/// Blocking reader. +/// +/// This trait is the `embedded-io` equivalent of [`std::io::Read`]. +pub trait Read: ErrorType { + /// Read some bytes from this source into the specified buffer, returning how many bytes were read. + /// + /// If no bytes are currently available to read: + /// - The method blocks until at least one byte becomes available; + /// - Once at least one (or more) bytes become available, a non-zero amount of those is copied to the + /// beginning of `buf`, and the amount is returned, *without waiting or blocking any further for + /// more bytes to become available*. + /// + /// If bytes are available to read: + /// - A non-zero amount of bytes is read to the beginning of `buf`, and the amount is returned immediately, + /// *without blocking and waiting for more bytes to become available*; + /// + /// Note that once some bytes are available to read, it is *not* guaranteed that all available bytes are returned. + /// It is possible for the implementation to read an amount of bytes less than `buf.len()` while there are more + /// bytes immediately available. + /// + /// This blocking behavior is important for the cases where `Read` represents the "read" leg of a pipe-like + /// protocol (a socket, a pipe, a serial line etc.). The semantics is that the caller - by passing a non-empty + /// buffer - does expect _some_ data (one or more bytes) - but _not necessarily `buf.len()` or more bytes_ - + /// to become available, before the peer represented by `Read` would stop sending bytes due to + /// application-specific reasons (as in the peer waiting for a response to the data it had sent so far). + /// + /// If the reader is at end-of-file (EOF), `Ok(0)` is returned. There is no guarantee that a reader at EOF + /// will always be so in the future, for example a reader can stop being at EOF if another process appends + /// more bytes to the underlying file. + /// + /// If `buf.len() == 0`, `read` returns without blocking, with either `Ok(0)` or an error. + /// The `Ok(0)` doesn't indicate EOF, unlike when called with a non-empty buffer. + fn read(&mut self, buf: &mut [u8]) -> Result; + + /// Read the exact number of bytes required to fill `buf`. + /// + /// This function calls `read()` in a loop until exactly `buf.len()` bytes have + /// been read, blocking if needed. + /// + /// If you are using [`ReadReady`] to avoid blocking, you should not use this function. + /// `ReadReady::read_ready()` returning true only guarantees the first call to `read()` will + /// not block, so this function may still block in subsequent calls. + fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), ReadExactError> { + while !buf.is_empty() { + match self.read(buf) { + Ok(0) => break, + Ok(n) => buf = &mut buf[n..], + Err(e) => return Err(ReadExactError::Other(e)), + } + } + if buf.is_empty() { + Ok(()) + } else { + Err(ReadExactError::UnexpectedEof) + } + } +} + +/// Blocking buffered reader. +/// +/// This trait is the `embedded-io` equivalent of [`std::io::BufRead`]. +pub trait BufRead: ErrorType { + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + /// + /// If no bytes are currently available to read, this function blocks until at least one byte is available. + /// + /// If the reader is at end-of-file (EOF), an empty slice is returned. There is no guarantee that a reader at EOF + /// will always be so in the future, for example a reader can stop being at EOF if another process appends + /// more bytes to the underlying file. + fn fill_buf(&mut self) -> Result<&[u8], Self::Error>; + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + fn consume(&mut self, amt: usize); +} + +/// Blocking writer. +/// +/// This trait is the `embedded-io` equivalent of [`std::io::Write`]. +pub trait Write: ErrorType { + /// Write a buffer into this writer, returning how many bytes were written. + /// + /// If the writer is not currently ready to accept more bytes (for example, its buffer is full), + /// this function blocks until it is ready to accept least one byte. + /// + /// If it's ready to accept bytes, a non-zero amount of bytes is written from the beginning of `buf`, and the amount + /// is returned. It is not guaranteed that *all* available buffer space is filled, i.e. it is possible for the + /// implementation to write an amount of bytes less than `buf.len()` while the writer continues to be + /// ready to accept more bytes immediately. + /// + /// Implementations must not return `Ok(0)` unless `buf` is empty. Situations where the + /// writer is not able to accept more bytes must instead be indicated with an error, + /// where the `ErrorKind` is `WriteZero`. + /// + /// If `buf` is empty, `write` returns without blocking, with either `Ok(0)` or an error. + /// `Ok(0)` doesn't indicate an error. + fn write(&mut self, buf: &[u8]) -> Result; + + /// Flush this output stream, blocking until all intermediately buffered contents reach their destination. + fn flush(&mut self) -> Result<(), Self::Error>; + + /// Write an entire buffer into this writer. + /// + /// This function calls `write()` in a loop until exactly `buf.len()` bytes have + /// been written, blocking if needed. + /// + /// If you are using [`WriteReady`] to avoid blocking, you should not use this function. + /// `WriteReady::write_ready()` returning true only guarantees the first call to `write()` will + /// not block, so this function may still block in subsequent calls. + /// + /// This function will panic if `write()` returns `Ok(0)`. + fn write_all(&mut self, mut buf: &[u8]) -> Result<(), Self::Error> { + while !buf.is_empty() { + match self.write(buf) { + Ok(0) => panic!("write() returned Ok(0)"), + Ok(n) => buf = &buf[n..], + Err(e) => return Err(e), + } + } + Ok(()) + } + + /// Write a formatted string into this writer, returning any error encountered. + /// + /// This function calls `write()` in a loop until the entire formatted string has + /// been written, blocking if needed. + /// + /// If you are using [`WriteReady`] to avoid blocking, you should not use this function. + /// `WriteReady::write_ready()` returning true only guarantees the first call to `write()` will + /// not block, so this function may still block in subsequent calls. + /// + /// Unlike [`Write::write`], the number of bytes written is not returned. However, in the case of + /// writing to an `&mut [u8]` its possible to calculate the number of bytes written by subtracting + /// the length of the slice after the write, from the initial length of the slice. + /// + /// ```rust + /// # use embedded_io::Write; + /// let mut buf: &mut [u8] = &mut [0u8; 256]; + /// let start = buf.len(); + /// let len = write!(buf, "{}", "Test").and_then(|_| Ok(start - buf.len())); + /// ``` + fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> Result<(), WriteFmtError> { + // Create a shim which translates a Write to a fmt::Write and saves + // off I/O errors. instead of discarding them + struct Adapter<'a, T: Write + ?Sized + 'a> { + inner: &'a mut T, + error: Result<(), T::Error>, + } + + impl fmt::Write for Adapter<'_, T> { + fn write_str(&mut self, s: &str) -> fmt::Result { + match self.inner.write_all(s.as_bytes()) { + Ok(()) => Ok(()), + Err(e) => { + self.error = Err(e); + Err(fmt::Error) + } + } + } + } + + let mut output = Adapter { + inner: self, + error: Ok(()), + }; + match fmt::write(&mut output, fmt) { + Ok(()) => Ok(()), + Err(..) => match output.error { + // check if the error came from the underlying `Write` or not + Err(e) => Err(WriteFmtError::Other(e)), + Ok(()) => Err(WriteFmtError::FmtError), + }, + } + } +} + +/// Blocking seek within streams. +/// +/// This trait is the `embedded-io` equivalent of [`std::io::Seek`]. +pub trait Seek: ErrorType { + /// Seek to an offset, in bytes, in a stream. + fn seek(&mut self, pos: SeekFrom) -> Result; + + /// Rewind to the beginning of a stream. + fn rewind(&mut self) -> Result<(), Self::Error> { + self.seek(SeekFrom::Start(0))?; + Ok(()) + } + + /// Returns the current seek position from the start of the stream. + fn stream_position(&mut self) -> Result { + self.seek(SeekFrom::Current(0)) + } +} + +/// Get whether a reader is ready. +/// +/// This allows using a [`Read`] or [`BufRead`] in a nonblocking fashion, i.e. trying to read +/// only when it is ready. +pub trait ReadReady: ErrorType { + /// Get whether the reader is ready for immediately reading. + /// + /// This usually means that there is either some bytes have been received and are buffered and ready to be read, + /// or that the reader is at EOF. + /// + /// If this returns `true`, it's guaranteed that the next call to [`Read::read`] or [`BufRead::fill_buf`] will not block. + fn read_ready(&mut self) -> Result; +} + +/// Get whether a writer is ready. +/// +/// This allows using a [`Write`] in a nonblocking fashion, i.e. trying to write +/// only when it is ready. +pub trait WriteReady: ErrorType { + /// Get whether the writer is ready for immediately writing. + /// + /// This usually means that there is free space in the internal transmit buffer. + /// + /// If this returns `true`, it's guaranteed that the next call to [`Write::write`] will not block. + fn write_ready(&mut self) -> Result; +} + +impl Read for &mut T { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> Result { + T::read(self, buf) + } +} + +impl BufRead for &mut T { + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + T::fill_buf(self) + } + + fn consume(&mut self, amt: usize) { + T::consume(self, amt); + } +} + +impl Write for &mut T { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + T::write(self, buf) + } + + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + T::flush(self) + } +} + +impl Seek for &mut T { + #[inline] + fn seek(&mut self, pos: SeekFrom) -> Result { + T::seek(self, pos) + } +} + +impl ReadReady for &mut T { + #[inline] + fn read_ready(&mut self) -> Result { + T::read_ready(self) + } +} + +impl WriteReady for &mut T { + #[inline] + fn write_ready(&mut self) -> Result { + T::write_ready(self) + } +}