【Rust&RaspberryPi】スイッチでLEDON/OFF
やること
- 回路作成
- スイッチ入力を受けつられるようにする
- スイッチを押下中にLED点灯するようにする
- スイッチのトグル化(LED ON/OFFを切り替える)する
- RaspberryPiのプルダウン回路を使用する
1. 回路作成
回路図
使用したポート
|PINNo. | PIN名称 | 入出設定 |
---|---|---|
18 | GPIO24 | 入力 |
20 | GND | - |
22 | GPIO25 | 出力 |
2. スイッチ入力を受けつられるようにする
動作概要
- スイッチをOFFにするとコンソールに"Switch is OFF."を表示する。
- スイッチをONにするとコンソールに"Switch is ON."を表示する。
crate設定
[dependencies]
rppal = "0.11.3"
ctrlc = "3.1.8"
実装
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パターンあるようです。
- pub fn read(&self) -> Level
- pub fn is_low(&self) -> bool
- pub fn is_high(&self) -> bool
今回はenum型のLevelを戻り値としている1.で実装しましたが、
2、3の方が使いやすいかもしれません。
3. スイッチを押下中にLED点灯するようにする
動作概要
- スイッチをOFFにするとLEDを消灯する。
- スイッチをONにするとLEDを点灯する。
crate設定
変更なし。
実装
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設定
変更なし。
実装
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 }
回路の都合なのか、立ち上がりエッジだとノイズの影響が強かったので、立ち下がりエッジで実装しました。
取得したエッジに合わせて引数を設定できます。
- 立ち上がりエッジ:RisingEdge
- 立ち下がりエッジ:FallingEdge
- 立ち上がり&立ち下がりエッジの両方: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設定
変更なし。
コード
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内部のプルダウン回路、プルアップ回路の使用はかなり簡単にできます。
これならブレッドボードに余計な配線がいらなくて良いので助かります。
プルダウン、プルアップは以下のメソッドで設定できます。
- プルダウン回路:into_input_pulldown()
- プルアップ回路:into_input_pullup())
感想
GPIOのインプット設定は、思ったより実装するのに時間がかかりました。
Rustのcrateの使い方&Result型やOption型に慣れていない&英語ができないなどなど重なりスムーズにはいきませんが、楽しくやれています。
Rustでロボットやラジコンもどきを作れるようになることを目指して、引き続き勉強していきます。
Rustに拘る必要があるのかは謎ですが、どうせ本職ではコーディングする機会が無いので、他の言語は後回しで、話題の言語で勉強続けていきます。
Discussion