👨‍🔧

【Rust&RaspberryPi】スイッチでLEDON/OFF

2021/03/29に公開

やること

  1. 回路作成
  2. スイッチ入力を受けつられるようにする
  3. スイッチを押下中にLED点灯するようにする
  4. スイッチのトグル化(LED ON/OFFを切り替える)する
  5. RaspberryPiのプルダウン回路を使用する

1. 回路作成

回路図

使用したポート

|PINNo. PIN名称 入出設定
18 GPIO24 入力
20 GND
22 GPIO25 出力

2. スイッチ入力を受けつられるようにする

動作概要

  • スイッチをOFFにするとコンソールに"Switch is OFF."を表示する。
  • スイッチをONにするとコンソールに"Switch is ON."を表示する。

crate設定

Cargo.toml
[dependencies]
rppal = "0.11.3"
ctrlc = "3.1.8"

実装

main.rs
  1 use std::error::Error;
  2 use std::thread;
  3 use std::time::Duration;
  4
  5 use rppal::gpio::Gpio;
  6 use rppal::gpio::Level;
  7 use rppal::system::DeviceInfo;
  8
  9 use std::sync::atomic::{AtomicBool, Ordering};
 10 use std::sync::Arc;
 11
 12 const GPIO_SWITCH: u8 = 24;
 13
 14 fn main() -> Result<(), Box<dyn Error>> {
 15     let running = Arc::new(AtomicBool::new(true));
 16     let r = running.clone();
 17
 18     ctrlc::set_handler(move || {
 19         r.store(false, Ordering::SeqCst);
 20     }).expect("Error setting Ctrl-C handler");
 21
 22     println!("Blinking an Switch Control LED {}", DeviceInfo::new()?.model());
 23
 24     let switch = Gpio::new()?.get(GPIO_SWITCH)?.into_input();
 25
 26     while running.load(Ordering::SeqCst) {
 27         thread::sleep(Duration::from_millis(500));
 28         if switch.read() == Level::High {
 29             println!("Switch is ON.");
 30         } else {
 31             println!("Switch is OFF.");
 32         }
 33     }
 34
 35     Ok(())
 36 }

