feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+1
View File
@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"143877c27bdb63f6aaf284554b4b165aca28fae6ab3d4a02403afa26915789d6","CHANGELOG.md":"d076abc90a94118754e74e065f2b9a8527c9fea300d81bcb1bab1bb5d4cc7b14","Cargo.toml":"2e98e7e78b70c2986e1a2ef2593b7d85cf4cdd198c8d8e872a5b2159f5b1cf16","Cargo.toml.orig":"ed43cf9db0666062fccced2d94d47f63bd33aacc9407b5e0abff8d0ee4330318","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"520bc4942f199aad3dfe939c475d67ffbddfea482e41fc4fcad8829b71affc99","README.md":"a8bdcd6b5f60cd0409e638446456b1693d44110f5764e28d149e4ab7d7e58ae0","src/delay.rs":"05960325c846fa20f0fe07a2bba686039ceb05aed6e35e2f7f23c1915b724012","src/digital.rs":"1185f7222cd9a2aec9003c510e2cd810719b6c930d1867c2e4161e747be3c812","src/i2c-shared-bus.svg":"a5fa34e610f9962479f077d57a6117d6a2ae3524012ec5c6275ca81308989c6e","src/i2c.rs":"28d44375daa30acf5488642948bda24a7030738a65f91727b9908cf7dfc3aad0","src/lib.rs":"43ad35769daa4e063d818bc7fb9bee9651548e7583b2fde5e4a104fcef0bfaf9","src/pwm.rs":"8c80846f9afc0b1b162156e4f948124c508dca636212d2db3d2a8fc5a4a66973","src/spi-shared-bus.svg":"8f789f2e90e7800c28e69a9726af0f0f86f11246759f09eb5a1f83321880421b","src/spi.rs":"c04a3f22c679f0f98f6f49e00bbc44dc72d1beba27aa4cc57afd17b2b872a828"},"package":"361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "f8c79fee1df1900ee59e90160729edfa16a99b63"
},
"path_in_vcs": "embedded-hal"
}
+342
View File
@@ -0,0 +1,342 @@
# Change Log
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
No unreleased changes yet.
## [v1.0.0] - 2023-12-28
- 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
*** This is (also) an alpha release with breaking changes (sorry) ***
### Changed
- The `embedded-hal` crate now contains blocking traits only. Import paths no longer contain `::blocking`.
### Added
- Implement `PartialOrd`, `Ord`, `Hash` for `can::StandardId`, `can::ExtendedId` and `can::Id` according to CAN bus arbitration rules
- Implement `Eq` for `i2c::Operation`
- Implement `PartialOrd`, `Ord`, `Hash` for `can::StandardId`, `can::ExtendedId` and `can::Id` according to CAN bus arbitration rules.
### Fixed
- Fixed documentation for `wait_for_rising_edge`.
### Removed
- `digital::blocking::IoPin` traits. See: [#340], [#397].
- `nb` traits are now available in a separate [`embedded-hal-nb`] crate.
- `spi::blocking::ExclusiveDevice` and `spi::blocking::ExclusiveDeviceError`. These have been moved to a separate [`embedded-hal-bus`] crate.
- Moved CAN traits to a separate [`embedded-can`] crate.
[`embedded-can`]: https://crates.io/crates/embedded-can
[`embedded-hal-nb`]: https://crates.io/crates/embedded-hal-nb
[`embedded-hal-bus`]: https://crates.io/crates/embedded-hal-bus
[#340]: https://github.com/rust-embedded/embedded-hal/issues/340
[#397]: https://github.com/rust-embedded/embedded-hal/issues/397
## [v1.0.0-alpha.8] - 2022-04-15
*** This is (also) an alpha release with breaking changes (sorry) ***
### Changed
- 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.
### Removed
- ADC traits: `adc::nb::OneShot` and `adc::nb::Channel`.
## [v1.0.0-alpha.7] - 2022-02-09
*** This is (also) an alpha release with breaking changes (sorry) ***
### Added
- `Error` traits for CAN, SPI, I2C and Serial are implemented for `Infallible`.
### Fixed
- Fixed blanket impl of `DelayUs` not covering the `delay_ms` method.
### Changed
- `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.
- `i2c`: unify all traits into a single `I2c` trait.
### Removed
- Traits with unconstrained associated types and their modules (See: [#324], [#354]):
- `capture::Capture`
- `pwm::Pwm`
- `pwm::PwmPin`
- `qei::Qei`
- `timer::Cancel`
- `timer::CountDown`
- `timer::Periodic`
- `watchdog::Disable`
- `watchdog::Enable`
- `watchdog::Watchdog`
[#324]: https://github.com/rust-embedded/embedded-hal/pull/324/
[#354]: https://github.com/rust-embedded/embedded-hal/pull/354
## [v1.0.0-alpha.6] - 2021-11-19
*** This is (also) an alpha release with breaking changes (sorry) ***
### Changed
- Use `u8` as default SPI as Serial Word type
- The Minimum Supported Rust Version (MSRV) is now 1.46.0
- Require all SPI and Serial word types to be `Copy`.
### Added
- Added `Can` Controller Area Network traits.
- `Error` traits for SPI, I2C and Serial traits. The error types used in those must
implement these `Error` traits, which implies providing a conversion to a common
set of error kinds. Generic drivers using these interfaces can then convert the errors
to this common set to act upon them.
### Removed
- Removed `DelayMs` in favor of `DelayUs` with `u32` as type for clarity.
## [v1.0.0-alpha.5] - 2021-09-11
*** This is (also) an alpha release with breaking changes (sorry) ***
### Added
- Added `IoPin` trait for pins that can change between being inputs or outputs
dynamically.
- Added `Debug` to all spi mode types.
- Add impls of all traits for references (`&T` or `&mut T` depending on the trait) when `T` implements the trait.
- SPI: Added blocking `Read` trait and `Read` transactional operation
- SPI: Added blocking `Transfer` trait with separate buffers (single-buffer `Transfer` has been renamed `TransferInplace`)
### 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 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.
- `blocking::Serial`: renamed `bwrite_all` to `write`, `bflush` to `flush.
- Removed `prelude` to avoid method name conflicts between different flavors (blocking, nb) of the same trait. Traits must now be manually imported.
- Removed the various `Default` marker traits.
- Removed `&[W]` returned slice in `spi::blocking::Transfer`.
- Require all associated error types to implement `core::fmt::Debug`.
### Removed
- Removed random number generation (`rng`) traits in favor of [rand_core](https://crates.io/crates/rand_core).
## [v1.0.0-alpha.4] - 2020-11-11
### Fixed
- Support for I2C addressing modes in `Transactional` I2C traits.
## [v1.0.0-alpha.3] - 2020-11-04
### Added
- `Transactional` SPI interface for executing groups of SPI transactions.
- `Transactional` I2C interface for executing groups of I2C transactions.
## [v1.0.0-alpha.2] - 2020-10-16
*** This is (also) an alpha release with breaking changes (sorry) ***
### Added
- 10-bit addressing mode for I2C traits.
- `try_set_state` method for `OutputPin` using an input `PinState` value.
### Changed
- I2C addressing modes are now selected via an `AddressMode` type parameter.
The trait features implementations for marker types `SevenBitAddress` and
`TenBitAddress`. `SevenBitAddress` is the default mode so this is not a
breaking change.
- The method `try_write` from the trait `blocking::i2c::WriteIter` trait
has been renamed `try_write_iter` for consistency.
- Updated `nb` dependency to version `1`.
- The watchdog API now uses move semantics. See [PR](https://github.com/rust-embedded/embedded-hal/pull/222).
- The ADC `Channel` trait now uses a stateful method to get the IDs.
## [v1.0.0-alpha.1] - 2020-06-16
*** This is an alpha release with breaking changes (sorry) ***
### Added
- A nonblocking trait for interfacing with random number generation hardware.
### Changed
- All traits have been marked as proven (`unproven` feature has been removed).
- All trait methods have been made fallible.
- All trait methods have been renamed `try_*` (i.e. `try_send`) for consistency.
- The `Capture`, `Pwm`, `PwmPin` and `Qei` traits have been moved into their own
`capture`, `pwm` and `qei` modules for consistency.
- Void has been replaced with `core::convert::Infallible` which should be used
in trait implementations where methods cannot fail.
- A new [process](https://github.com/rust-embedded/embedded-hal#how-to-add-a-new-trait)
has been adopted for the addition of traits to the embedded-hal.
- The ADC `Channel` trait now uses a constant to represent the IDs.
- The minimum supported Rust version is 1.35 due to [this issue](https://github.com/rust-lang/rust/issues/54973).
## [v0.2.3] - 2019-05-09
### Added
- A new version of the digital `OutputPin`, `StatefulOutputPin`, `ToggleableOutputPin`
and `InputPin` traits has been added under `digital::v2`. These traits are now
fallible and their methods now return a `Result` type as setting an output pin
and reading an input pin could potentially fail.
See [here](https://github.com/rust-embedded/embedded-hal/issues/95) for more info.
- Compatibility shims between `digital::v1` and `digital::v2` traits allowing v1 traits
to be implicitly promoted to v2, and for v2 traits to be explicitly cast to v1 wrappers.
### Changed
- The current versions of the `OutputPin`, `StatefulOutputPin`, `ToggleableOutputPin`
and `InputPin` traits have been marked as deprecated. Please use the new versions
included in `digital::v2`.
See [here](https://github.com/rust-embedded/embedded-hal/issues/95) for more info.
## [v0.2.2] - 2018-11-03
### Added
- Added the Rust Code of Conduct to this repository
- The first ADC-related trait. This is a simple trait for one-shot conversions.
- Iterator-based blocking write and write+read traits have been added to I2C and SPI.
- New helper constants for SPI modes.
- A new trait for a cancellable countdown.
- New traits for watchdog timer management, including startup, feeding, and stopping.
### Changed
- Updated docs to clarify I2C address bit widths and expectations.
## [v0.2.1] - 2018-05-14
### Changed
- Auto-generated documentation (docs.rs) now includes the unproven traits.
## [v0.2.0] - 2018-05-12
### Added
- A `ToggeableOutputPin` trait has been added. This trait contains a single method: `toggle` that
can be used to toggle the state of a push-pull pin.
### Changed
- [breaking-change] The signature of `CountDown.wait` changed; it now returns `nb::Result<(),
Void>`. Where [`Void`] is the stable alternative to the never type, `!`, provided by the stable
[`void`] crate. Implementations of the `CountDown` trait will have to be updated to use the new
signature. With this change this crate compiles on the stable and beta channels.
[`Void`]: https://docs.rs/void/1.0.2/void/enum.Void.html
[`void`]: https://crates.io/crates/void
- [breaking-change] the `OutputPin.is_{low,high}` methods have been moved into its own trait
`StatefulOutputPin` and renamed to `is_set_{low,high}`.
- It has been clarified in the documentation that `OutputPin` must be implemented for push-pull
output pins (and e.g. not for open drain output pins).
## [v0.1.3] - 2018-05-14
### Changed
- 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
### Added
- Unproven `blocking::serial::*` traits
## [v0.1.1] - 2018-02-06
### Added
- Unproven `digital::InputPin` trait
## v0.1.0 - 2018-01-16
Initial release
[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
[v1.0.0-alpha.6]: https://github.com/rust-embedded/embedded-hal/compare/v1.0.0-alpha.5...v1.0.0-alpha.6
[v1.0.0-alpha.5]: https://github.com/rust-embedded/embedded-hal/compare/v1.0.0-alpha.4...v1.0.0-alpha.5
[v1.0.0-alpha.4]: https://github.com/rust-embedded/embedded-hal/compare/v1.0.0-alpha.3...v1.0.0-alpha.4
[v1.0.0-alpha.3]: https://github.com/rust-embedded/embedded-hal/compare/v1.0.0-alpha.2...v1.0.0-alpha.3
[v1.0.0-alpha.2]: https://github.com/rust-embedded/embedded-hal/compare/v1.0.0-alpha.1...v1.0.0-alpha.2
[v1.0.0-alpha.1]: https://github.com/rust-embedded/embedded-hal/compare/v0.2.3...v1.0.0-alpha.1
[v0.2.3]: https://github.com/rust-embedded/embedded-hal/compare/v0.2.2...v0.2.3
[v0.2.2]: https://github.com/rust-embedded/embedded-hal/compare/v0.2.1...v0.2.2
[v0.2.1]: https://github.com/rust-embedded/embedded-hal/compare/v0.2.0...v0.2.1
[v0.2.0]: https://github.com/rust-embedded/embedded-hal/compare/v0.1.2...v0.2.0
[v0.1.2]: https://github.com/rust-embedded/embedded-hal/compare/v0.1.1...v0.1.2
[v0.1.1]: https://github.com/rust-embedded/embedded-hal/compare/v0.1.0...v0.1.1
+44
View File
@@ -0,0 +1,44 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.60"
name = "embedded-hal"
version = "1.0.0"
authors = [
"The Embedded HAL Team <embedded-hal@teams.rust-embedded.org>",
"Jorge Aparicio <jorge@japaric.io>",
"Jonathan 'theJPster' Pallant <github@thejpster.org.uk>",
]
description = " A Hardware Abstraction Layer (HAL) for embedded systems "
documentation = "https://docs.rs/embedded-hal"
readme = "README.md"
keywords = [
"hal",
"IO",
]
categories = [
"asynchronous",
"embedded",
"hardware-support",
"no-std",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/rust-embedded/embedded-hal"
[dependencies.defmt-03]
version = "0.3"
optional = true
package = "defmt"
[features]
defmt-03 = ["dep:defmt-03"]
+201
View File
@@ -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.
+25
View File
@@ -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.
+106
View File
@@ -0,0 +1,106 @@
[![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.60+-blue.svg)
# `embedded-hal`
> A Hardware Abstraction Layer (HAL) for embedded systems
This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team).
## 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)
- [`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.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
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
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.
+62
View File
@@ -0,0 +1,62 @@
//! Delays.
/// 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, 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.
#[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);
}
self.delay_ns(ms * NANOS_PER_MILLI);
}
}
impl<T> DelayNs for &mut T
where
T: DelayNs + ?Sized,
{
#[inline]
fn delay_ns(&mut self, ns: u32) {
T::delay_ns(self, ns);
}
#[inline]
fn delay_us(&mut self, us: u32) {
T::delay_us(self, us);
}
#[inline]
fn delay_ms(&mut self, ms: u32) {
T::delay_ms(self, ms);
}
}
+222
View File
@@ -0,0 +1,222 @@
//! Digital I/O.
use core::{convert::From, ops::Not};
#[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::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<T: ErrorType + ?Sized> ErrorType for &T {
type Error = T::Error;
}
impl<T: ErrorType + ?Sized> ErrorType for &mut T {
type Error = T::Error;
}
/// Digital output pin state.
///
/// Conversion from `bool` and logical negation are also implemented
/// for this type.
/// ```rust
/// # use embedded_hal::digital::PinState;
/// let state = PinState::from(false);
/// assert_eq!(state, PinState::Low);
/// 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,
/// High pin state.
High,
}
impl From<bool> for PinState {
#[inline]
fn from(value: bool) -> Self {
match value {
false => PinState::Low,
true => PinState::High,
}
}
}
impl Not for PinState {
type Output = PinState;
#[inline]
fn not(self) -> Self::Output {
match self {
PinState::High => PinState::Low,
PinState::Low => PinState::High,
}
}
}
impl From<PinState> 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.
///
/// *NOTE* the actual electrical state of the pin may not actually be low, e.g. due to external
/// electrical sources.
fn set_low(&mut self) -> Result<(), Self::Error>;
/// Drives the pin high.
///
/// *NOTE* the actual electrical state of the pin may not actually be high, e.g. due to external
/// electrical sources.
fn set_high(&mut self) -> Result<(), Self::Error>;
/// 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.
#[inline]
fn set_state(&mut self, state: PinState) -> Result<(), Self::Error> {
match state {
PinState::Low => self.set_low(),
PinState::High => self.set_high(),
}
}
}
impl<T: OutputPin + ?Sized> 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.
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(&mut self) -> Result<bool, Self::Error>;
/// Is the pin in drive low mode?
///
/// *NOTE* this does *not* read the electrical state of the pin.
fn is_set_low(&mut self) -> Result<bool, Self::Error>;
/// 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<T: StatefulOutputPin + ?Sized> StatefulOutputPin for &mut T {
#[inline]
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
T::is_set_high(self)
}
#[inline]
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
T::is_set_low(self)
}
#[inline]
fn toggle(&mut self) -> Result<(), Self::Error> {
T::toggle(self)
}
}
/// Single digital input pin.
pub trait InputPin: ErrorType {
/// Is the input pin high?
fn is_high(&mut self) -> Result<bool, Self::Error>;
/// Is the input pin low?
fn is_low(&mut self) -> Result<bool, Self::Error>;
}
impl<T: InputPin + ?Sized> InputPin for &mut T {
#[inline]
fn is_high(&mut self) -> Result<bool, Self::Error> {
T::is_high(self)
}
#[inline]
fn is_low(&mut self) -> Result<bool, Self::Error> {
T::is_low(self)
}
}
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Do not edit this file with editors other than diagrams.net -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" style="background-color: rgb(42, 42, 42);" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="381px" height="242px" viewBox="-0.5 -0.5 381 242" content="&lt;mxfile host=&quot;app.diagrams.net&quot; modified=&quot;2022-07-26T11:32:28.668Z&quot; agent=&quot;5.0 (X11)&quot; etag=&quot;ttHSAAff0ktqe0i_JMaC&quot; version=&quot;16.5.6&quot; type=&quot;device&quot;&gt;&lt;diagram id=&quot;GDSX7qAwtoJQx5dvUb6s&quot; name=&quot;Page-1&quot;&gt;7Vldd5owAP01PrpDQD58bMHaru2Zq+u6Pe1QiZCJhIVYdb9+QYJ8JKXWarXHdef0JDchJPfe3mShpdnTRZ+4cXCLPRi2VMVbtDSnpaqmpbPfKbDMAKAbZob4BHkcK4Ah+gs5qHB0hjyYVDpSjEOK4io4wlEER7SCuYTgebXbGIfVt8auDwVgOHJDEX1AHg0y1NKVAr+EyA/yNwOFtzy6o4lP8Czi74twBLOWqZsPw7smgevheQnSei3NJhjTrDRd2DBMac0Zy567eKZ1PWUCI7rJA/PJZHC3jB0afft6Mb00nESft3UjG+bJDWecCz5buszJWS0PpqMoLe18HiAKh7E7SlvnzA4MC+g0ZDXAimMUhjYOMVk9q3X09B/DE0rwBOYtK5q0c3EJfFVPkFC4KEF8SX2Ip5CSJevCW3N2ufPauaPmhYyaxbGgJKHa4aDLreOvhy44ZAVOo5xSuxO2B4bzS4Xx5Obx8SG5Q35bt/ZLqbL62Q17nSp7EvKAKiEP6HsjTxPIGzpnAn9sebRKktRfZeY45IbIj1iVZKs5T7lCLAjOOD5Fnpe+RSpKVbYd8G9W+ddF/qXe3QH70jQAIvn2zamQbx6YfFkSG2FKk4eeWNFPi7f2fY6yl5Qa9qDSiFELydtkGuOIDvmkJDvWqzXTq5qpksBS3lM08+NkvWpsF/bdfXEn7pP7ifoQjg8fNscW9d13ivqjJP/QUQ/EU44k669UNrri9L5f2T1WACcd/HUJD578oPOBo1/G3rtmP9BPOfyBcegAEg+bJxT/wDo0/eLu+3L+q//zv6yhdugNIB+4aQOAkXeWXk8WHJdkqRIGF4j+SMuful2L13+u6pqm8bqzKHV2lqXKABLElpWqlmGRd4HS9TT/DSV4RkZ8ro2XWdCrXKCKMpVk0CUq5BiBoUvRU/XaVSYNf8MAIzbltQtAV36Qy0fI1sMfUkt3obVx6huiURuHusSHVBhn5ZP1ore3ji5etDRZB7N+jdZZiw3KRqqYyDStV5toI9s0Ho6OxDbC+ceytvRN7brZ2sw3TEh3WeoWpx2ShvnWXpPbvrBhNuJuTanuNs82S6AjdUhd2E0NAl4YZ9/BIvuP5fYaJmy6tJZCK6wcNx9L505z8G+7gYD6TrSrJKh/oTF3mwTXi27/yx+7/9kY3s/uZveOcw3amugZpl5+msOEBtjHkRv2CrRmnKLPDcYxd8pvSOmSf/t1ZxRXnVecf/TSxgWaNy3ByjI31qz3toNQpuuLHUV/b2zcZw6lrFp8K86ULr7Fa71/&lt;/diagram&gt;&lt;/mxfile&gt;"><defs/><g><rect x="0" y="0" width="380" height="240" fill="#454545" stroke="none" pointer-events="all"/><rect x="20" y="20" width="120" height="150" fill="#000000" stroke="rgb(240, 240, 240)" pointer-events="all"/><rect x="50" y="60" width="80" height="20" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-end; width: 78px; height: 1px; padding-top: 70px; margin-left: 50px;"><div style="box-sizing: border-box; font-size: 0px; text-align: right;" data-drawio-colors="color: rgb(240, 240, 240); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(240, 240, 240); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">SDA</div></div></div></foreignObject><text x="128" y="74" fill="rgb(240, 240, 240)" font-family="Helvetica" font-size="12px" text-anchor="end">SDA</text></switch></g><rect x="50" y="80" width="80" height="20" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-end; width: 78px; height: 1px; padding-top: 90px; margin-left: 50px;"><div style="box-sizing: border-box; font-size: 0px; text-align: right;" data-drawio-colors="color: rgb(240, 240, 240); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(240, 240, 240); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">SCL</div></div></div></foreignObject><text x="128" y="94" fill="rgb(240, 240, 240)" font-family="Helvetica" font-size="12px" text-anchor="end">SCL</text></switch></g><rect x="30" y="30" width="100" height="20" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 40px; margin-left: 31px;"><div style="box-sizing: border-box; font-size: 0px; text-align: center;" data-drawio-colors="color: rgb(240, 240, 240); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(240, 240, 240); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;"><div>MCU</div></div></div></div></foreignObject><text x="80" y="44" fill="rgb(240, 240, 240)" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">MCU</text></sLine truncated

After

Width:  |  Height:  |  Size: 12 KiB

+432
View File
@@ -0,0 +1,432 @@
//! 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`].
//!
//! Through this marker types it is possible to implement each address mode for
//! the traits independently in `embedded-hal` implementations and device drivers
//! can depend only on the mode that they support.
//!
//! Additionally, the I2C 10-bit address mode has been developed to be fully
//! backwards compatible with the 7-bit address mode. This allows for a
//! software-emulated 10-bit addressing implementation if the address mode
//! 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.
//!
//! # Bus sharing
//!
//! 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.
//!
#![doc = include_str!("i2c-shared-bus.svg")]
//!
//! 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
//!
//! 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<SevenBitAddress>` or `I2c<TenBitAddress>` 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;
//! # const TEMP_REGISTER: u8 = 0x1;
//! pub struct TemperatureSensorDriver<I2C> {
//! i2c: I2C,
//! }
//!
//! impl<I2C: I2c> TemperatureSensorDriver<I2C> {
//! pub fn new(i2c: I2C) -> Self {
//! Self { i2c }
//! }
//!
//! pub fn read_temperature(&mut self) -> Result<u8, I2C::Error> {
//! let mut temp = [0];
//! self.i2c.write_read(ADDR, &[TEMP_REGISTER], &mut temp)?;
//! Ok(temp[0])
//! }
//! }
//! ```
//!
//! ## Device driver compatible only with 10-bit addresses
//!
//! ```
//! use embedded_hal::i2c::{Error, TenBitAddress, I2c};
//!
//! const ADDR: u16 = 0x158;
//! # const TEMP_REGISTER: u8 = 0x1;
//! pub struct TemperatureSensorDriver<I2C> {
//! i2c: I2C,
//! }
//!
//! impl<I2C: I2c<TenBitAddress>> TemperatureSensorDriver<I2C> {
//! pub fn new(i2c: I2C) -> Self {
//! Self { i2c }
//! }
//!
//! pub fn read_temperature(&mut self) -> Result<u8, I2C::Error> {
//! let mut 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<SevenBitAddress> for I2c0 {
//! fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> {
//! // ...
//! # Ok(())
//! }
//! }
//!
//! impl I2c<TenBitAddress> for I2c0 {
//! fn transaction(&mut self, address: u16, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> {
//! // ...
//! # Ok(())
//! }
//! }
//! ```
use crate::private;
#[cfg(feature = "defmt-03")]
use crate::defmt;
/// I2C error.
pub trait Error: core::fmt::Debug {
/// 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
/// code can act.
fn kind(&self) -> ErrorKind;
}
impl Error for core::convert::Infallible {
#[inline]
fn kind(&self) -> ErrorKind {
match *self {}
}
}
/// 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.
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.
NoAcknowledge(NoAcknowledgeSource),
/// The peripheral receive buffer was overrun.
Overrun,
/// A different error occurred. The original error may contain more information.
Other,
}
/// 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,
/// The device did not acknowledge the data. It may not be ready to process
/// requests at the moment.
Data,
/// Either the device did not acknowledge its address or the data, but it is
/// unknown which.
Unknown,
}
impl Error for ErrorKind {
#[inline]
fn kind(&self) -> ErrorKind {
*self
}
}
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"),
Self::ArbitrationLoss => write!(f, "The arbitration was lost"),
Self::NoAcknowledge(s) => s.fmt(f),
Self::Overrun => write!(f, "The peripheral receive buffer was overrun"),
Self::Other => write!(
f,
"A different error occurred. The original error may contain more information"
),
}
}
}
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"),
Self::Data => write!(f, "The device did not acknowledge the data"),
Self::Unknown => write!(f, "The device did not acknowledge its address or the data"),
}
}
}
/// I2C 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<T: ErrorType + ?Sized> ErrorType for &mut T {
type Error = T::Error;
}
/// 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 {}
/// 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.
pub type TenBitAddress = u16;
impl AddressMode for SevenBitAddress {}
impl AddressMode for TenBitAddress {}
/// 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(&'a mut [u8]),
/// Write data from the provided buffer.
Write(&'a [u8]),
}
/// Blocking I2C.
pub trait I2c<A: AddressMode = SevenBitAddress>: ErrorType {
/// Reads enough bytes from slave with `address` to fill `read`.
///
/// # I2C Events (contract)
///
/// ``` text
/// Master: ST SAD+R MAK MAK ... NMAK SP
/// Slave: SAK B0 B1 ... BN
/// ```
///
/// Where
///
/// - `ST` = start condition
/// - `SAD+R` = slave address followed by bit 1 to indicate reading
/// - `SAK` = slave acknowledge
/// - `Bi` = ith byte of data
/// - `MAK` = master acknowledge
/// - `NMAK` = master no acknowledge
/// - `SP` = stop condition
#[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`.
///
/// # I2C Events (contract)
///
/// ``` text
/// Master: ST SAD+W B0 B1 ... BN SP
/// Slave: SAK SAK SAK ... SAK
/// ```
///
/// Where
///
/// - `ST` = start condition
/// - `SAD+W` = slave address followed by bit 0 to indicate writing
/// - `SAK` = slave acknowledge
/// - `Bi` = ith byte of data
/// - `SP` = stop condition
#[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 `read` *in a
/// single transaction*.
///
/// # I2C Events (contract)
///
/// ``` text
/// Master: ST SAD+W O0 O1 ... OM SR SAD+R MAK MAK ... NMAK SP
/// Slave: SAK SAK SAK ... SAK SAK I0 I1 ... IN
/// ```
///
/// Where
///
/// - `ST` = start condition
/// - `SAD+W` = slave address followed by bit 0 to indicate writing
/// - `SAK` = slave acknowledge
/// - `Oi` = ith outgoing byte of data
/// - `SR` = repeated start condition
/// - `SAD+R` = slave address followed by bit 1 to indicate reading
/// - `Ii` = ith incoming byte of data
/// - `MAK` = master acknowledge
/// - `NMAK` = master no acknowledge
/// - `SP` = stop condition
#[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.
///
/// 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(
&mut self,
address: A,
operations: &mut [Operation<'_>],
) -> Result<(), Self::Error>;
}
impl<A: AddressMode, T: I2c<A> + ?Sized> I2c<A> for &mut T {
#[inline]
fn read(&mut self, address: A, read: &mut [u8]) -> Result<(), Self::Error> {
T::read(self, address, read)
}
#[inline]
fn write(&mut self, address: A, write: &[u8]) -> Result<(), Self::Error> {
T::write(self, address, write)
}
#[inline]
fn write_read(&mut self, address: A, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
T::write_read(self, address, write, read)
}
#[inline]
fn transaction(
&mut self,
address: A,
operations: &mut [Operation<'_>],
) -> Result<(), Self::Error> {
T::transaction(self, address, operations)
}
}
+21
View File
@@ -0,0 +1,21 @@
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
#![no_std]
pub mod delay;
pub mod digital;
pub mod i2c;
pub mod pwm;
pub mod spi;
mod private {
use crate::i2c::{SevenBitAddress, TenBitAddress};
pub trait Sealed {}
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;
+150
View File
@@ -0,0 +1,150 @@
//! 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::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<T: ErrorType + ?Sized> 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<T: SetDutyCycle + ?Sized> 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)
}
}
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Do not edit this file with editors other than diagrams.net -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="382px" height="322px" viewBox="-0.5 -0.5 382 322" content="&lt;mxfile host=&quot;app.diagrams.net&quot; modified=&quot;2022-02-15T23:37:14.963Z&quot; agent=&quot;5.0 (X11)&quot; etag=&quot;_1iMPbCJGwQuwz6QJ5_u&quot; version=&quot;16.5.6&quot;&gt;&lt;diagram id=&quot;GDSX7qAwtoJQx5dvUb6s&quot; name=&quot;Page-1&quot;&gt;7Vtdl6I2GP41XupJCCBcrjrTndPuWbu23e3VHkYyyhHBQhy1v74BEiUhKqN8OHX0QvKSz/d58uYJxA4aLre/RM5q/iV0sd/RgLvtoFFH0yDSTfqTWHbMYgOQWWaR5zLbwTDx/sXMyLOtPRfHQkYShj7xVqJxGgYBnhLB5kRRuBGzvYS+2OrKmeGCYTJ1/KL1u+eSeWa1DHCwf8bebM5bhnx8z850MYvCdcDaC8IAZ3eWDq+GZY3njhtucib00EHDKAxJdrXcDrGf+JV7LCv3eOTuvssRDkiZApvFYvxttxqR4I/fH5efzVFsbLoGQ+7V8dfMF6y3ZMedkw4PJ7WADhps5h7Bk5UzTe5uKB+obU6WPk1Bevni+f4w9MMoLYt0I/lSe0yicIH5ndRNaMDaxhHB26OjgntfUf7hcIlJtKNZWAHuXca8LmfU5gAjsphtnoMQ8YIOo85sX/XBh/SCuVHt0qHud8fm6KeGV4vfnp+/x9+8Wdew6nUpSD977xVcpXDoUe/povcUzoOawnnQqM15SOE806ftDlzvNWnR92ZBesP8Z51MnUGU9mufpFez5Hcy/JWXpD1JC2c3CmBQXxHR40qy5mFgJt6ZrAtokDjeo1HlE7MvPddNWlEiLHKgAjD7IphGEUzVRKhiHihDC6wKyS9Pk6/vD8qropoEZb9lKM9MSgbT18nTfcNktwyTXgam4eQnvG+YoGKZaxQnoyRO2p3jhFrGSaWNi2Fv+GdjME2pI3F0HU4vYUAmrFPHJMZbQDNE0DSFhARNgtZXgFaf+r5u72JeKL9RXc5TbV0KjG9SWfv4pf24dGvK2i4Vl5pUzTcJU9uqmU/nG5LNN4lT27IZntmpcj123yi1rpphcRc6GdOpA0YPfz0NH+gFvBPJJUPTuuaCqp3nexFdZuuqC5baEN697NJA2xGo3Ibw7oWXKiA1C5RqE/ihvIpA6W0DVWrDee/SS1MsUc3CVGrHKakxrTHQ3oNGg4rdaLMijVd8SqThwP2UHKdInOo7cexNRWREn+GtR34k1z3btlj67zSNEGLp0TaXebTLJcY48ujIEuAyW0BH+SOfYHVZOjccKktTu3xKri4bG3YLBz8Kb1jjcB1NWa6Tb/SJE80wOeHivpoSOcgNBeLcFmHfId6r2F8VDVgL49CjI9kzDtoi4/oSkbJhskIHLhXqkQ8FyITMvFCoJ+XkftBX0LTE8YnLaAp7ANgCTQ3broympg0/aFqCpvL+y7J7AF3GVNiXqyrFVEodZ5fLtkoyxCd6DKVQDoSDVvQiq7HaaaBacq+bBpy4XdADEIrstXS7XvbeNimhCUWI5aBXOnhKr8MgtHuWZh8+VqPBFJU43ncFi4AuscioOQaeZVH2JOpmaIRsui5UxCRalwHfzKS3BjtoSc3q4GQ35fxaH9YfHI3qpezRVf0SQscUDMJbD2kvue3R83n7/xvS7x/LScvjBcLTFqmk272+Wc+SLvWZ+6Je1qreMx1nLePNWVmbLueSrtUu0bXuRezMy9JbZKd0QNy6lJxn6qmKmFIzGtOB9RJTqzyc7p8MACA+GTCQ9WZqVkjGdrc+ulUJF6G8hWp2h648o1/L6oss42P5PUsqkQzmpaQ6ciqj6gAnP12CV628NHn4B1WW/fAXNfTwHw==&lt;/diagram&gt;&lt;/mxfile&gt;"><defs/><g><rect x="0" y="0" width="380" height="320" fill="#454545" stroke="none" pointer-events="all"/><rect x="20" y="20" width="120" height="150" fill="#000000" stroke="rgb(240, 240, 240)" pointer-events="all"/><rect x="50" y="60" width="80" height="20" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-end; width: 78px; height: 1px; padding-top: 70px; margin-left: 50px;"><div style="box-sizing: border-box; font-size: 0px; text-align: right;" data-drawio-colors="color: rgb(240, 240, 240); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(240, 240, 240); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><div align="right">SCK</div></div></div></div></foreignObject><text x="128" y="74" fill="rgb(240, 240, 240)" font-family="Helvetica" font-size="12px" text-anchor="end">SCK</text></switch></g><rect x="50" y="80" width="80" height="20" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-end; width: 78px; height: 1px; padding-top: 90px; margin-left: 50px;"><div style="box-sizing: border-box; font-size: 0px; text-align: right;" data-drawio-colors="color: rgb(240, 240, 240); "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(240, 240, 240); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><div align="right">MISO</div></div></div></div></foreignObject><text x="128" y="94" fill="rgb(240, 240, 240)" font-family="Helvetica" font-size="12px" text-anchor="end">MISO</text></switch></g><rect x="50" y="100" width="80" height="20" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-end; width: 78px; height: 1px; padding-top: 110px; margin-left: 50px;"><div style="box-sizing: border-box; font-size: 0px; text-align: right;" data-drawio-colors="color: rgb(240, 240, 240); "><div style="display: inline-block; Line truncated

