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)

この記事ではポテンショメーターのアナログ値を取得し、LEDの光の強弱を制御します

また、必要なモジュールを追加するため、 Nerves Livebook をカスタマイズします

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

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

## ポテンショメーター

矢印が描かれたツマミを回すことで抵抗値が変化します

![IMG_1642.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/1c7687ee-be77-d976-0e2e-d2c900e133ce.jpeg)

## ADC0834

ADC0834 は AD 変換器（AD コンバーター）です

![IMG_1643.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/71e7b9a2-5402-35a6-3b3f-5676e5ed169d.jpeg)

REF の電圧（5V）を基準として、CH0〜3の入力電圧（アナログ値）がどの程度なのか 0 〜　255 の 256 段階（8 bit）のデジタル値に変換します

## 回路の組み立て

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

ただし、 **LED に接続するピンはPWM対応が必須であるため、 GPIO22 ではなく GPIO12 を使います**

:::note warn
元の通りに回路を組むと、 LED への PWM 出力実行時に以下のエラーが返されます

```elixir
{:error, :not_hpwm_gpio}
```
:::

https://docs.sunfounder.com/projects/davinci-kit/ja/latest/2.1.4_potentiometer.html

- CS: AD 変換の開始終了を制御する
- CLK: 0 -> 1 に変化したタイミングで DI/DO の値を入出力する
- DI: データ入力
- DO: データ出力
- CH0: ポテンショメーターからの入力

## Nerves Livebook のカスタマイズ

以下の記事に従って Pigpiox 追加済の Nerves Livebook を使用します

https://qiita.com/RyoWakabayashi/private/9df8d216d6ffeca8c6d5

## ノートブックの実装

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

### ピンを開く

ADC0834 を制御するためのピンを開きます

```elixir
{:ok, cs} = Circuits.GPIO.open("GPIO17", :output)
{:ok, clk} = Circuits.GPIO.open("GPIO18", :output)
{:ok, dio} = Circuits.GPIO.open("GPIO27", :output)
```

`dio` は DI と DO の両方に接続し、必要に応じて入出力を切り替えて使用します

### モジュール定義

ADC0834 を制御するモジュールを定義します

```elixir
defmodule ADC do
  import Bitwise

  def get_result(dio, clk, cs, channel \\ 0) do
    sel = if channel > 1, do: 1, else: 0
    odd = channel &&& 1

    # AD 変換開始
    Circuits.GPIO.write(cs, 0)
    start_adc(dio, clk)

    # シングルエンド信号を使用（1本の信号だけを読む）
    single_end_mode(dio, clk)

    # チャンネル設定（デフォルトは CH0 を使用）
    set_odd(dio, clk, odd)
    set_sel(dio, clk, sel)

    # dio を入力に設定
    Circuits.GPIO.set_direction(dio, :input)

    # AD変換を実行し、各桁の値を取得
    dat1 =
      0..7
      |> Enum.reduce(0, fn _i, acc ->
        Circuits.GPIO.write(clk, 1)
        :timer.sleep(2)
        Circuits.GPIO.write(clk, 0)
        :timer.sleep(2)

        Circuits.GPIO.set_direction(dio, :input)

        bit = Circuits.GPIO.read(dio)
        acc = acc <<< 1 ||| bit
        acc
      end)

    # AD変換完了後に改めて逆順で各桁の値を取得
    dat2 =
      0..7
      |> Enum.reduce(0, fn i, acc ->
        bit = Circuits.GPIO.read(dio)
        acc = acc ||| bit <<< i
        Circuits.GPIO.write(clk, 1)
        :timer.sleep(2)
        Circuits.GPIO.write(clk, 0)
        :timer.sleep(2)

        acc
      end)

    # AD 変換終了
    Circuits.GPIO.write(cs, 1)
    Circuits.GPIO.set_direction(dio, :output)

    # 変換中と変換後の値が等しければ出力する
    if dat1 == dat2, do: dat1, else: 0
  end

  defp start_adc(dio, clk) do
    Circuits.GPIO.write(clk, 0)
    Circuits.GPIO.write(dio, 1)
    :timer.sleep(2)
    Circuits.GPIO.write(clk, 1)
    :timer.sleep(2)
  end

  defp single_end_mode(dio, clk) do
    Circuits.GPIO.write(clk, 0)
    Circuits.GPIO.write(dio, 1)
    :timer.sleep(2)
    Circuits.GPIO.write(clk, 1)
    :timer.sleep(2)
  end

  defp set_odd(dio, clk, odd) do
    Circuits.GPIO.write(clk, 0)
    Circuits.GPIO.write(dio, odd)
    :timer.sleep(2)
    Circuits.GPIO.write(clk, 1)
    :timer.sleep(2)
  end

  defp set_sel(dio, clk, sel) do
    Circuits.GPIO.write(clk, 0)
    Circuits.GPIO.write(dio, sel)
    :timer.sleep(2)
    Circuits.GPIO.write(clk, 1)
    :timer.sleep(2)
    Circuits.GPIO.write(clk, 0)
    :timer.sleep(2)
  end
end
```

### AD変換した結果を取得する

`ADC.get_result` でポテンショメーターのアナログ値を ADC0834 がデジタル値に変換した結果の値を取得します

```elixir
analog_val = ADC.get_result(dio, clk, cs)
```

### LEDの点灯

0 〜 255 の値をデューティー比として PWM を実行し、 LED の光の強弱を制御します

```elixir
duty = round(analog_val * 1_000_000 / 255)
duty / 10000
```

```elixir
Pigpiox.Pwm.hardware_pwm(12, 2000, duty)
```

ポテンショメーターを一番右に回している状態では AD 変換結果が 0 になり、 LED は光ません

![IMG_1634.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/dbaf1b00-44c0-cd7d-ba51-38af4f518ec8.jpeg)

少し左に回すと値が少し増え、 LED も少し光ります

![IMG_1635.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/e54106ef-9795-d55c-6cfa-41393322aff5.jpeg)

中間辺り

![IMG_1636.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/db92c9f8-1bf9-6e69-fb9e-96bacc70480e.jpeg)

かなり左に回す

![IMG_1638.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/7b0b0f72-b747-791d-691a-211421f31d14.jpeg)

最大出力

![IMG_1639.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/2dfa00ae-2fe6-796e-4386-1cb206f77e1a.jpeg)

### ポテンショメーターとLEDを連動させる

一定期間ポテンショメーターの値を取得してLEDを光らせる処理を繰り返し、連動させます

```elixir
1..300
|> Enum.map(fn _i ->
  analog_val = ADC.get_result(dio, clk, cs)
  duty = round(analog_val * 1_000_000 / 255)
  IO.inspect(duty / 10000)
  Pigpiox.Pwm.hardware_pwm(12, 2000, duty)
end)
```

<iframe width="455" height="808" src="https://www.youtube.com/embed/qyxJqrWLR9A" title="ポテンショメーターとLEDを連動" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

### ピンを閉じる

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

```elixir
Circuits.GPIO.close(cs)
Circuits.GPIO.close(clk)
Circuits.GPIO.close(dio)
```

## まとめ

ポテンショメーターと AD 変換器によって、アナログ操作をデジタル値に変換して制御することができました

AD 変換は IoT でも重要な役割を果たしますね