How can I make a wrapper function for a STM32 HAL library?

Hi,

I am new to Rust, and I have a question about how I can create a function that wraps up 'block!(tx.write( b'\r' )).ok();' specifically in the code below.

fn main() -> ! {
//...
    let serial = Serial::usart2( p.USART2, (tx, rx), 115_200.bps(), clocks, &mut rcc.apb1r1 );
    let ( mut tx, mut _rx ) = serial.split();

    block!(tx.write( b'\r' )).ok();
//...
}

The code in the above is from an example of STM32 HAL, I would have made a function that takes a reference to either serial or tx to call tx.write( ) inside something like :

fn send_byte<T>( tx : &mut T, byte : u8 ) {
    block!(tx.write( byte )).ok();
}

However, for both being generic types, it's so difficult to implement such a wrapper due mainly to my lack of experience and knowledge in Rust.

Here's additional information:

Can anyone suggest a simple way to do that?

Thanks,

please be specific or provide a link. there are a many different flavored HALs for stm32, and each has their own opinionated style of doing things.

without any information about the library, I would suggest the "extract function" code action of rust-analyzer. check your editor's document on how to activate LSP code actions, for example, in vscode default key binding is CTRL . ("ctrl" and "dot"), you select the line and trigger the code action, there' should be a pop up menu with an entry of "extract into function" or similar.

Here's the link to the example source code in GitHub:

Also, I've tried this,

use crate::hal::serial;
//...
fn send_bytes_wait<T: serial::Write<u8>>( tx: T, byte: u8) {
    tx.write( b'\n' );
}

but what the compiler says is:

84 | fn send_bytes_wait<T: serial::Write<u8>>( tx: T, byte: u8) {
   |                               ^^^^^ private trait

As it indicates, it seems like trying to make use of that trait itself is not allowed being a private one. Now I wonder if there will be any realistic simple solution to what I want to have.

Looking back at them again, I don't understand why trying to call write() through a separate function, which I think does make sense, isn't allowed, while it's allowed to call tx.write() in the main. That may just be due to lack of knowledge though. But compared to C++, it's weird since it's like calling protected method where it's created is allowed but not through a function elsewhere.

ok, so it's this stm32l4xx crate. TLDR in this case: use embedded_hal::serial::Write as bounds for the tx.write() method.


how to create a wrapper anyway

from the document, the return type of serial.split() is (Tx<_>, Rx<_>) where the Tx<_> and Rx<_> types are parameterized with a marker type to select the corresponding hardware peripheral.

from the definition of Tx type, the selector/marker types doesn't have any trait bounds:

so if you want to take a Tx<_> argument, just need a generic type without any bounds, something like:

fn send<S>(tx: &mut Tx<S>) {
}

but you cannot do anything with this type signature, because the methods are implemented directly on concrete type Tx<USART1> and Tx<USART2>, not generically. so you must also implement your function over the concrete types directly.

but it's still possible if you insist, it's just a little bit complicated.

unlike c++, rust doesn't allow overloading function name, nor does it allow a generic function to have specialized implementation on concrete type, i.e. the following is INVALID:

fn send_byte<S>(tx: &mut Tx<S>, byte: u8);
fn send_byte<>(tx: &mut Tx<USART1>, byte: u8) {}
fn send_byte<>(tx: &mut Tx<USART2>, byte: u8) {}

but you can define your wrapper "function" as an associated function on traits, and implement the trait on the types:

// this is like to declare a function which can be overloaded over the `Self` type
trait SendByte {
    fn send_byte(&mut self, byte: u8);
}
// implementation
impl SendByte for Tx<USART1> {
    fn send_byte(&mut self, byte: u8) {
        // here we have concrete Self: Tx<USART1>
        // so we can do whatever the type supports
        block!(self.write(byte)).ok();
    }
}
impl SendByte for Tx<USART2> {
    //...
}

if you like, you can provide a plain (i.e. non-associated) function wrapper using the trait we just defined above, something like this:

fn send_byte<Tx: SendByte>(tx: &mut Tx, byte: u8) {
    tx.send_byte(byte)
}
1 Like

that is not a private trait defined in that module, it's a an (privately) imported trait from an external crate embedded-hal:

it is not re-exported (i.e. use vs pub use) so if you want to use that trait, you'll have to import the embedded-hal::serial::Write instead.

just be sure to use the same version as the one used by this stm32 crate.

1 Like

The way you suggested worked fine for me!

fn main() -> ! {

//...
   loop {
      //...
      send_byte( &mut tx, num );
      //...
   }
}

//...
trait SendByte {
    fn send_byte(&mut self, byte: u8);
}

impl SendByte for Tx<USART2> {
    fn send_byte(&mut self, byte: u8) {
        // here we have concrete Self: Tx<USART1>
        // so we can do whatever the type supports
        block!(self.write(byte)).ok();
    }
}

fn send_byte<Tx: SendByte>(tx: &mut Tx, byte: u8) {
    tx.send_byte(byte);
}

Thank you so much for your help!!

the trait and associated function is only needed if you want to provide a unified (overloaded, or feels like "generic") function name for multiple implementations. if you only ever need a wrapper for the single Tx<USART2> type, you can use it directly, because it's a concrete type (not generic).

what I described is a common trick to emulate function name overloading (similar to C++), the most common case is to dispatch on the type of a single argument. it makes sense in this particular situation, because how the Tx<_> types are only implemented over concrete parameter types, but I do not recommend to use this trick for arbitrary "overloading" of unrelated types.

1 Like

Okay, I will try to keep that in mind. Maybe I can get to the real meaning of what you've said more working on Rust going forward. Thank you again for the help!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.