访问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();
}编译器抱怨在into_push_pull_output()之后,p12已经“移动”了。
目标是能够动态地将一个GPIO引脚从输出切换到输入。
如何在Rust中实现这一点?
按照马克的回答编辑(见下文),这是用STM32G031J SO-8的3个GPIO引脚对6个LED进行充电的STM32G0代码
#![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();
}
}发布于 2021-01-18 01:30:50
为什么p12要“搬家”?
为了澄清"move“消息:您正在使用的API (例如,into_push_pull_output()函数)具有以下签名:
pub fn into_push_pull_output(
self,
moder: &mut MODER,
otyper: &mut OTYPER
) -> PA12<Output<PushPull>>这里有很多东西,但需要注意的是,第一个参数是self,而不是&self或&mut self。这意味着如果调用p12.into_push_pull_output(),则值p12的ownership将从您的函数中转移到into_push_pull_output函数中。由于所有权已转移,因此您正在编写的函数不再可以使用p12的值。换句话说,即使没有循环,也可能会遇到以下错误:
let p12 = gpioa.pa12;
p12.into_push_pull_output().set_low().unwrap();
return p12; // ERROR! `p12` has already moved!但是为什么开发人员要制作一个能阻止我使用p12值的API呢?
在其他语言和框架中,一种常见的模式是让单个对象表示外围设备,具有改变该对象的方法,然后将可以使用该外围设备做的所有可能的事情都作为方法。代码最终可能如下所示:
let mut p12 = gpioa.pa12;
p12.into_floating_input();
p12.set_low();这段代码有一个问题。如果IO引脚处于“output”状态,set_low可能是一个有效的操作,但如果代码试图在IO引脚处于输入状态时运行此操作,则可能是一个错误。理想情况下,我们希望能够在编译时检测到此类问题。
许多Rust嵌入式项目中的API将在编译时发现这些类型的错误,而不是使用“类型状态”模式。这将使用Rust语言的所有权特性来强制您在切换状态时获取新值。该新值可能具有不同的类型,并且该新类型将只具有对管脚的状态有效的方法。
例如,在调用into_floating_input()之后,您的管脚将由类型为PA12<Input<Floating>>的值表示。同样,在调用into_push_pull_output()之后,您的管脚将由类型为PA12<Output<PushPull>>的值表示。这两种类型为它们实现了不同的方法。
因此,使用许多用于Rust的嵌入式API将要求您在编写代码时改变思维方式。您可以使用多个p12值来表示引脚,而不是使用p12值来表示引脚处于特定状态。Rust编译器将确保一次只有一个这样的值在运行,并且仅当pin处于特定状态时才允许调用任何方法。从这个角度看,我上面展示的有buggy的代码是在编译时捕获的:
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();如果您还没有,请查看Rust Embedded Book中的“静态保证”一章,了解有关此模式的更多信息。
好的,这听起来很好,但是我如何在循环中使用它呢?
要做到这一点,一种简单的方法是为备用状态定义一个起始变量,然后确保在循环结束时将一个值重新分配回起始状态变量。当然,这个值需要具有正确的类型(即,引脚需要处于相同的“状态”)。
// 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();
}https://stackoverflow.com/questions/65755746
复制相似问题