Reputation: 4007
A snippet of Rust STM32xxx_hal code to access GPIO
let gpioa = dp.GPIOA.split(&mut rcc);
let mut p12 = gpioa.pa12;
loop {
p12.into_push_pull_output().set_low().unwrap();
//after some processing, time to switch p12 to input
p12.into_floating_input();
}
The compiler complains that after into_push_pull_output()
p12 has "moved".
The goal is to be able to switch one GPIO pin from output to input dynamically.
How to achieve this in Rust?
[EDIT] After following Mark's (see below) answer, here is the STM32G0 code to Charlieplexing 6 LEDs with 3 GPIO pins of STM32G031J SO-8
#![deny(warnings)]
#![deny(unsafe_code)]
#![no_main]
#![no_std]
// #[allow(unused)]
// use panic_halt;
#[allow(unused)]
use panic_semihosting;
use cortex_m_rt::entry;
use stm32g0xx_hal as hal;
use hal::prelude::*;
use hal::rcc::Config;
use hal::stm32;
#[entry]
fn main() -> ! {
let dp = stm32::Peripherals::take().expect("cannot take peripherals");
let mut rcc = dp.RCC.freeze(Config::lsi());
let mut delay = dp.TIM16.delay(&mut rcc);
let gpioa = dp.GPIOA.split(&mut rcc);
let gpiob = dp.GPIOB.split(&mut rcc);
let mut l3 = gpioa.pa12.into_push_pull_output();
let mut l2 = gpioa.pa11.into_push_pull_output();
let mut l1 = gpiob.pb7.into_push_pull_output();
loop {
let _l1b = l1.into_floating_input();
l3.set_high().unwrap();
l2.set_low().unwrap();
delay.delay(500.ms());
l2.set_high().unwrap();
l3.set_low().unwrap();
delay.delay(500.ms());
l1 = _l1b.into_push_pull_output();
let _l2b = l2.into_floating_input();
l1.set_high().unwrap();
l3.set_low().unwrap();
delay.delay(500.ms());
l3.set_high().unwrap();
l1.set_low().unwrap();
delay.delay(500.ms());
l2 = _l2b.into_push_pull_output();
let _l3b = l3.into_floating_input();
l1.set_high().unwrap();
l2.set_low().unwrap();
delay.delay(500.ms());
l2.set_high().unwrap();
l1.set_low().unwrap();
delay.delay(500.ms());
l3 = _l3b.into_push_pull_output();
}
}
Upvotes: 3
Views: 2143
Reputation: 43061
To clarify the "move" message: the API you are utilizing (e.g., the into_push_pull_output()
function) has the following signature:
pub fn into_push_pull_output(
self,
moder: &mut MODER,
otyper: &mut OTYPER
) -> PA12<Output<PushPull>>
There's a lot there, but the important thing to note is that the first argument is self
, as opposed to &self
or &mut self
. This means that if you call p12.into_push_pull_output()
, then the ownership of value p12
is transferred out of your function and into the into_push_pull_output
function. Since the ownership was transferred, the value of p12
is no longer useable by the function you are writing. In other words, even without a loop, you can run into this error:
let p12 = gpioa.pa12;
p12.into_push_pull_output().set_low().unwrap();
return p12; // ERROR! `p12` has already moved!
In other languages and frameworks, a common pattern is to have a single object represent the peripheral, have methods which mutate that object, and then have all the possible things you can do with that peripheral as methods. The code could end up looking like this:
let mut p12 = gpioa.pa12;
p12.into_floating_input();
p12.set_low();
This code has a problem. While set_low
might be a valid operation if the IO pin is in the "output' state, it is probably a bug if the code is trying to run this operation while the IO pin is in the input state. Ideally we'd like to be able to detect this kind of problem at compile-time.
The API in many Rust embedded projects will find these types of bugs at compile-time instead by using the "type state" pattern. This will use the Rust language's ownership features to force you into getting a new value whenever you switch states. That new value may have a different type, and that new type will only have methods that are valid for the state of the pin.
For example, after calling into_floating_input()
, your pin will be represented by a value with the type PA12<Input<Floating>>
. Likewise, after calling into_push_pull_output()
, your pin will be represented by a value with the type PA12<Output<PushPull>>
. These two types have different methods implemented for them.
Thus, using many of the embedded APIs for Rust will ask of you to change your mindset when writing code. Rather than having a p12
value representing a pin, you might have multiple p12
values, each representing the pin as it is in a specific state. The Rust compiler will make sure that you only have one such value operating at a time, and that any methods you call will only be allowed when the pin is seen in a specific state. When looked at it this way, the buggy code I showed above is caught at compile-time:
let p12 = gpioa.pa12;
// `into_floating_input()` returns a new value, which represents the pin in
// the "floating input" state.
let new_p12 = p12.into_floating_input();
// This will cause a compiler error, since the type `PA12<Input<Floating>>`
// of `new_p12` wouldn't have a `set_low()` method.
new_p12.set_low();
If you haven't already, check out the "Static Guarantees" chapter in the Rust Embedded Book for more on this pattern.
One simple way to do this is to define a starting variable for the alternate state, then make sure at the end of the loop you reassign a value back to the starting state variable. Of course, this value needs to have the correct type (i.e., the pin needs to be in the same "state").
// Define a value which has a specific type `PA12<Output<PushPull>>`
let mut p12 = gpioa.pa12.into_push_pull_output().set_low().unwrap();
loop {
// Use `let` to assign a new value, which has the type `PA12<Input<Floating>>`.
let p12_input = p12.into_floating_input();
// Perform method calls on `p12_input`
// ...
// Get ourselves back to a `PA12<Output<PushPull>>` value.
p12 = p12_input.into_push_pull_output().set_low().unwrap();
}
Upvotes: 5