用于访问 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)
为了澄清“移动”消息:您正在使用的 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)
在其他语言和框架中,常见的模式是让单个对象代表外围设备,具有改变该对象的方法,然后将您可以使用该外围设备执行的所有可能操作作为方法。代码最终可能如下所示:
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)
| 归档时间: |
|
| 查看次数: |
1640 次 |
| 最近记录: |