Rust x Raspberry Pi Pico で超音波測距

Rust
Sponsored

概要

この記事では、 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 を参照のこと。

https://github.com/doraneko94/rp_pico_examples/blob/main/src/ultrasonic_ranging.rs

解説

それぞれの細かなテクニックについては、以下の実装例集に記載してある。

組込みRust実装例集―Raspberry Pi Picoで電子工作
概要 この記事は、Raspberry Pi PicoにRust言語を使って組込みを行う際のTips集です。 各電子パーツを使って実現したいことから、対応するコードの記法を探せるようになっています。 適宜追加するので、ちょくちょく見に来てもら...

タクトスイッチ

// 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_flgtrue であるなら「押し続けられている」と判断して何もしない。

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();

echoGPIO16 に pull down 入力として、 triggerGPIO17 に 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クレート

GitHub - doraneko94/serial_write: Simplifying serial output in a no_std environment, both string and numeric.
Simplifying serial output in a no_std environment, both string and numeric. - GitHub - doraneko94/serial_write: Simplifying serial output in a no_std environmen...

を用いる。

// Display the distance
let _ = writer.write_f64(distance, 2, &mut serial);
let _ = writer.writeln_str("cm", &mut serial);

計測された距離をシリアル通信により送信する。

詳細は実装例集を参照のこと。

Comments