Skip to content

Commit

Permalink
Async serial uart read (esp-rs#620)
Browse files Browse the repository at this point in the history
* implement embassy async uart read

* Add embassy async read support for uart

* changes based on review

* fix CI failures

* change review jessebraham#2

* fixed re-opened PR number

* changes review no.3

---------

Co-authored-by: Scott Mabin <[email protected]>
  • Loading branch information
2 people authored and playfulFence committed Sep 20, 2023
1 parent fb4d864 commit 1f74c8b
Show file tree
Hide file tree
Showing 16 changed files with 607 additions and 61 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix rom::crc docs
- Add octal PSRAM support for ESP32-S3 (#610)
- Add MD5 functions from ESP ROM (#618)
- Add embassy async `read` support for `uart` (#620)

### Changed

Expand Down
135 changes: 109 additions & 26 deletions esp-hal-common/src/uart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ const UART_FIFO_SIZE: u16 = 128;

/// Custom serial error type
#[derive(Debug)]
pub enum Error {}
pub enum Error {
#[cfg(feature = "async")]
ReadBufferFull,
}

/// UART configuration
pub mod config {
Expand Down Expand Up @@ -994,11 +997,15 @@ mod asynch {
use core::task::Poll;

use cfg_if::cfg_if;
use embassy_futures::select::select;
use embassy_sync::waitqueue::AtomicWaker;
use procmacros::interrupt;

use super::{Error, Instance};
use crate::{uart::UART_FIFO_SIZE, Uart};
use crate::{
uart::{RegisterBlock, UART_FIFO_SIZE},
Uart,
};

cfg_if! {
if #[cfg(all(uart0, uart1, uart2))] {
Expand All @@ -1016,6 +1023,8 @@ mod asynch {
pub(crate) enum Event {
TxDone,
TxFiFoEmpty,
RxFifoFull,
RxCmdCharDetected,
}

pub(crate) struct UartFuture<'a, T: Instance> {
Expand All @@ -1034,6 +1043,14 @@ mod asynch {
.register_block()
.int_ena
.modify(|_, w| w.txfifo_empty_int_ena().set_bit()),
Event::RxFifoFull => instance
.register_block()
.int_ena
.modify(|_, w| w.rxfifo_full_int_ena().set_bit()),
Event::RxCmdCharDetected => instance
.register_block()
.int_ena
.modify(|_, w| w.at_cmd_char_det_int_ena().set_bit()),
}

Self { event, instance }
Expand All @@ -1055,6 +1072,20 @@ mod asynch {
.read()
.txfifo_empty_int_ena()
.bit_is_clear(),
Event::RxFifoFull => self
.instance
.register_block()
.int_ena
.read()
.rxfifo_full_int_ena()
.bit_is_clear(),
Event::RxCmdCharDetected => self
.instance
.register_block()
.int_ena
.read()
.at_cmd_char_det_int_ena()
.bit_is_clear(),
}
}
}
Expand All @@ -1079,11 +1110,42 @@ mod asynch {
where
T: Instance,
{
pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
let mut read_bytes = 0;

select(
UartFuture::new(Event::RxCmdCharDetected, self.inner()),
UartFuture::new(Event::RxFifoFull, self.inner()),
)
.await;

while let Ok(byte) = self.read_byte() {
if read_bytes < buf.len() {
buf[read_bytes] = byte;
read_bytes += 1;
} else {
return Err(Error::ReadBufferFull);
}
}

Ok(read_bytes)
}

async fn write(&mut self, words: &[u8]) -> Result<(), Error> {
for chunk in words.chunks(UART_FIFO_SIZE as usize) {
for &byte in chunk {
self.write_byte(byte).unwrap() // should never fail
let mut offset: usize = 0;
loop {
let mut next_offset =
offset + (UART_FIFO_SIZE - self.uart.get_tx_fifo_count()) as usize;
if next_offset > words.len() {
next_offset = words.len();
}
for &byte in &words[offset..next_offset] {
self.write_byte(byte).unwrap(); // should never fail
}
if next_offset == words.len() {
break;
}
offset = next_offset;
UartFuture::new(Event::TxFiFoEmpty, self.inner()).await;
}
Ok(())
Expand Down Expand Up @@ -1111,42 +1173,63 @@ mod asynch {
}
}

fn intr_handler(uart: &RegisterBlock) -> bool {
let int_ena_val = uart.int_ena.read();
let int_raw_val = uart.int_raw.read();
if int_ena_val.txfifo_empty_int_ena().bit_is_set()
&& int_raw_val.txfifo_empty_int_raw().bit_is_set()
{
uart.int_ena.write(|w| w.txfifo_empty_int_ena().clear_bit());
return true;
}
if int_ena_val.tx_done_int_ena().bit_is_set() && int_raw_val.tx_done_int_raw().bit_is_set()
{
uart.int_ena.write(|w| w.tx_done_int_ena().clear_bit());
return true;
}
if int_ena_val.at_cmd_char_det_int_ena().bit_is_set()
&& int_raw_val.at_cmd_char_det_int_raw().bit_is_set()
{
uart.int_clr
.write(|w| w.at_cmd_char_det_int_clr().set_bit());
uart.int_ena
.write(|w| w.at_cmd_char_det_int_ena().clear_bit());
return true;
}
if int_ena_val.rxfifo_full_int_ena().bit_is_set()
&& int_raw_val.rxfifo_full_int_raw().bit_is_set()
{
uart.int_clr.write(|w| w.rxfifo_full_int_clr().set_bit());
uart.int_ena.write(|w| w.rxfifo_full_int_ena().clear_bit());
return true;
}
false
}

#[cfg(uart0)]
#[interrupt]
fn UART0() {
let uart = unsafe { &*crate::peripherals::UART0::ptr() };
uart.int_ena.modify(|_, w| {
w.txfifo_empty_int_ena()
.clear_bit()
.tx_done_int_ena()
.clear_bit()
});
WAKERS[0].wake();
if intr_handler(uart) {
WAKERS[0].wake();
}
}

#[cfg(uart1)]
#[interrupt]
fn UART1() {
let uart = unsafe { &*crate::peripherals::UART1::ptr() };
uart.int_ena.modify(|_, w| {
w.txfifo_empty_int_ena()
.clear_bit()
.tx_done_int_ena()
.clear_bit()
});
WAKERS[1].wake();
if intr_handler(uart) {
WAKERS[1].wake();
}
}

#[cfg(uart2)]
#[interrupt]
fn UART2() {
let uart = unsafe { &*crate::peripherals::UART2::ptr() };
uart.int_ena.modify(|_, w| {
w.txfifo_empty_int_ena()
.clear_bit()
.tx_done_int_ena()
.clear_bit()
});
WAKERS[2].wake();
if intr_handler(uart) {
WAKERS[2].wake();
}
}
}
1 change: 1 addition & 0 deletions esp32-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ sha2 = { version = "0.10.6", default-features = false}
smart-leds = "0.3.0"
ssd1306 = "0.7.1"
static_cell = "1.0.0"
heapless = "0.7.16"

[features]
default = ["rt", "vectored", "xtal40mhz"]
Expand Down
75 changes: 70 additions & 5 deletions esp32-hal/examples/embassy_serial.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
//! embassy serial
//!
//! This is an example of running the embassy executor and asynchronously
//! writing to a uart.
//! writing to and reading from uart
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]

use core::fmt::Write;

use embassy_executor::Executor;
use embassy_time::{Duration, Timer};
use embassy_time::{with_timeout, Duration};
use esp32_hal::{
clock::ClockControl,
embassy,
Expand All @@ -20,18 +22,79 @@ use esp32_hal::{
Uart,
};
use esp_backtrace as _;
use esp_hal_common::uart::config::AtCmdConfig;
use heapless::Vec;
use static_cell::StaticCell;

// rx_fifo_full_threshold
const READ_BUF_SIZE: usize = 128;
// EOT (CTRL-D)
const AT_CMD: u8 = 0x04;

#[embassy_executor::task]
async fn run(mut uart: Uart<'static, UART0>) {
// max message size to receive
// leave some extra space for AT-CMD characters
const MAX_BUFFER_SIZE: usize = 10 * READ_BUF_SIZE + 16;
// timeout read
const READ_TIMEOUT: Duration = Duration::from_secs(10);

let mut rbuf: Vec<u8, MAX_BUFFER_SIZE> = Vec::new();
let mut wbuf: Vec<u8, MAX_BUFFER_SIZE> = Vec::new();
loop {
embedded_hal_async::serial::Write::write(&mut uart, b"Hello async write!!!\r\n")
if rbuf.is_empty() {
embedded_hal_async::serial::Write::write(
&mut uart,
b"Hello async serial. Enter something ended with EOT (CTRL-D).\r\n",
)
.await
.unwrap();
} else {
wbuf.clear();
write!(&mut wbuf, "\r\n-- received {} bytes --\r\n", rbuf.len()).unwrap();
embedded_hal_async::serial::Write::write(&mut uart, wbuf.as_slice())
.await
.unwrap();
embedded_hal_async::serial::Write::write(&mut uart, rbuf.as_slice())
.await
.unwrap();
embedded_hal_async::serial::Write::write(&mut uart, b"\r\n")
.await
.unwrap();
}
embedded_hal_async::serial::Write::flush(&mut uart)
.await
.unwrap();
Timer::after(Duration::from_millis(1_000)).await;

// set rbuf full capacity
rbuf.resize_default(rbuf.capacity()).ok();
let mut offset = 0;
loop {
match with_timeout(READ_TIMEOUT, uart.read(&mut rbuf[offset..])).await {
Ok(r) => {
if let Ok(len) = r {
offset += len;
if offset == 0 {
rbuf.truncate(0);
break;
}
// if set_at_cmd is used than stop reading
if len < READ_BUF_SIZE {
rbuf.truncate(offset);
break;
}
} else {
// buffer is full
break;
}
}
Err(_) => {
// Timeout
rbuf.truncate(offset);
break;
}
}
}
}
}

Expand Down Expand Up @@ -66,7 +129,9 @@ fn main() -> ! {
#[cfg(feature = "embassy-time-timg0")]
embassy::init(&clocks, timer_group0.timer0);

let uart0 = Uart::new(peripherals.UART0, &mut system.peripheral_clock_control);
let mut uart0 = Uart::new(peripherals.UART0, &mut system.peripheral_clock_control);
uart0.set_at_cmd(AtCmdConfig::new(None, None, None, AT_CMD, None));
uart0.set_rx_fifo_full_threshold(READ_BUF_SIZE as u16);

interrupt::enable(Interrupt::UART0, interrupt::Priority::Priority1).unwrap();

Expand Down
1 change: 1 addition & 0 deletions esp32c2-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ lis3dh-async = "0.7.0"
sha2 = { version = "0.10.6", default-features = false}
ssd1306 = "0.7.1"
static_cell = "1.0.0"
heapless = "0.7.16"

[features]
default = ["rt", "vectored", "xtal40mhz"]
Expand Down
Loading

0 comments on commit 1f74c8b

Please sign in to comment.