Rust 嵌入将 GPIO 引脚从输出更改为输入

Zhe*_* Hu 3 embedded rust

用于访问 GPIO 的 Rust STM32xxx_hal 代码片段

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();

}

Run Code Online (Sandbox Code Playgroud)

编译器抱怨into_push_pull_output()p12 已“移动”。

目标是能够将一个 GPIO 引脚从输出动态切换为输入。

如何在 Rust 中实现这一点?

[编辑]在遵循Mark的(见下文)答案后,这里是STM32G0代码,用于Charlieplexing具有STM32G031J SO-8的3个GPIO引脚的6个LED

#![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();

    }
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*eth 5

为什么p12“搬家”了

为了澄清“移动”消息:您正在使用的 API(例如函数into_push_pull_output())具有以下签名:

pub fn into_push_pull_output(
    self,
    moder: &mut MODER,
    otyper: &mut OTYPER
) -> PA12<Output<PushPull>>
Run Code Online (Sandbox Code Playgroud)

其中有很多内容,但需要注意的重要一点是第一个参数是self,而不是&selfor &mut self。这意味着如果您调用p12.into_push_pull_output(),那么值的所有权p12将从您的函数转移到该into_push_pull_output函数中。由于所有权已转移,p12您正在编写的函数不再可以使用 的值。换句话说,即使没有循环,您也可能会遇到以下错误:

let p12 = gpioa.pa12;
p12.into_push_pull_output().set_low().unwrap();
return p12; // ERROR! `p12` has already moved!
Run Code Online (Sandbox Code Playgroud)

但为什么开发人员要创建一个 API 来阻止我使用 p12 值呢?

在其他语言和框架中,常见的模式是让单个对象代表外围设备,具有改变该对象的方法,然后将您可以使用该外围设备执行的所有可能操作作为方法。代码最终可能如下所示:

let mut p12 = gpioa.pa12;
p12.into_floating_input();
p12.set_low();
Run Code Online (Sandbox Code Playgroud)

这段代码有问题。虽然set_low如果 IO 引脚处于“输出”状态,这可能是一个有效的操作,但如果代码尝试在 IO 引脚处于输入状态时运行此操作,则可能是一个错误。理想情况下,我们希望能够在编译时检测此类问题。

许多 Rust 嵌入式项目中的 API 将在编译时发现这些类型的错误,而不是使用“类型状态”模式。这将使用 Rust 语言的所有权功能来强制您在切换状态时获得新值。该新值可能具有不同的类型,并且该新类型将仅具有对引脚状态有效的方法。

例如,调用 后into_floating_input(),您的 pin 将由类型为 的值表示PA12<Input<Floating>>。同样,调用 后into_push_pull_output(),您的 pin 将由类型为 的值表示PA12<Output<PushPull>>。这两种类型有不同的实现方法。

因此,使用 Rust 的许多嵌入式 API 会要求您在编写代码时改变思维方式。p12您可能有多个值,p12每个值代表处于特定状态的引脚,而不是使用一个代表引脚的值。Rust 编译器将确保您一次只有一个这样的值运行,并且只有当引脚处于特定状态时才允许您调用的任何方法。从这个角度来看,我上面展示的有缺陷的代码在编译时就被捕获了:

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();
Run Code Online (Sandbox Code Playgroud)

如果您还没有,请查看Rust 嵌入式书籍中的“静态保证”一章,了解有关此模式的更多信息。

好吧,这听起来很不错,但是我该如何循环使用它呢?

一种简单的方法是为备用状态定义一个起始变量,然后确保在循环结束时将值重新分配回起始状态变量。当然,这个值需要具有正确的类型(即引脚需要处于相同的“状态”)。

// 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();
}
Run Code Online (Sandbox Code Playgroud)