ラズパイ入門記録(出力編)
多分そのうち飽きて、またいつか始めるのでその時のために備忘
sudo snap install rpi-imager
マイクロSDへのOSの書き込みやwifi設定はrpi-imagerで行った。
簡単、便利。
下記を教材とする。
付属CDよりもサイトの日本語が読みやすい。初めて知ったワード
ブレッドボード:電子装置のプロトタイピングの構築基盤。差し込んだりするだけで簡単電子工作。
GPIO:General-Purpose Input/Output。拡張ボードでブレッドボードに引き出せる。
GPIO拡張ボード:GPIOを拡張したボード。各ピンに名前がついており、覚えられない。言語やライブラリによって各ピンの呼び方が違うみたい。
まず220Ωの抵抗器を陽極(LEDの長いピン)に接続し、それから抵抗器を3.3 Vの電源に接続し、LEDの陰極(短いピン)をRaspberry PiのGPIO17に接続する。したがって、LEDをオンにするには、GPIO17を低(0V)レベルにする必要がある。
いきなり全然わからない。
LEDは、長いピンが正極に接続され、もう一方が負極に接続されている場合に点灯する。直接電源に接続すると破損の危険があるので、160Ω以上の抵抗を直列に接続すること。
ちゃんと読む。
Rustでの操作にはrppalというのがいいのだろうか。
現在の最新バージョンは0.13.1。
ピンの指定はBCM。
Rustで操作するのは隙あらばRust使って練習していきたい気分なだけでそれ以上の理由はない。
PWM:パルス幅変調。デジタル手段でアナログ結果を取得するための技術。信号がオンである時間とオフである時間を長くしたり短くしたりする方式。
RGB LEDで遊ぶ。RGB LEDの色は輝度によって変化し、輝度はPWMで調整可能。
Raspberry PiのハードウェアPWM出力用チャネルが1つだけなので、3つのチャネルを必要とするRGB LEDの制御は本来難しいが、softPwmライブラリを使えばできるらしい。
rppalで行けそう。オプションでhalを追加。
これがLチカか。楽しい。
pub fn rgb_led() -> Result<(), Box<dyn Error>> {
const COLOR_FLAGS: [u64; 7] = [0b000, 0b100, 0b010, 0b001, 0b110, 0b101, 0b011];
const GPIO_LED_RED: u8 = 17;
const GPIO_LED_GREEN: u8 = 18;
const GPIO_LED_BLUE: u8 = 27;
const DURATION: u64 = 100 * 100;
let mut pin1 = Gpio::new()?.get(GPIO_LED_RED)?.into_output();
let mut pin2 = Gpio::new()?.get(GPIO_LED_GREEN)?.into_output();
let mut pin3 = Gpio::new()?.get(GPIO_LED_BLUE)?.into_output();
let light_led = |pin: &mut OutputPin, palse_width: u64| {
pin.set_pwm(
Duration::from_micros(DURATION),
Duration::from_micros(palse_width),
)
};
for flags in COLOR_FLAGS {
light_led(&mut pin1, (flags & 0b100) * 100)?;
light_led(&mut pin2, (flags & 0b010) * 100)?;
light_led(&mut pin3, (flags & 0b001) * 100)?;
thread::sleep(Duration::from_millis(500));
}
Ok(())
}
74HC595について簡単に理解したい。
操作方法がわかりやすい動画
pub fn segment7() -> Result<(), Box<dyn Error>> {
let turn_high_and_low = |pin: &mut OutputPin, duration: Duration| {
pin.set_high();
thread::sleep(duration);
pin.set_low();
};
const SEG_CODE: [u8; 16] = [
0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79,
0x71,
];
const GPO17: u8 = 17;
const GPO18: u8 = 18;
const GPO27: u8 = 27;
let mut pin_sdi = Gpio::new()?.get(GPO17)?.into_output();
let mut pin_rclk = Gpio::new()?.get(GPO18)?.into_output();
let mut pin_srclk = Gpio::new()?.get(GPO27)?.into_output();
pin_sdi.set_low();
pin_rclk.set_low();
pin_srclk.set_low();
for code in SEG_CODE {
for i in 0..8 {
if 0x80 & (code << i) != 0 {
pin_sdi.set_high();
} else {
pin_sdi.set_low();
}
turn_high_and_low(&mut pin_srclk, Duration::from_millis(1));
}
turn_high_and_low(&mut pin_rclk, Duration::from_millis(1000));
}
pin_sdi.set_low();
for _ in 0..8 {
turn_high_and_low(&mut pin_srclk, Duration::from_millis(1));
}
turn_high_and_low(&mut pin_rclk, Duration::from_millis(0));
Ok(())
}
次は4桁の7セグメントディスプレイをチカチカさせる。
今更だがアノードコモンとカソードコモンについてググる。
アノードはLEDのプラス側(足が長い方)、カソードはLEDのマイナス側。
つまりアノードコモンはプラス側を共有し、マイナス側の電流をLOWにすることで制御する。
カソードコモンはその逆。
マニュアルはこのページ。
place_pinsがLEDの各桁のアノードに対応している。全部highにしている。
今度はGPIO24、GPIO23、GPIO18をlowにすることでLEDを光らせるため、上のコードとはSEG_CODEが反転している。
ピンの定数とturn_high_and_low関数(もっとマトモな名前があるはず)は外に追い出した。
マニュアルのサンプルコードと同様、タイマーを使ってカウントアップしていくつもり。
timer使うの初めてなのでとりあえずちゃんとカウントアップしていくか練習。
guard関数とかはtimerのドキュメントのほぼ丸写し。
pub fn four_digit_segment7() -> Result<(), Box<dyn Error>> {
let timer = timer::Timer::new();
let count = Arc::new(Mutex::new(0));
let guard = {
let count = count.clone();
timer.schedule_repeating(chrono::Duration::seconds(1), move || {
*count.lock().unwrap() += 1;
})
};
let seg_code: [u8; 10] = [0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90];
let mut pin_sdi = Gpio::new()?.get(GPIO24)?.into_output();
let mut pin_rclk = Gpio::new()?.get(GPIO23)?.into_output();
let mut pin_srclk = Gpio::new()?.get(GPIO18)?.into_output();
let mut place_pins: [OutputPin; 4] = [
Gpio::new()?.get(SPIMOSI)?.into_output(),
Gpio::new()?.get(GPIO22)?.into_output(),
Gpio::new()?.get(GPIO27)?.into_output(),
Gpio::new()?.get(GPIO17)?.into_output(),
];
for p in &mut place_pins {
p.set_high();
}
for code in seg_code {
for i in 0..8 {
if 0x80 & (code << i) != 0 {
pin_sdi.set_high();
} else {
pin_sdi.set_low();
}
turn_high_and_low(&mut pin_srclk, Duration::from_millis(1));
}
turn_high_and_low(&mut pin_rclk, Duration::from_millis(1000));
println!("{:?}", count);
}
pin_sdi.set_high();
for _ in 0..8 {
turn_high_and_low(&mut pin_srclk, Duration::from_millis(1));
}
turn_high_and_low(&mut pin_rclk, Duration::from_millis(0));
Ok(())
}
1桁目に2、2桁目に1、3桁目に0、4桁目は3、みたいな感じでそれぞれの桁に表示したい数字を一瞬だけ光らせる操作を高速に行うことによって全部の桁がずっと光っているように見せる。
見ていて楽しいのは100msで1カウントアップぐらいかなあ。
fn clear_display(sdi: &mut OutputPin, rclk: &mut OutputPin, srclk: &mut OutputPin, is_anode: bool) {
if is_anode {
sdi.set_low();
} else {
sdi.set_high();
}
for _ in 0..8 {
turn_high_and_low(srclk, Duration::from_millis(0));
}
turn_high_and_low(rclk, Duration::from_millis(0));
}
fn hc595_shift(sdi: &mut OutputPin, rclk: &mut OutputPin, srclk: &mut OutputPin, code: u8) {
for i in 0..8 {
if 0x80 & (code << i) != 0 {
sdi.set_high();
} else {
sdi.set_low();
}
turn_high_and_low(srclk, Duration::from_millis(0));
}
turn_high_and_low(rclk, Duration::from_millis(0));
}
pub fn four_digit_segment7() -> Result<(), Box<dyn Error>> {
let timer = timer::Timer::new();
let count = Arc::new(Mutex::new(0));
let guard = {
let count = count.clone();
timer.schedule_repeating(chrono::Duration::milliseconds(100), move || {
*count.lock().unwrap() += 1;
})
};
let seg_code: [u8; 10] = [0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90];
let mut pin_sdi = Gpio::new()?.get(GPIO24)?.into_output();
let mut pin_rclk = Gpio::new()?.get(GPIO23)?.into_output();
let mut pin_srclk = Gpio::new()?.get(GPIO18)?.into_output();
let mut place_pins: [OutputPin; 4] = [
Gpio::new()?.get(SPIMOSI)?.into_output(),
Gpio::new()?.get(GPIO22)?.into_output(),
Gpio::new()?.get(GPIO27)?.into_output(),
Gpio::new()?.get(GPIO17)?.into_output(),
];
let mut pick_digit = |digit: usize| {
for p in &mut place_pins {
p.set_low();
}
place_pins[digit].set_high();
};
let mut light_1digit = |count: usize, digit: usize| {
let base: i32 = 10;
clear_display(&mut pin_sdi, &mut pin_rclk, &mut pin_srclk, false);
pick_digit(digit);
hc595_shift(
&mut pin_sdi,
&mut pin_rclk,
&mut pin_srclk,
seg_code[count / (base.pow(digit as u32) as usize) % 10],
);
};
while *count.lock().unwrap() < 10000 {
light_1digit(*count.lock().unwrap(), 0);
light_1digit(*count.lock().unwrap(), 1);
light_1digit(*count.lock().unwrap(), 2);
light_1digit(*count.lock().unwrap(), 3);
}
clear_display(&mut pin_sdi, &mut pin_rclk, &mut pin_srclk, false);
drop(guard);
Ok(())
}
ctrl-cで止めたとき、全部のLEDが消灯した状態で止まってほしい。
調べてみたらctrlcというcrateがあるらしい。
次はLEDドットマトリクス。
光らせるだけなのはちょっと飽きてきたが、74HC595を2つ使ったりよい練習になるだろう。この回路には抵抗がない。これでいいのか考える。
抵抗はあったほうが良さそうな気がする。ちゃんとした根拠はないが。
それとも8本並列になるから電流恐るるに足らず、ということなのだろうか。
とりあえずサンプルの回路に気の向くまま抵抗を配置し、光り具合で考えることにする。
電気に関して取っ掛かりになるような知識すらない。
こちらの解説はなんとなく読みやすい気がするのでサラッと目を通しておく。
求めていた情報だ。
もっと多くの LED を点灯させたい場合には並列にするしかないわけだが,この時,並列する線の一個一個に抵抗を付ける必要がある.豆電球とは違って,並列した LED の全てに電流が均等に分かれてくれないからだ.
これまで入力1つに対して220Ωの抵抗1個とLED1個で光らせたりしていたわけで、1個1個につけなくても良い気がする。
GPIO17、GPIO18、GPIO27からの入力にそれぞれ220Ωの抵抗を付けてみた。
サンプルプログラムでの動作は確認できた。特に抵抗の有無で明るさが変わったりはしていない気がする。
まあ…回路についてはとりあえずいいか。
どう光らせているのか理解しRustで書く。
冷静になってみると74HC595の制御に抵抗があっても意味ない気がする。
じゃあやっぱ8個抵抗を付けるかというと面倒…。
VCCやGNDに付けたらOKだったりするか?
今のところ壊れないで光ってくれているので今後理解したいこととしておく。
「1つ目の74HC595の最後の出力が2つ目の74HC595のSDIなの頭こんがらがるな~」
など考えてこんがらがっていたけど、単純に1つ目で溢れたやつが2つ目にシフトしていくだけか。
というわけでサンプルコードのRust版。ほぼそのままだ。
fn hc595_in(sdi: &mut OutputPin, srclk: &mut OutputPin, code: u8) {
for i in 0..8 {
if 0x80 & (code << i) != 0 {
sdi.set_high();
} else {
sdi.set_low();
}
turn_high_and_low(srclk, Duration::from_millis(0));
}
}
fn hc595_out(rclk: &mut OutputPin) {
turn_high_and_low(rclk, Duration::from_millis(0));
}
pub fn light_led_dot_matrix() -> Result<(), Box<dyn Error>> {
let code_h: [u8; 20] = [
0x01, 0xff, 0x80, 0xff, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff,
];
let code_l: [u8; 20] = [
0x00, 0x7f, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfd, 0xfb,
0xf7, 0xef, 0xdf, 0xbf, 0x7f,
];
let mut pin_sdi = Gpio::new()?.get(GPIO17)?.into_output();
let mut pin_rclk = Gpio::new()?.get(GPIO18)?.into_output();
let mut pin_srclk = Gpio::new()?.get(GPIO27)?.into_output();
loop {
for i in 0..code_h.len() {
hc595_in(&mut pin_sdi, &mut pin_srclk, code_l[i]);
hc595_in(&mut pin_sdi, &mut pin_srclk, code_h[i]);
hc595_out(&mut pin_rclk);
thread::sleep(Duration::from_millis(100));
}
for i in (0..code_h.len()).rev() {
hc595_in(&mut pin_sdi, &mut pin_srclk, code_l[i]);
hc595_in(&mut pin_sdi, &mut pin_srclk, code_h[i]);
hc595_out(&mut pin_rclk);
thread::sleep(Duration::from_millis(100));
}
}
Ok(())
}
ctrlcで消灯して終了するように。
pub fn light_led_dot_matrix() -> Result<(), Box<dyn Error>> {
let code_h: [u8; 20] = [
0x01, 0xff, 0x80, 0xff, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff,
];
let code_l: [u8; 20] = [
0x00, 0x7f, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfd, 0xfb,
0xf7, 0xef, 0xdf, 0xbf, 0x7f,
];
let mut pin_sdi = Gpio::new()?.get(GPIO17)?.into_output();
let mut pin_rclk = Gpio::new()?.get(GPIO18)?.into_output();
let mut pin_srclk = 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) {
for i in 0..code_h.len() {
hc595_in(&mut pin_sdi, &mut pin_srclk, code_l[i]);
hc595_in(&mut pin_sdi, &mut pin_srclk, code_h[i]);
hc595_out(&mut pin_rclk);
thread::sleep(Duration::from_millis(100));
}
for i in (0..code_h.len()).rev() {
hc595_in(&mut pin_sdi, &mut pin_srclk, code_l[i]);
hc595_in(&mut pin_sdi, &mut pin_srclk, code_h[i]);
hc595_out(&mut pin_rclk);
thread::sleep(Duration::from_millis(100));
}
}
clear_display(&mut pin_sdi, &mut pin_rclk, &mut pin_srclk, true);
Ok(())
}
次はLCD1602とやらを光らせる。まずはI2Cってなんぞやってところからかな。
とりあえずサンプルプログラム動かそうとしたが動かない。
raspi-configでi2cは有効にしたし、/boot/config.txtに"dtparam=i2c_arm=on"とあるのを確認し再起動した。
"i2cdetect -y 1"で0x27が有効になってるのも確認した。
配線は楽だからいつでも戻ってこれる。ここは後回し。
解決。コントラストの問題だった。
次、アクティブブザー。
アクティブブザーは通電すれば自力で音を出せるところがパッシブブザーと違うらしい。
トランジスタが回路に登場。
今回使うのはPNP型で、NPN型というのもあるそう。
NとかPは何かというと、P型半導体とN型半導体のこと。
N型半導体には負電荷を持つ自由電子があり、電圧をかけると電流が流れる。
P型半導体は電子の空席、正孔があり、電圧をかけると正孔に電子が流れて正孔が移動し電流が流れる。
トランジスタの端子は部品の型番が刻印されている面(今回使用する「2SC1740S」の場合は「C1740」と記載されている)を手前にした状態が左右の向きの基準となります。電子工作でよく使われるトランジスタはこの向きで左の足からエミッタ(E)・コレクタ(C)・ベース(B)の順番に並んでいるものが多いです。この「E・C・B」の順番は語呂合わせで「え・く・ぼ」と呼ばれており、データシートを確認する手間を省きたいときはこのようにして端子の順番を見ることがよくあります。
可愛い。覚えやすい。
回路図見ていて「う~ん?」となったので確認したら、今回使用するS8550は左からE・B・Cの並びだった。
アクティブブザー、猫がビックリするのであまり遊ばないほうが良さそう。
ピピピピ鳴らすだけのコード。
pub fn beep_active_buzzer() -> Result<(), Box<dyn Error>> {
let mut beep_pin = Gpio::new()?.get(GPIO17)?.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) {
beep_pin.set_low();
thread::sleep(Duration::from_millis(100));
beep_pin.set_high();
thread::sleep(Duration::from_millis(10));
}
beep_pin.set_high();
Ok(())
}
次はパッシブブザー。音楽を流すらしい。楽しそう。
サンプルコードで用意されている周波数の音を順番に鳴らす。
set_pwm_frequencyの第2引数はデューティーサイクル、今回は音量と考えて良さそう。
pub fn beep_passive_buzzer() -> Result<(), Box<dyn Error>> {
// Frequency of Bass tone in C major
const CL: [f64; 8] = [0.0, 131.0, 147.0, 165.0, 175.0, 196.0, 211.0, 248.0];
// Frequency of Midrange tone in C major
const CM: [f64; 8] = [0.0, 262.0, 294.0, 330.0, 350.0, 393.0, 441.0, 495.0];
// Frequency of Treble tone in C major
const CH: [f64; 8] = [0.0, 525.0, 589.0, 661.0, 700.0, 786.0, 882.0, 990.0];
// Retrieve the GPIO pin and configure it as an output.
let mut beep_pin = Gpio::new()?.get(GPIO17)?.into_output();
for freq in CL {
beep_pin.set_pwm_frequency(freq, 0.1)?;
thread::sleep(Duration::from_millis(500));
}
for freq in CM {
beep_pin.set_pwm_frequency(freq, 0.1)?;
thread::sleep(Duration::from_millis(500));
}
for freq in CH {
beep_pin.set_pwm_frequency(freq, 0.1)?;
thread::sleep(Duration::from_millis(500));
}
Ok(())
}
我ながら暇だなって思った。
pub fn beep_passive_buzzer() -> Result<(), Box<dyn Error>> {
struct CDEFGAB {
c: f64,
d: f64,
e: f64,
f: f64,
g: f64,
a: f64,
b: f64,
}
const M_TONE: CDEFGAB = CDEFGAB {
c: 261.626,
d: 293.665,
e: 329.628,
f: 349.228,
g: 391.995,
a: 440.0,
b: 493.883,
};
const H_TONE: CDEFGAB = CDEFGAB {
c: 523.251,
d: 587.33,
e: 659.255,
f: 698.456,
g: 783.991,
a: 880.0,
b: 987.767,
};
let mut beep_pin = Gpio::new()?.get(GPIO17)?.into_output();
let mut beep = |tone: f64, duty_cycle: f64, millis: u64| {
let _ = beep_pin.set_pwm_frequency(tone, duty_cycle);
thread::sleep(Duration::from_millis(millis));
};
for i in 0..3 {
// ときめく
beep(M_TONE.b, 0.1, 500);
beep(H_TONE.d, 0.1, 500);
beep(M_TONE.a, 0.1, 500);
beep(M_TONE.b, 0.1, 500);
// こころの
beep(M_TONE.a, 0.1, 500);
beep(H_TONE.d, 0.1, 500);
beep(H_TONE.d, 0.1, 480);
beep(H_TONE.d, 0.0, 20);
beep(H_TONE.e, 0.1, 500);
if i == 0 {
// もーしょん
beep(739.938, 0.1, 500);
beep(H_TONE.d, 0.1, 500);
beep(M_TONE.b, 0.1, 500);
beep(M_TONE.a, 0.1, 480);
beep(M_TONE.a, 0.0, 20);
// が
beep(M_TONE.a, 0.1, 500);
beep(M_TONE.a, 0.0, 1500);
} else if i == 1 {
// やまない
beep(H_TONE.d, 0.1, 500);
beep(H_TONE.e, 0.1, 500);
beep(H_TONE.a, 0.1, 750);
beep(H_TONE.a, 0.0, 250);
// の
beep(H_TONE.d, 0.1, 1000);
beep(H_TONE.d, 0.0, 1000);
} else {
// プログラ
beep(739.938, 0.1, 500);
beep(H_TONE.d, 0.1, 500);
beep(M_TONE.b, 0.1, 500);
beep(M_TONE.a, 0.1, 480);
beep(H_TONE.a, 0.0, 20);
// ム しりた
beep(M_TONE.a, 0.1, 480);
beep(H_TONE.a, 0.0, 20);
beep(M_TONE.a, 0.0, 500);
beep(M_TONE.a, 0.1, 250);
beep(M_TONE.b, 0.1, 250);
beep(M_TONE.b, 0.0, 250);
beep(H_TONE.d, 0.1, 250);
// い しりた
beep(H_TONE.d, 0.1, 500);
beep(H_TONE.d, 0.0, 500);
beep(M_TONE.a, 0.1, 250);
beep(M_TONE.b, 0.1, 250);
beep(M_TONE.b, 0.0, 250);
beep(H_TONE.d, 0.1, 250);
// い ねえもっと
beep(H_TONE.d, 0.1, 250);
beep(M_TONE.b, 0.0, 250);
beep(M_TONE.a, 0.0, 500);
beep(739.938, 0.1, 500);
beep(H_TONE.d, 0.1, 500);
// つきあって
beep(H_TONE.d, 0.1, 500);
beep(M_TONE.a, 0.1, 500);
beep(H_TONE.e, 0.1, 750);
beep(H_TONE.e, 0.08, 250);
beep(H_TONE.d, 0.1, 1000);
}
}
beep(H_TONE.d, 0.0, 1000);
for i in 0..4 {
// ダンスロボットダンス
beep(H_TONE.d, 0.1, 200);
beep(H_TONE.d, 0.0, 550);
beep(H_TONE.d, 0.1, 230);
beep(H_TONE.d, 0.01, 20);
beep(H_TONE.d, 0.1, 200);
beep(H_TONE.d, 0.0, 300);
beep(554.365, 0.1, 250);
beep(H_TONE.d, 0.1, 250);
if i == 0 || i == 2 {
beep(M_TONE.a, 0.0, 500);
beep(M_TONE.a, 0.1, 500);
beep(739.938, 0.1, 500);
beep(H_TONE.d, 0.1, 450);
beep(H_TONE.d, 0.0, 50);
} else if i == 1 {
beep(M_TONE.a, 0.0, 500);
beep(H_TONE.d, 0.1, 500);
beep(H_TONE.e, 0.1, 230);
beep(H_TONE.e, 0.05, 20);
beep(H_TONE.d, 0.1, 450);
beep(H_TONE.d, 0.0, 50);
} else {
beep(H_TONE.d, 0.0, 500);
beep(H_TONE.d, 0.1, 500);
beep(H_TONE.a, 0.1, 250);
beep(H_TONE.a, 0.0, 250);
beep(H_TONE.d, 0.1, 500);
}
}
Ok(())
}
楽譜はこちらを使わせていただきました。
次からはモーター。
L293Dは、高電圧と高電流のチップで統合された4チャネルモータードライバーである。
モーターを動かすための便利グッズ。
おそらくL293Dのおかげで簡単に回すことができる。
pub fn motor() -> Result<(), Box<dyn Error>> {
let mut pin_en1 = Gpio::new()?.get(GPIO22)?.into_output();
let mut pin_1a = Gpio::new()?.get(GPIO27)?.into_output();
let mut pin_2a = Gpio::new()?.get(GPIO17)?.into_output();
pin_en1.set_high();
pin_1a.set_high();
pin_2a.set_low();
thread::sleep(Duration::from_secs(3));
pin_2a.set_high();
thread::sleep(Duration::from_secs(3));
pin_1a.set_low();
thread::sleep(Duration::from_secs(3));
Ok(())
}
次はサーボ。
配線もコードも別に難しくなさそうなので、どういうところに使われてるものなのかとかを見たいところ。
例えば、自動車製造工場で稼働する産業用ロボットは、部品をピッキングしたり、溶接したり、塗装したり、常に同じ動作を正確に繰り返しながら、大量の自動車を作り出しています。ロボットに内蔵されているサーボモータに指示を出すと、決められた位置や速度や回転力(トルク)で忠実に動いてくれます。
そのため、いまやサーボモータは、超高速や超精密な制御を行う産業機械の構成要素として、必要不可欠なものになっています。例えば、前出の産業用ロボットはもちろん、工作機械、電子部品の実装装置、半導体・液晶製造装置、射出成形機、ラベル包装機、プレス機械、医療機器など、様々な利用シーンで大活躍しているのです。
サーボはパルス幅変調は20ミリ秒に1パルスを期待している。
パルスの長さで角度が操作でき、1.5msで90度。
0.5msが最小で0度。2.5msが最大で180度。
ご家庭で使うとしたらパッと思いつくのはスマートロックとか、あとは固定の猫カメラの向きかえるとかに使えそう?
pub fn servomotor() -> Result<(), Box<dyn Error>> {
const PERIOD_MS: u64 = 20;
const PULSE_MIN_US: u64 = 500;
const PULSE_NEUTRAL_US: u64 = 1500;
const PULSE_MAX_US: u64 = 2500;
let mut pin_servo = Gpio::new()?.get(GPIO18)?.into_output();
for i in PULSE_NEUTRAL_US..PULSE_MAX_US {
pin_servo.set_pwm(Duration::from_millis(PERIOD_MS), Duration::from_micros(i))?;
thread::sleep(Duration::from_millis(10));
}
for i in PULSE_MIN_US..PULSE_NEUTRAL_US {
pin_servo.set_pwm(Duration::from_millis(PERIOD_MS), Duration::from_micros(i))?;
thread::sleep(Duration::from_millis(10));
}
Ok(())
}
rppalのソフトウェアPWMのサンプルコードはサーボモーターを動かすものになっている。
次はステッピングモーター。
何がどうなって動くのかなどを理解できる気がしないが、多分youtube探したらわかりやすい解説あるんじゃないかな。
思ったよりあるな…。とりあえずぱっと出てきた中で一番短いもの。
ドライバーボードというものを使って操作する。
IN1からIN4に入力をするのだが…ワイヤを接続するようなピン(?)がない。
キットの説明書の写真とか見るに普通に付いてるはずなので加工漏れか?
トホホ~。
探して買う。
届いたのでやっていく。
1相励磁: 電流を流す配線を1つづつ切り替えて回す方法。常に1本にしか電流を流さないので低電力だが切り替わりが発生するのでガタガタうるさい。
2相励磁: 電流を1本目に流してる途中で2本目にも流し、2本目の途中で3本目にも流し…とする方法。当然電力はone phase onより使うがパワフルでなめらか静か。
1-2相励磁: 1相励磁と2相励磁を交互に繰り返す方式。回転のスピードは前の2つの半分になるが更になめらか。
というわけでまずは1相励磁。
pub fn stepper_motor() -> Result<(), Box<dyn Error>> {
let mut pins = [
Gpio::new()?.get(GPIO18)?.into_output(),
Gpio::new()?.get(GPIO23)?.into_output(),
Gpio::new()?.get(GPIO24)?.into_output(),
Gpio::new()?.get(GPIO25)?.into_output(),
];
let one_step = [
[true, false, false, false],
[false, true, false, false],
[false, false, true, false],
[false, false, false, true],
];
loop {
for step in one_step {
for i in 0..4 {
if step[i] {
pins[i].set_high();
} else {
pins[i].set_low();
}
thread::sleep(Duration::from_micros(1000));
}
}
}
}
2相励磁。さっきのコードだとsleepの長さが500マイクロ秒だと高い音を立てるだけで回らなくなったけどこっちなら回る。
滑らかさはよくわからない。
pub fn stepper_motor() -> Result<(), Box<dyn Error>> {
let mut pins = [
Gpio::new()?.get(GPIO18)?.into_output(),
Gpio::new()?.get(GPIO23)?.into_output(),
Gpio::new()?.get(GPIO24)?.into_output(),
Gpio::new()?.get(GPIO25)?.into_output(),
];
let two_step = [
[true, true, false, false],
[false, true, true, false],
[false, false, true, true],
[true, false, false, true],
];
loop {
for step in two_step {
for i in 0..4 {
if step[i] {
pins[i].set_high();
} else {
pins[i].set_low();
}
thread::sleep(Duration::from_micros(1000));
}
}
}
}
1-2相励磁。
let half_step = [
[true, false, false, false],
[true, true, false, false],
[false, true, false, false],
[false, true, true, false],
[false, false, true, false],
[false, false, true, true],
[false, false, false, true],
[true, false, false, true],
];
入力編、最後はリレー。
じっくり理解しよう。
ダイオード:電流を一方向にしか流れないようにするもの(ざっくり)。
アノードとカソードを持ち、順方向にちゃんと繋がないと電流が流れづらい。
オームの法則に従わず、一定以上の電圧で一気に電流が流れるようになる。
つまり当たり前だが発光ダイオードと同じ性質。ただしあんまり光らない。
わざと逆方向に繋いで使うという技もあるらしい。
リレー:電流が流れる→電磁力が発生する→その影響で他の回路が繋がる
回路図のとおりにつなげた気がするが、思った通りに動かない。
最初GPIO17をlowにする(LEDがつく)→GPIO17をhighにする(LEDがついたまま)
最初GPIO17をhighにする(LEDがつかない)→GPIO17をlowにする(LEDがつく)→GPIO17をhighにする(LEDがついたまま)
6つあるピンの中央のセットの電流で制御。
トランジスタを変えたら普通に動いた。
トランジスタが壊れてるとかではなく、NPNじゃなくてPNP使ってた。
無駄に悩んだおかげでリレーの回路については理解できた気がする。
なんでダイオードが回路にあるのかはわかってないな。
色々試してるときもわからないから置いてなかったが、今無くても動いている。
逆起電力というものの対策らしい。頭が働いているときに見る。
入門キットでの出力編が終わりなのでこっちはクローズ。
続きは入力編。