Embedded Driver with UART

Hey,

I wanted to write a driver for a lidar sensor in the context of the embedded HAL async. The driver should be passed a UART. It appears that there is nothing for a UART in the embedded HAL async and you have to use embedded IO async. Are there any examples anywhere that can be used for guidance? For I2C, for example, there is quite a lot to be found. In my current project, I am using the embassy_nrf crate for my hardware abstraction. It seems as if no traits from embedded IO async are used in the embassy_nrf UART module. I'm a little confused about what the idiomatic way is to write a driver with UART in the Rust embedded ecosystem.

Thanks in advance.

I played a little around and figured out how to build a nrf generic driver. Its also compiled and seems to work.

use embassy_nrf::uarte::{Uarte, Instance, Error};

pub struct NrfDriver<'d, T> 
where
    T: Instance
{
    uarte: T,
}

impl<'d, T> nrfDriver<'d, T>
where
    T: Instance,
{
    pub fn new(uarte: T) -> Self {
        Self { uarte }
    }

    pub async fn do_something(&mut self) -> Result<(), Error> {
        let buffer = [0x01, 0x02, 0x03];
        self.uarte.write(&buffer).await?;
        Ok(())
    }
}

I also tried to port the driver to embedded_io_async. But the problem is that the uarte from nrf seems to not implement embedded_io_async. So I could not use the generic driver with the Uarte modul from the embassy_nrf crate.

use embedded_io_async::{Read, Write, ErrorType};

pub enum GenericDriverError<E> {
    Peripheral(E),
    NoAnswerReceived,
}

impl<E> From<E> for GenericDriverError <E> {
    fn from(error: E) -> Self {
        GenericDriverError::Peripheral(error)
    }
}

pub struct GenericDriver<T> 
where
    T: Read + Write + ErrorType
{
    uarte: T,
}

impl<T> GenericDriver<T>
where
    T: Read + Write + ErrorType,
{
    pub fn new(uarte: T) -> Self {
        Self { uarte }
    }

    pub async fn do_something(&mut self) -> Result<(), GenericDriverError <T::Error>> {
        let buffer = [0x01, 0x02, 0x03];
        self.uarte.write(&buffer).await?;
        Ok(())
    }
}

The goal is to use the driver on a nrf and also on a stm32 controller, so I want to make the driver controller agnostic. Does anyone have any idea how this could work?

Uarte does not implement Read, but BufferedUarte does. The documentation explains when each should be used.

So far, I haven't had any real problems with the borrow checker, but the type system in Rust is driving me a little crazy right now. How would I transfer a BufferedUarte or a driver instance to an embassy task? If I understand correctly, there are no generic tasks. In other words, the following things cannot be done:

#[embassy_executor::task]
pub async fn task(uarte: BufferedUarte<'static, U, T>) 
where
    U: UarteInstance,
    T: TimerInstance,
{
    let mut driver = GenericDriver::new(uarte);
    ...
}
#[embassy_executor::task]
pub async fn task(driver: GenericDriver<T>) 
where
   T: Read + Write + ErrorType,
{
   ...
}

I define the peripherals in main.

let p = embassy_nrf::init(Default::default());

Even if I initialize bufferedUarte and the driver within the task, I still have to pass a reference to p somehow?

Maybe you just pass GenericDriver to your task?

#[embassy_executor::task]
async fn driver_task(mut driver: GenericDriver<&'static mut BufferedUarte<'static>>) {
    match driver.do_something().await {
        Ok(()) => info!("Success!"),
        Err(_e) => info!("Error"),
    }
}