RustでGPIOの入力を制御するために必要なこととか。

注意:古いバージョンのクレートを使用しています
2022/11/02: 新バージョンで書き直し

開発環境

  • NanoPi NEO(RAM 512MB)
    ※ボタンとOLEDがセットになったキット
  • Rust Version:1.64.0(stable-armv7-unknown-linux-gnueabihf)

準備

/dev/gpiochip0を一般ユーザーが使用できるようにする。 gpioというグループを作成して、そのグループ内のユーザーであれば使用可能にする。

  1. グループ作成
    $sudo addgroup gpio
    
  2. nanopiユーザーをgpioグループに追加
    $sudo usermod -aG gpio nanopi
    
  3. udevルールを適用してgpioグループのユーザーならgpiochip0を使用可能にする
    $sudo nano /etc/udev/rules.d/99-gpiod.rules
    SUBSYSTEM=="gpio", KERNEL=="gpiochip[01]", GROUP="gpio", MODE="0660"
    

コード

Cargo.toml

GPIO制御をするためにgpio-cdevクレート、非同期処理をするのでfuturesクレートtokioクレートを追加する。

[dependencies]
futures = "0.3.25"
tokio = {version = "0.2", features = ["macros"]}
gpio-cdev = {version = "0.4", features=["async-tokio"]}

gpio-cdevクレートはtokioクレートの0.2系バージョンを要求するため、tokioのバージョンは"0.2"を指定する。

main.rs

use futures::stream::StreamExt;
use gpio_cdev::{AsyncLineEventHandle, Chip, EventRequestFlags, LineRequestFlags};
use std::error::Error;

async fn async_main() -> Result<(), Box<dyn Error>> {
    //使用するGPIOキャラクターデバイスを指定する
    let mut chip = Chip::new("/dev/gpiochip0")?;
    
    //使用するピン番号のハンドルを取得する
    let lines = [chip.get_line(0)?, chip.get_line(2)?, chip.get_line(3)?];

    //対応するピン番号のイベントハンドルを取得する
    //どの状態変化を検出するかを引数で指定する
    let events = lines
        .iter()
        .map(|line| {
            line.events(
                LineRequestFlags::INPUT, //入力として使用する
                EventRequestFlags::BOTH_EDGES, //ボタンが押されたときと、ボタンから手を離したときを検知する
                "gpioevents",
            )
        })
        .collect::<Result<Vec<_>, _>>()?;
    
    //イベントを非同期で受け取れるようにする
    let handles = events
        .into_iter()
        .map(AsyncLineEventHandle::new)
        .collect::<Result<Vec<_>, _>>()?;

    let (mut handle1, mut handle2, mut handle3) = {
        let mut iter = handles.into_iter();
        (
            iter.next().unwrap(),
            iter.next().unwrap(),
            iter.next().unwrap()
        )
    };

    loop {
        //どれか1つのボタンが押されたときに標準出力へ出力する
        let (name, event) = tokio::select! {
            Some(Ok(e)) = handle1.next() => {
                ("F1", e.event_type())
            },
            Some(Ok(e))  = handle2.next() => {
                ("F2", e.event_type())
            },
            Some(Ok(e))  = handle3.next() => {
                ("F3", e.event_type())
            },
            else => break,
        };
        println!("{}: {:?}", name, event);
    }
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    async_main().await?;
    Ok(())
}

動作gif

特記事項

  • async_mainは内部で.awaitするため、async fnとして実行する。
  • tokio::select!は複数の非同期処理を待ち受け、どれか1つの計算が完了したときに値を返す。すべての処理完了を待つtokio::join!と対になっている感じ。