NanoPi NEOのボタン入力をRustで検知してみる
2022-10-30RustでGPIOの入力を制御するために必要なこととか。
注意:古いバージョンのクレートを使用しています
2022/11/02: 新バージョンで書き直し
開発環境
- NanoPi NEO(RAM 512MB)
※ボタンとOLEDがセットになったキット - Rust Version:1.64.0(stable-armv7-unknown-linux-gnueabihf)
準備
/dev/gpiochip0を一般ユーザーが使用できるようにする。 gpioというグループを作成して、そのグループ内のユーザーであれば使用可能にする。
- グループ作成
$sudo addgroup gpio
- nanopiユーザーをgpioグループに追加
$sudo usermod -aG gpio nanopi
- 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(())
}
特記事項
- async_mainは内部で.awaitするため、async fnとして実行する。
- tokio::select!は複数の非同期処理を待ち受け、どれか1つの計算が完了したときに値を返す。すべての処理完了を待つtokio::join!と対になっている感じ。