Created
June 3, 2025 08:42
-
-
Save torkleyy/8b38bb98978a367c5c281b690334dd54 to your computer and use it in GitHub Desktop.
How to read from UART at high speed in Rust with serialport-rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! High-performance UART reader that uses a dedicated thread to ensure no data is lost. | |
use std::{ | |
collections::VecDeque, | |
io::{ErrorKind, Result}, | |
pin::Pin, | |
task::{Context, Poll}, | |
thread, | |
time::Duration, | |
}; | |
use tokio::{ | |
io::{AsyncRead, ReadBuf}, | |
sync::mpsc::{unbounded_channel, UnboundedReceiver}, | |
}; | |
pub fn open_uart_stream(path: &str, baud: u32) -> Result<Box<dyn AsyncRead + Unpin + Send>> { | |
let (tx, rx) = unbounded_channel::<Vec<u8>>(); | |
// Open the serial port in blocking mode with a small timeout | |
let mut port = serialport::new(path, baud) | |
.timeout(Duration::from_millis(5)) | |
.open()?; | |
thread::spawn(move || { | |
let mut buf = [0u8; 16_384]; | |
loop { | |
match port.read(&mut buf) { | |
Ok(n) if n > 0 => { | |
// Send the exact slice we received | |
let chunk = buf[..n].to_vec(); | |
if tx.send(chunk).is_err() { | |
// Receiver was dropped: exit thread | |
break; | |
} | |
} | |
Err(ref e) if e.kind() == ErrorKind::TimedOut => { | |
// No data right now: loop again | |
continue; | |
} | |
Err(_) => { | |
// Some other error or port closed: exit thread | |
break; | |
} | |
_ => continue, | |
} | |
} | |
}); | |
let reader = UartAsyncReader { | |
rx, | |
buffer: VecDeque::new(), | |
}; | |
Ok(Box::new(reader)) | |
} | |
struct UartAsyncReader { | |
rx: UnboundedReceiver<Vec<u8>>, | |
buffer: VecDeque<u8>, | |
} | |
impl AsyncRead for UartAsyncReader { | |
fn poll_read( | |
mut self: Pin<&mut Self>, | |
cx: &mut Context<'_>, | |
buf: &mut ReadBuf<'_>, | |
) -> Poll<Result<()>> { | |
while self.buffer.is_empty() { | |
match Pin::new(&mut self.rx).poll_recv(cx) { | |
Poll::Ready(Some(chunk)) => { | |
self.buffer.extend(chunk); | |
} | |
Poll::Ready(None) => { | |
// Sender side hung up: no more data ever coming => EOF | |
return Poll::Ready(Ok(())); | |
} | |
Poll::Pending => { | |
// No chunk available right now | |
return Poll::Pending; | |
} | |
} | |
} | |
// At this point, we have ≥1 byte in self.buffer. Fill the ReadBuf. | |
let to_read = std::cmp::min(self.buffer.len(), buf.remaining()); | |
// Drain exactly 'to_read' bytes out of our VecDeque | |
let mut tmp = Vec::with_capacity(to_read); | |
for _ in 0..to_read { | |
if let Some(b) = self.buffer.pop_front() { | |
tmp.push(b); | |
} | |
} | |
buf.put_slice(&tmp); | |
Poll::Ready(Ok(())) | |
} | |
} | |
impl Unpin for UartAsyncReader {} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment