Arduino + ELEGOO Starter Kit で 4桁7セグメント LEDディスプレイを使う 

# はじめに
この記事では，Arduinoで4桁7セグメントLEDディスプレイを扱う方法を概説します．
本稿ではELEGOO Starter Kit（[紹介記事](https://burariweb.info/electronic-work/elegoo-arduino-starter-kit-comparison.html)，[公式ページ](https://www.elegoo.com/collections/uno-r3-starter-kits)）を想定しますが，パーツに関しては互換のものがあれば問題ありません．
詳細な説明は省いています．知りたい方は各節の参考リンクをご利用ください．

# 使うもの
* Arduino UNO
* ブレッドボード
* ジャンパワイヤ
* シフトレジスタIC（SN74HC595）
* ダイナミック接続4桁赤色7セグメントLED表示器　カソードコモン（OSL40562-LR）
* （おまけ）タップスイッチ

# とにかく試したい人

## プログラム

<details><summary>サンプルコード</summary>


```c
//4つのデジタル数字のディスプレイのサンプルプログラム

//// 定数
//状態定義
int DIG_ON=LOW;
int DIG_OFF=HIGH;
//ピン番号
//2～12なら互換，13はLED連動
int P_DATA=2;
int P_SUBMIT=3;
int P_CLOCK=4;
int P_DIG1=5;
int P_DIG2=6;
int P_DIG3=7;
int P_DIG4=8;
int P_SWITCH=13;

////グローバル変数
//int isba0=1; //↓初期化フラグ
//int ba0=0; //ひとつ前のスイッチの状態
int isbt=1; //↓初期化フラグ
unsigned long bt=0; //開始時の時刻
unsigned long counter=0; //ディスプレイに表示したい数字
int keta=0; //今ループで表示させる桁目

//ICに入力するビット．液晶のON_OFFに対応．
//最上位ビットをドットにしました．
unsigned char digits[]={
  0b01111110, //0
  0b00110000, //1
  0b01101101, //2
  0b01111001, //3
  0b00110011, //4
  0b01011011, //5
  0b01011111, //6
  0b01110010, //7
  0b01111111, //8
  0b01111011  //9
};

void setup() {
  //ピンモード設定
  pinMode(P_SUBMIT,OUTPUT);
  pinMode(P_CLOCK,OUTPUT);
  pinMode(P_DATA,OUTPUT);
  pinMode(P_DIG1,OUTPUT);
  pinMode(P_DIG2,OUTPUT);
  pinMode(P_DIG3,OUTPUT);
  pinMode(P_DIG4,OUTPUT);
  pinMode(P_SWITCH,INPUT_PULLUP);
  //ピンの初期値
  digitalWrite(P_SUBMIT,LOW);
  digitalWrite(P_CLOCK,LOW);
  digitalWrite(P_DATA,LOW);
  digitalWrite(P_DIG1,DIG_OFF);
  digitalWrite(P_DIG2,DIG_OFF);
  digitalWrite(P_DIG3,DIG_OFF);
  digitalWrite(P_DIG4,DIG_OFF);
  //他初期化いろいろ
  //Serial.begin(9600);
  isbt=1;
  bt=0;
  counter=0;
  keta=0;
}

void loop() {
  // ループごとに1文字づつ表示するダイナミックディスプレイ．
  // ループの遅延が大きくなるとちらつきが大きくなるので注意．
  // delayはもってのほか，Serial.printもかなり遅延するので注意．
  // 処理が増えるとだんだんフレームレートが落ちるのでチラつきがち．

//  //スイッチ押しでカウントアップする場合
//  int a0= digitalRead(P_SWITCH);
//  //Serial.println(a0);
//  if(isba0){
//    isba0=0;
//    ba0=a0;
//  }
//  int isswon=0;
//  if(!ba0 && a0){ //スイッチを押した瞬間を捉える
//    isswon=1;
//  }
//  ba0=a0;
//  if(isswon){
//    counter++;
//  }

  //時間でカウントアップする場合
  unsigned long ct = millis(); //intだと駄目
  if(isbt){
    isbt=0;
    bt=ct;
  }
  //5日で一周するので長まわしに注意
  counter=ct-bt;

  //メインループの1ループごとに1桁づつ表示
  DisplayDigit(counter, keta);
  keta=(keta+1)%4;
  
}

//入力の数numのketa桁目の数字を該当のdigitに表示する
//keta=0は一番左（4桁目）
void DisplayDigit(unsigned long num, int keta){
  //当該桁の数字を抽出
  int digit=0;
  if(keta==3){
    digit = num%10;
  }
  else if(keta==2){
    digit = (num/10)%10;
  }
  else if(keta==1){
    digit = (num/100)%10;
  }
  else if(keta==0){
    digit = (num/1000)%10;
  }

  //当該桁の出力処理
  shiftOut(P_DATA, P_CLOCK, LSBFIRST, digits[digit]|((keta&1)?0b10000000:0b00000000)); //桁によってドット付けてみる
  //shiftOut(P_DATA, P_CLOCK, LSBFIRST, digits[digit]|0b10000000); //全部ドットつける
  //shiftOut(P_DATA, P_CLOCK, LSBFIRST, digits[digit]&0b01111111); //全部ドットけす
  digitalWrite(P_SUBMIT, HIGH); //溜めたレジスタをON→OFFで送出
  digitalWrite(P_SUBMIT, LOW);
  digitalWrite(P_DIG1+keta, DIG_ON); //桁指定で出力値を表示
  
  delay(3); //表示時間つくる
  
  //全桁消去＆出力値リセット処理
  digitalWrite(P_DIG1+keta, DIG_OFF); //桁指定解除（これで全桁消える）
}
```
</details>


## 配線図

![7seg4digits.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171332/f2c4cc0f-a3de-8b98-55fb-2b0a2e726f33.png)

※LEDディスプレイについては，作図ソフトFritzingに同じピン配置のものがなく代替のものを置いています．上側6ピン下側6ピンとして読み替えてください．

<details><summary>タップスイッチでカウントアップする版</summary>

![7seg4digits_wswith.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171332/efaada55-296d-1832-0be4-fa894ae1720a.png)

</details>

## 注意点

* loop関数内に以下が入っていると表示がチラついたりします．
    * delay
    * Serial.print
    * 他いろいろ重そうな処理


# 4桁7セグLED

こちらの記事が詳しいです：[4桁7セグメントLEDで始めるArduino](https://garretlab.web.fc2.com/arduino/introduction/beginning_with_7segment_led/)

## なにそれ

* 7つのセグメントのLEDで構成された一つのデジタル数字が，4桁分あるものです．

## 解説

### 1桁の7セグ

まず1桁だけ考えます．
一桁には，7つの光るところ＋ドットの計8つのLEDが含まれています．これはつまるところ**8つのLEDを使うのと同じ**です．
このすべてのLEDのON/OFFを入力することで，数字を一つ作れるでしょう．対応する8つのピンに，HIGH / LOWを掛けることでON/OFFの状態を変えられます．

<img width="200" alt="1keta_7seg.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171332/78ece4a4-e915-1e87-9379-e2e5ae1aae24.png">

↑絵的には概ねこんなピンイメージです．（注意）実際のピンの配置とは異なります．

<details><summary>実際のピンの配置</summary>
※5161ASの場合<br />

<img width="200" alt="1keta_7seg_pinorder.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171332/6fa4118e-eaf9-5b97-85c1-08e4273267f5.png">

3，8はGNDに接続する．
</details>

<details><summary>Vccないの？</summary>
この機器の場合はありません．カソードコモンってやつです．
カソードとは，極性のあるLEDのマイナス側です．逆に，アノードがプラス側です．

LEDを愚直に置くと下図のようになりますが，明らかにGND（カソード）は共有してもいいですよね？なので，共有（コモン）して上図のように1本化しているわけです．
ちなみに，プラス側が共有されているアノードコモンというタイプもあります．買うときにはどっちにするか気をつけましょう．

<img width="200" alt="1keta_7seg_uncommon.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171332/4c1e847f-ceda-c840-94a6-22c190d18446.png">

</details>


### 4桁の7セグ

残念ながら，4桁の数字を一気に同時に表示することはできません．
なぜならば，それを回路実装しようとすると4x8=32ピンも必要になり，まったくコンパクトではないからです．

ではどうするかというと，「**人間には知覚できないレベルで超高速に1桁目→2桁目→3桁目→4桁目と順に表示する**」ことで，同時に点灯しているように見せかけます．
これを**ダイナミック点灯制御**といいます．

1. $k \in [1,2,3,4]$ について，以下を繰り返す：
    1. 8つのLEDのON/OFFを決める
    2. 操作する桁$k$の選択ピン（P_DIGk）をLOWにして選択状態にし，LEDを点灯する
    3. 少し待つ（サンプルではdelay(3)）
    4. P_DIGkを非選択状態（HIGH）にして消灯する

これだと，選択4ピン＋ON/OFF8ピンの12ピンで済みます．

<img width="180" alt="ダイナミック点灯制御.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171332/ff5c28f0-25df-b328-184a-a1bfda7508eb.png">

↑ダイナミック点灯制御の流れ．


<img width="300" alt="4keta_7seg.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171332/7216a588-5119-73a5-f7ca-c8315d4ee4a3.png">

↑絵的にはこんなピンイメージ．P_DIGxが選択ピン．（注意）実際のピンの配置とは異なります．

<details><summary>実際のピンの配置</summary>
※OSL40562-LRの場合<br />

<img width="200" alt="4keta_7seg_pinorder.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171332/6559e9da-828b-5041-183f-517a32aeaff2.png">

6，8，9，12はそれぞれDIG4，DIG3，DIG2，DIG1．
</details>

<details><summary>GNDないの？</summary>
はい．というか，P_DIGxに変わりました．

P_DIG2～4をHIGHにしておき，P_DIG1をLOWにすると，P_DIG1方面の回路の電位差が生まれ，そっちにだけ電気が流れるようになるという仕掛けです．

（Tips：電流は水流と性質が似てます．水は高いところから低いところへ流れ，傾きがないと流れません．電気も同じで，電位（＝電圧）が高いところから低いところへ流れ，電位差（＝傾き）がないと流れません．）

<img width="300" alt="dig_select.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171332/4d1d4bd7-c630-0189-f35a-211f9cdc5d59.png">

</details>

Arduinoでは，「人間には知覚できないレベルで超高速」に切り替えるために，loop関数内で1ループにつき1桁表示するようにプログラミングします．ここで，loop関数内のフレームレートを超高速に保つ必要があります．そのため，delayやSerial.printのような遅延要素は敵になります．

# シフトレジスタIC

より詳しい参考情報です．
* [Arduino 日本語リファレンス shiftOut関数](http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=2369)
* [シフトレジスタ (74HC595) と Arduino を使って複数の LED を制御する方法](https://iot.keicode.com/arduino/arduino-shift-register-sn74hc595.php)

## なんで使うの？

* ピンの節約のためです．

## 解説

上記で4桁7セグは12ピンで足りると書きましたが，**12ピンすらも使いたくないのがArduino**です．
12ピンも使ったら，デジタルピンのほとんどを使い果たしてしまいます．実質デジタルピンが11ピンほどしかないArduino UNOではそれは困ります．

それを解決してくれるのが，「**シフトレジスタIC**」なるものです．黒いゲジゲジ．
これは，8つの出力をICに溜めて，一気に出力してくれる優れものです．
このICには記憶機能があり，計8つのON/OFFの出力を覚えてくれます．そしてそのICへの入力は3ピンだけで実現します．

以下のような形でICに出力を覚えさせます．

1. 以下を8回繰り返す：
    1. 現在注目している出力ピンの記憶したい出力値を決める＠入力ピン1（P_DATA）
    2. 記憶領域（レジスタ）への「記憶」の実行を指示する＠入力ピン2（P_CLOCK）
        1. ここでICの中では，まず前までに送ってあった，8つのレジスタの中にあるデータが１つずつ後ろにずれます（シフト）
        2. 一番先頭の桁に出力値が記憶されます．

この8回繰り返す操作は，ArduinoのshiftOut関数がやってくれますので，サンプルプログラムはそちらを使っていますが，一応これを自力でプログラムしてもできます．

さらにその後，この覚えたICの出力値を実際に「送出」する指示を出します＠入力ピン3（P_SUBMIT）

<img width="300" alt="shift_IC.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171332/fb7d6778-a841-36b5-d613-e0fdf49bc6a2.png">

↑流れの概要．ピン配置は実際のものと異なります．

これで5ピン減って，**7ピン**（IC用3ピン＋桁決め4ピン）だけ使えば4桁7セグが扱えるようになります．

<details><summary>実際のピンの配置</summary>
※74HC595の場合<br />

<img width="300" alt="shift_IC_pinorder.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171332/e62eff99-e80b-0c29-7a89-dd6baf90bd51.png">

VCC，SRCLR'は5Vに，GND，OE'はGNDに接続する．
</details>

<details><summary>実際の表記との対応</summary>
本記事はいろいろと本物の名称と違う表記をしています．

P_DATAは，SER（シリアルデータ; **SER**ial data），
P_CLOCKは，SRCLK（**S**hift **R**egister **CL**oc**K**），
P_SUBMITは，いわゆるラッチですが，RCLK（storage **R**egistrer **CL**oc**K**）にあたります．

またレジスタはQA～QGの8つに対応していますが，SRCLKの立ち上がりのタイミングでQA→QB→QC→…とシフトし，QAにSERの状態が記録されます．

本稿のサンプルプログラムでは，shiftOut関数にLSBFIRSTを指定しています．これはshiftOutに入れた数字の下位ビットから順に流し込んでいく設定です．
$b_8b_7b_6b_5b_4b_3b_2b_1$というビット列の数字を指定したとき，shiftOut関数の実行によりこのようにレジスタに記憶されます．

||QA|QB|QC|QD|QE|QF|QG|QH|
|:-|:-|:-|:-|:-|:-|:-|:-|:-|
|①|$b_1$||||||||
|②|$b_2$|$b_1$|||||||
|③|$b_3$|$b_2$|$b_1$||||||
|...|||||||||
|⑧|$b_8$|$b_7$|$b_6$|$b_5$|$b_4$|$b_3$|$b_2$|$b_1$|


</details>

# さらなる発展

* Arduinoには非同期実行機能もある版があります．定期的に高速に数値を出力する処理の非同期実行をする命令を書けば，メインループの遅延を気にする必要が薄れます．[こちらの記事](https://garretlab.web.fc2.com/arduino/introduction/beginning_with_7segment_led/)の下の方を参考にしてみてください．
* 減らそうと思えば桁決め4ピンも節約できる気がしてきます．[デマルチプレクサIC](https://eng.kice.tokyo/logic/demultiplexer/)を使えば4ピンが2ピンに減りそうです．