From 9131236b756464abcaf5f755f0257590ff17fded Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 8 Mar 2023 00:48:58 +0100 Subject: [PATCH 1/2] serial: add ReadExact, ReadUntilIdle --- embedded-hal-async/src/serial.rs | 42 ++++++++++++++++++++++++++++++ embedded-hal/src/serial.rs | 44 +++++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/embedded-hal-async/src/serial.rs b/embedded-hal-async/src/serial.rs index 60ee1bc05..631989745 100644 --- a/embedded-hal-async/src/serial.rs +++ b/embedded-hal-async/src/serial.rs @@ -2,6 +2,48 @@ pub use embedded_hal::serial::{Error, ErrorKind, ErrorType}; +/// Read an exact amount of words from a serial interface +/// +/// Some serial interfaces support different data sizes (8 bits, 9 bits, etc.); +/// This can be encoded in this trait via the `Word` type parameter. +pub trait ReadExact: 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, Word: 'static + Copy> ReadExact for &mut T { + async fn read_exact(&mut self, read: &mut [Word]) -> Result<(), Self::Error> { + T::read_exact(self, read).await + } +} + +/// Read words from a 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: 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; +} + +impl, Word: 'static + Copy> ReadUntilIdle for &mut T { + async fn read_until_idle(&mut self, read: &mut [Word]) -> Result { + T::read_until_idle(self, read).await + } +} + /// Write half of a serial interface pub trait Write: ErrorType { /// Writes a slice, blocking until everything has been written. diff --git a/embedded-hal/src/serial.rs b/embedded-hal/src/serial.rs index ab9be722b..0a6a0dc14 100644 --- a/embedded-hal/src/serial.rs +++ b/embedded-hal/src/serial.rs @@ -73,7 +73,49 @@ impl ErrorType for &mut T { type Error = T::Error; } -/// Write half of a serial interface (blocking variant) +/// Read an exact amount of words from a serial interface +/// +/// Some serial interfaces support different data sizes (8 bits, 9 bits, etc.); +/// This can be encoded in this trait via the `Word` type parameter. +pub trait ReadExact: 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, Word: 'static + Copy> ReadExact for &mut T { + fn read_exact(&mut self, read: &mut [Word]) -> Result<(), Self::Error> { + T::read_exact(self, read) + } +} + +/// Read words from a 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: 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; +} + +impl, Word: 'static + Copy> ReadUntilIdle for &mut T { + fn read_until_idle(&mut self, read: &mut [Word]) -> Result { + T::read_until_idle(self, read) + } +} + +/// Write half of a serial interface. pub trait Write: ErrorType { /// Writes a slice, blocking until everything has been written /// From 427dfe1b24ba652656d67b9ebeb968c17fbf0072 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 8 Mar 2023 01:16:27 +0100 Subject: [PATCH 2/2] serial: document buffered vs unbuffered traits. --- embedded-hal-async/src/serial.rs | 6 +++-- embedded-hal/src/serial.rs | 40 ++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/embedded-hal-async/src/serial.rs b/embedded-hal-async/src/serial.rs index 631989745..660f36ded 100644 --- a/embedded-hal-async/src/serial.rs +++ b/embedded-hal-async/src/serial.rs @@ -1,8 +1,10 @@ //! 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 a serial interface +/// 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. @@ -19,7 +21,7 @@ impl, Word: 'static + Copy> ReadExact for &mut T { } } -/// Read words from a serial interface, until the line becomes idle. +/// 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. diff --git a/embedded-hal/src/serial.rs b/embedded-hal/src/serial.rs index 0a6a0dc14..b6122f4a8 100644 --- a/embedded-hal/src/serial.rs +++ b/embedded-hal/src/serial.rs @@ -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 { @@ -73,7 +109,7 @@ impl ErrorType for &mut T { type Error = T::Error; } -/// Read an exact amount of words from a serial interface +/// 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. @@ -90,7 +126,7 @@ impl, Word: 'static + Copy> ReadExact for &mut T { } } -/// Read words from a serial interface, until the line becomes idle. +/// 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.