Can you pass GPIO configs into independent functions using STM32 Hal crates?

Hello,
Im trying to write a fuction that reads the value of encoders(they need to be read only once hence no interrupts), but i get something like this


the function itself looks like this

I've tried following the embedded rust book concurrency example,with mutex but it's not quite similar and doesn't use HAL
Would this work in RTIC as a software task?

please paste code and error messages in text format. screenshots are not easy to read on some devices.

the HAL make use of the linearity of rust type system to encode hardware resources. typically, you split the type into individual parts and then configure them. the types in HAL crates are most used at initialization phase and configuration phase, after that, you should not use them directly (at least not as a whole structure)

so if your function takes a reference to the GPIO types as argument, you are doing it wrong.

it's certainly possible to use RTIC, but you need to structure your code according to RTIC architecture, most importantly, you should think carefully what resources are owned by which tasks, and how to share resources between tasks. normally, the init task don't need to shared resources with running tasks, but instead returns configured resource by value, and the owner task of the resource will declare its ownership in its local resource description.

Thank you for quick answer,

pub fn configure_encoders(gpioe: &stm32f1xx_hal::gpio::gpioe::Parts, gpiob: &stm32f1xx_hal::gpio::gpiob::Parts, gpioc: &stm32f1xx_hal::gpio::gpioc::Parts)->(u8){
    let mut value:u8=0;   
    let encoderdata_1=gpioe.pe6.into_pull_up_input(&mut gpioe.crl);
    let encoderdata_2=gpioc.pc15.into_pull_up_input(&mut gpioc.crh);
    let encoderdata_3=gpioc.pc14.into_pull_up_input(&mut gpioc.crh);
    let encoderdata_4=gpioc.pc13.into_pull_up_input(&mut gpioc.crh);
    let encoderdata_5=gpioe.pe2.into_pull_up_input(&mut gpioe.crl);
    let encoderdata_6=gpioe.pe5.into_pull_up_input(&mut gpioe.crl);
    let encoderdata_7=gpioe.pe4.into_pull_up_input(&mut gpioe.crl);
    let encoderdata_8=gpioe.pe3.into_pull_up_input(&mut gpioe.crl);
    if encoderdata_1.is_high(){value=value|1<<7;}
    if encoderdata_2.is_high(){ value=value|1<<6}
    if encoderdata_3.is_high(){value=value|1<<5}
    if encoderdata_4.is_high(){value=value|1<<4}
    if encoderdata_5.is_high(){value=value|1<<3}
    if encoderdata_6.is_high(){value=value|1<<2}
    if encoderdata_7.is_high(){value=value|1<<1}
    if encoderdata_8.is_high(){value=value|1}
    value=!value;
    return value;
}

That is the function code,

stm32f1xx_hal::gpio::Pin::<P, N, MODE>::into_pull_up_input` takes ownership of the receiver `self`, which moves `gpioe.pe6
That's the error message, it applies to every pin in the function.

ok, I'm not familiar with the stm32 hardware resources, but let me try to guess from your code.

as I said, the gpioe::Parts type should only be used during intialization and functional configuration, and you do it by move pins from it. for example, you should do something like this in the initialization code:

struct MyEncoder {
    pin1: PE6<Input<Pullup>>,
    pin2: PC15<Input<Pullup>>,
    //....
}
fn configure_encoder(
    pin1: PE6<Input<Floating>>,
    pin2: PC15<Input<Floating>>,
    //...
) -> MyEncoder {
    todo!()
}
fn main() {
    let periph = pac::Peripherals::take().unwrap();
    let gpioe = periph.GPIOE.split();
    let gpioc = periph.GPIOC.split();
    //...
    let encoder = configure_encoder(
            gpioe.pe6.into_pull_up_input(&mut gpioe.crl),
            gpioc.pc15.into_pull_up_input(&mut gpioc.crh),
            //...
            );
}

then you can write your encoder driver with the custom type MyEncoder:

pub fn read_data(encoder: &mut MyEncoder) -> u8 {
    let mut value = 8u8;
    if encoder.pin1.is_high() { value |= 0x80; }
    if encoder.pin1.is_high() { value |= 0x40; }
    //...
    value
}

technically you don't have to define a struct, you can always use these individual pins directly, but it's tedious and error prone to spell out 8 pins every time you call them:

pub fn configure_encoder(
    pin1: PE6<Input<Floating>>,
    pin2: PC15<Input<Floating>>,
    //...
) -> (
    PE6<Input<Pullup>>,
    PC15<Input<Pullup>>
    //...
) {
    todo!()
}
pub fn read_encoder_data(
    pin1: &mut PE6<Input<Pullup>>,
    pin2: &mut PC15<Input<Pullup>>,
    //...
) -> u8 {
    //...
}

they key point is, you can turn from a Pin<P, N, Mode1> to Pin<P, N, Mode2>, but only once, then the Mode1 pin disappears, and you have a Mode2 pin. this is called linear type. so when you configure the pins, you should take them by value (i.e. move) and return pins with different types. but for the operational function, you should take them by exclusive reference (i.e. mut reference) so the compiler can check it's not possible to access them concurrently.

it's a bit complicated than your typical embeded C programs, but once you write the type write, a vast portion of common mistakes when using C is now checked by the compiler and guaranteed to work.

1 Like