After

Width:  |  Height:  |  Size: 21 KiB

+506
View File
@@ -0,0 +1,506 @@
//! Blocking SPI master mode traits.
//!
//! # Bus vs Device
//!
//! SPI allows sharing a single bus between many SPI devices. The SCK, MOSI and MISO lines are
//! wired in parallel to all the devices, and each device gets a dedicated chip-select (CS) line from the MCU, like this:
//!
#![doc= include_str!("spi-shared-bus.svg")]
//!
//! CS is usually active-low. When CS is high (not asserted), SPI devices ignore all incoming data, and
//! don't drive MISO. When CS is low (asserted), the device is active: reacts to incoming data on MOSI and
//! drives MISO with the response data. By asserting one CS or another, the MCU can choose to which
//! SPI device it "talks" to on the (possibly shared) bus.
//!
//! This bus sharing is common when having multiple SPI devices in the same board, since it uses fewer MCU
//! pins (`n+3` instead of `4*n`), and fewer MCU SPI peripherals (`1` instead of `n`).
//!
//! However, it poses a challenge when building portable drivers for SPI devices. The driver needs to
//! be able to talk to its device on the bus, while not interfering with other drivers talking to other
//! devices.
//!
//! To solve this, `embedded-hal` has two kinds of SPI traits: **SPI bus** and **SPI device**.
//!
//! ## Bus
//!
//! 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.
//!
//! ## Device
//!
//! 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.
//!
//! An [`SpiDevice`] allows initiating [transactions](SpiDevice::transaction) against the target device on the bus. A transaction
//! 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.
//!
//! # 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.
//! 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, SpiDevice, Operation};
//! pub struct MyDriver<SPI> {
//! spi: SPI,
//! }
//!
//! impl<SPI> MyDriver<SPI>
//! where
//! SPI: SpiDevice,
//! {
//! pub fn new(spi: SPI) -> Self {
//! Self { spi }
//! }
//!
//! pub fn read_foo(&mut self) -> Result<[u8; 2], MyError<SPI::Error>> {
//! let mut buf = [0; 2];
//!
//! // `transaction` asserts and deasserts CS for us. No need to do it manually!
//! self.spi.transaction(&mut [
//! Operation::Write(&[0x90]),
//! Operation::Read(&mut buf),
//! ]).map_err(MyError::Spi)?;
//!
//! Ok(buf)
//! }
//! }
//!
//! #[derive(Copy, Clone, Debug)]
//! enum MyError<SPI> {
//! Spi(SPI),
//! // Add other errors for your driver here.
//! }
//! ```
//!
//! 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;
//! pub struct MyDriver<SPI> {
//! spi: SPI,
//! }
//!
//! impl<SPI> MyDriver<SPI>
//! where
//! SPI: SpiBus,
//! {
//! pub fn new(spi: SPI) -> Self {
//! Self { spi }
//! }
//!
//! pub fn read_foo(&mut self) -> Result<[u8; 2], MyError<SPI::Error>> {
//! let mut buf = [0; 2];
//! self.spi.write(&[0x90]).map_err(MyError::Spi)?;
//! self.spi.read(&mut buf).map_err(MyError::Spi)?;
//! Ok(buf)
//! }
//! }
//!
//! #[derive(Copy, Clone, Debug)]
//! enum MyError<SPI> {
//! Spi(SPI),
//! // Add other errors for your driver here.
//! }
//! ```
//!
//! If you're (ab)using SPI to **implement other protocols** by bitbanging (WS2812B, onewire, generating arbitrary waveforms...), use [`SpiBus`].
//! SPI bus sharing doesn't make sense at all in this case. By requiring [`SpiBus`] you disallow sharing, ensuring correct operation.
//!
//! # For HAL authors
//!
//! 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
//! the HAL-independent implementations).
//!
//! HALs **must not** add infrastructure for sharing at the [`SpiBus`] level. User code owning a [`SpiBus`] must have the guarantee
//! of exclusive access.
//!
//! # Flushing
//!
//! To improve performance, [`SpiBus`] implementations are allowed to return before the operation is finished, i.e. when the bus is still not
//! idle. This allows pipelining SPI transfers with CPU work.
//!
//! 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`](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`](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`](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`](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;
#[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.
IdleLow,
/// Clock signal high when idle.
IdleHigh,
}
/// 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.
CaptureOnFirstTransition,
/// Data in "captured" on the second clock transition.
CaptureOnSecondTransition,
}
/// SPI mode.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
pub struct Mode {
/// Clock polarity.
pub polarity: Polarity,
/// Clock phase.
pub phase: Phase,
}
/// Helper for CPOL = 0, CPHA = 0.
pub const MODE_0: Mode = Mode {
polarity: Polarity::IdleLow,
phase: Phase::CaptureOnFirstTransition,
};
/// Helper for CPOL = 0, CPHA = 1.
pub const MODE_1: Mode = Mode {
polarity: Polarity::IdleLow,
phase: Phase::CaptureOnSecondTransition,
};
/// Helper for CPOL = 1, CPHA = 0.
pub const MODE_2: Mode = Mode {
polarity: Polarity::IdleHigh,
phase: Phase::CaptureOnFirstTransition,
};
/// Helper for CPOL = 1, CPHA = 1.
pub const MODE_3: Mode = Mode {
polarity: Polarity::IdleHigh,
phase: Phase::CaptureOnSecondTransition,
};
/// 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
/// code can act.
fn kind(&self) -> ErrorKind;
}
impl Error for core::convert::Infallible {
#[inline]
fn kind(&self) -> ErrorKind {
match *self {}
}
}
/// 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.
Overrun,
/// 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.
FrameFormat,
/// An error occurred while asserting or deasserting the Chip Select pin.
ChipSelectFault,
/// A different error occurred. The original error may contain more information.
Other,
}
impl Error for ErrorKind {
#[inline]
fn kind(&self) -> ErrorKind {
*self
}
}
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::ModeFault => write!(
f,
"Multiple devices on the SPI bus are trying to drive the slave select pin"
),
Self::FrameFormat => write!(
f,
"Received data does not conform to the peripheral configuration"
),
Self::ChipSelectFault => write!(
f,
"An error occurred while asserting or deasserting the Chip Select pin"
),
Self::Other => write!(
f,
"A different error occurred. The original error may contain more information"
),
}
}
}
/// SPI error type trait.
///
/// This just defines the error type, to be used by the other SPI traits.
pub trait ErrorType {
/// Error type.
type Error: Error;
}
impl<T: ErrorType + ?Sized> ErrorType for &mut T {
type Error = T::Error;
}
/// 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<Word: Copy + 'static = u8>: ErrorType {
/// Perform a transaction against the device.
///
/// - Locks the bus
/// - Asserts the CS (Chip Select) pin.
/// - 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, 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.
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(&mut [Operation::Read(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 write within a transaction.
///
/// This is a convenience method equivalent to `device.transaction(&mut [Operation::Write(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(&mut [Operation::Transfer(read, write)]`.
///
/// See also: [`SpiDevice::transaction`], [`SpiBus::transfer`]
#[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(&mut [Operation::TransferInPlace(buf)]`.
///
/// See also: [`SpiDevice::transaction`], [`SpiBus::transfer_in_place`]
#[inline]
fn transfer_in_place(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> {
self.transaction(&mut [Operation::TransferInPlace(buf)])
}
}
impl<Word: Copy + 'static, T: SpiDevice<Word> + ?Sized> SpiDevice<Word> for &mut T {
#[inline]
fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> {
T::transaction(self, operations)
}
#[inline]
fn read(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> {
T::read(self, buf)
}
#[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)
}
}
/// 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<Word: Copy + 'static = u8>: 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 [module-level documentation](self) for details.
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error>;
/// 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>;
/// Write and read simultaneously. `write` is written to the slave on MOSI and
/// words received on MISO are stored in `read`.
///
/// It is allowed for `read` and `write` to have different lengths, even zero length.
/// The transfer runs for `max(read.len(), write.len())` words. If `read` is shorter,
/// incoming words after `read` has been filled will be discarded. If `write` is shorter,
/// the value of words sent in MOSI after all `write` has been sent is implementation-defined,
/// typically `0x00`, `0xFF`, or configurable.
///
/// Implementations are allowed to return before the operation is
/// complete. See the [module-level documentation](self) for details.
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 [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<T: SpiBus<Word> + ?Sized, Word: Copy + 'static> SpiBus<Word> 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)
}
}