Merge pull request #4176 from matt-rodgers/cdc-acm-embedded-io-async

Implement embedded-io-async traits for USB CDC ACM
This commit is contained in:
Ulf Lilleengen 2025-05-09 12:38:52 +02:00 committed by GitHub
commit 8b80195843
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 110 additions and 0 deletions

View File

@ -20,3 +20,4 @@ features = ["defmt"]
[dependencies]
defmt = { version = "0.3", optional = true }
embedded-io-async = "0.6.1"

View File

@ -395,3 +395,12 @@ pub enum EndpointError {
/// The endpoint is disabled.
Disabled,
}
impl embedded_io_async::Error for EndpointError {
fn kind(&self) -> embedded_io_async::ErrorKind {
match self {
Self::BufferOverflow => embedded_io_async::ErrorKind::OutOfMemory,
Self::Disabled => embedded_io_async::ErrorKind::NotConnected,
}
}
}

View File

@ -52,6 +52,7 @@ embassy-sync = { version = "0.6.2", path = "../embassy-sync" }
embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" }
defmt = { version = "0.3", optional = true }
embedded-io-async = "0.6.1"
log = { version = "0.4.14", optional = true }
heapless = "0.8"

View File

@ -410,6 +410,18 @@ impl<'d, D: Driver<'d>> Sender<'d, D> {
}
}
impl<'d, D: Driver<'d>> embedded_io_async::ErrorType for Sender<'d, D> {
type Error = EndpointError;
}
impl<'d, D: Driver<'d>> embedded_io_async::Write for Sender<'d, D> {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
let len = core::cmp::min(buf.len(), self.max_packet_size() as usize);
self.write_packet(&buf[..len]).await?;
Ok(len)
}
}
/// CDC ACM class packet receiver.
///
/// You can obtain a `Receiver` with [`CdcAcmClass::split`]
@ -451,6 +463,93 @@ impl<'d, D: Driver<'d>> Receiver<'d, D> {
pub async fn wait_connection(&mut self) {
self.read_ep.wait_enabled().await;
}
/// Turn the `Receiver` into a [`BufferedReceiver`].
///
/// The supplied buffer must be large enough to hold max_packet_size bytes.
pub fn into_buffered(self, buf: &'d mut [u8]) -> BufferedReceiver<'d, D> {
BufferedReceiver {
receiver: self,
buffer: buf,
start: 0,
end: 0,
}
}
}
/// CDC ACM class buffered receiver.
///
/// It is a requirement of the [`embedded_io_async::Read`] trait that arbitrarily small lengths of
/// data can be read from the stream. The [`Receiver`] can only read full packets at a time. The
/// `BufferedReceiver` instead buffers a single packet if the caller does not read all of the data,
/// so that the remaining data can be returned in subsequent calls.
///
/// If you have no requirement to use the [`embedded_io_async::Read`] trait or to read a data length
/// less than the packet length, then it is more efficient to use the [`Receiver`] directly.
///
/// You can obtain a `BufferedReceiver` with [`Receiver::into_buffered`].
///
/// [`embedded_io_async::Read`]: https://docs.rs/embedded-io-async/latest/embedded_io_async/trait.Read.html
pub struct BufferedReceiver<'d, D: Driver<'d>> {
receiver: Receiver<'d, D>,
buffer: &'d mut [u8],
start: usize,
end: usize,
}
impl<'d, D: Driver<'d>> BufferedReceiver<'d, D> {
fn read_from_buffer(&mut self, buf: &mut [u8]) -> usize {
let available = &self.buffer[self.start..self.end];
let len = core::cmp::min(available.len(), buf.len());
buf[..len].copy_from_slice(&self.buffer[..len]);
self.start += len;
len
}
/// Gets the current line coding. The line coding contains information that's mainly relevant
/// for USB to UART serial port emulators, and can be ignored if not relevant.
pub fn line_coding(&self) -> LineCoding {
self.receiver.line_coding()
}
/// Gets the DTR (data terminal ready) state
pub fn dtr(&self) -> bool {
self.receiver.dtr()
}
/// Gets the RTS (request to send) state
pub fn rts(&self) -> bool {
self.receiver.rts()
}
/// Waits for the USB host to enable this interface
pub async fn wait_connection(&mut self) {
self.receiver.wait_connection().await;
}
}
impl<'d, D: Driver<'d>> embedded_io_async::ErrorType for BufferedReceiver<'d, D> {
type Error = EndpointError;
}
impl<'d, D: Driver<'d>> embedded_io_async::Read for BufferedReceiver<'d, D> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
// If there is a buffered packet, return data from that first
if self.start != self.end {
return Ok(self.read_from_buffer(buf));
}
// If the caller's buffer is large enough to contain an entire packet, read directly into
// that instead of buffering the packet internally.
if buf.len() > self.receiver.max_packet_size() as usize {
return self.receiver.read_packet(buf).await;
}
// Otherwise read a packet into the internal buffer, and return some of it to the caller
self.start = 0;
self.end = self.receiver.read_packet(&mut self.buffer).await?;
return Ok(self.read_from_buffer(buf));
}
}
/// Number of stop bits for LineCoding