esp-hal icon indicating copy to clipboard operation
esp-hal copied to clipboard

Allow changing UART configuration after splitting

Open Szybet opened this issue 3 months ago • 5 comments

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

Szybet avatar Sep 18 '25 13:09 Szybet

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 :)

bugadani avatar Sep 18 '25 13:09 bugadani

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.

Dominaezzz avatar Sep 18 '25 14:09 Dominaezzz

the current API should definitely not be stabilised as is.

It's not going to be, split UART is an unstable feature.

bugadani avatar Sep 18 '25 14:09 bugadani

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

Szybet avatar Sep 22 '25 07:09 Szybet

Yup, that's pretty much what I was gonna sketch up

Dominaezzz avatar Sep 22 '25 07:09 Dominaezzz