Allow changing UART configuration after splitting
Related to: https://github.com/esp-rs/esp-hal/pull/4039
I'm looking for a way (which will be accepted in a pull request) to apply config to uart after splitting it. Currently it's a lie, doesn't apply the whole config, just rx, tx related parts: https://docs.espressif.com/projects/rust/esp-hal/1.0.0-rc.0/esp32c6/src/esp_hal/uart.rs.html#1125-1135
One such method: https://github.com/esp-rs/esp-hal/pull/4039#issuecomment-3252227340
I think we have considered a function that takes both halves of the UART peripheral, and I don't think it is the best solution. Usually you want to split the peripheral to send it to two different contexts (tasks, threads, whatever), so gathering them back is a problem.
I personally still think the different halves should take different config structs. But as I was voted down originally, let's see what others have to say, too :)
But as I was voted down originally, let's see what others have to say, too :)
Oh Did this happen in private (outside GitHub)? I had assumed it was an oversight, the current API should definitely not be stabilised as is.
so gathering them back is a problem.
I think this can be solved with two mutexes. I'll sketch up something when my free time returns to me.
the current API should definitely not be stabilised as is.
It's not going to be, split UART is an unstable feature.
With mutexes something like (just a incomplete sketch):
//! embassy serial
//!
//! This is an example of running the embassy executor and asynchronously
//! writing to and reading from UART.
#![no_std]
#![no_main]
use embassy_executor::Spawner;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex, signal::Signal};
use esp_backtrace as _;
use esp_hal::{
Async,
timer::timg::TimerGroup,
uart::{AtCmdConfig, Config, RxConfig, Uart, UartRx, UartTx},
};
use static_cell::StaticCell;
// fifo_full_threshold (RX)
const READ_BUF_SIZE: usize = 64;
// EOT (CTRL-D)
const AT_CMD: u8 = 0x04;
struct UartRxWrapper(UartRx<'static, Async>);
struct UartTxWrapper(UartTx<'static, Async>);
unsafe impl Send for UartRxWrapper {}
unsafe impl Send for UartTxWrapper {}
static RX_LOCK: Mutex<CriticalSectionRawMutex, Option<UartRxWrapper>> = Mutex::new(None);
static TX_LOCK: Mutex<CriticalSectionRawMutex, Option<UartTxWrapper>> = Mutex::new(None);
static RX_TX_CONFIG_CHANGE_SIGNAL = Signal::new();
esp_bootloader_esp_idf::esp_app_desc!();
#[embassy_executor::task]
async fn writer(signal: &'static Signal<NoopRawMutex, usize>) {
let mut tx_lock = TX_LOCK.lock().await;
loop { select(
async
use core::fmt::Write;
let tx_option = tx_lock.take();
let mut tx = tx_option.unwrap();
loop {
let bytes_read = signal.wait().await;
signal.reset();
write!(tx.0, "\r\n-- received {} bytes --\r\n", bytes_read).unwrap();
embedded_io_async::Write::flush(&mut tx.0).await.unwrap();
*tx_lock = Some(tx);
}
}, RX_TX_CONFIG_CHANGE_SIGNAL).await
}
//robimy drop tx jak jest signal, później przyjmuje go z poworotem
}
#[embassy_executor::task]
async fn reader(signal: &'static Signal<NoopRawMutex, usize>) {
const MAX_BUFFER_SIZE: usize = 10 * READ_BUF_SIZE + 16;
//tu tak samo
let mut rbuf: [u8; MAX_BUFFER_SIZE] = [0u8; MAX_BUFFER_SIZE];
let mut offset = 0;
loop {
let mut rx_lock = RX_LOCK.lock().await;
let rx_option = rx_lock.take();
let mut rx = rx_option.unwrap();
let r = embedded_io_async::Read::read(&mut rx.0, &mut rbuf[offset..]).await;
match r {
Ok(len) => {
offset += len;
esp_println::println!("Read: {len}, data: {:?}", &rbuf[..offset]);
offset = 0;
signal.signal(len);
}
Err(e) => esp_println::println!("RX Error: {:?}", e),
}
*rx_lock = Some(rx);
}
}
#[esp_hal_embassy::main]
async fn main(spawner: Spawner) {
esp_println::println!("Init!");
esp_println::logger::init_logger_from_env();
let peripherals = esp_hal::init(esp_hal::Config::default());
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_hal_embassy::init(timg0.timer0);
// Default pins for Uart communication
cfg_if::cfg_if! {
if #[cfg(feature = "esp32")] {
let (tx_pin, rx_pin) = (peripherals.GPIO1, peripherals.GPIO3);
} else if #[cfg(feature = "esp32c2")] {
let (tx_pin, rx_pin) = (peripherals.GPIO20, peripherals.GPIO19);
} else if #[cfg(feature = "esp32c3")] {
let (tx_pin, rx_pin) = (peripherals.GPIO21, peripherals.GPIO20);
} else if #[cfg(feature = "esp32c6")] {
let (tx_pin, rx_pin) = (peripherals.GPIO16, peripherals.GPIO17);
} else if #[cfg(feature = "esp32h2")] {
let (tx_pin, rx_pin) = (peripherals.GPIO24, peripherals.GPIO23);
} else if #[cfg(any(feature = "esp32s2", feature = "esp32s3"))] {
let (tx_pin, rx_pin) = (peripherals.GPIO43, peripherals.GPIO44);
}
}
let config = Config::default()
.with_rx(RxConfig::default().with_fifo_full_threshold(READ_BUF_SIZE as u16));
let mut uart0 = Uart::new(peripherals.UART0, config)
.unwrap()
.with_tx(tx_pin)
.with_rx(rx_pin)
.into_async();
uart0.set_at_cmd(AtCmdConfig::default().with_cmd_char(AT_CMD));
let (rx, tx) = uart0.split();
static SIGNAL: StaticCell<Signal<NoopRawMutex, usize>> = StaticCell::new();
let signal = &*SIGNAL.init(Signal::new());
{
let mut rx_lock = RX_LOCK.lock().await;
*rx_lock = Some(UartRxWrapper(rx));
let mut tx_lock = TX_LOCK.lock().await;
*tx_lock = Some(UartTxWrapper(tx));
}
spawner.spawn(reader(signal)).ok();
spawner.spawn(writer(signal)).ok();
// Something like
/*
RX_TX_CONFIG_CHANGE_SIGNAL.signal();
Timer::after_secs(20).await;
let mut rx_lock = RX_LOCK.lock().await;
let rx_option = rx_lock.take();
let mut rx = rx_option.unwrap();
let mut tx_lock = TX_LOCK.lock().await;
let tx_option = tx_lock.take();
let mut tx = tx_option.unwrap();
Uart::split_apply_config(&mut rx.0, &mut tx.0);
*tx_lock = Some(tx);
*rx_lock = Some(rx);
*/
}
So signal releases the rx,tx from the mutex and allows to gather them together to change them
Yup, that's pretty much what I was gonna sketch up