diff --git a/.github/bors.toml b/.github/bors.toml index cd9fa23ed..19e66e614 100644 --- a/.github/bors.toml +++ b/.github/bors.toml @@ -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", ] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 946f35b09..11153d04f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7f10361ed..1a49c83c4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 495576757..a268cf32d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/README.md b/README.md index 5fc04539d..3d4df0664 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![crates.io](https://img.shields.io/crates/d/embedded-hal.svg)](https://crates.io/crates/embedded-hal) [![crates.io](https://img.shields.io/crates/v/embedded-hal.svg)](https://crates.io/crates/embedded-hal) [![Documentation](https://docs.rs/embedded-hal/badge.svg)](https://docs.rs/embedded-hal) -![Minimum Supported Rust Version](https://img.shields.io/badge/rustc-1.35+-blue.svg) +![Minimum Supported Rust Version](https://img.shields.io/badge/rustc-1.40+-blue.svg) # `embedded-hal` @@ -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 diff --git a/src/fmt.rs b/src/fmt.rs index 220c55e09..8d5c141ea 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -3,7 +3,8 @@ //! TODO write example of usage use core::fmt::{Result, Write}; -impl Write for dyn crate::serial::nb::Write + '_ +impl Write + for dyn crate::serial::nb::Write + '_ where Word: From, { diff --git a/src/i2c.rs b/src/i2c.rs index 1466b7f33..0dbda4256 100644 --- a/src/i2c.rs +++ b/src/i2c.rs @@ -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 for I2c0 //! { -//! # type Error = (); +//! # type Error = ErrorKind; //! # //! fn write(&mut self, addr: u8, output: &[u8]) -> Result<(), Self::Error> { //! // ... @@ -42,7 +42,7 @@ //! //! impl Write for I2c0 //! { -//! # type Error = (); +//! # type Error = ErrorKind; //! # //! fn write(&mut self, addr: u16, output: &[u8]) -> Result<(), Self::Error> { //! // ... @@ -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, //! } //! -//! impl TemperatureSensorDriver +//! impl TemperatureSensorDriver //! where //! I2C: WriteRead, //! { @@ -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, //! } //! -//! impl TemperatureSensorDriver +//! impl TemperatureSensorDriver //! where //! I2C: WriteRead, //! { @@ -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, + /// 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. @@ -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 { /// Error type - type Error: core::fmt::Debug; + type Error: Error; /// Reads enough bytes from slave with `address` to fill `buffer` /// @@ -158,7 +212,7 @@ pub mod blocking { /// Blocking write pub trait Write { /// Error type - type Error: core::fmt::Debug; + type Error: Error; /// Writes bytes to slave with address `address` /// @@ -190,7 +244,7 @@ pub mod blocking { /// Blocking write (iterator version) pub trait WriteIter { /// Error type - type Error: core::fmt::Debug; + type Error: Error; /// Writes bytes to slave with address `address` /// @@ -216,7 +270,7 @@ pub mod blocking { /// Blocking write + read pub trait WriteRead { /// 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* @@ -264,7 +318,7 @@ pub mod blocking { /// Blocking write (iterator version) + read pub trait WriteIterRead { /// 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* @@ -314,7 +368,7 @@ pub mod blocking { /// This allows combining operations within an I2C transaction. pub trait Transactional { /// Error type - type Error: core::fmt::Debug; + type Error: Error; /// Execute the provided operations on the I2C bus. /// @@ -353,7 +407,7 @@ pub mod blocking { /// This allows combining operation within an I2C transaction. pub trait TransactionalIter { /// Error type - type Error: core::fmt::Debug; + type Error: Error; /// Execute the provided operations on the I2C bus (iterator version). /// diff --git a/src/lib.rs b/src/lib.rs index f1582259e..37e6335f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,24 +143,16 @@ //! // convenience type alias //! pub type Serial1 = Serial; //! -//! /// Serial interface error -//! #[derive(Debug)] -//! pub enum Error { -//! /// Buffer overrun -//! Overrun, -//! // omitted: other error variants -//! } -//! //! impl hal::serial::nb::Read for Serial { -//! type Error = Error; +//! type Error = hal::serial::ErrorKind; //! -//! fn read(&mut self) -> nb::Result { +//! fn read(&mut self) -> nb::Result { //! // 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() { @@ -174,14 +166,14 @@ //! } //! //! impl hal::serial::nb::Write for Serial { -//! 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(()) //! } @@ -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(serial: &mut S, cb: &mut CircularBuffer) //! where -//! S: hal::serial::nb::Write, +//! S: hal::serial::nb::Write, //! { //! loop { //! if let Some(byte) = cb.peek() { @@ -397,9 +388,9 @@ //! # } //! # struct Serial1; //! # impl hal::serial::nb::Write 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 { diff --git a/src/serial/blocking.rs b/src/serial/blocking.rs index df9bc8e90..4b2900a0f 100644 --- a/src/serial/blocking.rs +++ b/src/serial/blocking.rs @@ -7,7 +7,7 @@ /// Write half of a serial interface (blocking variant) pub trait Write { /// 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 /// diff --git a/src/serial/mod.rs b/src/serial/mod.rs index cdb6e5f1c..e33f45773 100644 --- a/src/serial/mod.rs +++ b/src/serial/mod.rs @@ -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" + ), + } + } +} diff --git a/src/serial/nb.rs b/src/serial/nb.rs index d11475ea5..13a845d10 100644 --- a/src/serial/nb.rs +++ b/src/serial/nb.rs @@ -6,7 +6,7 @@ /// This can be encoded in this trait via the `Word` type parameter. pub trait Read { /// Read error - type Error: core::fmt::Debug; + type Error: crate::serial::Error; /// Reads a single word from the serial interface fn read(&mut self) -> nb::Result; @@ -23,7 +23,7 @@ impl, Word> Read for &mut T { /// Write half of a serial interface pub trait Write { /// Write error - type Error: core::fmt::Debug; + type Error: crate::serial::Error; /// Writes a single word to the serial interface fn write(&mut self, word: Word) -> nb::Result<(), Self::Error>; diff --git a/src/spi/blocking.rs b/src/spi/blocking.rs index 8cc4c1107..b98207277 100644 --- a/src/spi/blocking.rs +++ b/src/spi/blocking.rs @@ -7,7 +7,7 @@ /// Blocking transfer pub trait Transfer { /// Error type - type Error: core::fmt::Debug; + type Error: crate::spi::Error; /// Writes and reads simultaneously. The contents of `words` are /// written to the slave, and the received words are stored into the same @@ -26,7 +26,7 @@ impl, W> Transfer for &mut T { /// Blocking write pub trait Write { /// Error type - type Error: core::fmt::Debug; + type Error: crate::spi::Error; /// Writes `words` to the slave, ignoring all the incoming words fn write(&mut self, words: &[W]) -> Result<(), Self::Error>; @@ -43,7 +43,7 @@ impl, W> Write for &mut T { /// Blocking write (iterator version) pub trait WriteIter { /// Error type - type Error: core::fmt::Debug; + type Error: crate::spi::Error; /// Writes `words` to the slave, ignoring all the incoming words fn write_iter(&mut self, words: WI) -> Result<(), Self::Error> @@ -77,7 +77,7 @@ pub enum Operation<'a, W: 'static> { /// as part of a single SPI transaction pub trait Transactional { /// Associated error type - type Error: core::fmt::Debug; + type Error: crate::spi::Error; /// Execute the provided transactions fn exec<'a>(&mut self, operations: &mut [Operation<'a, W>]) -> Result<(), Self::Error>; diff --git a/src/spi/mod.rs b/src/spi/mod.rs index 0a99e9379..c367e70d8 100644 --- a/src/spi/mod.rs +++ b/src/spi/mod.rs @@ -53,3 +53,57 @@ pub const MODE_3: Mode = Mode { polarity: Polarity::IdleHigh, phase: Phase::CaptureOnSecondTransition, }; + +/// SPI error +pub trait Error: core::fmt::Debug { + /// Convert error to a generic SPI error kind + /// + /// 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; +} + +/// 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)] +#[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, + /// 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::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::Other => write!( + f, + "A different error occurred. The original error may contain more information" + ), + } + } +} diff --git a/src/spi/nb.rs b/src/spi/nb.rs index 675d15d81..c7d377ef9 100644 --- a/src/spi/nb.rs +++ b/src/spi/nb.rs @@ -18,7 +18,7 @@ /// `Word` types to allow operation in both modes. pub trait FullDuplex { /// An enumeration of SPI errors - type Error: core::fmt::Debug; + type Error: crate::spi::Error; /// Reads the word stored in the shift register ///