概要
この記事では、 Raspberry Pi Pico に超音波測距モジュール HC-SR04
を接続し、それらを Rust 言語によって制御して、物体との距離を測る方法を説明する。
仕様
(※写真と回路図は、 超音波測距モジュールの向きや配線が異なるので注意)
タクトスイッチを押すと、超音波測距モジュールの送受信機の前にある物体との距離を計測する。
計測した距離はシリアル通信によって、PC等に送信される。
LED を接続した場合は、距離が近いほど明るさを強くする。
超音波測距モジュールのデータシートによると、測定可能範囲は 2-450cm である。
必要なもの
- Raspberry Pi Pico
- 超音波測距モジュール (HC-SR04)
- タクトスイッチ
- 抵抗(適量)
- ジャンパワイヤ(適量)
- (LED)
回路
回路図
解説
HC-SR04 (VCC)
電源。VBUS
(5V) に接続する。
HC-SR04 (Trig)
Raspberry Pi Pico から、超音波送信を開始するためのパルスを入力する。
GPIO17
に接続。
HC-SR04 (Echo)
超音波を受信したときにパルスを出力する。
同一の抵抗3つを介して、ブレットボード反対側のグランド (GND) に繋ぐ。ここで、1つ目の抵抗の下流を GPIO16
に接続し、Raspberry Pi Pico への入力とする。
HC-SR04 の出力電圧は 5V であるため、 3.3V で動作する Raspberry Pi Pico にそのまま入力すると故障の原因となる。そのため、抵抗を用いて入力が \(5 \times \frac{2}{3}=3.3\ldots V\) となるように調節している。
HC-SR04 (GND)
グランド。Raspberry Pi Pico の GND
に接続する。
タクトスイッチ
GPIO5
に接続し、電圧を制御する。
LED
なくても良い。測定した距離に合わせて PWM で明るさを変える。
+極を GPIO3
に接続し、抵抗を介してー極をGND
に繋ぐ。
コード
GitHub
コードはGitHub上に公開されている。使用しているクレート等は、 ../Cargo.toml
を参照のこと。
解説
それぞれの細かなテクニックについては、以下の実装例集に記載してある。
タクトスイッチ
// Set an input from a switch to gpio5
let switch = pins.gpio5.into_pull_up_input();
タクトスイッチの制御ピンを GPIO5
に設定。 Pull up 入力としたため、ボタンが押されていない場合は high
となる。
// Switch on/off
let mut switch_flg = false;
switch_flg
でボタンの状態を管理する。ボタンが押されていない状態では false
となる。
// Switch on
if switch.is_low().ok().unwrap() {
if switch_flg {
continue;
} else {
// Trigger ultrasonic pulse
// ...
switch_flg = true;
// ...
}
} else {
switch_flg = false;
}
スイッチが押された場合( low
)、 switch_flg
が true
であるなら「押し続けられている」と判断して何もしない。
false
であるなら「新規に押された」と判断して、超音波測距用のパルスを発する。
HC-SR04
// Set an input from a ultrasonic ranging sensor (echo) to gpio16
let echo = pins.gpio16.into_pull_down_input();
// Set an output to a ultrasonic ranging sensor (trigger) from gpio17
let mut trigger = pins.gpio17.into_push_pull_output();
echo
を GPIO16
に pull down 入力として、 trigger
を GPIO17
に push pull 出力として設定。
// Create a timer
let timer = hal::timer::Timer::new(pac.TIMER, &mut pac.RESETS);
時間計測用のタイマーを定義する。
// Trigger ultrasonic pulse
trigger.set_low().ok().unwrap();
delay.delay_us(2);
trigger.set_high().ok().unwrap();
delay.delay_us(10);
trigger.set_low().ok().unwrap();
データシートの使用例にしたがい、 10 マイクロ秒のパルスを trigger
から発すると、 HC-SR04
から超音波が発射される。
// Measure the time it took for the pulse to come back
let mut time_low = 0;
let mut time_high = 0;
while echo.is_low().ok().unwrap() {
time_low = timer.get_counter().ticks();
}
while echo.is_high().ok().unwrap() {
time_high = timer.get_counter().ticks();
}
let time = time_high - time_low;
echo=low
の状態は超音波が発射されるまで維持され、この間 time_low
を更新し続けることで、この変数には最終的に「超音波が発射されたときの時間」が入る。
その後、 echo=high
となるが、この状態は受信機が反射波を捕らえるまで維持される。すなわち、この間 time_high
を更新し続けることで、この変数には最終的に「反射波を受信したときの時間」が入る。
これらの差を求めることで、「超音波が出て、物体に反射されて帰ってくるまでの時間(マイクロ秒)」が求まる( time
)。
// Convert the time to the distance (cm)
let distance = time as f64 * 0.0343 / 2.0;
音速は \(343\mathrm{m/s}=3.43\times 10^4\mathrm{cm/s}=0.0343\mathrm{cm/ \mu s}\) より、これで time
を割ることによって距離が求まる。
ただし、これは HC-SR04
から物体までの往復の距離であるため、さらに 2 で割ったものが物体までの距離である。
(LED)
// Init PWMs
let mut pwm_slices = hal::pwm::Slices::new(pac.PWM, &mut pac.RESETS);
// Configure PWM1
let pwm = &mut pwm_slices.pwm1;
pwm.enable();
pwm.set_top(24999);
pwm.set_div_int(100);
pwm.set_div_frac(0);
// Output channel B on PWM1 to the LED pin
let channel = &mut pwm.channel_b;
channel.output_to(pins.gpio3);
距離を光の強度で表現するための LED を GPIO3
に設定し、 PWM として制御する。
PWM の詳細は実装例集を参照のこと。
// Adjust the brightness of the LED according to the distance
if distance > 100.0 {
channel.set_duty(1000);
} else {
channel.set_duty((64535 as f64 * ((100.0 - distance) / 100.0)) as u16 + 1000);
}
距離が 100cm 以上のときは duty 比( 16 ビット符号なし整数で指定)を最低値 1000 に、それよりも距離が短いときは 1000-65535 の範囲で距離が短いほど強くする。
$$\mathrm{Duty}=(65535-1000)\times\frac{100-\mathrm{distance}}{100}+1000$$
シリアル通信
let mut writer = Writer::new();
シリアル通信による文字列・数字の表示にはserial_write
クレート
を用いる。
// Display the distance
let _ = writer.write_f64(distance, 2, &mut serial);
let _ = writer.writeln_str("cm", &mut serial);
計測された距離をシリアル通信により送信する。
詳細は実装例集を参照のこと。
Comments