Nerves Livebook で 4 桁 7 セグメントディスプレイに文字を表示する

## はじめに

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)

この記事では 4 桁 7 セグメントディスプレイに文字を表示します

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

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

## 4 桁 7 セグメントディスプレイ

前回の記事で使った 7 セグメントディスプレイが 4 桁分繋がっています

![IMG_1582.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/51117c62-8418-f844-0724-812a5091167c.jpeg)

前回の 7 セグメントディスプレイはカソードコモン（CC）でしたが、今回はアノードコモン（CA）になっています

逆方向になっているため、 0 で光って 1 で消えます

つまり前回は `0` の文字を `0b00111111` で表していましたが、今回は `0b11000000` で表します

また、 4 桁ありますが、実は同時に 4 桁全て光らせることはできません

一桁ずつ光らせるのを高速に切り替えることで、人の目には 4 桁同時に光っているかのように見せます

例えば `1234` と表示される場合、以下のように順に光らせています

```
   4
  3 
 2  
1   
```

## 回路の組み立て

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

https://docs.sunfounder.com/projects/davinci-kit/ja/latest/1.1.5_4-Digit_7-segment_display.html

## ノートブックの実装

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

### 準備

ビット演算のために `Bitwise` を読み込みます

```elixir
import Bitwise
```

### ピンを開く

GPIO の各ピンを出力用に開きます

まずは前回と同じシフトレジスタ 74HC595 に信号を送るためのピンを開きます

```elixir
{:ok, sdi} = Circuits.GPIO.open("GPIO24", :output)
{:ok, rclk} = Circuits.GPIO.open("GPIO23", :output)
{:ok, srclk} = Circuits.GPIO.open("GPIO18", :output)
```

次に、光らせる桁を制御するための 4 つのピンを開きます

```elixir
place_pins =
  [
    "SPI_MOSI",
    "GPIO22",
    "GPIO27",
    "GPIO17"
  ]
  |> Enum.map(fn label ->
    label
    |> Circuits.GPIO.open(:output)
    |> elem(1)
  end)
```

4 つのうち 1 つだけに `1` を送ることで、指定の桁を光らせます

### 関数の定義

使用する関数を諸々定義します

`clear_display` では全ての LED に `1` を書き込むことで何も光っていない状態にします 

```elixir
clear_display = fn sdi, rclk, srclk ->
  Enum.each(1..8, fn _ ->
    Circuits.GPIO.write(sdi, 1)
    Circuits.GPIO.write(srclk, 1)
    Circuits.GPIO.write(srclk, 0)
  end)

  Circuits.GPIO.write(rclk, 1)
  Circuits.GPIO.write(rclk, 0)
end
```

`shift_out` は前回と同じで、シフトレジスタに指定した信号を順次送ってから並列伝送する関数です

```elixir
shift_out = fn data, sdi, rclk, srclk ->
  for bit <- 0..7 do
    bit = data <<< bit &&& 0x80
    Circuits.GPIO.write(sdi, bit)
    Circuits.GPIO.write(srclk, 1)
    Process.sleep(1)
    Circuits.GPIO.write(srclk, 0)
  end

  Circuits.GPIO.write(rclk, 1)
  Process.sleep(1)
  Circuits.GPIO.write(rclk, 0)
end
```

`pick_digit` 関数で光らせる桁を指定します

一旦全て `0` にしておいてから光らせたい桁のピンだけ `1` にしています

```elixir
pick_digit = fn place_pins, digit ->
  Enum.each(place_pins, fn pin ->
    Circuits.GPIO.write(pin, 0)
  end)

  place_pins
  |> Enum.at(digit)
  |> Circuits.GPIO.write(1)
end
```

これらを組み合わせて、指定した文字を指定した桁に表示する関数 `display` を作ります

```elixir
display = fn char, place, place_pins, sdi, rclk, srclk ->
  clear_display.(sdi, rclk, srclk)

  pick_digit.(place_pins, place)

  shift_out.(char, sdi, rclk, srclk)
end
```

### COOL を表示する

`COOL` の文字を表示させてみます

反転だと考えにくかったので、前回と同じカソード用の 2 進数でコードを指定しておいて、 `~~~` 演算子で反転させます

```elixir
cool_code = [
  0b00111001,
  0b00111111,
  0b00111111,
  0b00111000
]

0..100
|> Enum.map(fn _ ->
  ~~~Enum.at(cool_code, 3)
  |> display.(0, place_pins, sdi, rclk, srclk)

  ~~~Enum.at(cool_code, 2)
  |> display.(1, place_pins, sdi, rclk, srclk)

  ~~~Enum.at(cool_code, 1)
  |> display.(2, place_pins, sdi, rclk, srclk)

  ~~~Enum.at(cool_code, 0)
  |> display.(3, place_pins, sdi, rclk, srclk)
end)
```

画質を `720p60` 以上にすれば COOL の文字が見えます

それより低いとバラバラに光っているように見えます

<iframe width="437" height="776" src="https://www.youtube.com/embed/W69plW0bBO4" title="4桁7セグメントディスプレイCOOL" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

GIF だとこんな感じで、全く同時に光っているようには見えません

![cool.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/4972682e-9a3e-939c-666f-b06af1a73d08.gif)

スクリーンショットだと各文字が個別に光っているのが分かります

![スクリーンショット 2024-06-27 21.54.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/55b730ca-9c69-5acd-ebb1-fa10c7847057.png)

![スクリーンショット 2024-06-27 21.55.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/8b63bff3-a332-23a4-60ba-3716d88a788d.png)

![スクリーンショット 2024-06-27 21.56.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/d6c8ceac-73c0-958f-bf61-8d23dd751d07.png)

![スクリーンショット 2024-06-27 21.56.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/44a54878-064c-8e46-7ac7-53b3a6f89261.png)

### 4桁カウンターを表示する

数字をどんどんカウントアップしていくカウンターを表示します

```elixir
seg_code = [0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90]
```

```elixir
0..1000
|> Enum.map(fn counter ->
  seg_code
  |> Enum.at(rem(counter, 10))
  |> display.(0, place_pins, sdi, rclk, srclk)

  seg_code
  |> Enum.at(div(rem(counter, 100), 10))
  |> display.(1, place_pins, sdi, rclk, srclk)

  seg_code
  |> Enum.at(div(rem(counter, 1000), 100))
  |> display.(2, place_pins, sdi, rclk, srclk)

  seg_code
  |> Enum.at(div(rem(counter, 10000), 1000))
  |> display.(3, place_pins, sdi, rclk, srclk)
end)
```

<iframe width="437" height="776" src="https://www.youtube.com/embed/uyZEuRuquas" 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
Circuits.GPIO.close(sdi)
Circuits.GPIO.close(rclk)
Circuits.GPIO.close(srclk)
```

```elixir
place_pins
|> Enum.map(fn pin ->
  Circuits.GPIO.close(pin)
end)
```

## まとめ

Nerves Livebook から 4 桁 7 セグメントディスプレイに文字を表示できました

高速に切り替える技も色々応用ができそうですね