|
|
|
@ -0,0 +1,367 @@
|
|
|
|
|
//! CDC-ACM class implementation, aka Serial over USB.
|
|
|
|
|
|
|
|
|
|
use core::convert::Infallible;
|
|
|
|
|
|
|
|
|
|
use embassy_futures::join::join;
|
|
|
|
|
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
|
|
|
|
use embassy_sync::pipe::Pipe;
|
|
|
|
|
pub use embassy_sync::pipe::{TryReadError, TryWriteError};
|
|
|
|
|
use embassy_usb_driver::{Driver, EndpointError};
|
|
|
|
|
|
|
|
|
|
use crate::Builder;
|
|
|
|
|
|
|
|
|
|
pub mod low_level;
|
|
|
|
|
|
|
|
|
|
/// Internal state for CDC-ACM
|
|
|
|
|
pub struct State<'a, const TX_BUF: usize, const RX_BUF: usize> {
|
|
|
|
|
ll: low_level::State<'a>,
|
|
|
|
|
rx: Pipe<NoopRawMutex, RX_BUF>,
|
|
|
|
|
tx: Pipe<NoopRawMutex, TX_BUF>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, const TX_BUF: usize, const RX_BUF: usize> State<'a, TX_BUF, RX_BUF> {
|
|
|
|
|
/// Create a new `State`.
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
ll: low_level::State::new(),
|
|
|
|
|
rx: Pipe::new(),
|
|
|
|
|
tx: Pipe::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// USB CDC-ACM serial port.
|
|
|
|
|
pub struct SerialPort<'d, const TX_BUF: usize, const RX_BUF: usize> {
|
|
|
|
|
rx: &'d Pipe<NoopRawMutex, RX_BUF>,
|
|
|
|
|
tx: &'d Pipe<NoopRawMutex, TX_BUF>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// USB CDC-ACM serial port reader
|
|
|
|
|
pub struct Reader<'d, const RX_BUF: usize> {
|
|
|
|
|
rx: &'d Pipe<NoopRawMutex, RX_BUF>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// USB CDC-ACM serial port writer
|
|
|
|
|
pub struct Writer<'d, const TX_BUF: usize> {
|
|
|
|
|
tx: &'d Pipe<NoopRawMutex, TX_BUF>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Background task runner for a CDC-ACM serial port.
|
|
|
|
|
///
|
|
|
|
|
/// You must run `run()` in the background to make the serial port work. Either spawn
|
|
|
|
|
/// it as a separate task, or use `join` or `select`.
|
|
|
|
|
pub struct Runner<'d, D: Driver<'d>, const TX_BUF: usize, const RX_BUF: usize> {
|
|
|
|
|
ll: low_level::CdcAcmClass<'d, D>,
|
|
|
|
|
rx: &'d Pipe<NoopRawMutex, RX_BUF>,
|
|
|
|
|
tx: &'d Pipe<NoopRawMutex, TX_BUF>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'d, D: Driver<'d>, const TX_BUF: usize, const RX_BUF: usize> Runner<'d, D, TX_BUF, RX_BUF> {
|
|
|
|
|
/// Run background processing for this serial port.
|
|
|
|
|
///
|
|
|
|
|
/// You must run this in the background to make the serial port work. Either spawn
|
|
|
|
|
/// it as a separate task, or use `join` or `select`.
|
|
|
|
|
pub async fn run(self) -> ! {
|
|
|
|
|
let (mut ll_tx, mut ll_rx) = self.ll.split();
|
|
|
|
|
|
|
|
|
|
let rx_fut = async {
|
|
|
|
|
let mut buf = [0u8; 64];
|
|
|
|
|
loop {
|
|
|
|
|
ll_rx.wait_connection().await;
|
|
|
|
|
loop {
|
|
|
|
|
match ll_rx.read_packet(&mut buf).await {
|
|
|
|
|
Ok(n) => {
|
|
|
|
|
self.rx.write_all(&buf[..n]).await;
|
|
|
|
|
}
|
|
|
|
|
Err(EndpointError::BufferOverflow) => unreachable!(),
|
|
|
|
|
Err(EndpointError::Disabled) => break,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let tx_fut = async {
|
|
|
|
|
let mut buf = [0u8; 64];
|
|
|
|
|
loop {
|
|
|
|
|
ll_tx.wait_connection().await;
|
|
|
|
|
|
|
|
|
|
let mut needs_zlp = false;
|
|
|
|
|
|
|
|
|
|
loop {
|
|
|
|
|
let n = if needs_zlp {
|
|
|
|
|
// USB transfer end is signaled by a not full-sized packet (less than max_packet_size).
|
|
|
|
|
// if last packet was full-sized and we have no more data, we must send
|
|
|
|
|
// a zero-length packet (ZLP). If we don't, the host might not process the data we've
|
|
|
|
|
// sent because it'll think the transfer is still not done.
|
|
|
|
|
self.tx.try_read(&mut buf).unwrap_or(0)
|
|
|
|
|
} else {
|
|
|
|
|
self.tx.read(&mut buf).await
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match ll_tx.write_packet(&buf[..n]).await {
|
|
|
|
|
Ok(()) => {}
|
|
|
|
|
Err(EndpointError::BufferOverflow) => unreachable!(),
|
|
|
|
|
Err(EndpointError::Disabled) => break,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the packet was full-sized, record this for the next loop iteration.
|
|
|
|
|
needs_zlp = n == ll_tx.max_packet_size() as usize;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
join(rx_fut, tx_fut).await;
|
|
|
|
|
unreachable!()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'d, const TX_BUF: usize, const RX_BUF: usize> SerialPort<'d, TX_BUF, RX_BUF> {
|
|
|
|
|
/// Create a new CDC ACM serial port.
|
|
|
|
|
///
|
|
|
|
|
/// This returns two objects:
|
|
|
|
|
/// - The `CdcAcmClass`: this is what you use to actually read/write bytes from the serial port.
|
|
|
|
|
/// - A `Runner`. This contains a `run()` function that you must run in the background
|
|
|
|
|
/// to make the serial port work. Either spawn it as a separate task, or use `join` or `select`.
|
|
|
|
|
pub fn new<D: Driver<'d>>(
|
|
|
|
|
builder: &mut Builder<'d, D>,
|
|
|
|
|
state: &'d mut State<'d, TX_BUF, RX_BUF>,
|
|
|
|
|
max_packet_size: u16,
|
|
|
|
|
) -> (Self, Runner<'d, D, TX_BUF, RX_BUF>) {
|
|
|
|
|
let ll = low_level::CdcAcmClass::new(builder, &mut state.ll, max_packet_size);
|
|
|
|
|
(
|
|
|
|
|
Self {
|
|
|
|
|
tx: &state.tx,
|
|
|
|
|
rx: &state.rx,
|
|
|
|
|
},
|
|
|
|
|
Runner {
|
|
|
|
|
ll,
|
|
|
|
|
tx: &state.tx,
|
|
|
|
|
rx: &state.rx,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get a writer for this pipe.
|
|
|
|
|
pub fn writer(&self) -> Writer<'_, TX_BUF> {
|
|
|
|
|
Writer { tx: self.tx }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get a reader for this pipe.
|
|
|
|
|
pub fn reader(&self) -> Reader<'_, RX_BUF> {
|
|
|
|
|
Reader { rx: self.rx }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Write some bytes to the pipe.
|
|
|
|
|
///
|
|
|
|
|
/// This method writes a nonzero amount of bytes from `buf` into the pipe, and
|
|
|
|
|
/// returns the amount of bytes written.
|
|
|
|
|
///
|
|
|
|
|
/// If it is not possible to write a nonzero amount of bytes because the pipe's buffer is full,
|
|
|
|
|
/// this method will wait until it isn't. See [`try_write`](Self::try_write) for a variant that
|
|
|
|
|
/// returns an error instead of waiting.
|
|
|
|
|
///
|
|
|
|
|
/// It is not guaranteed that all bytes in the buffer are written, even if there's enough
|
|
|
|
|
/// free space in the pipe buffer for all. In other words, it is possible for `write` to return
|
|
|
|
|
/// without writing all of `buf` (returning a number less than `buf.len()`) and still leave
|
|
|
|
|
/// free space in the pipe buffer. You should always `write` in a loop, or use helpers like
|
|
|
|
|
/// `write_all` from the `embedded-io` crate.
|
|
|
|
|
pub async fn write(&self, buf: &[u8]) -> usize {
|
|
|
|
|
self.tx.write(buf).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Write all bytes to the pipe.
|
|
|
|
|
///
|
|
|
|
|
/// This method writes all bytes from `buf` into the pipe
|
|
|
|
|
pub async fn write_all(&self, mut buf: &[u8]) {
|
|
|
|
|
while !buf.is_empty() {
|
|
|
|
|
let n = self.write(buf).await;
|
|
|
|
|
buf = &buf[n..];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Attempt to immediately write some bytes to the pipe.
|
|
|
|
|
///
|
|
|
|
|
/// This method will either write a nonzero amount of bytes to the pipe immediately,
|
|
|
|
|
/// or return an error if the pipe is empty. See [`write`](Self::write) for a variant
|
|
|
|
|
/// that waits instead of returning an error.
|
|
|
|
|
pub fn try_write(&self, buf: &[u8]) -> Result<usize, TryWriteError> {
|
|
|
|
|
self.tx.try_write(buf)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Read some bytes from the pipe.
|
|
|
|
|
///
|
|
|
|
|
/// This method reads a nonzero amount of bytes from the pipe into `buf` and
|
|
|
|
|
/// returns the amount of bytes read.
|
|
|
|
|
///
|
|
|
|
|
/// If it is not possible to read a nonzero amount of bytes because the pipe's buffer is empty,
|
|
|
|
|
/// this method will wait until it isn't. See [`try_read`](Self::try_read) for a variant that
|
|
|
|
|
/// returns an error instead of waiting.
|
|
|
|
|
///
|
|
|
|
|
/// It is not guaranteed that all bytes in the buffer are read, even if there's enough
|
|
|
|
|
/// space in `buf` for all. In other words, it is possible for `read` to return
|
|
|
|
|
/// without filling `buf` (returning a number less than `buf.len()`) and still leave bytes
|
|
|
|
|
/// in the pipe buffer. You should always `read` in a loop, or use helpers like
|
|
|
|
|
/// `read_exact` from the `embedded-io` crate.
|
|
|
|
|
pub async fn read(&self, buf: &mut [u8]) -> usize {
|
|
|
|
|
self.rx.read(buf).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Attempt to immediately read some bytes from the pipe.
|
|
|
|
|
///
|
|
|
|
|
/// This method will either read a nonzero amount of bytes from the pipe immediately,
|
|
|
|
|
/// or return an error if the pipe is empty. See [`read`](Self::read) for a variant
|
|
|
|
|
/// that waits instead of returning an error.
|
|
|
|
|
pub fn try_read(&self, buf: &mut [u8]) -> Result<usize, TryReadError> {
|
|
|
|
|
self.rx.try_read(buf)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Clear the data in the receive buffer.
|
|
|
|
|
pub fn clear_rx_buffer(&self) {
|
|
|
|
|
self.rx.clear()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Clear the data in the transmit buffer.
|
|
|
|
|
pub fn clear_tx_buffer(&self) {
|
|
|
|
|
self.tx.clear()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'d, const TX_BUF: usize> Writer<'d, TX_BUF> {
|
|
|
|
|
/// Write some bytes to the pipe.
|
|
|
|
|
///
|
|
|
|
|
/// This method writes a nonzero amount of bytes from `buf` into the pipe, and
|
|
|
|
|
/// returns the amount of bytes written.
|
|
|
|
|
///
|
|
|
|
|
/// If it is not possible to write a nonzero amount of bytes because the pipe's buffer is full,
|
|
|
|
|
/// this method will wait until it isn't. See [`try_write`](Self::try_write) for a variant that
|
|
|
|
|
/// returns an error instead of waiting.
|
|
|
|
|
///
|
|
|
|
|
/// It is not guaranteed that all bytes in the buffer are written, even if there's enough
|
|
|
|
|
/// free space in the pipe buffer for all. In other words, it is possible for `write` to return
|
|
|
|
|
/// without writing all of `buf` (returning a number less than `buf.len()`) and still leave
|
|
|
|
|
/// free space in the pipe buffer. You should always `write` in a loop, or use helpers like
|
|
|
|
|
/// `write_all` from the `embedded-io` crate.
|
|
|
|
|
pub async fn write(&self, buf: &[u8]) -> usize {
|
|
|
|
|
self.tx.write(buf).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Write all bytes to the pipe.
|
|
|
|
|
///
|
|
|
|
|
/// This method writes all bytes from `buf` into the pipe
|
|
|
|
|
pub async fn write_all(&self, mut buf: &[u8]) {
|
|
|
|
|
while !buf.is_empty() {
|
|
|
|
|
let n = self.write(buf).await;
|
|
|
|
|
buf = &buf[n..];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Attempt to immediately write some bytes to the pipe.
|
|
|
|
|
///
|
|
|
|
|
/// This method will either write a nonzero amount of bytes to the pipe immediately,
|
|
|
|
|
/// or return an error if the pipe is empty. See [`write`](Self::write) for a variant
|
|
|
|
|
/// that waits instead of returning an error.
|
|
|
|
|
pub fn try_write(&self, buf: &[u8]) -> Result<usize, TryWriteError> {
|
|
|
|
|
self.tx.try_write(buf)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Clear the data in the transmit buffer.
|
|
|
|
|
pub fn clear_tx_buffer(&self) {
|
|
|
|
|
self.tx.clear()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'d, const RX_BUF: usize> Reader<'d, RX_BUF> {
|
|
|
|
|
/// Read some bytes from the pipe.
|
|
|
|
|
///
|
|
|
|
|
/// This method reads a nonzero amount of bytes from the pipe into `buf` and
|
|
|
|
|
/// returns the amount of bytes read.
|
|
|
|
|
///
|
|
|
|
|
/// If it is not possible to read a nonzero amount of bytes because the pipe's buffer is empty,
|
|
|
|
|
/// this method will wait until it isn't. See [`try_read`](Self::try_read) for a variant that
|
|
|
|
|
/// returns an error instead of waiting.
|
|
|
|
|
///
|
|
|
|
|
/// It is not guaranteed that all bytes in the buffer are read, even if there's enough
|
|
|
|
|
/// space in `buf` for all. In other words, it is possible for `read` to return
|
|
|
|
|
/// without filling `buf` (returning a number less than `buf.len()`) and still leave bytes
|
|
|
|
|
/// in the pipe buffer. You should always `read` in a loop, or use helpers like
|
|
|
|
|
/// `read_exact` from the `embedded-io` crate.
|
|
|
|
|
pub async fn read(&self, buf: &mut [u8]) -> usize {
|
|
|
|
|
self.rx.read(buf).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Attempt to immediately read some bytes from the pipe.
|
|
|
|
|
///
|
|
|
|
|
/// This method will either read a nonzero amount of bytes from the pipe immediately,
|
|
|
|
|
/// or return an error if the pipe is empty. See [`read`](Self::read) for a variant
|
|
|
|
|
/// that waits instead of returning an error.
|
|
|
|
|
pub fn try_read(&self, buf: &mut [u8]) -> Result<usize, TryReadError> {
|
|
|
|
|
self.rx.try_read(buf)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Clear the data in the receive buffer.
|
|
|
|
|
pub fn clear_rx_buffer(&self) {
|
|
|
|
|
self.rx.clear()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<const TX_BUF: usize, const RX_BUF: usize> embedded_io::Io for SerialPort<'_, TX_BUF, RX_BUF> {
|
|
|
|
|
type Error = Infallible;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<const TX_BUF: usize, const RX_BUF: usize> embedded_io::asynch::Read for SerialPort<'_, TX_BUF, RX_BUF> {
|
|
|
|
|
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
|
|
|
|
Ok(SerialPort::read(self, buf).await)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<const TX_BUF: usize, const RX_BUF: usize> embedded_io::asynch::Write for SerialPort<'_, TX_BUF, RX_BUF> {
|
|
|
|
|
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
|
|
|
|
Ok(SerialPort::write(self, buf).await)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn flush(&mut self) -> Result<(), Self::Error> {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<const TX_BUF: usize, const RX_BUF: usize> embedded_io::Io for &SerialPort<'_, TX_BUF, RX_BUF> {
|
|
|
|
|
type Error = Infallible;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<const TX_BUF: usize, const RX_BUF: usize> embedded_io::asynch::Read for &SerialPort<'_, TX_BUF, RX_BUF> {
|
|
|
|
|
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
|
|
|
|
Ok(SerialPort::read(self, buf).await)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<const TX_BUF: usize, const RX_BUF: usize> embedded_io::asynch::Write for &SerialPort<'_, TX_BUF, RX_BUF> {
|
|
|
|
|
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
|
|
|
|
Ok(SerialPort::write(self, buf).await)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn flush(&mut self) -> Result<(), Self::Error> {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<const RX_BUF: usize> embedded_io::Io for Reader<'_, RX_BUF> {
|
|
|
|
|
type Error = Infallible;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<const RX_BUF: usize> embedded_io::asynch::Read for Reader<'_, RX_BUF> {
|
|
|
|
|
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
|
|
|
|
Ok(Reader::read(self, buf).await)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<const TX_BUF: usize> embedded_io::Io for Writer<'_, TX_BUF> {
|
|
|
|
|
type Error = Infallible;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<const TX_BUF: usize> embedded_io::asynch::Write for Writer<'_, TX_BUF> {
|
|
|
|
|
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
|
|
|
|
Ok(Writer::write(self, buf).await)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn flush(&mut self) -> Result<(), Self::Error> {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|