ラズパイ入門記録(入力編)
これまで
引き続き下記を教材とする。
付属CDよりもサイトの日本語が読みやすい。
まずはなんの変哲もないボタンをやっていく。
ボタンを押すと何かが起こるというのは絶対に楽しい。
10kΩの抵抗が登場。ピンにインプットとして入れるにはこれぐらいの抵抗を噛ませる必要があるということだろうか。
回路図の意味がよくわからなかったが、通常時(ボタン内の回路が繋がっている)はGNDの方に電流が流れボタンを押すとGPIO18の方に電流が流れるってことかな。
まあ他に解釈のしようもないんだけど、GNDに流れる道があるならそっちに全部流れるというイメージがついていない。
10kΩの抵抗はプルアップ抵抗と呼ばれるものらしい。
まだ色々腹落ちしていないのでいくつかわかりやすそうな記事読んだりする。
ボタンを押したときに一瞬オフになってすぐオンになる(またはその逆)ということがある。
ボタンの不良というよりかは回路の組み方に問題がある?
ボタンを押している間だけLEDが消える。理想とはちょっと違うな。
pub fn button() -> Result<(), Box<dyn Error>> {
let input = Gpio::new()?.get(GPIO18)?.into_input();
let mut output = Gpio::new()?.get(GPIO17)?.into_output();
loop {
if input.is_high() {
output.set_low();
} else {
output.set_high();
}
}
}
もっといい方法がありそうだがまずは力技。
pub fn button() -> Result<(), Box<dyn Error>> {
let input = Gpio::new()?.get(GPIO18)?.into_input();
let mut output = Gpio::new()?.get(GPIO17)?.into_output();
let mut pressed = 0;
output.set_high();
loop {
if pressed == 1 && output.is_set_high() {
output.set_low();
while input.is_low() {}
pressed = 0;
} else if pressed == 1 && output.is_set_low() {
output.set_high();
while input.is_low() {}
pressed = 0;
} else if pressed == 0 && input.is_low() {
pressed = 1;
}
}
}
set_interruptの第一引数はTrigger列挙体(Disabled,RisingEdge,FallingEdge,Both)。
RisingEdgeとFallingEdgeはイメージつくが他の2つはあんまり使わないんじゃないかと思って確認もしていない。
セットしたらpoll_interruptでブロックする。第一引数はresetで、trueかfalseかでキャッシュされたイベントをクリアするかどうか(何が変わるのかわかってない)。
第2引数はタイムアウト時間。ブロックする時間を指定できる。
pub fn button() -> Result<(), Box<dyn Error>> {
let mut input = Gpio::new()?.get(GPIO18)?.into_input();
let mut output = Gpio::new()?.get(GPIO17)?.into_output();
output.set_high();
input
.set_interrupt(rppal::gpio::Trigger::FallingEdge)
.expect("failed to set_interrupt.");
loop {
match input.poll_interrupt(true, None) {
Ok(_) => output.toggle(),
Err(e) => println!("{}", e),
}
}
}
下記のようにしてボタンをポチポチすると最初に押してから2秒後にLEDがついたり消えたりする。
キャッシュしたイベントをクリアするようにしておけば勿論そんな挙動はしない。
ボタン使ってる分には基本trueかな。
match input.poll_interrupt(false, None) {
Ok(_) => {
thread::sleep(Duration::from_secs(2));
output.toggle();
},
Err(e) => println!("{}", e),
}
次はスライドスイッチ。どんな仕組みなのか考えたこともなかった。別にこれに限った話ではないが。
コンデンサとは。
交流電流とは。
スイッチを切り替えてから3秒くらい待たないでもう一度切り替えるとラズパイ落ちるっぽい?
落とすとマズそうな間はラズパイ本体の赤LEDも消えてる。いつもどうだったか、あまり覚えてないからこれがどういうことなのかはやっぱりわからない。
低電圧状態になると消灯するらしい。
スイッチ切り替え直後に試すと"throttled=0x50005"と返ってきた。
じゃあ電源あれば満足なのかと電源モジュールを繋いだら何も気にせずスイッチを切り替えられるようになった。
落ち着いて回路図を見る。ここで逆起電力とやらが発生しているのでは?
ダイオードを追加したところ解決。電源モジュールなんてなくともスイッチ切り替え放題になった。
面白いところはないが。
pub fn slide_button() -> Result<(), Box<dyn Error>> {
let mut led_1 = Gpio::new()?.get(GPIO22)?.into_output();
let mut led_2 = Gpio::new()?.get(GPIO27)?.into_output();
let input_pin = Gpio::new()?.get(GPIO17)?.into_input();
loop {
if input_pin.is_high() {
led_1.set_low();
led_2.set_high();
println!("LED1 on");
thread::sleep(Duration::from_secs(1));
} else {
led_2.set_low();
led_1.set_high();
println!("LED2 on");
thread::sleep(Duration::from_secs(1));
}
}
}
Trigger::Bothを使うとしたらここだなと思い使ってみた。
スライドしまくっているといつの間にかスライド位置と点灯するLEDの組み合わせが変わっていたりする。
// 省略
input_pin
.set_interrupt(rppal::gpio::Trigger::Both)
.expect("failed to set_interrupt.");
loop {
match input_pin.poll_interrupt(true, None) {
Ok(_) => {
led_1.toggle();
led_2.toggle();
}
Err(e) => println!("{}", e),
}
}
チルトスイッチ。傾きの検出に使われるらしい。
これだとちょっとブレッドボードを小突いただけでLEDが切り替わる。
pub fn tilt() -> Result<(), Box<dyn Error>> {
let mut led_1 = Gpio::new()?.get(GPIO22)?.into_output();
let mut led_2 = Gpio::new()?.get(GPIO27)?.into_output();
let mut input_pin = Gpio::new()?.get(GPIO17)?.into_input();
led_1.set_high();
led_2.set_low();
input_pin
.set_interrupt(rppal::gpio::Trigger::Both)
.expect("failed to set_interrupt.");
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
ctrlc::set_handler(move || {
r.store(false, Ordering::SeqCst);
})
.expect("Error setting Ctrl-C handler");
while running.load(Ordering::SeqCst) {
match input_pin.poll_interrupt(true, None) {
Ok(_) => {
led_1.toggle();
led_2.toggle();
thread::sleep(Duration::from_millis(500));
}
Err(e) => println!("{}", e),
}
}
led_1.set_low();
led_2.set_low();
Ok(())
}
サンプルコードをちゃんと見てsleepを挟むようにした。
Trigger::Bothで待ち受けても、HighかLowかで処理を振り分けられるよう。
pub fn tilt() -> Result<(), Box<dyn Error>> {
let mut led_1 = Gpio::new()?.get(GPIO22)?.into_output();
let mut led_2 = Gpio::new()?.get(GPIO27)?.into_output();
let mut input_pin = Gpio::new()?.get(GPIO17)?.into_input();
input_pin
.set_interrupt(rppal::gpio::Trigger::Both)
.expect("failed to set_interrupt.");
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
ctrlc::set_handler(move || {
r.store(false, Ordering::SeqCst);
})
.expect("Error setting Ctrl-C handler");
while running.load(Ordering::SeqCst) {
match input_pin.poll_interrupt(true, None) {
Ok(trigger) => {
thread::sleep(Duration::from_millis(10));
match trigger {
Some(rppal::gpio::Level::High) => {
led_1.set_high();
led_2.set_low();
}
Some(rppal::gpio::Level::Low) => {
println!("Tilt!");
led_2.set_high();
led_1.set_low();
}
None => break,
}
thread::sleep(Duration::from_millis(500));
}
Err(_) => println!("\nEnd"),
}
}
led_1.set_low();
led_2.set_low();
Ok(())
}
ポテンショメータ。つまみで抵抗値を変化させられるものらしい。
そんな名前だったのか。
ADC0834というADコンバータによって、アナログ信号をデジタル信号に変換することでつまみの回し具合でLEDの明るさを調整する。
C言語での例をもとにどう操作してるのか整理する。
get_ADC_Result関数で明るさを操作する値を取得していて、呼び出し時に渡す値は常に0(channel)。
get_ADC_Result側でselとoddという変数があるがこれはchannelをもとに決まるので引数の値が0のときはselが0、oddが1。
スタートビットを送り、SGLをhigh、ODDをhigh、Select Bitをlowで送ることでチャンネルを1に設定。
次にhighを2回送っているがこれがわからない。マニュアルでも無視しろって書いてあるが気になりすぎる。教えてくれよ。
まあ、こういうときはやらなかったらどうなるのか確かめてみればいいんだよな。
と思ってcloneしていたソース弄ろうとしたらCLKをlowにするだけになっていた。
無駄に悩んだ。マニュアルページよりもgithubの最新ソース見ながら進めたほうがいいかも知れない。
最初のクロックをスカすのは、MSB-First Dataの前が1つ空いてるからか。
get_ADC_Resultの2つ目のループでCLK操作の前にdat2の値の計算に入っているのはLSBがMSB-First DataとLSB First Dataの最下位ビットだから。
サンプルコード、どうしてDIとDOを1つのピンでまかなっているんだろう。節約?
気に食わなかったのでDOにGPIO23を割り当てたが動いた。
どうも変換をしくってそう。Cのサンプルコードでの変換後の値と比べると1bit足りない感じの値、明るさ。
fn snd_bit(clk_pin: &mut OutputPin, input_pin: &mut OutputPin, value: u8) {
clk_pin.set_low();
thread::sleep(Duration::from_micros(2));
if value == 0 {
input_pin.set_low();
} else {
input_pin.set_high();
}
clk_pin.set_high();
thread::sleep(Duration::from_micros(2));
}
fn rcv_bit(clk_pin: &mut OutputPin, output_pin: &mut InputPin) -> u8 {
let result;
clk_pin.set_low();
thread::sleep(Duration::from_micros(2));
if output_pin.is_high() {
result = 1;
} else {
result = 0;
}
clk_pin.set_high();
thread::sleep(Duration::from_micros(2));
return result;
}
pub fn potentiometer() -> Result<(), Box<dyn Error>> {
let mut msb = 0;
let mut lsb = 0;
let mut adc_cs = Gpio::new()?.get(GPIO17)?.into_output();
let mut adc_do = Gpio::new()?.get(GPIO23)?.into_input();
let mut adc_di = Gpio::new()?.get(GPIO27)?.into_output();
let mut adc_clk = Gpio::new()?.get(GPIO18)?.into_output();
let mut led = Gpio::new()?.get(GPIO22)?.into_output();
loop {
// 変換の開始
adc_cs.set_low();
// スタートビット
snd_bit(&mut adc_clk, &mut adc_di, 1);
// SGL
snd_bit(&mut adc_clk, &mut adc_di, 1);
// ODD
snd_bit(&mut adc_clk, &mut adc_di, 1);
// Select
snd_bit(&mut adc_clk, &mut adc_di, 0);
// スカされるbit。送信する値に意味なし。
snd_bit(&mut adc_clk, &mut adc_di, 0);
// MSB-First Data
for _ in 0..7 {
msb = msb << 1 | rcv_bit(&mut adc_clk, &mut adc_do);
}
// MSB-First DataとLSB-First Dataの最下位bit
adc_clk.set_low();
thread::sleep(Duration::from_micros(2));
msb = msb << 1 | if adc_do.is_high() { 1 } else { 0 };
lsb = if adc_do.is_high() { 1 } else { 0 };
adc_clk.set_high();
// LSB-First Data
for i in 1..8 {
lsb = rcv_bit(&mut adc_clk, &mut adc_do) << i | lsb;
}
// 変換の終了
adc_cs.set_high();
// LED点灯
if msb == lsb {
led.set_pwm_frequency(2000.0, (msb as f64) / 255.0)?;
}
}
}
次はキーパッド。
まだまだ汚く見えるけど、とりあえず動く。
サンプルコードとの違いは、同時押しの判定がシビアになっていることと、連続で同じキーを押しても印字してくれること。
pub fn keypad() -> Result<(), Box<dyn Error>> {
const KEYS: [char; 16] = [
'1', '2', '3', 'A', '4', '5', '6', 'B', '7', '8', '9', 'C', '*', '0', '#', 'D',
];
let row = [GPIO18, GPIO23, GPIO24, GPIO25];
let col = [SPIMOSI, GPIO22, GPIO27, GPIO17];
let mut row_pins = row
.map(|pin| -> Result<OutputPin, Box<dyn Error>> {
Ok(Gpio::new()?.get(pin)?.into_output())
})
.map(|result: Result<OutputPin, Box<dyn Error>>| result.unwrap());
let col_pins = col
.map(|pin| -> Result<InputPin, Box<dyn Error>> { Ok(Gpio::new()?.get(pin)?.into_input()) })
.map(|result: Result<InputPin, Box<dyn Error>>| result.unwrap());
let mut pressed: HashSet<char> = vec![].into_iter().collect();
let mut last_pressed: HashSet<char> = vec![].into_iter().collect();
loop {
for (i, output) in &mut row_pins.iter_mut().enumerate() {
output.set_high();
for (j, input) in col_pins.iter().enumerate() {
if input.is_high() {
pressed.insert(KEYS[i * 4 + j]);
}
}
thread::sleep(Duration::from_millis(1));
output.set_low();
}
if pressed.difference(&last_pressed).collect::<Vec<_>>().len() != 0 {
println!("{:?}", pressed.difference(&last_pressed));
}
last_pressed.clone_from(&pressed);
pressed.clear();
thread::sleep(Duration::from_millis(100));
}
}
明日はジョイスティックやるぞ。
大体ポテンショメーターと一緒のコードになるのか。
今度はCH0とCH1に繋いでいるので、ポテンショメーターのときみたいに0決め打ちでは行けない。
あんまりやる気が出ないが先を見るとADC関数はちょくちょく登場するので今度こそちゃんと動作するものを書いておきたい…。
ADC0834を構造体に切り出すか〜としてみたらAD変換はメソッドであるべきだなと思った。色々とこれでいいのかわからない。ラズパイで遊んでないでなんか設計の本を読んだほうがいい気がする。
ちょいと呼び出してみたところサンプルコードと同じような値を返してきてくれる。
ポテンショメータのときの変換関数との違いは毎回ピンを作り直しているかどうかぐらいのつもりなんだけども…。
impl Adc0834 {
fn new(adc_cs: u8, adc_do: u8, adc_di: u8, adc_clk: u8) -> Self {
Self {
adc_cs,
adc_do,
adc_di,
adc_clk,
}
}
fn get_adc_result(&self, ch_pin: u8) -> Result<u8, Box<dyn Error>> {
let mut adc_cs = Gpio::new()?.get(self.adc_cs)?.into_output();
let mut adc_do = Gpio::new()?.get(self.adc_do)?.into_input();
let mut adc_di = Gpio::new()?.get(self.adc_di)?.into_output();
let mut adc_clk = Gpio::new()?.get(self.adc_clk)?.into_output();
// 変換の開始
adc_cs.set_low();
// スタートビット
snd_bit(&mut adc_clk, &mut adc_di, 1);
// SGL
snd_bit(&mut adc_clk, &mut adc_di, 1);
// ODD
snd_bit(&mut adc_clk, &mut adc_di, ch_pin & 1 as u8);
// Select
snd_bit(&mut adc_clk, &mut adc_di, if ch_pin > 1 { 1 } else { 0 });
// スカされるクロック。送信する値に意味なし。
snd_bit(&mut adc_clk, &mut adc_di, 1);
let mut msb = 0;
let mut lsb;
// MSB-First Data
for _ in 0..7 {
msb = msb << 1 | rcv_bit(&mut adc_clk, &mut adc_do);
}
// MSB-First DataとLSB-First Dataの最下位bit
adc_clk.set_low();
thread::sleep(Duration::from_micros(2));
msb = msb << 1 | if adc_do.is_high() { 1 as u8 } else { 0 as u8 };
lsb = if adc_do.is_high() { 1 } else { 0 };
adc_clk.set_high();
// LSB-First Data
for i in 1..8 {
lsb = rcv_bit(&mut adc_clk, &mut adc_do) << i | lsb;
}
// 変換の終了
adc_cs.set_high();
if lsb == msb {
Ok(lsb)
} else {
println!("something is wrong!!!");
Ok(0)
}
}
}
ピンには内蔵のプルアップ(あとプルダウン)抵抗があるらしく、それを最初に有効にしてInputpinを作れるよう。
pub fn joystick() -> Result<(), Box<dyn Error>> {
let button = Gpio::new()?.get(GPIO22)?.into_input_pullup();
let mut x_val;
let mut y_val;
let adc = Adc0834::new(GPIO17, GPIO23, GPIO27, GPIO18);
loop {
x_val = adc.get_adc_result(0)?;
y_val = adc.get_adc_result(1)?;
println!(
"x: {}, y: {}, button: {}",
x_val,
y_val,
if button.is_low() {
"pressed"
} else {
"not pressed"
}
);
thread::sleep(Duration::from_millis(100));
}
}
次からはセンサー系。まずはフォトレジスタ。明るさによって変化する可変抵抗。
サンプルコードはポテンショメータと同じ。
1つ前で作った構造体を使って動作することを確認。
部屋を真っ暗にするとLEDがつく。
pub fn photoregister() -> Result<(), Box<dyn Error>> {
let mut val;
let adc = Adc0834::new(GPIO17, GPIO23, GPIO27, GPIO18);
let mut led = Gpio::new()?.get(GPIO22)?.into_output();
loop {
val = adc.get_adc_result(0)?;
led.set_pwm_frequency(2000.0, (val as f64) / 255.0)?;
println!("val: {}", val);
thread::sleep(Duration::from_millis(100));
}
}
部屋を真っ暗にしてボタンを押下した状態でテレビリモコンを近づけるとちゃんと検知してLEDが消えたりする。
…10cm以内ぐらいまで近づけるとなので、実用性はちょっと思いつかない。
とはいえ自分の目には見えない何かにちゃんと反応してくれるものが作れるのは楽しい。
次はサーミスタ。こちらは熱によって抵抗値が変化するものらしい。
その特性を利用して温度を測ったりするのに使われると。
寒くなってきたしちょうどいいな(?)。
とりあえずpythonのサンプルコード動かしてみたら196.63℃と表示された。
体感的にはそんなはずないので回路かコードが間違っている。
回路を組み間違えていた。17℃だった。よかったよかった。
数値計算にはnumクレイトを。
計算の意味はさっぱりわからないです。
pub fn thermistor() -> Result<(), Box<dyn Error>> {
use num::Float;
let mut analog_val: u8;
let mut vr: f64;
let mut rt: f64;
let mut temp: f64;
let mut cel: f64;
let mut fah: f64;
let adc = Adc0834::new(GPIO17, GPIO23, GPIO27, GPIO18);
loop {
analog_val = adc.get_adc_result(0).expect("adc failed");
vr = 5.0 * analog_val as f64 / 255.0;
rt = 10000.0 * vr / (5.0 - vr);
temp = 1.0 / ((Float::ln(rt / 10000.0) / 3950.0) + (1.0 / (273.15 + 25.0)));
cel = temp - 273.15;
fah = cel * 1.8 + 32.0;
println!("cel: {}, fah: {}", cel, fah);
thread::sleep(Duration::from_millis(100));
}
}
区切りいい感じにしてると更新しなくてはという気持ちがなくなる。
次はDHT-11をやる。温湿度計。良いコーディングライフには適切な温度・湿度が必要だ。
うまく取れないな…。
コード
struct Dht11 {
pin: u8,
max_tim: u8,
}
impl Dht11 {
pub fn new(pin: u8) -> Self {
Dht11 { pin, max_tim: 85 }
}
pub fn read(&self) -> Result<((i8, i8), (i8, i8)), Box<dyn Error>> {
let mut pin = Gpio::new()?.get(self.pin)?.into_output();
pin.set_high();
thread::sleep(Duration::from_micros(1));
pin.set_low();
thread::sleep(Duration::from_millis(18));
pin.set_high();
thread::sleep(Duration::from_micros(40));
drop(pin);
let pin = Gpio::new()?.get(self.pin)?.into_input();
let mut last_state: Level = Level::High;
let mut data: [i8; 5] = [0; 5];
let mut times_collect_bit = 0;
'main_loop: for i in 0..self.max_tim {
let mut counter = 0;
while pin.read() == last_state {
counter += 1;
thread::sleep(Duration::from_micros(1));
if counter == 255 {
break 'main_loop;
}
}
last_state = pin.read();
// ignore first 3 transitions
if i >= 4 && i % 2 == 0 {
data[times_collect_bit / 8] <<= 1;
if counter > 50 {
data[times_collect_bit / 8] |= 1;
}
times_collect_bit += 1;
}
}
if times_collect_bit >= 40 && data[4] == (data[0] + data[1] + data[2] + data[3]) {
Ok(((data[0], data[1]), (data[2], data[3])))
} else {
Err("Data not good, skip!".into())
}
}
}
このコードなどを参考にしてどうにかしたい。
人のコード読むの勉強になるから習慣づけたいですね。
それはそれとして値は取れなかったです。きっとしょうもないミスだと思うけど諦め。
まあ温湿度なんて測りたかったら温湿度計買いましょうね。
次はPIR。こいつを使って猫が水を飲んでいるか監視しようと思う。
とりあえずサンプルコード写経。pwmのことは何もわからない。
pir自体はonかoffかしかないのでシンプルなもんである。
pub fn pir() -> Result<(), Box<dyn Error>> {
let pir = Gpio::new()?.get(GPIO17)?.into_input();
let mut red = Gpio::new()?.get(GPIO18)?.into_output();
let mut blue = Gpio::new()?.get(GPIO22)?.into_output();
let mut green = Gpio::new()?.get(GPIO27)?.into_output();
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
ctrlc::set_handler(move || {
r.store(false, Ordering::SeqCst);
})
.expect("Error setting Ctrl-C handler");
while running.load(Ordering::SeqCst) {
if pir.is_high() {
red.set_pwm_frequency(100.0, 100.0)?;
green.set_pwm_frequency(100.0, 100.0)?;
blue.set_pwm_frequency(0.0, 100.0)?;
} else {
red.set_pwm_frequency(0.0, 100.0)?;
green.set_pwm_frequency(0.0, 100.0)?;
blue.set_pwm_frequency(100.0, 100.0)?;
}
}
Ok(())
}
トリガーで。
pub fn pir() -> Result<(), Box<dyn Error>> {
let mut pir = Gpio::new()?.get(GPIO17)?.into_input();
let mut red = Gpio::new()?.get(GPIO18)?.into_output();
let mut blue = Gpio::new()?.get(GPIO22)?.into_output();
let mut green = Gpio::new()?.get(GPIO27)?.into_output();
pir.set_interrupt(rppal::gpio::Trigger::Both)?;
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
ctrlc::set_handler(move || {
r.store(false, Ordering::SeqCst);
})
.expect("Error setting Ctrl-C handler");
while running.load(Ordering::SeqCst) {
match pir.poll_interrupt(true, None) {
Ok(trigger) => match trigger {
Some(rppal::gpio::Level::High) => {
red.set_pwm_frequency(100.0, 100.0)?;
green.set_pwm_frequency(100.0, 100.0)?;
blue.set_pwm_frequency(0.0, 100.0)?;
}
Some(rppal::gpio::Level::Low) => {
red.set_pwm_frequency(0.0, 100.0)?;
green.set_pwm_frequency(0.0, 100.0)?;
blue.set_pwm_frequency(100.0, 100.0)?;
}
None => (),
},
_ => break,
}
}
Ok(())
}
色々怪しいししばらくいじってなかったらだいぶ忘れたけど多分ラズパイ完全に理解したのであとは実践をしていく。