Skip to content

Instantly share code, notes, and snippets.

@torkleyy
Created June 3, 2025 08:42
Show Gist options
  • Save torkleyy/8b38bb98978a367c5c281b690334dd54 to your computer and use it in GitHub Desktop.
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
//! 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