Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add error traits for communication interfaces #296

Merged
merged 7 commits into from
Oct 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/bors.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ status = [
"ci-linux (stable, x86_64-unknown-linux-gnu)",
"ci-linux (stable, thumbv6m-none-eabi)",
"ci-linux (stable, thumbv7m-none-eabi)",
"ci-linux (1.35.0, x86_64-unknown-linux-gnu)",
"ci-linux (1.40.0, x86_64-unknown-linux-gnu)",
"ci-linux-test (stable)",
"ci-linux-test (1.36.0, x86_64-unknown-linux-gnu)",
"ci-linux-test (1.40.0, x86_64-unknown-linux-gnu)",
"fmt",
]
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:

include:
# Test MSRV
- rust: 1.35.0
- rust: 1.40.0
TARGET: x86_64-unknown-linux-gnu

# Test nightly but don't fail
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
rust: [stable]

include:
- rust: 1.36.0 # Higher than the MSRV due to dependencies.
- rust: 1.40.0
TARGET: x86_64-unknown-linux-gnu

# Test nightly but don't fail
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Added
- Added `IoPin` trait for pins that can change between being inputs or outputs
dynamically.
- `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.
- 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.

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.35+-blue.svg)
![Minimum Supported Rust Version](https://img.shields.io/badge/rustc-1.40+-blue.svg)

# `embedded-hal`

Expand Down Expand Up @@ -108,7 +108,7 @@ As stated before, `embedded-hal` `-alpha` versions are _not guaranteed_ to be co

## Minimum Supported Rust Version (MSRV)

This crate is guaranteed to compile on stable Rust 1.35 and up. It *might*
This crate is guaranteed to compile on stable Rust 1.40 and up. It *might*
compile with older versions but that may change in any new patch release.

## License
Expand Down
3 changes: 2 additions & 1 deletion src/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
//! TODO write example of usage
use core::fmt::{Result, Write};

impl<Word, Error: core::fmt::Debug> Write for dyn crate::serial::nb::Write<Word, Error = Error> + '_
impl<Word, Error: crate::serial::Error> Write
for dyn crate::serial::nb::Write<Word, Error = Error> + '_
where
Word: From<u8>,
{
Expand Down
84 changes: 69 additions & 15 deletions src/i2c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@
//! Here is an example of an embedded-hal implementation of the `Write` trait
//! for both modes:
//! ```
//! # use embedded_hal::i2c::{SevenBitAddress, TenBitAddress, blocking::Write};
//! # use embedded_hal::i2c::{ErrorKind, SevenBitAddress, TenBitAddress, blocking::Write};
//! /// I2C0 hardware peripheral which supports both 7-bit and 10-bit addressing.
//! pub struct I2c0;
//!
//! impl Write<SevenBitAddress> for I2c0
//! {
//! # type Error = ();
//! # type Error = ErrorKind;
//! #
//! fn write(&mut self, addr: u8, output: &[u8]) -> Result<(), Self::Error> {
//! // ...
Expand All @@ -42,7 +42,7 @@
//!
//! impl Write<TenBitAddress> for I2c0
//! {
//! # type Error = ();
//! # type Error = ErrorKind;
//! #
//! fn write(&mut self, addr: u16, output: &[u8]) -> Result<(), Self::Error> {
//! // ...
Expand All @@ -56,14 +56,14 @@
//! For demonstration purposes the address mode parameter has been omitted in this example.
//!
//! ```
//! # use embedded_hal::i2c::blocking::WriteRead;
//! # use embedded_hal::i2c::{blocking::WriteRead, Error};
//! const ADDR: u8 = 0x15;
//! # const TEMP_REGISTER: u8 = 0x1;
//! pub struct TemperatureSensorDriver<I2C> {
//! i2c: I2C,
//! }
//!
//! impl<I2C, E: core::fmt::Debug> TemperatureSensorDriver<I2C>
//! impl<I2C, E: Error> TemperatureSensorDriver<I2C>
//! where
//! I2C: WriteRead<Error = E>,
//! {
Expand All @@ -79,14 +79,14 @@
//! ### Device driver compatible only with 10-bit addresses
//!
//! ```
//! # use embedded_hal::i2c::{TenBitAddress, blocking::WriteRead};
//! # use embedded_hal::i2c::{Error, TenBitAddress, blocking::WriteRead};
//! const ADDR: u16 = 0x158;
//! # const TEMP_REGISTER: u8 = 0x1;
//! pub struct TemperatureSensorDriver<I2C> {
//! i2c: I2C,
//! }
//!
//! impl<I2C, E: core::fmt::Debug> TemperatureSensorDriver<I2C>
//! impl<I2C, E: Error> TemperatureSensorDriver<I2C>
//! where
//! I2C: WriteRead<TenBitAddress, Error = E>,
//! {
Expand All @@ -101,6 +101,60 @@

