首先在 cargo.toml
的依赖项 [dependencies]
下增加一个依赖。
stm32f103xx-hal = { git = "https://gitee.com/EmergingTechnologies/stm32f103xx-hal.git" }
这是一个开源 hal 库。
然后我在 examples/led.rs
里添加了一个学习用例。
#![no_std]
#![no_main]
extern crate panic_halt; // 如是不引入此库,则要自己实现一个 panic 处理函数。好像
use cortex_m::asm;
use cortex_m_rt::entry;
use stm32f103xx_hal as hal;
use hal::device;
use hal::prelude::*;
#[entry]
fn main() -> ! {
asm::nop(); // To not have main optimize to do
let p = device::Peripherals::take().unwrap();
let mut rcc = p.RCC.constrain();
let mut gpiob = p.GPIOB.split(&mut rcc.apb2);
let mut led_red = gpiob.pb5.into_push_pull_output(&mut gpiob.crl);
let mut led_green= gpiob.pb0.into_push_pull_output(&mut gpiob.crl);
let mut led_blue= gpiob.pb1.into_push_pull_output(&mut gpiob.crl);
led_green.set_low();
led_blue.set_high();
led_red.set_high();
loop {
}
}
大部内容网上或 B 站都有解释,此我就不作过多表述了。
我主要想记录 GPIO 的使用,以及一些分享经验。
device::Peripherals::take().unwrap()
是创建一个外设实例。请注意,其为单例模式,即其在整个程序运行过程,只产生一次,源码是这样写的。因此 take()
返回的是一个 Option
类型。
p.RCC.constrain()
可以理解为获取一个整理过的 Rcc
实例。注意,经过 constrain()
后,RCC 变成了 Rcc。为何如此 "多此一举" 。开始我也不明,直到源码里有这样一句话,此函数是把 RCC 适配为其他库使用的形式。也就是说,此函数是适配器的作用。
p.GPIOB.split(&mut gpiob.crl)
与上例是一样的功能。
接下来,就是操作 GPIO 了。
gpiob.pb5.into_push_pull_output(&mut gpiob.crl)
设置的 pin 引脚的工作模式。
请仔细看 led_red
变量前的修饰符为 mut
。我未看过源码,不知其内部是否含有状态。但可以这样理解,led.set_high()
是要改变 LED 的状态,所以此处声明为 mut
,则与实际状态可变相对应。
另外在 vscode 里,提示 set_high()
为不推荐使用,应使用 data::v2
里的。其意思是,此库里 set_high()
是来自另一个库里的 traits
里(忘记哪个了),但这个 traits
有一个问题,是不会返回失败情况(好像是吧),所以该库又推出了一个 set_high()
的 traits
放在 data::v2
里。
这个说明不一定正确,你最好自己看源码。
注: 在学习此 hal 库使用时,我发现了一个误区,就是 别以为引入一个类型,就能用他所有的方法。下面开始说明。
rust 和 python 在这点上类似,那我先以 python 为例。
当你在一个文件里定义了一个类 class
,那么你在其他文件还可以为这个 class
添加新的方法。例如,
# a.py
class A:
def func1():
...
# b.py
A.func2 = func
# main1.py
import a
a.func1() # 正确
a.func2() # 错误,因为 `A.func2 = func` 未被解释器跑过
# main2.py
import a
import b # 把 A.func2=func 跑了一次,那么 func2 就会添加到 A 的 __dict__ 里
上述代码未经测试,可能内容有错误,但大概意思就该明白了。
同样,Rust 也存在这样说法。因为其 impl
可以在不同位置多次声明的。不过 Rust 存在的孤儿原则,使得其不会如 python 那样乱飞,起码你找其出处时,要不是在类型定义处,要不在 traits 定义处。
下面举一个不知在哪看到的源码,请注意,未经测试
let a = 1.cm();
let a = 50.hz();
从内容可以看出,1.cm()
和 50.hz()
代表的是长度的 1 cm 和频率 1 Hz。
其实现我猜是这样的,
/* length.rs */
pub struct CentreMeter(f32);
pub trait Length {
pub fn cm(&self) -> CentreMeter;
}
impl Length for i32 {
pub fn cm(&self) -> CentreMeter {
CentreMeter(self as f32)
}
}
因此,你只要导入此文件的 Length
,就可以为 i32
增加新的功能了。直接使用 impl i32{}
,不知可不可以。我还未试过。
此网站说明了为什么 Peripherals
被设计成单例模式