在树莓派上玩转 Rust :使用 GPIO 点亮 LED

上一节我们已经配置好了交叉编译环境,并成功运行了“Hello, world!”。不过,我记得在我刚开始学习 Arduino 的时候,也有一个非常经典的程序——Blink,这似乎也是算是 “Hello, world!” ?于是,我们今天的主题是,使用 Rust 点亮 LED。这是 Arduino 的 Blink 代码:

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

接下来我们用 Rust 实现看起来差不多的代码。有两个选择,其一是直接使用 rppalsysfs_gpio 等库,其二是在不借助第三方库的情况下,直接调用 Linux 内核提供的接口——GPIO Sysfs Interface for Userspace。为了一探究竟,我们选择第二种。

接下来,先用ssh登录到树莓派,然后运行以下两条命令:

$ sudo mount -t debugfs debugfs /tmp
$ sudo cat /tmp/gpio
gpiochip0: GPIOs 0-53, parent: platform/20200000.gpio, pinctrl-bcm2835:
 gpio-47  (                    |led0                ) out lo 

debugfs 是一种用于内核调试的虚拟文件系统。通过第一条命令,我们将其挂载到了 /tmp 下面,用后通过查看 gpio 文件的内容,就得到了 gpio的使用状态。可以看到这个树莓派有 0-53gpio,其中gpio-47已经被使用。不过,我们可以使用的也只有0-27号,其他的引脚没有导出,或被用作其他用途。

这是一张树莓派 gpio的对照表,我们按以下电路连好 LED 和电阻(大概470欧姆左右)。

这里用的是gpio23。根据 GPIO Sysfs Interface for Userspace 的说明,我们先要进入 /sys/class/gpio 目录:

$ cd /sys/class/gpio
$ ls
export  gpiochip0  unexport

然后向 export目录写入 23,以便打开gpio23:

$ sudo echo 23 > export
$ ls
export  gpio23  gpiochip0  unexport

然后你会发现出现了一个gpio23目录,进入此目录,再向其中的 direction 文件写入 out,设置gpio模式为输出:

$ cd gpio23
$ sudo echo out > direction

现在,如果你的电路连接没问题的话,我们就可以向value文件写入10分别控制 LED 的亮与灭了:

$ sudo echo 1 > value // 亮
$ sudo echo 0 > value // 灭

使用完毕后,记得关闭:

$ sudo echo 23 > unexport
$ ls
export  gpiochip0  unexport

除了输出,还可以将gpio设置为输入 sudo echo out > direction,通过读取value文件的值是1还是0,而得知此gpio是高电平还是低电平。

接下来,就是使用 Rust 来实现这些步骤。不过,这里我们仅仅实现需要的部分,剩下的留给你探究。

use std::fs;
use std::fs::{File, OpenOptions};
use std::io::{self, Write, Read};
use std::io::ErrorKind::{Other};

#[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord)]
pub enum Direction {
    In,
    Out
}

#[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord)]
#[repr(u8)]
pub enum Value {
    Low = 0,
    High = 1
}

#[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord)]
pub struct Pin {
    pub num: usize
}

impl Pin {
    pub fn new(num: usize) -> Pin {
        Pin { num }
    }

    fn write_sys_file(&self, file_name: &str, value: &str) -> io::Result<()> {
        let path = format!("/sys/class/gpio/gpio{}/{}", self.num, file_name);

        let mut file = OpenOptions::new().write(true).open(&path)?;
        file.write_all(value.as_bytes())?;
        
        Ok(())
    }

    fn read_sys_file(&self, file_name: &str) -> io::Result<String> {
        let path = format!("/sys/class/gpio/gpio{}/{}", self.num, file_name);

        let mut file = File::open(&path)?;
        let mut s = String::new();
        file.read_to_string(&mut s)?;

        Ok(s)
    }

    pub fn is_exported(&self) -> bool {
        fs::metadata(&format!("/sys/class/gpio/gpio{}", self.num)).is_ok()
    }

    pub fn export(&self) -> io::Result<()> {
        if !self.is_exported() {
            let mut file = OpenOptions::new().write(true).open("/sys/class/gpio/export")?;
            file.write_all(format!("{}", self.num).as_bytes())?;
        }

        Ok(())
    }

    pub fn unexport(&self) -> io::Result<()> {
        if self.is_exported() {
            let mut file = OpenOptions::new().write(true).open("/sys/class/gpio/unexport")?;
            file.write_all(format!("{}", self.num).as_bytes())?;
        }

        Ok(())
    }

    pub fn direction(&self) -> io::Result<Direction> {
        match self.read_sys_file("direction") {
            Ok(s) => {
                match s.trim() {
                    "in" => Ok(Direction::In),
                    "out" => Ok(Direction::Out),
                    other => Err(io::Error::new(
                        Other,
                        format!("direction file contents {}", other)
                    ))
                }
            }
            Err(e) => Err(::std::convert::From::from(e)),
        }
    }

    pub fn set_direction(&self, dir: Direction) -> io::Result<()> {
        self.write_sys_file("direction", match dir {
                Direction::In => "in",
                Direction::Out => "out"
            })?;

        Ok(())
    }

    pub fn value(&self) -> io::Result<Value> {
        match self.read_sys_file("value") {
            Ok(s) => {
                match s.trim() {
                    "1" => Ok(Value::High),
                    "0" => Ok(Value::Low),
                    other => Err(io::Error::new(
                        Other,
                        format!("value file contents {}", other)
                    ))
                }
            }
            Err(e) => Err(e),
        }
    }

    pub fn set_value(&self, value: Value) -> io::Result<()> {
        self.write_sys_file("value", match value {
                Value::Low => "0",
                Value::High => "1",
            })?;

        Ok(())
    }
}

接下来,我们就可以用 Rust 点亮 LED了:

fn main() {
    let pin = Pin::new(4);

    pin.export().unwrap();

    pin.set_direction(Direction::Out).unwrap();

    loop {
        pin.set_value(Value::High).unwrap();

        sleep(Duration::from_secs(1));

        pin.set_value(Value::Low).unwrap();

        sleep(Duration::from_secs(1));
    }
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注

85 − = 81