Nerves Livebook で超音波センサーから距離を計測する

## はじめに

Nerves Livebook で Raspberry Pi 用スターター電子工作キットを楽しんでいます

[SunFounder Raspberry Pi 用のスターター電子工作キット](https://amzn.asia/d/04vHhGKQ)

これまでの記事

- [Nerves Livebook で Raspberry Pi 4 のLチカ](https://qiita.com/RyoWakabayashi/items/8119260ceeea462d6b7b)
- [Nerves Livebook + Delux で簡単Lチカ](https://qiita.com/RyoWakabayashi/items/281afab89249dbf68cd5)
- [MacBook 上の Livebook から Raspberry Pi 上の Livebook に接続してLチカする](https://qiita.com/RyoWakabayashi/private/844da38ff24efa3b923c)
- [Nerves Livebook から GPIO 拡張ボードの LED を点滅させる](https://qiita.com/RyoWakabayashi/private/bda56ee6987b30857266)
- [Nerves Livebook から RGB LED を制御する（ソフトPWMもやってみる）](https://qiita.com/RyoWakabayashi/private/143fda67a1798bd788b7)
- [Nerves Livebook から LED 棒グラフを制御する](https://qiita.com/RyoWakabayashi/items/a6e8b712388d68bdca44)
- [Nerves Livebook でボタンとLEDを連動させる](https://qiita.com/RyoWakabayashi/items/9bf9475d3c5d65cdfa4e)
- [Nerves Livebook で 7 セグメントディスプレイに文字を表示する](https://qiita.com/RyoWakabayashi/items/d13d86d91c86e816d899)
- [Nerves Livebook で 4 桁 7 セグメントディスプレイに文字を表示する](https://qiita.com/RyoWakabayashi/items/32edd890ade613cb4e37)
- [Nerves Livebook で LED ドットマトリックスにアニメーションを表示する](https://qiita.com/RyoWakabayashi/private/16f110cdd4febd0aedc1)
- [Nerves Livebook でアクティブブザーを鳴らす](https://qiita.com/RyoWakabayashi/private/b932c04c3050c22b373f)
- [Nerves Livebook でパッシブブザーを演奏しドレミの歌を奏でる](https://qiita.com/RyoWakabayashi/items/c203e40bd2004cb95aae)
- [Nerves Livebook でポテンショメーターからアナログ値を取得してデジタル値に変換する](https://qiita.com/RyoWakabayashi/private/ea1a620c3114b064dc2c)
- [Nerves Livebook でフォトレジスター（光センサー）からアナログ値を取得してデジタル値に変換する](https://qiita.com/RyoWakabayashi/items/d6948b9f7df2ead8d9e4)

この記事では超音波センサーを使い、対象物までの距離を計測します

実装したノートブックはこちら

https://github.com/RyoWakabayashi/elixir-learning/blob/main/livebooks/nerves_livebook/ultrasonic_sensor.livemd

## 超音波センサー

超音波(人間の耳に聞こえない音)を正面に向けて出力し、跳ね返ってきた超音波を検出します

出力してから跳ね返ってくるまでの時間によって、正面にある物体がどれくらい離れているか、つまり距離を計測することが可能です

![IMG_2220.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/0d414358-02c2-84e7-fc73-ff59b037e201.jpeg)

イルカや蝙蝠がやっているエコーロケーションですね

https://natgeo.nikkeibp.co.jp/atcl/news/21/020500061/

## 回路の組み立て

オンライン説明書の通りに回路を組みます

https://docs.sunfounder.com/projects/davinci-kit/ja/latest/2.2.5_ultrasonic_sensor_module.html

## ノートブックの実装

Nerves Livebook を開き、以降のコードを実行していきます

### ピンを開く

超音波センサーを制御するためのピンを開きます

```elixir
{:ok, trig} = Circuits.GPIO.open("GPIO23", :output)
{:ok, echo} = Circuits.GPIO.open("GPIO24", :input)
```

`trig` を超音波発信用の出力、 `echo` を超音波検出用の入力にしています

### モジュール定義

超音波センサーの制御用モジュールを定義します

```elixir
defmodule UltrasonicSensor do
  def distance(trig, echo) do
    Circuits.GPIO.write(trig, 0)
    Process.sleep(1)

    # 1 msec の超音波を送信
    Circuits.GPIO.write(trig, 1)
    Process.sleep(1)
    Circuits.GPIO.write(trig, 0)

    # 最初は 0 の状態を確認
    time_1 = wait_for(echo, 0)

    # 1 になるまでの時間を取得
    time_2 = wait_for(echo, 1)

    during = Time.diff(time_2, time_1, :microsecond)

    # 空気中の音速を使って距離(cm)に変換（往復なので半分にする）
    during * 340 / 2 / 10000
  end

  defp wait_for(echo, value) do
    1..10000
    |> Enum.reduce_while(nil, fn _i, _acc ->
      if Circuits.GPIO.read(echo) == value do
        {:cont, Time.utc_now()}
      else
        {:halt, Time.utc_now()}
      end
    end)
  end
end
```

`Circuits.GPIO.write(trig, 1)` によって超音波を発します

`Process.sleep(1)` で 1 ミリ秒待ってから `Circuits.GPIO.write(trig, 0)` にすると、 1ミリ秒だけ超音波を出していたことになります

`Circuits.GPIO.read(echo)` の結果が 0 から 1 に変化したときが、超音波の跳ね返ってきたときです

跳ね返ってくるまでの時間と音速（340 m/s）を使うと、正面の物体までの距離が計算できます

### 距離の計測

実際に距離を計測してみます

100 回連続して計測している間、超音波センサーの前で手のひらを前後させてみました

```elixir
1..100
|> Enum.map(fn _ ->
  UltrasonicSensor.distance(trig, echo) |> IO.inspect()

  Process.sleep(100)
end)
```

<iframe width="1141" height="642" src="https://www.youtube.com/embed/2hanTPDB2Hk" title="超音波センサーで距離計測" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

### 距離のグラフ化

数字だけだとピンとこないので、グラフで距離をリアルタイムにプロットしてみました

```elixir
distance_plot =
  VegaLite.new(width: 700)
  |> VegaLite.mark(:line)
  |> VegaLite.encode_field(:x, "index", type: :quantitative)
  |> VegaLite.encode_field(:y, "distance", type: :quantitative)
  |> Kino.VegaLite.new()
```

```elixir
Kino.VegaLite.clear(distance_plot)

1..300
|> Enum.map(fn index ->
  distance = UltrasonicSensor.distance(trig, echo)

  IO.inspect(distance)

  plot_data = %{
    index: index,
    distance: distance
  }

  Kino.VegaLite.push(distance_plot, plot_data)
  Process.sleep(100)
end)
```

<iframe width="1141" height="642" src="https://www.youtube.com/embed/gWx5W7viPO0" title="超音波センサーの距離計測結果をグラフ化" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

今度はリバテープの箱を対象物にしています

https://libatape.jp/product/bansoko/

### ピンを閉じる

遊び終わったらピンを閉じます

```elixir
Circuits.GPIO.close(trig)
Circuits.GPIO.close(echo)
```

## まとめ

手の平だと計測があまり安定せず、 Qiitan ぬいぐるみだとほとんど正しく計測できませんでした

リバテープの箱のように、超音波をしっかり跳ね返してくれるものでないと上手くいかない、ということが分かりました