use crate::private;

/// 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;
}

/// 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)]
#[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,
eldruin marked this conversation as resolved.
Show resolved Hide resolved
/// The arbitration was lost, e.g. electrical problems with the clock signal
ArbitrationLoss,
/// The device did not acknowledge its address. The device may be missing.
NoAcknowledgeAddress,
/// The device did not acknowled the data. It may not be ready to process requests at the moment.
NoAcknowledgeData,
/// The peripheral receive buffer was overrun
Overrun,
/// 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::Bus => write!(f, "Bus error occurred"),
Self::ArbitrationLoss => write!(f, "The arbitration was lost"),
Self::NoAcknowledgeAddress => write!(f, "The device did not acknowledge its address"),
Self::NoAcknowledgeData => write!(f, "The device did not acknowledge the data"),
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"
),
}
}
}

/// Address mode (7-bit / 10-bit)
///
/// Note: This trait is sealed and should not be implemented outside of this crate.
Expand All @@ -119,12 +173,12 @@ impl AddressMode for TenBitAddress {}
/// Blocking I2C traits
pub mod blocking {

use super::{AddressMode, SevenBitAddress};
use super::{AddressMode, Error, SevenBitAddress};

/// Blocking read
pub trait Read<A: AddressMode = SevenBitAddress> {
/// Error type
type Error: core::fmt::Debug;
type Error: Error;

/// Reads enough bytes from slave with `address` to fill `buffer`
///
Expand Down Expand Up @@ -158,7 +212,7 @@ pub mod blocking {
/// Blocking write
pub trait Write<A: AddressMode = SevenBitAddress> {
/// Error type
type Error: core::fmt::Debug;
type Error: Error;

/// Writes bytes to slave with address `address`
///
Expand Down Expand Up @@ -190,7 +244,7 @@ pub mod blocking {
/// Blocking write (iterator version)
pub trait WriteIter<A: AddressMode = SevenBitAddress> {
/// Error type
type Error: core::fmt::Debug;
type Error: Error;

/// Writes bytes to slave with address `address`
///
Expand All @@ -216,7 +270,7 @@ pub mod blocking {
/// Blocking write + read
pub trait WriteRead<A: AddressMode = SevenBitAddress> {
/// Error type
type Error: core::fmt::Debug;
type Error: Error;

/// Writes bytes to slave with address `address` and then reads enough bytes to fill `buffer` *in a
/// single transaction*
Expand Down Expand Up @@ -264,7 +318,7 @@ pub mod blocking {
/// Blocking write (iterator version) + read
pub trait WriteIterRead<A: AddressMode = SevenBitAddress> {
/// Error type
type Error: core::fmt::Debug;
type Error: Error;

/// Writes bytes to slave with address `address` and then reads enough bytes to fill `buffer` *in a
/// single transaction*
Expand Down Expand Up @@ -314,7 +368,7 @@ pub mod blocking {
/// This allows combining operations within an I2C transaction.
pub trait Transactional<A: AddressMode = SevenBitAddress> {
/// Error type
type Error: core::fmt::Debug;
type Error: Error;

/// Execute the provided operations on the I2C bus.
///
Expand Down Expand Up @@ -353,7 +407,7 @@ pub mod blocking {
/// This allows combining operation within an I2C transaction.
pub trait TransactionalIter<A: AddressMode = SevenBitAddress> {
/// Error type
type Error: core::fmt::Debug;
type Error: Error;

/// Execute the provided operations on the I2C bus (iterator version).
///
Expand Down
31 changes: 11 additions & 20 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,24 +143,16 @@
//! // convenience type alias
//! pub type Serial1 = Serial<USART1>;
//!
//! /// Serial interface error
//! #[derive(Debug)]
//! pub enum Error {
//! /// Buffer overrun
//! Overrun,
//! // omitted: other error variants
//! }
//!
//! impl hal::serial::nb::Read<u8> for Serial<USART1> {
//! type Error = Error;
//! type Error = hal::serial::ErrorKind;
//!
//! fn read(&mut self) -> nb::Result<u8, Error> {
//! fn read(&mut self) -> nb::Result<u8, Self::Error> {
//! // read the status register
//! let isr = self.usart.sr.read();
//!
//! if isr.ore().bit_is_set() {
//! // Error: Buffer overrun
//! Err(nb::Error::Other(Error::Overrun))
//! Err(nb::Error::Other(Self::Error::Overrun))
//! }
//! // omitted: checks for other errors
//! else if isr.rxne().bit_is_set() {
Expand All @@ -174,14 +166,14 @@
//! }
//!
//! impl hal::serial::nb::Write<u8> for Serial<USART1> {
//! type Error = Error;
//! type Error = hal::serial::ErrorKind;
//!
//! fn write(&mut self, byte: u8) -> nb::Result<(), Error> {
//! fn write(&mut self, byte: u8) -> nb::Result<(), Self::Error> {
//! // Similar to the `read` implementation
//! # Ok(())
//! }
//!
//! fn flush(&mut self) -> nb::Result<(), Error> {
//! fn flush(&mut self) -> nb::Result<(), Self::Error> {
//! // Similar to the `read` implementation
//! # Ok(())
//! }
Expand Down Expand Up @@ -327,12 +319,11 @@
//! use embedded_hal as hal;
//! use hal::nb;
//!
//! use hal::serial::nb::Write;
//! use ::core::convert::Infallible;
//! use hal::serial::{ErrorKind, nb::Write};
//!
//! fn flush<S>(serial: &mut S, cb: &mut CircularBuffer)
//! where
//! S: hal::serial::nb::Write<u8, Error = Infallible>,
//! S: hal::serial::nb::Write<u8, Error = ErrorKind>,
//! {
//! loop {
//! if let Some(byte) = cb.peek() {
Expand Down Expand Up @@ -397,9 +388,9 @@
//! # }
//! # struct Serial1;
//! # impl hal::serial::nb::Write<u8> for Serial1 {
//! # type Error = Infallible;
//! # fn write(&mut self, _: u8) -> nb::Result<(), Infallible> { Err(::nb::Error::WouldBlock) }
//! # fn flush(&mut self) -> nb::Result<(), Infallible> { Err(::nb::Error::WouldBlock) }
//! # type Error = ErrorKind;
//! # fn write(&mut self, _: u8) -> nb::Result<(), Self::Error> { Err(::nb::Error::WouldBlock) }
//! # fn flush(&mut self) -> nb::Result<(), Self::Error> { Err(::nb::Error::WouldBlock) }
//! # }
//! # struct CircularBuffer;
//! # impl CircularBuffer {
Expand Down
2 changes: 1 addition & 1 deletion src/serial/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/// Write half of a serial interface (blocking variant)
pub trait Write<Word> {
/// The type of error that can occur when writing
type Error: core::fmt::Debug;
type Error: crate::serial::Error;

/// Writes a slice, blocking until everything has been written
///
Expand Down
55 changes: 55 additions & 0 deletions src/serial/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,58 @@

pub mod blocking;
pub mod nb;

/// 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;
}

/// 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"
),
}
}
}
Loading