コード解説

  • GPIOの入力設定
 24     let switch = Gpio::new()?.get(GPIO_SWITCH)?.into_input();
  • GPIOの状態読み取り
 28         if switch.read() == Level::High {

入力の読み取り方は以下の3パターンあるようです。

  1. pub fn read(&self) -> Level
  2. pub fn is_low(&self) -> bool
  3. pub fn is_high(&self) -> bool

今回はenum型のLevelを戻り値としている1.で実装しましたが、
2、3の方が使いやすいかもしれません。

3. スイッチを押下中にLED点灯するようにする

動作概要

  • スイッチをOFFにするとLEDを消灯する。
  • スイッチをONにするとLEDを点灯する。

crate設定

変更なし。

実装

main.rs
  1 use std::error::Error;
  2 use std::thread;
  3 use std::time::Duration;
  4
  5 use rppal::gpio::{Gpio, Level, InputPin, OutputPin};
  6 use rppal::system::DeviceInfo;
  7
  8 use std::sync::atomic::{AtomicBool, Ordering};
  9 use std::sync::Arc;
 10
 11 const GPIO_SWITCH: u8 = 24;
 12 const GPIO_LED: u8    = 25;
 13
 14 fn main() -> Result<(), Box<dyn Error>> {
 15     let running = Arc::new(AtomicBool::new(true));
 16     let r = running.clone();
 17
 18     ctrlc::set_handler(move || {
 19         r.store(false, Ordering::SeqCst);
 20     }).expect("Error setting Ctrl-C handler");
 21
 22     println!("Blinking an Switch Control LED {}", DeviceInfo::new()?.model());
 23
 24     let switch = Gpio::new()?.get(GPIO_SWITCH)?.into_input();
 25     let mut led= Gpio::new()?.get(GPIO_LED)?.into_output();
 26
 27     while running.load(Ordering::SeqCst) {
 28         thread::sleep(Duration::from_millis(10));
 29         pin_output_switch(&switch, &mut led);
 30     }
 31
 32     led.set_low();
 33     Ok(())
 34 }
 35
 36 fn pin_output_switch(input_pin: &InputPin, output_pin: &mut OutputPin) {
 37     if input_pin.read() == Level::High {
 38         output_pin.set_high();
 39     } else {
 40         output_pin.set_low();
 41     }
 42 }

コード解説

  • スイッチ入力に合わせて切り替える処理を関数化
 36 fn pin_output_switch(input_pin: &InputPin, output_pin: &mut OutputPin) {
  • スイッチ入力を判定しているIF文をledを接続しているピンのHigh、Lowに変更しました。
 37     if input_pin.read() == Level::High {
 38         output_pin.set_high();
 39     } else {
 40         output_pin.set_low();
 41     }

4. スイッチのトグル化(LED ON/OFFを切り替える)する

動作概要

  • LED消灯中にスイッチを押して、離すとLEDが点灯する。
  • LED点灯中にスイッチを押して、離すとLEDが消灯する。

crate設定

変更なし。

実装

main.rs
  1 use std::error::Error;
  2
  3 use rppal::gpio::{Gpio, Trigger};
  4 use rppal::system::DeviceInfo;
  5
  6 use std::sync::atomic::{AtomicBool, Ordering};
  7 use std::sync::Arc;
  8
  9 const GPIO_SWITCH: u8 = 24;
 10 const GPIO_LED: u8    = 25;
 11
 12 fn main() -> Result<(), Box<dyn Error>> {
 13     let running = Arc::new(AtomicBool::new(true));
 14     let r = running.clone();
 15
 16     ctrlc::set_handler(move || {
 17         r.store(false, Ordering::SeqCst);
 18     }).expect("Error setting Ctrl-C handler");
 19
 20     println!("Blinking an Switch Control LED {}", DeviceInfo::new()?.model());
 21
 22     let mut switch = Gpio::new()?.get(GPIO_SWITCH)?.into_input();
 23     let mut led= Gpio::new()?.get(GPIO_LED)?.into_output();
 24
 25     let r = switch.set_interrupt(Trigger::FallingEdge);
 26     match r {
 27         Ok(n) => n,
 28         Err(e) => println!("Error: {:?}", e),
 29     }
 30
 31     while running.load(Ordering::SeqCst) {
 32         let level = switch.poll_interrupt(true, None);
 33         match level {
 34             Ok(_) => led.toggle(),
 35             Err(e) => {
 36                 println!("Error: {:?}", e);
 37                 break;
 38             }
 39         }
 40     }
 41
 42     led.set_low();
 43     let r = switch.clear_interrupt();
 44     match r {
 45         Ok(n) => n,
 46         Err(e) => println!("Error: {:?}", e),
 47     }
 48     Ok(())
 49 }

コード解説

  • スイッチの立ち下りエッジを取得する為のポーリング設定。
 25     let r = switch.set_interrupt(Trigger::FallingEdge);
 26     match r {
 27         Ok(n) => n,
 28         Err(e) => println!("Error: {:?}", e),
 29     }

回路の都合なのか、立ち上がりエッジだとノイズの影響が強かったので、立ち下がりエッジで実装しました。
取得したエッジに合わせて引数を設定できます。

  1. 立ち上がりエッジ:RisingEdge
  2. 立ち下がりエッジ:FallingEdge
  3. 立ち上がり&立ち下がりエッジの両方:Both

RustのResult型で、Okなら何もしないの実装ってこれで合っているんでしょうか??

  • トリガー検出時のLEDの点灯、消灯の切り替え。
 32         let level = switch.poll_interrupt(true, None);
 33         match level {
 34             Ok(_) => led.toggle(),
 35             Err(e) => {
 36                 println!("Error: {:?}", e);
 37                 break;
 38             }
 39         }

poll_interruptメソッドの引数は、「リセット」、「タイムアウト時間」でした。
ポーリングで割り込み監視しているので、ちゃんと設定しないとプログラムが止まってしまいそうですが、今回は、タイムアウト時間のNoneにしています。
その為、10ms待機の処理は削除しました。
また、OutputPinにはtoggleメソッドがあったので、切り替え処理の関数は1行にできました。

  • スイッチの立ち下がりエッジを所得するポーリング設定のクリア。
 43     let r = switch.clear_interrupt();
 44     match r {
 45         Ok(n) => n,
 46         Err(e) => println!("Error: {:?}", e),
 47     }

今回は、入力が一つなので、シンプルに作成しましたが、入力が複数ある場合などは、非同期のポーリングメソッドなど他のメソッドを使う必要がありそうです。

エラー内容

ctrl+Cを押すと以下のエラーが出力されるようになりました。

Error: Io(Os { code: 4, kind: Interrupted, message: "Interrupted system call" })

おそらく原因は以下のコードの間は同期ポーリング割り込み設定中であり、そのタイミングで、ctrl+Cの割り込みで停止している為だと思います。
こちらの対策については、もう少し勉強してから確認してみます。

 25     let r = switch.set_interrupt(Trigger::FallingEdge);
 ~
 43     let r = switch.clear_interrupt();

5. RaspberryPiのプルダウン回路を使用する

動作概要

  • 変更なし。
    プルダウン回路をブレッドボード上で実現していましたが、これを、RaspberryPiの回路内で実装してもらう設定で、コードを記載する。

crate設定

変更なし。

コード

main.rs
  1 use std::error::Error;
  2
  3 use rppal::gpio::{Gpio, Trigger};
  4 use rppal::system::DeviceInfo;
  5
  6 use std::sync::atomic::{AtomicBool, Ordering};
  7 use std::sync::Arc;
  8
  9 const GPIO_SWITCH: u8 = 24;
 10 const GPIO_LED: u8    = 25;
 11
 12 fn main() -> Result<(), Box<dyn Error>> {
 13     let running = Arc::new(AtomicBool::new(true));
 14     let r = running.clone();
 15
 16     ctrlc::set_handler(move || {
 17         r.store(false, Ordering::SeqCst);
 18     }).expect("Error setting Ctrl-C handler");
 19
 20     println!("Blinking an Switch Control LED {}", DeviceInfo::new()?.model());
 21
 22     let mut switch = Gpio::new()?.get(GPIO_SWITCH)?.into_input_pulldown();
 23     let mut led= Gpio::new()?.get(GPIO_LED)?.into_output();
 24
 25     let r = switch.set_interrupt(Trigger::FallingEdge);
 26     match r {
 27         Ok(n) => n,
 28         Err(e) => println!("Error: {:?}", e),
 29     }
 30
 31     while running.load(Ordering::SeqCst) {
 32         let level = switch.poll_interrupt(true, None);
 33         match level {
 34             Ok(_) => led.toggle(),
 35             Err(e) => {
 36                 println!("Error: {:?}", e);
 37                 break;
 38             }
 39         }
 40     }
 41
 42     led.set_low();
 43     let r = switch.clear_interrupt();
 44     match r {
 45         Ok(n) => n,
 46         Err(e) => println!("Error: {:?}", e),
 47     }
 48     Ok(())
 49 }

コード解説

  • Pinのインプット設定をプルダウンにする。
 22     let mut switch = Gpio::new()?.get(GPIO_SWITCH)?.into_input_pulldown();

RaspberryPi内部のプルダウン回路、プルアップ回路の使用はかなり簡単にできます。
これならブレッドボードに余計な配線がいらなくて良いので助かります。
プルダウン、プルアップは以下のメソッドで設定できます。

  1. プルダウン回路:into_input_pulldown()
  2. プルアップ回路:into_input_pullup())

感想

GPIOのインプット設定は、思ったより実装するのに時間がかかりました。
Rustのcrateの使い方&Result型やOption型に慣れていない&英語ができないなどなど重なりスムーズにはいきませんが、楽しくやれています。
Rustでロボットやラジコンもどきを作れるようになることを目指して、引き続き勉強していきます。
Rustに拘る必要があるのかは謎ですが、どうせ本職ではコーディングする機会が無いので、他の言語は後回しで、話題の言語で勉強続けていきます。

Discussion