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

serial: add ReadExact, ReadUntilIdle #349

Closed
wants to merge 2 commits into from
Closed
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
44 changes: 44 additions & 0 deletions embedded-hal-async/src/serial.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,51 @@
//! Serial interface
//!
//! See the documentation on [`embedded_hal::serial`] for details.

pub use embedded_hal::serial::{Error, ErrorKind, ErrorType};

/// Read an exact amount of words from an *unbuffered* serial interface
///
/// Some serial interfaces support different data sizes (8 bits, 9 bits, etc.);
/// This can be encoded in this trait via the `Word` type parameter.
pub trait ReadExact<Word: 'static + Copy = u8>: ErrorType {
/// Read an exact amount of words.
///
/// This does not return until exactly `read.len()` words have been read.
async fn read_exact(&mut self, read: &mut [Word]) -> Result<(), Self::Error>;
}

impl<T: ReadExact<Word>, Word: 'static + Copy> ReadExact<Word> for &mut T {
async fn read_exact(&mut self, read: &mut [Word]) -> Result<(), Self::Error> {
T::read_exact(self, read).await
}
}

/// Read words from an *unbuffered* serial interface, until the line becomes idle.
///
/// Some serial interfaces support different data sizes (8 bits, 9 bits, etc.);
/// This can be encoded in this trait via the `Word` type parameter.
pub trait ReadUntilIdle<Word: 'static + Copy = u8>: ErrorType {
/// Read words until the line becomes idle.
///
/// Returns the amount of words received.
///
/// This returns at the earliest of either:
/// - at least 1 word has been received, and then the line becomes idle
/// - exactly `read.len()` words have been read (the buffer is full)
///
/// The serial line is considered idle after a timeout of it being constantly
/// at high level. The exact timeout is implementation-defined, but it should be
/// short, around 1 or 2 words' worth of time.
async fn read_until_idle(&mut self, read: &mut [Word]) -> Result<usize, Self::Error>;
}

impl<T: ReadUntilIdle<Word>, Word: 'static + Copy> ReadUntilIdle<Word> for &mut T {
async fn read_until_idle(&mut self, read: &mut [Word]) -> Result<usize, Self::Error> {
T::read_until_idle(self, read).await
}
}

/// Write half of a serial interface
pub trait Write<Word: 'static + Copy = u8>: ErrorType {
/// Writes a slice, blocking until everything has been written.
Expand Down
80 changes: 79 additions & 1 deletion embedded-hal/src/serial.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,40 @@
//! Serial traits
//!
//! # Buffered vs Unbuffered
//!
//! There is two main ways a serial receiver can operate: buffered and unbuffered.
//!
//! - **Buffered**: The serial driver has a reasonably-sized buffer in RAM. Incoming bytes
//! are placed into the buffer automatically without user code having to actively do
//! anything (using either DMA, or a high-priority interrupt). User code reads bytes
//! out of the buffer. Bytes are only lost if received while buffer is full.
//! - **Unbuffered**: The serial driver has no buffer (or a buffer so small that it's negligible,
//! for example nRF chips have a 4-byte buffer in hardware, and some STM32 chips have a 1-byte
//! buffer). User code does "read" operations that pop bytes directly from the hardware
//! into the user's buffer. Bytes received while the user code is not actively doing a read
//! *at that very instant* are lost.
//!
//! For example:
//! - Linux's /dev/ttyX is buffered.
//! - Most Rust HALs offer unbuffered serial drivers at the time of writing.
//! - Some HALs (such as `embassy`) offer both buffered and unbuffered.
//!
//! There are tradeoffs when deciding which one to use. Unbuffered is the simplest, and allows for
//! the lowest memory usage since data can be transferred directly from hardware to the user's buffer, avoiding
//! the need for intermediary drivers. However, with unbuffered it's very easy to **lose data** if the code
//! spends too much time between read calls. This can be solved either by using buffered serial at the cost of
//! more RAM usage, or using hardware flow control (RTS/CTS) at the cost of using more MCU pins.
//!
//! The read traits in this API ([`ReadExact`] and [`ReadUntilIdle`]) are intended to **model unbuffered serial interfaces**.
//! Data that arrives when your code is not running a `read_*()` call **is lost**.
//!
//! Drivers should only use these traits when the use case allows for it. For example, `ReadUntilIdle` can be used
//! for packet-wise communications, but you have to ensure the protocol guarantees enough idle time between
//! packets so that you won't lose data for the next packet while processing the previous one.
//!
//! Drivers that require **buffered** serial ports should use [`embedded-io`](https://docs.rs/embedded-io) instead. These
//! traits allow for a much more `std::io`-like usage, and implementations guarantee data is not lost until the
//! (much bigger) buffer overflows.

/// Serial error
pub trait Error: core::fmt::Debug {
Expand Down Expand Up @@ -73,7 +109,49 @@ impl<T: ErrorType> ErrorType for &mut T {
type Error = T::Error;
}

/// Write half of a serial interface (blocking variant)
/// Read an exact amount of words from an *unbuffered* serial interface
///
/// Some serial interfaces support different data sizes (8 bits, 9 bits, etc.);
/// This can be encoded in this trait via the `Word` type parameter.
pub trait ReadExact<Word: 'static + Copy = u8>: ErrorType {
/// Read an exact amount of words.
///
/// This does not return until exactly `read.len()` words have been read.
fn read_exact(&mut self, read: &mut [Word]) -> Result<(), Self::Error>;
}

impl<T: ReadExact<Word>, Word: 'static + Copy> ReadExact<Word> for &mut T {
fn read_exact(&mut self, read: &mut [Word]) -> Result<(), Self::Error> {
T::read_exact(self, read)
}
}

/// Read words from an *unbuffered* serial interface, until the line becomes idle.
///
/// Some serial interfaces support different data sizes (8 bits, 9 bits, etc.);
/// This can be encoded in this trait via the `Word` type parameter.
pub trait ReadUntilIdle<Word: 'static + Copy = u8>: ErrorType {
/// Read words until the line becomes idle.
///
/// Returns the amount of words received.
///
/// This returns at the earliest of either:
/// - at least 1 word has been received, and then the line becomes idle
/// - exactly `read.len()` words have been read (the buffer is full)
///
/// The serial line is considered idle after a timeout of it being constantly
/// at high level. The exact timeout is implementation-defined, but it should be
/// short, around 1 or 2 words' worth of time.
fn read_until_idle(&mut self, read: &mut [Word]) -> Result<usize, Self::Error>;
}

impl<T: ReadUntilIdle<Word>, Word: 'static + Copy> ReadUntilIdle<Word> for &mut T {
fn read_until_idle(&mut self, read: &mut [Word]) -> Result<usize, Self::Error> {
T::read_until_idle(self, read)
}
}

/// Write half of a serial interface.
pub trait Write<Word: Copy = u8>: ErrorType {
/// Writes a slice, blocking until everything has been written
///
Expand Down