Generics in STM32 HAL RTIC

Hello!
I have a problem with generics in Rust. I would like to add input to the vector.

Example 1:

[..]
let d_in1 = gpiod.pd7.into_floating_input(&mut gpiod.crl);
let d_in2 = gpiod.pd6.into_floating_input(&mut gpiod.crl);
let v = [d_in1, d_in2];

Error:

    |
200 |                 let v = [d_in1, d_in2];
    |                                 ^^^^^ expected `7`, found `6`
    |
    = note: expected struct `stm32f1xx_hal::gpio::Pin<_, _, 7>`
               found struct `stm32f1xx_hal::gpio::Pin<_, _, 6>`

Example 2:

I have this struct:

#[derive(Debug)]
pub struct InputsOperationStrategy<I, E, const N: usize>
where
    E: 'static,
    I: InputPin<Error = E>,
{
    inputs: heapless::Vec<I, N>,
}

impl<I, E, const N: usize> InputsOperationStrategy<I, E, N>
where
    E: 'static,
    I: InputPin<Error = E>,
{
    pub fn new() -> Self {
        Self {
            inputs: heapless::Vec::new(),
        }
    }

    pub fn push_input(&mut self, input: I) {
        let _ = self.inputs.push(input);
    }
}

and now I would like to push input to the struct (field inputs vector):

      let mut inputs = InputsOperationStrategy::new();
      inputs.push_input(d_in1);
      inputs.push_input(d_in2);

Error:

200 |                 inputs.push_input(d_in1);
    |                 -            ----- this argument has type `stm32f1xx_hal::gpio::Pin<'D', 7>`...
    |                 |
    |                 ... which causes `inputs` to have type `operations::InputsOperationStrategy<stm32f1xx_hal::gpio::Pin<'D', 7>, core::convert::Infallible, _>`
201 |                 inputs.push_input(d_in2);
    |                   ---------- ^^^^^ expected `7`, found `6`
    |                   |
    |                   arguments to this method are incorrect

How to do it?
Please help!

Full example:

/*
[dependencies]
cortex-m-rtic = "1.1.4"
embedded-hal = "0.2.6"
heapless = "0.7.16"
panic-halt = "0.2.0"

[dependencies.stm32f1xx-hal]
version = "0.10.0"
features = ["rt", "stm32f103", "high"]
[features]
test = []

[build]
target = "thumbv7m-none-eabi"
*/

#![deny(unsafe_code)]
#![no_std]
#![no_main]

#[cfg(not(test))]
use panic_halt as _;

use embedded_hal::digital::v2::InputPin;

#[derive(Debug)]
pub struct InputsOperationStrategy<I, E, const N: usize>
where
    E: 'static,
    I: InputPin<Error = E>,
{
    inputs: heapless::Vec<I, N>,
}

impl<I, E, const N: usize> InputsOperationStrategy<I, E, N>
where
    E: 'static,
    I: InputPin<Error = E>,
{
    pub fn new() -> Self {
        Self {
            inputs: heapless::Vec::new(),
        }
    }

    pub fn push_input(&mut self, input: I) {
        let _ = self.inputs.push(input);
    }
}

#[rtic::app(device = stm32f1xx_hal::pac)]
mod app {
    #[shared]
    struct Shared {}

    #[local]
    struct Local {}

    #[init]
    fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
        let mut flash = cx.device.FLASH.constrain();
        let rcc = cx.device.RCC.constrain();
        let clocks = rcc
            .cfgr
            .use_hse(8.MHz())
            .sysclk(72.MHz())
            .pclk1(36.MHz())
            .pclk2(18.MHz())
            .hclk(72.MHz())
            .freeze(&mut flash.acr);
        let mut gpiod = cx.device.GPIOD.split();

        let in1 = gpiod.pd7.into_floating_input(&mut gpiod.crl);
        let in2 = gpiod.pd6.into_floating_input(&mut gpiod.crl);

        let mut inputs = InputsOperationStrategy::new();

        inputs.push_input(in1);
        inputs.push_input(in2);
        
        (Shared {}, Local {}, init::Monotonics())
    }
}

Vec<T> (including the one supplied by heapless) is what is called an "homogeneous collection". This is a fancy term that means "everything in this collection is of exactly the same type."

InputPin is a trait, which is implemented by your HAL implementation (stmf1xx-hal)... And that crate implements InputPin for its own Pin type, which has generic parameters. One of these parameters, const N is the pin number. Because of this, the HAL pins that you are referencing are not the same type. They cannot be inserted into an homogeneous collection with static dispatch.

They can be inserted into an homogeneous collection with dynamic dispatch through trait objects. See The Book for details. Some additional restrictions apply (object safety, but you don't have to worry about it because InputPin is object-safe). There are three ways to create trait objects:

  1. Box<dyn Trait>
  2. &dyn Trait
  3. &mut dyn Trait

It looks like you are passing ownership to the collection. If that is the case, you will need to use Box:

#[derive(Debug)]
pub struct InputsOperationStrategy<E, const N: usize>
where
    E: 'static,
{
    inputs: heapless::Vec<Box<dyn InputPin<Error = E>>, N>,
}

// usage ...
fn test() {
    let mut inputs = InputsOperationStrategy::new();
    inputs.push_input(Box::new(d_in1));
    inputs.push_input(Box::new(d_in2));

    // Do something with `inputs` ...
}

If you don't want to use Box, then your InputOperationStrategy will need to make public the lifetime of the loans it contains:

#[derive(Debug)]
pub struct InputsOperationStrategy<'a, E, const N: usize>
where
    E: 'static,
{
    inputs: heapless::Vec<&'a dyn InputPin<Error = E>, N>,
}

// usage ...
fn test() {
    let mut inputs = InputsOperationStrategy::new();
    inputs.push_input(&d_in1);
    inputs.push_input(&d_in2);

    // Do something with `inputs`
}

The problem with this is that you cannot return inputs from the function where it is created (that would drop the pins that it references, when they go out of scope). So your inputs needs to be declared at the top level of your call stack (e.g. before entering the main loop). Or you could statically allocate it. Both of these have ergonomics challenges. [1]


  1. And all loans have a lower-bound lifetime of 'a, so you will be limited to where in the call stack they can be pushed to the vector... It's not a good idea to try to use this "dynamically" as you can when passing ownership to the vector. ↩ī¸Ž

1 Like

Thanks for your reply.
I found a simpler solution.

use stm32f1xx_hal::gpio::ErasedPin;
let mut v: Vec<ErasedPin<stm32f1xx_hal::gpio::Input>, 2> = heapless::Vec::new();
v.push(d_in1.erase());
v.push(d_in2.erase());

@parasyte
Do you use heapless::pool::Box or classic Box in your solution?
Because I have:

cannot find type `Box` in this scope
help: consider importing one of these items
    |
28  +     use heapless::pool::Box;
    |
28  +     use heapless::pool::singleton::Box;

I can't use &dyn Trait because:

 #[shared]
    struct Shared {
       my_trait: &'a dyn MyTrait
   }

error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)`
   --> app/src/main.rs:109:8
    |
109 |     fn init(cx: init::Context) -> (Shared<'a>, Local, init::Monotonics) {

Box requires alloc. (See my answer in your other thread.)

The heapless Box will not work because dyn Trait is unsized and their Box impls do not have a ?Sized bound.

OK. Thanks. I created two solutions.
Solution1 (enum) - github
Solution2 (function ptr) - github
Which is better?

For what purpose am I evaluating which of those options is better?

more clean. more rustic. :wink:

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.