4桁7セグメントLEDで始めるArduino

概要

ArduinoでのHello Worldは、LEDチカチカ(Lチカ)が定番です。少しひねくれて、4桁7セグメントLEDを使ってLチカを行います。

できるだけ、LEDの種類に依存しないように説明していきたいと思います。古い版(カソードコモン版)は、こちらを参照してください。

私は、OSL40562-LBというカソードコモンのダイナミック接続4桁高輝度青色7セグメントLED(小数点があるのでLEDは1桁あたり8個)を使い、動作確認しました。

i
このページでは4桁7セグメントLEDを利用するための概念を説明しているだけです。実際には、LEDドライバ等を利用するのが簡単だと思います。一部手抜きの部分もあるので注意してください。また、最適なコードであるというわけでもありません。

目的

Arduinoをどう使えばLチカできるのかを考えていきます。Lチカそのものというよりは、Arduinoの使い方に重点をおいています。

今回は4桁のダイナミック接続7セグメントLEDを使います。最終的にはダイナミック点灯制御の実験を行います。LED1個の制御→1桁の制御(数字の表示)→4桁の制御と順に拡張していきます。

目次

必要なもの

このページで必要なものは以下の通りです。

  • Arduino Uno
  • 4桁7セグメントLED
  • 抵抗x8(利用するLEDにより異なります)
  • 配線用ケーブルなど
  • 必要に応じてトランジスタ等

プログラム作成にあたって

プログラムを作成していくうえで、できるだけ、4桁7セグメントLEDの種類や、各セグメントのオン・オフの方法に依存しないようにするために、いくつかの共通化を行いました。

4桁7セグメントLEDの種類

4桁7セグメントLEDには、以下の種類があります。

種類 説明
カソードコモン LEDのカソード側を共通化したもの
アノードコモン LEDのアノード側を共通化したもの

コモン側の端子を選択することで、「桁」を選択し、その桁のセグメントを制御(点灯・消灯)します。

ピンを示す変数名

カソードコモンのLEDとアノードコモンのLEDとで、プログラムを同一にするために、ピンを表す変数を、カソード、アノードという名前ではなく、digit(桁)とsegmentという名前を利用するようにしました。具体的には、以下の変数名を利用しています。

変数 説明 カソードコモンの場合 アノードコモンの場合
digitPin/digitPins コモン側のピンを表す変数。桁を表す。 カソードピン アノードピン
segmentPin/segmentPins セグメント側のピンを表す変数。セグメントを表す。 アノードピン カソードピン

LEDのオン・オフ

LEDを点灯させるためには、アノードを電源のプラスに、カソードを電源のマイナスに接続します。

ただし、Arduinoなどのマイコンを用いて4桁7セグメントLEDを制御する場合は、マイコンの出力と実際にLEDにかかる電圧とは、必ず同じになるわけではありません。

このため、今回利用するプログラムでは、digitalWrite()で指定する2番目の引数を、HIGHとLOWではなく、以下に示すマクロにしました。

マクロ 説明
DIGIT_ON ディジットを選択するときの値
DIGIT_OFF ディジットを選択しないときの値
SEGMENT_ON セグメントを点灯するときの値
SEGMENT_OFF セグメントを消灯するときの値

原理的には、XX_ON が決まれば、XX_OFFは自動的に決められますが、今回は手動で定義しました(間違いのもとですが)。

以下に、DIGIT_ONとSEGMENT_ONの組み合わせについて、簡単に説明します。

# DIGIT_ON SEGMENT_ON 主な使い方 備考
1 HIGH HIGH カソードコモン、コモン側にNPNトランジスタなど
2 HIGH LOW アノードコモン、ソースドライバ不使用
3 LOW HIGH カソードコモン、シンクドライバ不使用 LEDに逆電圧がかかります。点灯させない桁は、LOWではなく、pinMode()で、INPUTにするのがいいと思います。
4 LOW LOW アノードコモン、コモン側にPNPトランジスタなど

あくまで、主な使い方なので、各自の回路に合わせて適切に設定してください。この定義を間違えると、LEDが点灯しなかったり、LEDやArduinoが壊れたりするので注意してください。

各スケッチの先頭部分に定義しているので、環境に合わせて定義してください。デフォルトでは、カソードコモン(シンクドライバ不使用)の設定となっています。

今回利用した7セグメントLEDのデータシート

OSL40562-LBという7セグメントLEDを利用しました。他の7セグメントLEDでもピン番号を読みかえれば動作すると思います。抵抗の値は変える必要があります。

まずは、OSL40562-LBのデータシートで特性を調べます。

電圧と電流

電圧と電流の関係は以下の通りです。

項目 内容
順方向電圧 標準3.3V、最大4V
電流 20mA

5Vの電圧をかけるときに接続する電流制限用の抵抗は、(5-3.3)/0.02 = 85オームです。ただ、このLEDはとても明るいので、私は240Ωの抵抗を用いました。手元にそれしかなかったとも言います。ダイナミック点灯では少し暗くなります。

!
Arduino UnoのI/Oピンに流せる最大電流は40mAです。また、ATmega328Pの5Vのピンに流すことのできる最大の電流は200mAです。これ以上の電流を流すとチップが壊れる可能性があるので注意してください。

カソードを接続するピンには、前述の抵抗を用いた場合、最大、1.7V/240Ω* 8=56.7mA(小数点を含めて全て点灯した場合)の電流が流れる可能性があります。小数点を使わない場合でも、1.7V/240Ω*7=49.6mAの電流が流れる可能性があるので注意してください。

ピン

この7セグメントLEDのピンは以下の図の通りです。

各ピンの接続は以下の通りです。図中のアルファベット/数字がセグメントの記号(A-G、DP)とピン番号です。カソードは、左の桁から、12番(DIG1)、9番(DIG2)、8番(DIG3)、6番(DIG4)です。例えば、12番をLOWにし、1番をHIGHにすれば、一番左の桁の左下のLED(E)が点灯します。

ピン番号 1 2 3 4 5 6 7 8 9 10 11 12
接続先 E D DP C G DIG4 B DIG3 DIG2 F A DIG1

セグメントをひとつ点灯する その1

実施内容

Arduinoは単なる電源として利用し、セグメントを一つ点灯します。Arduinoは単なる電源なので、スケッチはありません。Arduinoの5V出力を、抵抗を通じてLEDのアノードに、GNDをカソードに接続します。一番左の桁の左下(E)のセグメントを点灯させるには、1番に5V、12番にGNDを接続します。

Arduinoで制御を行う前に、制御対象が単体で動作することを確認しておくと、スケッチが悪いのか制御対象そのものが悪いのかの切り分けが簡単になります。

ピン接続

ピンの接続は以下の通りです。()内は、OSL40562-LBの場合のピン番号です。

LEDの種類 5V GND
カソードコモン E(1) DIG1(12)
アノードコモン DIG1 E

セグメントをひとつ点灯する その2

実施内容

Arduinoのスケッチを使って、セグメントを一つ点灯します。ここでは、一番左の桁のEセグメントを点灯します。

考え方

DIG1(一番左の桁)とEセグメントをオンにすると、一番左の桁の左下のセグメントを点灯することができるはずです。

ピン接続

以下のピン接続を前提にスケッチを書いていきます。

Arduinoのピン番号 LEDのピン OSL40562-LB のピン
13 DIG1 12
2 E 1

Arduinoのスケッチを書く

ArduinoでデジタルピンにHIGHやLOWを出力を行うには、以下の操作を行う必要があります。

  • デジタルピンを「出力モード」に設定する
  • デジタルピンに出力する

デジタルピンを出力モードにするには、pinMode()という関数を利用します。具体的には、pinMode(ピン番号, モード);と記述します。詳細は、リファレンスを確認してください。

また、デジタルピンに出力するには、digitalWrite()という関数を利用します。具体的には、digitalWrite(ピン番号、出力);と記述します。詳細は、リファレンスを確認してください。

Arduinoでは、通常、C++/C言語を用いて記述します。ただし、main()関数はArduinoソフトウェア自身が定義しているため、利用者はmain()関数を定義する必要はありません。Arduinoで定義しているmain()関数は、その中で、setup()という最初に一度だけ実行される関数と、その後無限に実行されるloop()という関数を呼び出しています。このため、setup()とloop()の2つの関数を利用者が定義します。双方の関数とも引数も戻り値も持たない関数です。つまり、以下の形の関数を定義します。

1
2
3
4
5
void setup() {
}
 
void loop() {
}

接続する機器の初期化などをsetup()で行い、接続する機器の制御をloop()で行うことが多いです。関数が分かれているので、グローバル変数を多用することになります。

LEDを点灯するには以下を行います。

  • 初期設定(setup())
    • LEDを接続したデジタルピンを「出力モード」に設定する。
  • 以下を繰り返す(loop())
    • ディジットをオンにする。
    • セグメントをオンにする。

今回は、ただ単にセグメントを点灯させるだけですが、setup()の中でデジタルピンの出力モードの設定を、loop()の中でデジタルピンへの出力を行うことにします。LEDはつけっぱなしなので、loop()に入れた処理をsetup()に入れても問題はありません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// LEDをオン・オフする際の出力
#define DIGIT_ON LOW
#define DIGIT_OFF HIGH
#define SEGMENT_ON HIGH
#define SEGMENT_OFF LOW

const int digitPin = 13;   // ディジット(コモン)に接続するArduinoのピン
const int segmentPin = 2;  // セグメントに接続するArduinoのピン

// setup() は,最初に一度だけ実行される
void setup() {
  pinMode(digitPin, OUTPUT);    // digitPinを出力モードに設定する
  pinMode(segmentPin, OUTPUT);  // segmentPinを出力モードに設定する
}

// loop() は,setup ()実行後,無限に実行される
void loop() {
  digitalWrite(digitPin, DIGIT_ON);      // digitPinをオンにする
  digitalWrite(segmentPin, SEGMENT_ON);  // segmentPinをオンにする
}

セグメントを点滅する

実施内容

セグメントを点滅させるスケッチを作成します。これでLチカ完成です(たぶん)。

考え方

セグメントをひとつ点灯する その2では、セグメントの点灯だけを行いましたが、次は点滅させてみます。そのためには、点灯→消灯を繰り返します。

今回は、1桁しか使っていないので、いろいろな制御方法がありますが、ディジットはオンにしたまま、セグメントをオン・オフしました。

ピン接続

以下のピン接続を前提にスケッチを書いていきます。Arduinoのピン番号と7セグメントLEDのピン番号とが一つずれているので注意してください。

Arduinoのピン番号 LEDのピン OSL40562-LB のピン
2 E 1
13 DIG1 12

Arduinoのスケッチを書く

セグメントを0V(LOW)にすれば、セグメントは消灯します。単純に点灯と消灯を繰り返すと、点滅速度が速すぎて人間の目には点滅しているようには見えません(この特性を後で利用することになります)。このため、点灯と消灯の間に少し待ち時間を入れます。

  • 初期設定(setup())
    • LEDを接続したデジタルピンを「出力モード」に設定する。
  • 以下を繰り返す(loop())
    • ディジットをオンにする(loop()の中で変更しないので、setup()で実行してもいい)。
    • セグメントをオンにする。
    • しばらく待つ。
    • セグメントをオフにする。
    • しばらく待つ。

Arduinoでは、delay()という関数が用意されていて、ミリ秒単位で処理を中断することができます。マイクロ秒単位で中断するdelayMicroseconds()という関数も用意されています。今回は500ミリ秒間隔で点滅させてみます。

なお、delay()を実行中は、他の処理を行うことができないので、他のスケッチを書くときは注意してください。delay()を使わずに、実行タイミングを指定する方法は、こちらを参照してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// LEDをオン・オフする際の出力
#define DIGIT_ON LOW
#define DIGIT_OFF HIGH
#define SEGMENT_ON HIGH
#define SEGMENT_OFF LOW

const int digitPin = 13;   // ディジット(コモン)に接続するArduinoのピン
const int segmentPin = 2;  // セグメントに接続するArduinoのピン

// setup() は,最初に一度だけ実行される
void setup() {
  pinMode(digitPin, OUTPUT);    // digitPinを出力モードに設定する
  pinMode(segmentPin, OUTPUT);  // segmentPinを出力モードに設定する
}

// loop() は,setup ()実行後,無限に実行される
void loop() {
  digitalWrite(digitPin, DIGIT_ON);       // digitPinをオンにする
  digitalWrite(segmentPin, SEGMENT_ON);   // segmentPinをオンにする
  delay(500);                             // 500ms 待つ
  digitalWrite(segmentPin, SEGMENT_OFF);  // segmentPinをオフにする
  delay(500);                             // 500ms 待つ
}

セグメント(1桁)をランダムに点灯する

実施内容

7セグメントLEDの一つのセグメントだけをチカチカさせているだけではもったいないので、次は全部のセグメント(小数点含む)をチカチカさせます。一度に点灯するセグメントは一つで、どのセグメントを点灯するのかは、ランダムに決めることにします。

考え方

今までは同じセグメントだけを点灯したり、点滅したりさせましたが、今回は複数のセグメントを点滅します。

  • 点灯させるセグメントをランダムに選ぶ。
  • 選んだセグメントを点灯する。
  • しばらく待つ(待たないと全部のセグメントが点灯したように見えます。しかも暗い)。
  • 点灯させたセグメントを消灯する。

ピン接続

以下のピン接続を前提にスケッチを書いていきます。ディジットピンの一部は使っていないので注意してください。

Arduinoのピン番号 LEDのピン OSL40562-LB のピン
2 E 1
3 D 2
4 DP 3
5 C 4
6 G 5
8 B 7
11 F 10
12 A 11
13 DIG1 12

Arduinoのスケッチを書く

今回はピンをたくさん使います。しかも、セグメントのピンは、2/3/4/5/6/8/11/12と少し跳んでいます。このため、単純にfor文などでループしても、目的のピン番号を得ることはできません。このため、セグメントのピン番号を配列に格納します。配列は、同じ型の変数を複数まとめて定義することができる仕組みです。

配列の名前をsegmentPinsとし、以下のように格納します。C++/Cの配列の添え字は0から始まります。

segmentPinsの添え字 Arduinoのピン番号
0 2
1 3
2 4
3 5
4 6
5 8
6 11
7 12

こうすることで、配列の添え字を0から7まで変化させると、添え字に対応したピン番号を得ることができ、スケッチが簡単になります。

配列の要素数は、配列全体の大きさを配列の要素1個の大きさで割ることで求めることができます。変数の大きさは、sizeof演算子を利用することで、知ることができます。

9
const int numberOfSegmentPins = sizeof(segmentPins) / sizeof(segmentPins[0]);  // セグメントの数

ランダムな数字を取得するには、random()という関数を利用します。今回は、上記の通り、segmentPinsの添え字を表す0から7までの数字が必要なので、random(8);を呼び出します。

後は、選択したピンをしばらく点灯して、消灯するということを繰り返すだけです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// LEDをオン・オフする際の出力
#define DIGIT_ON LOW
#define DIGIT_OFF HIGH
#define SEGMENT_ON HIGH
#define SEGMENT_OFF LOW

const int digitPin = 13;                                                       // ディジット(コモン)に接続するArduinoのピン
const int segmentPins[] = {2, 3, 4, 5, 6, 8, 11, 12};                          // セグメントピンに接続するArduinoのピン
const int numberOfSegmentPins = sizeof(segmentPins) / sizeof(segmentPins[0]);  // セグメントの数

// setup() は,最初に一度だけ実行される
void setup() {
  pinMode(digitPin, OUTPUT);  // digitPinを出力モードに設定する
  for (int i = 0; i < numberOfSegmentPins; i++) {
    pinMode(segmentPins[i], OUTPUT);  // segmentPinsを出力モードに設定する
  }
}

// loop() は,setup ()実行後,無限に実行される
void loop() {
  int segmentPin = segmentPins[random(numberOfSegmentPins)];  // 点灯するピンを選択する

  digitalWrite(digitPin, DIGIT_ON);       // digitPinをオンにする
  digitalWrite(segmentPin, SEGMENT_ON);   // 選択したピンをオンにする
  delay(100);                             // 少し待つ
  digitalWrite(segmentPin, SEGMENT_OFF);  // 選択したピンをオフにする
}

1桁の数字を表示する

実施内容

今までは、各セグメントを一つずつ点灯しただけでしたが、今回は、1桁の数字を表示させます。

4桁ありますが、まだ1桁だけを使います。

まずは、0から9の数字をどのように点灯するかを考えます。今回は、以下のようにします。6、7、9は好みが分かれるかもしれません。

上記で示した、表示する数字と点灯させるセグメントとの関係を整理します。一番左が表示させる数字で、右側のA~Gが点灯させるセグメントです。

数字 A B C D E F G
0
1
2
3
4
5
6
7
8
9

考え方

上記の表に従って、同時にセグメントを点灯します。

  • 表示したい数字を決める。
  • 数字に対応したセグメントを点灯する。

ピン接続

以下のピン接続を前提にスケッチを書いていきます。

Arduinoのピン番号 LEDのピン OSL40562-LB のピン
2 E 1
3 D 2
4 DP 3
5 C 4
6 G 5
8 B 7
11 F 10
12 A 11
13 DIG1 12

スケッチを書く

スケッチは4種類書きました。同じことを行うのに、関数を使って徐々に記述量を減らしていきます。

べたに表示する

0から9までを、上記の表に従ってただひたすら点灯します。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// LEDをオン・オフする際の出力
#define DIGIT_ON LOW
#define DIGIT_OFF HIGH
#define SEGMENT_ON HIGH
#define SEGMENT_OFF LOW

const int digitPin = 13;     // ディジット(コモン)に接続するArduinoのピン
const int segmentPinA = 12;  // セグメントピンに接続するArduinoのピン
const int segmentPinB = 8;
const int segmentPinC = 5;
const int segmentPinD = 3;
const int segmentPinE = 2;
const int segmentPinF = 11;
const int segmentPinG = 6;

// setup() は,最初に一度だけ実行される
void setup() {
  // ピンを出力モードに設定する
  pinMode(digitPin, OUTPUT);
  pinMode(segmentPinA, OUTPUT);
  pinMode(segmentPinB, OUTPUT);
  pinMode(segmentPinC, OUTPUT);
  pinMode(segmentPinD, OUTPUT);
  pinMode(segmentPinE, OUTPUT);
  pinMode(segmentPinF, OUTPUT);
  pinMode(segmentPinG, OUTPUT);
}

// loop() は,setup ()実行後,無限に実行される
void loop() {
  // digitPinをオンにする
  digitalWrite(digitPin, DIGIT_ON);

  // 各数字で点灯するピンをオンに、消灯するピンをオフにする
  /* 0 */
  digitalWrite(segmentPinA, SEGMENT_ON);
  digitalWrite(segmentPinB, SEGMENT_ON);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_ON);
  digitalWrite(segmentPinE, SEGMENT_ON);
  digitalWrite(segmentPinF, SEGMENT_ON);
  digitalWrite(segmentPinG, SEGMENT_OFF);
  delay(500);

  /* 1 */
  digitalWrite(segmentPinA, SEGMENT_OFF);
  digitalWrite(segmentPinB, SEGMENT_ON);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_OFF);
  digitalWrite(segmentPinE, SEGMENT_OFF);
  digitalWrite(segmentPinF, SEGMENT_OFF);
  digitalWrite(segmentPinG, SEGMENT_OFF);
  delay(500);

  /* 2 */
  digitalWrite(segmentPinA, SEGMENT_ON);
  digitalWrite(segmentPinB, SEGMENT_ON);
  digitalWrite(segmentPinC, SEGMENT_OFF);
  digitalWrite(segmentPinD, SEGMENT_ON);
  digitalWrite(segmentPinE, SEGMENT_ON);
  digitalWrite(segmentPinF, SEGMENT_OFF);
  digitalWrite(segmentPinG, SEGMENT_ON);
  delay(500);

  /* 3 */
  digitalWrite(segmentPinA, SEGMENT_ON);
  digitalWrite(segmentPinB, SEGMENT_ON);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_ON);
  digitalWrite(segmentPinE, SEGMENT_OFF);
  digitalWrite(segmentPinF, SEGMENT_OFF);
  digitalWrite(segmentPinG, SEGMENT_ON);
  delay(500);

  /* 4 */
  digitalWrite(segmentPinA, SEGMENT_OFF);
  digitalWrite(segmentPinB, SEGMENT_ON);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_OFF);
  digitalWrite(segmentPinE, SEGMENT_OFF);
  digitalWrite(segmentPinF, SEGMENT_ON);
  digitalWrite(segmentPinG, SEGMENT_ON);
  delay(500);

  /* 5 */
  digitalWrite(segmentPinA, SEGMENT_ON);
  digitalWrite(segmentPinB, SEGMENT_OFF);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_ON);
  digitalWrite(segmentPinE, SEGMENT_OFF);
  digitalWrite(segmentPinF, SEGMENT_ON);
  digitalWrite(segmentPinG, SEGMENT_ON);
  delay(500);

  /* 6 */
  digitalWrite(segmentPinA, SEGMENT_ON);
  digitalWrite(segmentPinB, SEGMENT_OFF);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_ON);
  digitalWrite(segmentPinE, SEGMENT_ON);
  digitalWrite(segmentPinF, SEGMENT_ON);
  digitalWrite(segmentPinG, SEGMENT_ON);
  delay(500);

  /* 7 */
  digitalWrite(segmentPinA, SEGMENT_ON);
  digitalWrite(segmentPinB, SEGMENT_ON);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_OFF);
  digitalWrite(segmentPinE, SEGMENT_OFF);
  digitalWrite(segmentPinF, SEGMENT_ON);
  digitalWrite(segmentPinG, SEGMENT_OFF);
  delay(500);

  /* 8 */
  digitalWrite(segmentPinA, SEGMENT_ON);
  digitalWrite(segmentPinB, SEGMENT_ON);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_ON);
  digitalWrite(segmentPinE, SEGMENT_ON);
  digitalWrite(segmentPinF, SEGMENT_ON);
  digitalWrite(segmentPinG, SEGMENT_ON);
  delay(500);

  /* 9 */
  digitalWrite(segmentPinA, SEGMENT_ON);
  digitalWrite(segmentPinB, SEGMENT_ON);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_ON);
  digitalWrite(segmentPinE, SEGMENT_OFF);
  digitalWrite(segmentPinF, SEGMENT_ON);
  digitalWrite(segmentPinG, SEGMENT_ON);
  delay(500);
}

関数化する

前記のプログラムでも問題なく数字を表示できますが、数字を表示するたびに毎回すべてのセグメントの点灯・消灯をコーディングしていては、スケッチが大きくなりすぎるし、間違いが混入する原因になります。このため、各数字を表示させる部分を関数化します。まずは、0から9までそれぞれの数字の表示を個別の関数にしてみます。

具体的には、display0()からdisplay9()を定義しました。これにより、0を表示したいときは、display0()を呼び出すだけですむようになります。このような小さいスケッチではありがたみは少ないですが、複雑なスケッチになってくると、同じ処理をひとまとめにして再利用できる関数は有効です。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// LEDをオン・オフする際の出力
#define DIGIT_ON LOW
#define DIGIT_OFF HIGH
#define SEGMENT_ON HIGH
#define SEGMENT_OFF LOW

const int digitPin = 13;     // ディジット(コモン)に接続するArduinoのピン
const int segmentPinA = 12;  // セグメントピンに接続するArduinoのピン
const int segmentPinB = 8;
const int segmentPinC = 5;
const int segmentPinD = 3;
const int segmentPinE = 2;
const int segmentPinF = 11;
const int segmentPinG = 6;

void display0() {
  digitalWrite(segmentPinA, SEGMENT_ON);
  digitalWrite(segmentPinB, SEGMENT_ON);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_ON);
  digitalWrite(segmentPinE, SEGMENT_ON);
  digitalWrite(segmentPinF, SEGMENT_ON);
  digitalWrite(segmentPinG, SEGMENT_OFF);
}

void display1() {
  digitalWrite(segmentPinA, SEGMENT_OFF);
  digitalWrite(segmentPinB, SEGMENT_ON);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_OFF);
  digitalWrite(segmentPinE, SEGMENT_OFF);
  digitalWrite(segmentPinF, SEGMENT_OFF);
  digitalWrite(segmentPinG, SEGMENT_OFF);
}

void display2() {
  digitalWrite(segmentPinA, SEGMENT_ON);
  digitalWrite(segmentPinB, SEGMENT_ON);
  digitalWrite(segmentPinC, SEGMENT_OFF);
  digitalWrite(segmentPinD, SEGMENT_ON);
  digitalWrite(segmentPinE, SEGMENT_ON);
  digitalWrite(segmentPinF, SEGMENT_OFF);
  digitalWrite(segmentPinG, SEGMENT_ON);
}

void display3() {
  digitalWrite(segmentPinA, SEGMENT_ON);
  digitalWrite(segmentPinB, SEGMENT_ON);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_ON);
  digitalWrite(segmentPinE, SEGMENT_OFF);
  digitalWrite(segmentPinF, SEGMENT_OFF);
  digitalWrite(segmentPinG, SEGMENT_ON);
}

void display4() {
  digitalWrite(segmentPinA, SEGMENT_OFF);
  digitalWrite(segmentPinB, SEGMENT_ON);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_OFF);
  digitalWrite(segmentPinE, SEGMENT_OFF);
  digitalWrite(segmentPinF, SEGMENT_ON);
  digitalWrite(segmentPinG, SEGMENT_ON);
}

void display5() {
  digitalWrite(segmentPinA, SEGMENT_ON);
  digitalWrite(segmentPinB, SEGMENT_OFF);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_ON);
  digitalWrite(segmentPinE, SEGMENT_OFF);
  digitalWrite(segmentPinF, SEGMENT_ON);
  digitalWrite(segmentPinG, SEGMENT_ON);
}

void display6() {
  digitalWrite(segmentPinA, SEGMENT_ON);
  digitalWrite(segmentPinB, SEGMENT_OFF);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_ON);
  digitalWrite(segmentPinE, SEGMENT_ON);
  digitalWrite(segmentPinF, SEGMENT_ON);
  digitalWrite(segmentPinG, SEGMENT_ON);
}

void display7() {
  digitalWrite(segmentPinA, SEGMENT_ON);
  digitalWrite(segmentPinB, SEGMENT_ON);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_OFF);
  digitalWrite(segmentPinE, SEGMENT_OFF);
  digitalWrite(segmentPinF, SEGMENT_ON);
  digitalWrite(segmentPinG, SEGMENT_OFF);
}
void display8() {
  digitalWrite(segmentPinA, SEGMENT_ON);
  digitalWrite(segmentPinB, SEGMENT_ON);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_ON);
  digitalWrite(segmentPinE, SEGMENT_ON);
  digitalWrite(segmentPinF, SEGMENT_ON);
  digitalWrite(segmentPinG, SEGMENT_ON);
}

void display9() {
  digitalWrite(segmentPinA, SEGMENT_ON);
  digitalWrite(segmentPinB, SEGMENT_ON);
  digitalWrite(segmentPinC, SEGMENT_ON);
  digitalWrite(segmentPinD, SEGMENT_ON);
  digitalWrite(segmentPinE, SEGMENT_OFF);
  digitalWrite(segmentPinF, SEGMENT_ON);
  digitalWrite(segmentPinG, SEGMENT_ON);
}

// setup() は,最初に一度だけ実行される
void setup() {
  // ピンを出力モードに設定する
  pinMode(digitPin, OUTPUT);
  pinMode(segmentPinA, OUTPUT);
  pinMode(segmentPinB, OUTPUT);
  pinMode(segmentPinC, OUTPUT);
  pinMode(segmentPinD, OUTPUT);
  pinMode(segmentPinE, OUTPUT);
  pinMode(segmentPinF, OUTPUT);
  pinMode(segmentPinG, OUTPUT);
}

// loop() は,setup ()実行後,無限に実行される
void loop() {
  // digitPinをオンにする
  digitalWrite(digitPin, DIGIT_ON);

  display0();
  delay(500);
  display1();
  delay(500);
  display2();
  delay(500);
  display3();
  delay(500);
  display4();
  delay(500);
  display5();
  delay(500);
  display6();
  delay(500);
  display7();
  delay(500);
  display8();
  delay(500);
  display9();
  delay(500);
}

もっと関数化する

先ほど関数化は行いましたが、似たような処理がたくさん書いてあり無駄が多いようにも思えます。今度は、関数に引数を与えて、その数字を表示するようにしてみます。引数はLEDに表示する一桁の整数とします。

引数を与えて数字を表示するためには、引数により表示するセグメントを変更する必要があります。switch文を使って引数により処理を切り替えても構いませんが、ここでは、配列を利用してみます。前述の数字と点灯させるセグメントの関係をほぼそのまま利用します。

まず、表のAからGまでを逆に並べます(逆に並べるのは単に趣味の問題です)。その後、●を1、空欄を0と置き換えると、以下のような表になります。GからAまでを2進数7ケタの数字とみなすと、表示する数字に対応する数値ができあがります。

Arduinoソフトウェアが利用しているavr-gccでは、0bという接頭辞をつけると、2進数とみなされます。C++14からは、標準化されているようです。

入力 G F E D C B A 表示(2進表記)
0 0 1 1 1 1 1 1 0b00111111
1 0 0 0 0 1 1 0 0b00000110
2 1 0 1 1 0 1 1 0b01011011
3 1 0 0 1 1 1 1 0b01001111
4 1 1 0 0 1 1 0 0b01100110
5 1 1 0 1 1 0 1 0b01101101
6 1 1 1 1 1 0 1 0b01111101
7 0 1 0 0 1 1 1 0b00100111
8 1 1 1 1 1 1 1 0b01111111
9 1 1 0 1 1 1 1 0b01101111

ここでできた2進数の各桁をそのままセグメントに反映させれば数字が表示されます。2進数の各桁を一つずつ取り出し、1ならばセグメントを点灯し、0ならば消灯します。例えば、セグメントAを点灯するかどうかは、0b00000001と論理積をとり、0でなければ点灯、0であれば消灯します。

上記の、「1ならばセグメントを点灯し、0ならば消灯します」を実現するために、ここでは、二択条件演算子を利用しました。

条件式 ? 条件が成立したとき : 条件が成立しなかったとき ;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// LEDをオン・オフする際の出力
#define DIGIT_ON LOW
#define DIGIT_OFF HIGH
#define SEGMENT_ON HIGH
#define SEGMENT_OFF LOW

const int digitPin = 13;     // ディジット(コモン)に接続するArduinoのピン
const int segmentPinA = 12;  // セグメントピンに接続するArduinoのピン
const int segmentPinB = 8;
const int segmentPinC = 5;
const int segmentPinD = 3;
const int segmentPinE = 2;
const int segmentPinF = 11;
const int segmentPinG = 6;

// 数字と表示させるセグメントの関係
const int digits[] = {
    0b00111111,  // 0
    0b00000110,  // 1
    0b01011011,  // 2
    0b01001111,  // 3
    0b01100110,  // 4
    0b01101101,  // 5
    0b01111101,  // 6
    0b00100111,  // 7
    0b01111111,  // 8
    0b01101111,  // 9
};

// 数字を表示する
void displayNumber(int n) {
  // digits[n]の各ビットを調べて対応するセグメントを点灯・消灯する
  digitalWrite(segmentPinA, digits[n] & 0b00000001 ? SEGMENT_ON : SEGMENT_OFF);
  digitalWrite(segmentPinB, digits[n] & 0b00000010 ? SEGMENT_ON : SEGMENT_OFF);
  digitalWrite(segmentPinC, digits[n] & 0b00000100 ? SEGMENT_ON : SEGMENT_OFF);
  digitalWrite(segmentPinD, digits[n] & 0b00001000 ? SEGMENT_ON : SEGMENT_OFF);
  digitalWrite(segmentPinE, digits[n] & 0b00010000 ? SEGMENT_ON : SEGMENT_OFF);
  digitalWrite(segmentPinF, digits[n] & 0b00100000 ? SEGMENT_ON : SEGMENT_OFF);
  digitalWrite(segmentPinG, digits[n] & 0b01000000 ? SEGMENT_ON : SEGMENT_OFF);
}

// setup() は,最初に一度だけ実行される
void setup() {
  // ピンを出力モードに設定する
  pinMode(digitPin, OUTPUT);
  pinMode(segmentPinA, OUTPUT);
  pinMode(segmentPinB, OUTPUT);
  pinMode(segmentPinC, OUTPUT);
  pinMode(segmentPinD, OUTPUT);
  pinMode(segmentPinE, OUTPUT);
  pinMode(segmentPinF, OUTPUT);
  pinMode(segmentPinG, OUTPUT);
}

// loop() は,setup ()実行後,無限に実行される
void loop() {
  digitalWrite(digitPin, DIGIT_ON);
  displayNumber(0);
  delay(500);
  displayNumber(1);
  delay(500);
  displayNumber(2);
  delay(500);
  displayNumber(3);
  delay(500);
  displayNumber(4);
  delay(500);
  displayNumber(5);
  delay(500);
  displayNumber(6);
  delay(500);
  displayNumber(7);
  delay(500);
  displayNumber(8);
  delay(500);
  displayNumber(9);
  delay(500);
}

もっともっと関数化する

「もっと関数化する」のスケッチでも構いませんが、displayNumber()の中に、たくさんdigitalWrite()がならんでいて、もう少し短くできそうです。

まずは、「セグメントをランダムに点灯する」で行ったように、セグメントピンを配列に格納します。ただし、今回は、セグメントAから順に格納したので、並び順が異なることに気を付けてください。

segmentPins[] 格納されているピン番号
0 セグメントAに接続しているArduinoのピン番号
1 セグメントBに接続しているArduinoのピン番号
2 セグメントCに接続しているArduinoのピン番号
3 セグメントDに接続しているArduinoのピン番号
4 セグメントEに接続しているArduinoのピン番号
5 セグメントFに接続しているArduinoのピン番号
6 セグメントGに接続しているArduinoのピン番号

次に、「もっと関数化する」のdisplayNumber()を眺めるとわかりますが、segmentA、segmentBと進んでいくたびに、論理積をとる数値の1の位置が左に1ビットずつ移動していることがわかります。

表示したい数字をnとしたとき、各セグメントを点灯させるかどうかは、下の表のように決めることができます。「<<」は、左辺の数値を左に右辺の数字ビットシフトするための演算子です。例えば、0b00000100 は1 << 2 と同じです。

segmentPins[] 対応するセグメント 点灯・消灯の判断(その1) 点灯・消灯の判断(その2)
0 A digits[n] & 0b00000001 digits[n] & (1 « 0)
1 B digits[n] & 0b00000010 digits[n] & (1 « 1)
2 C digits[n] & 0b00000100 digits[n] & (1 « 2)
3 D digits[n] & 0b00001000 digits[n] & (1 « 3)
4 E digits[n] & 0b00010000 digits[n] & (1 « 4)
5 F digits[n] & 0b00100000 digits[n] & (1 « 5)
6 G digits[n] & 0b01000000 digits[n] & (1 « 6)

ここで、一番左の数字(segmentPins[]の添え字)と一番右の数字が同じことが重要です。これにより、数字のnを表示する際に、segmentPins[i]は、以下のように制御することができます。

1
digitalWrite(segmentPins[i], digits[n] & (1 << i) ? HIGH : LOW);

最後に、スケッチを示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// LEDをオン・オフする際の出力
#define DIGIT_ON LOW
#define DIGIT_OFF HIGH
#define SEGMENT_ON HIGH
#define SEGMENT_OFF LOW

const int digitPin = 13;                                                       // ディジット(コモン)に接続するArduinoのピン
const int segmentPins[] = {12, 8, 5, 3, 2, 11, 6};                             // セグメントピンに接続するArduinoのピン
const int numberOfSegmentPins = sizeof(segmentPins) / sizeof(segmentPins[0]);  // セグメントの数

// 数字と表示させるセグメントの関係
const int digits[] = {
    0b00111111,  // 0
    0b00000110,  // 1
    0b01011011,  // 2
    0b01001111,  // 3
    0b01100110,  // 4
    0b01101101,  // 5
    0b01111101,  // 6
    0b00100111,  // 7
    0b01111111,  // 8
    0b01101111,  // 9
};

// 数字を表示する
void displayNumber(int n) {
  // digits[n]の各ビットを調べて対応するセグメントを点灯・消灯する
  for (int i = 0; i < numberOfSegmentPins; i++) {
    digitalWrite(segmentPins[i], digits[n] & (1 << i) ? SEGMENT_ON : SEGMENT_OFF);
  }
}

// setup() は,最初に一度だけ実行される
void setup() {
  pinMode(digitPin, OUTPUT);  // digitPinを出力モードに設定する
  for (int i = 0; i < numberOfSegmentPins; i++) {
    pinMode(segmentPins[i], OUTPUT);  // segmentPinsを出力モードに設定する
  }
}

// loop() は,setup ()実行後,無限に実行される
void loop() {
  digitalWrite(digitPin, DIGIT_ON);
  displayNumber(0);
  delay(500);
  displayNumber(1);
  delay(500);
  displayNumber(2);
  delay(500);
  displayNumber(3);
  delay(500);
  displayNumber(4);
  delay(500);
  displayNumber(5);
  delay(500);
  displayNumber(6);
  delay(500);
  displayNumber(7);
  delay(500);
  displayNumber(8);
  delay(500);
  displayNumber(9);
  delay(500);
}

「関数化する」「もっと関数化する」「もっともっと関数化する」のスケッチのサイズは以下の通りでした。intをuint8_tに替えるともう少し減るかもしれません。

コンパイラも進歩しているようで、Arduino 1.8.10ではさらにサイズが小さくなりました。

スケッチ コンパイル後のサイズ(Arduino 1.6.5 コンパイル後のサイズ(Arduino 1.8.10)
関数化する 1840バイト 1578バイト
もっと関数化する 1438バイト 1284バイト
もっともっと関数化する 1392バイト 1216バイト
i
関数化することでスケッチのサイズは小さくなりますが、性能(実行速度)が向上するとは限りません。

セグメント(4桁)をランダムに点灯する

実施内容

今までは1桁しか利用していませんでしたが、今回は4桁全てを対象に、ランダムにチカチカさせます。delay()で待つ時間を小さくしたこともあり、ちょっと派手になります。

考え方

  • 点灯させるセグメントをランダムに選ぶ。ただし、今回は、ディジット(点灯するする桁)とセグメント(表示するセグメント)の両方を選ぶ。
  • 選んだセグメントを点灯する。
  • しばらく待つ。
  • 点灯させたセグメントを消灯する。

ピン接続

以下のピン接続を前提にスケッチを書いていきます。小数点のピンも含めて全部使います。

Arduinoのピン番号 LEDのピン OSL40562-LB のピン
2 E 1
3 D 2
4 DP 3
5 C 4
6 G 5
7 DIG4 6
8 B 7
9 DIG3 8
10 DIG2 9
11 F 10
12 A 11
13 DIG1 12

Arduinoのスケッチを書く

セグメント(1桁)をランダムに点灯する」を4桁に拡張します。ディジットを選択する処理を追加するだけです。ただし、点灯しないディジットはオフにしておく必要があります(そうしないと、目的とは異なる桁のセグメントも点灯します)。このため、setup()の中で、ディジットをオフに設定しておき、loop()の中で、選択したディジットとセグメントをオンにします。カソードコモンのLEDでは、LEDに逆電圧をかけているので自己責任でお願いします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// LEDをオン・オフする際の出力
#define DIGIT_ON LOW
#define DIGIT_OFF HIGH
#define SEGMENT_ON HIGH
#define SEGMENT_OFF LOW

const int digitPins[] = {7, 9, 10, 13};                                        // ディジット(コモン)に接続するArduinoのピン
const int segmentPins[] = {12, 8, 5, 3, 2, 11, 6};                             // セグメントピンに接続するArduinoのピン
const int numberOfDigitPins = sizeof(digitPins) / sizeof(digitPins[0]);        // ディジットの数
const int numberOfSegmentPins = sizeof(segmentPins) / sizeof(segmentPins[0]);  // セグメントの数

// setup() は,最初に一度だけ実行される
void setup() {
  for (int i = 0; i < numberOfDigitPins; i++) {
    pinMode(digitPins[i], OUTPUT);  // digitPinsを出力モードに設定する
    digitalWrite(digitPins[i], DIGIT_OFF);
  }
  for (int i = 0; i < numberOfSegmentPins; i++) {
    pinMode(segmentPins[i], OUTPUT);  // segmentPinsを出力モードに設定する
  }
}

void loop() {
  int digitPin = digitPins[random(numberOfDigitPins)];        // 点灯するディジットを選択する
  int segmentPin = segmentPins[random(numberOfSegmentPins)];  // 点灯するセグメントを選択する

  digitalWrite(digitPin, DIGIT_ON);
  digitalWrite(segmentPin, SEGMENT_ON);
  delay(10);
  digitalWrite(digitPin, DIGIT_OFF);
  digitalWrite(segmentPin, SEGMENT_OFF);
}

ひとつのセグメントの点灯時間は少し短めにしてみました。

4桁を同時に点灯する

実施内容

今までは同時にはひとつのセグメントしか点灯させていませんでした。しかし、 セグメント(4桁)をランダムに点灯するのスケッチを動作させると、同時には一つのセグメントしか点灯させていないにもかかわらず、複数のセグメントが同時に点灯しているように見えたと思います。ここでは、ダイナミック点灯の原理を簡単に説明した後、以下の図のように各桁の異なるセグメントを同時に点灯させてみます。

LEDの点灯方法

OSL40562-LBには全部で32個のLEDがあります(各桁8個x4桁)。一つのLEDにはアノードとカソードの2つのピンがあるため、単純に考えると、32x2=64のピンが必要となります。しかし、実際にはピンの数は12個です。これは、ダイナミック点灯と呼ばれる方式を採用しているためです。

まず各桁ごとに分解して考えます。各桁には8個のLEDがあるため、本来は16個のピンが必要ですが、カソードをセグメント内で共用してみます。これにより、アノード用8個、カソード用1個のピンで済むようにしています(カソードを共用するのでカソードコモンと呼びます。アノードを共用することもでき、この場合は、アノードコモンと呼びます)。こうすることにより、1桁あたり9個のピンにすることができます。しかし、このままでは、9x4=36本のピンが必要となります。

次に、各桁間で、アノードを共用することを考えます。そうすれば、全体で、アノード用8個、カソード用4個で済むようになります。

ただし、こうしてしまうと、同時にすべてのLEDを制御することはできなくなります。この問題を解決するために考えられたのが、ダイナミック点灯です。ダイナミック点灯では、LEDを常時点灯させるのではなく、人間が気づかないくらい早い周期で、点灯(制御)するLEDを切り替えていきます。今回は、4つあるカソードを一つ選択(選択する桁のカソードをLOWにし、その他の桁のLEDをHIGHにします)し、選択した桁のアノードのHIGH・LOWを制御することで、その桁に表示する数字を制御します。

考え方

上記の「LEDの点灯方法」に従って、1桁ずつ表示していきます。以下を繰り返します。

  • 表示桁を選択(一つのディジットだけをオンに)する。
  • 表示するセグメントを光らせる(セグメントをオンにする)。
  • ディジットをすべてオフにする(実際はオンにしたディジットだけをオフにすれば十分です)。
  • 選択したディジットをオフにする。

ピン接続

以下のピン接続を前提にスケッチを書いていきます。小数点のピンも含めて全部書いてありますが、全てのピンを使っているわけではありません。

Arduinoのピン番号 LEDのピン OSL40562-LB のピン
2 E 1
3 D 2
4 DP 3
5 C 4
6 G 5
7 DIG4 6
8 B 7
9 DIG3 8
10 DIG2 9
11 F 10
12 A 11
13 DIG1 12

Arduinoのスケッチを書く

各桁で表示させるセグメントは一つなので、turnOnPins[]という配列に、各桁で点灯させるピンに接続しているArduinoのピン番号を書いてあります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// LEDをオン・オフする際の出力
#define DIGIT_ON LOW
#define DIGIT_OFF HIGH
#define SEGMENT_ON HIGH
#define SEGMENT_OFF LOW

const int digitPins[] = {7, 9, 10, 13};                                        // ディジット(コモン)に接続するArduinoのピン
const int segmentPins[] = {12, 8, 5, 3, 2, 11, 6};                             // セグメントピンに接続するArduinoのピン
const int numberOfDigitPins = sizeof(digitPins) / sizeof(digitPins[0]);        // ディジットの数
const int numberOfSegmentPins = sizeof(segmentPins) / sizeof(segmentPins[0]);  // セグメントの数

// 各桁で点灯するセグメントに接続しているArduinoのピン番号。
const int turnOnPins[] = {3, 12, 11, 2};

// setup() は,最初に一度だけ実行される
void setup() {
  for (int i = 0; i < numberOfDigitPins; i++) {
    pinMode(digitPins[i], OUTPUT);  // digitPinsを出力モードに設定する
    digitalWrite(digitPins[i], DIGIT_OFF);
  }
  for (int i = 0; i < numberOfSegmentPins; i++) {
    pinMode(segmentPins[i], OUTPUT);  // segmentPinsを出力モードに設定する
  }
}

void loop() {
  for (int i = 0; i < numberOfDigitPins; i++) {  // 表示桁を選択
    digitalWrite(digitPins[i], DIGIT_ON);        // ディジットをオンに
    digitalWrite(turnOnPins[i], SEGMENT_ON);     // セグメントをオンに
    delay(3);                                    // 明るさ調整用。点滅しない程度に。
    digitalWrite(digitPins[i], DIGIT_OFF);       // ディジットをオフに
    digitalWrite(turnOnPins[i], SEGMENT_OFF);    // セグメントをオフに
  }
}

4桁の数字を表示する(カウンタ)

実施内容

0から9999までの数字を順に表示させてみます。

考え方

1桁の数字を表示する4桁を同時に点灯するの考え方を融合します。

  • 表示桁を選択(一つのディジットだけをオンに)する。
  • 数字に対応したセグメントを光らせる(セグメントをオンにする)。
  • セグメントをすべてオフにする(実際はオンにしたセグメントだけをオフにすれば十分です)。
  • 選択したディジットをオフにする。

ピン接続

以下のピン接続を前提にスケッチを書いていきます。小数点のピンも含めて全部書いてありますが、全てのピンを使っているわけではありません。

Arduinoのピン番号 LEDのピン OSL40562-LB のピン
2 E 1
3 D 2
4 DP 3
5 C 4
6 G 5
7 DIG4 6
8 B 7
9 DIG3 8
10 DIG2 9
11 F 10
12 A 11
13 DIG1 12

スケッチ

i
このスケッチでは、表示の調整に、delay()を利用しています。表示だけを行う分には問題はありませんが、センサーやアクチュエータ―を組み合わせて利用する際は、問題が発生する可能性が高いので注意してください。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// LEDをオン・オフする際の出力
#define DIGIT_ON LOW
#define DIGIT_OFF HIGH
#define SEGMENT_ON HIGH
#define SEGMENT_OFF LOW

const int digitPins[] = {7, 9, 10, 13};                                        // ディジット(コモン)に接続するArduinoのピン
const int segmentPins[] = {12, 8, 5, 3, 2, 11, 6};                             // セグメントピンに接続するArduinoのピン
const int numberOfDigitPins = sizeof(digitPins) / sizeof(digitPins[0]);        // ディジットの数
const int numberOfSegmentPins = sizeof(segmentPins) / sizeof(segmentPins[0]);  // セグメントの数

// 数字と表示させるセグメントの関係
const int digits[] = {
    0b00111111,  // 0
    0b00000110,  // 1
    0b01011011,  // 2
    0b01001111,  // 3
    0b01100110,  // 4
    0b01101101,  // 5
    0b01111101,  // 6
    0b00100111,  // 7
    0b01111111,  // 8
    0b01101111,  // 9
};

// 数字を表示する
void displayNumber(int n) {
  // digits[n]の各ビットを調べて対応するセグメントを点灯・消灯する
  for (int i = 0; i < numberOfSegmentPins; i++) {
    digitalWrite(segmentPins[i], digits[n] & (1 << i) ? SEGMENT_ON : SEGMENT_OFF);
  }
}

// セグメントをすべてオフにする
void clearSegments() {
  for (int j = 0; j < numberOfSegmentPins; j++) {
    digitalWrite(segmentPins[j], SEGMENT_OFF);
  }
}

// 4桁の数字を表示する
void displayNumbers(int n) {
  for (int i = 0; i < numberOfDigitPins; i++) {  // 右の桁からディジットを選択する
    digitalWrite(digitPins[i], DIGIT_ON);        // ディジットをオンにする
    displayNumber(n % 10);                       // 10で割った余りを求めて、1の位を求め、表示する
    delay(1);
    clearSegments();                        // セグメントをすべてオフにする
    digitalWrite(digitPins[i], DIGIT_OFF);  // ディジットをオフにする
    n /= 10;                                // 10で割り、次に表示する数字を、1の位に移す
  }
}

// setup() は,最初に一度だけ実行される
void setup() {
  for (int i = 0; i < numberOfDigitPins; i++) {
    pinMode(digitPins[i], OUTPUT);  // digitPinsを出力モードに設定する
    digitalWrite(digitPins[i], DIGIT_OFF);
  }
  for (int i = 0; i < numberOfSegmentPins; i++) {
    pinMode(segmentPins[i], OUTPUT);  // segmentPinsを出力モードに設定する
  }
}

void loop() {
  for (int i = 0; i < 10000; i++) {  // 0から9999までを表示する。
    displayNumbers(i);
  }
}

4桁の数字を表示する(表示装置)

実施内容

「4桁の数字を表示する(カウンタ)」では、4桁の数字を表示しましたが、実は、あまりいい構造になっていません。ダイナミック点灯では常時LEDの表示を行う必要がありますが、このプログラムでは、数字を表示する(displayNumbers())を呼び出したときにしか表示していません。このため、たとえば、loop()の中でdelay()を入れてしまうと、一瞬数字が表示されてその後消えてしまいます。delay()を使うなという話もありますが…

この問題を解決するために、表示する数字の設定と数字の表示を分離します。数字の設定は任意のタイミングで実施し、表示はloop()とは独立に一定周期の割り込みベースで行います。割り込みは、何らかの条件に基づき、現在実行している通常のプログラムとは異なるプログラムを一時的に実行する機構です。Arduino Unoで採用しているプロセッサでは、クロックによる割り込みやピン入力による割り込みなどが利用できます。ESP-WROOM-32は、マルチコア・マルチタスクを利用できるので、表示専用のタスクを作成することも可能です。

割り込みが発生したときに実行させるプログラム(関数)は、ISR()というマクロを利用して登録します。

graph LR f1("setNumbers()") -->|変数を設定する| v1["numberToDisplay"] v1 --> |設定した変数を<br>定期的に表示する| f2("displayNumbers()") style f1 fill:white, stroke:black, stroke-width:4px style f2 fill:white, stroke: black, stroke-width:4px style v1 fill:white, stroke: black, stroke-width:4px linkStyle 0,1 fill: none

なお、割り込みを利用せず、また、delay()も利用しない方法も可能です。詳細は、実行タイミングの指定を参照してください。ただし、loop()の中でLEDを点灯することになるので、loop()の実行に要する時間によっては、うまく表示されない可能性も考えられます。

考え方

表示(displayNumbers())については、1回の割り込みで、1桁だけを表示するように変更しました。

割り込みの設定部分を以下に示します。

  • 割り込みベクタの設定を行う。
  • 割り込みの設定を行う。

割り込みベクタについては、toneの内部構造のページを参照してください。

ピン接続

以下のピン接続を前提にスケッチを書いていきます。小数点のピンも含めて全部書いてありますが、全てのピンを使っているわけではありません。

Arduinoのピン番号 LEDのピン OSL40562-LB のピン
2 E 1
3 D 2
4 DP 3
5 C 4
6 G 5
7 DIG4 6
8 B 7
9 DIG3 8
10 DIG2 9
11 F 10
12 A 11
13 DIG1 12

スケッチ

TIMER2を利用したスケッチです。tone()とデジタルピンの3番と11番へのPWM出力に干渉してしまいます。

このスケッチはレジスタを直接触っているので、機種依存です。Arduino Unoでの動作は確認しましたが、他のArduinoでは動かないこともあります。Arduinoはプロセッサの違いを隠してくれているので通常はこのようなことは起こりません。レジスタを直接触るなどは、あまりよくない方法かもしれません。タイマ割り込みを扱うMsTimer2などのライブラリがあるのでそちらを使うのがいいと思います。この後に、MsTimer2を利用した場合のスケッチも載せました。

numbersToDisplayは、異なるコンテキストで共有する変数なので、読み書き中は割り込みを禁止にしておくのがいいと思います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// LEDをオン・オフする際の出力
#define DIGIT_ON LOW
#define DIGIT_OFF HIGH
#define SEGMENT_ON HIGH
#define SEGMENT_OFF LOW

const int digitPins[] = {7, 9, 10, 13};                                        // ディジット(コモン)に接続するArduinoのピン
const int segmentPins[] = {12, 8, 5, 3, 2, 11, 6};                             // セグメントピンに接続するArduinoのピン
const int numberOfDigitPins = sizeof(digitPins) / sizeof(digitPins[0]);        // ディジットの数
const int numberOfSegmentPins = sizeof(segmentPins) / sizeof(segmentPins[0]);  // セグメントの数
int numbersToDisplay = 0;                                                      // LEDに表示する数字を保持する変数

// 数字と表示させるセグメントの関係
const int digits[] = {
    0b00111111,  // 0
    0b00000110,  // 1
    0b01011011,  // 2
    0b01001111,  // 3
    0b01100110,  // 4
    0b01101101,  // 5
    0b01111101,  // 6
    0b00100111,  // 7
    0b01111111,  // 8
    0b01101111,  // 9
};

// 数字を表示する
void displayNumber(int n) {
  // digits[n]の各ビットを調べて対応するセグメントを点灯・消灯する
  for (int i = 0; i < numberOfSegmentPins; i++) {
    digitalWrite(segmentPins[i], digits[n] & (1 << i) ? SEGMENT_ON : SEGMENT_OFF);
  }
}

// セグメントをすべてオフにする
void clearSegments() {
  for (int j = 0; j < numberOfSegmentPins; j++) {
    digitalWrite(segmentPins[j], SEGMENT_OFF);
  }
}

// 4桁の数字を表示する
void displayNumbers() {
    static int n = numberOfDigitPins;
    static int digit = 0;
    int div[] = {1, 10, 100, 1000};

    clearSegments();                            // セグメントをすべてオフにする
    digitalWrite(digitPins[digit], DIGIT_OFF);  // ディジットをオフにする
    digit = (digit + 1) % numberOfDigitPins;    // 次のディジットを求める
    if (digit == 0) {
        n = numbersToDisplay;  // numberToDisplayの値を書き換えないために変数にコピー
    }
    digitalWrite(digitPins[digit], DIGIT_ON);   // ディジットをオンにする
    displayNumber((n / div[digit]) % 10);       // ディジットに対応する数字を表示する
}

// 表示する数字をセットする
void setNumbers(int n) {
  numbersToDisplay = n;
}

// setup() は,最初に一度だけ実行される
void setup() {
  for (int i = 0; i < numberOfDigitPins; i++) {
    pinMode(digitPins[i], OUTPUT);  // digitPinsを出力モードに設定する
    digitalWrite(digitPins[i], DIGIT_OFF);
  }
  for (int i = 0; i < numberOfSegmentPins; i++) {
    pinMode(segmentPins[i], OUTPUT);  // segmentPinsを出力モードに設定する
  }

  // f = クロック周波数 / ( 分周比 * 255 * 2)
  // 分周比=64, 比較レジスタの1値=255 -> f = 16000000 / (64 * 255 * 2) = 490 Hz
  OCR2A = 255;                  // 255クロックごとに割り込みをかける
  TCCR2B = 0b100;               // 分周比を32に設定する
  bitWrite(TIMSK2, OCIE2A, 1);  // TIMER2を許可する
}

void loop() {
  for (int i = 0; i < 10000; i++) {
    setNumbers(i);
    delay(1000);
  }
}

ISR(TIMER2_COMPA_vect) {
  displayNumbers();
}

写りが悪いですが、上記のプログラムが動作している様子です。

4桁の数字を表示する(MsTimer2)

実施内容

割り込みを実現するために、MsTimer2を利用します。呼び出し間隔(今回は1ミリ秒を指定)と、呼び出す関数(displayNumbers())を指定して、タイマを開始するだけです。

考え方

割り込みの設定部分を以下に示します。

  • タイマ割り込み間隔と、割り込みが発生したときに呼び出す関数を指定する。
  • タイマを開始する。

ピン接続

以下のピン接続を前提にスケッチを書いていきます。小数点のピンも含めて全部書いてありますが、全てのピンを使っているわけではありません。

Arduinoのピン番号 LEDのピン OSL40562-LB のピン
2 E 1
3 D 2
4 DP 3
5 C 4
6 G 5
7 DIG4 6
8 B 7
9 DIG3 8
10 DIG2 9
11 F 10
12 A 11
13 DIG1 12

スケッチ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <MsTimer2.h>

// LEDをオン・オフする際の出力
#define DIGIT_ON LOW
#define DIGIT_OFF HIGH
#define SEGMENT_ON HIGH
#define SEGMENT_OFF LOW

const int digitPins[] = {7, 9, 10, 13};                                        // ディジット(コモン)に接続するArduinoのピン
const int segmentPins[] = {12, 8, 5, 3, 2, 11, 6};                             // セグメントピンに接続するArduinoのピン
const int numberOfDigitPins = sizeof(digitPins) / sizeof(digitPins[0]);        // ディジットの数
const int numberOfSegmentPins = sizeof(segmentPins) / sizeof(segmentPins[0]);  // セグメントの数
int numbersToDisplay = 0;                                                      // LEDに表示する数字を保持する変数

// 数字と表示させるセグメントの関係
const int digits[] = {
    0b00111111,  // 0
    0b00000110,  // 1
    0b01011011,  // 2
    0b01001111,  // 3
    0b01100110,  // 4
    0b01101101,  // 5
    0b01111101,  // 6
    0b00100111,  // 7
    0b01111111,  // 8
    0b01101111,  // 9
};

// 数字を表示する
void displayNumber(int n) {
  // digits[n]の各ビットを調べて対応するセグメントを点灯・消灯する
  for (int i = 0; i < numberOfSegmentPins; i++) {
    digitalWrite(segmentPins[i], digits[n] & (1 << i) ? SEGMENT_ON : SEGMENT_OFF);
  }
}

// セグメントをすべてオフにする
void clearSegments() {
  for (int j = 0; j < numberOfSegmentPins; j++) {
    digitalWrite(segmentPins[j], SEGMENT_OFF);
  }
}

// 4桁の数字を表示する
void displayNumbers() {
    static int n = numberOfDigitPins;
    static int digit = 0;
    int div[] = {1, 10, 100, 1000};

    clearSegments();                            // セグメントをすべてオフにする
    digitalWrite(digitPins[digit], DIGIT_OFF);  // ディジットをオフにする
    digit = (digit + 1) % numberOfDigitPins;    // 次のディジットを求める
    if (digit == 0) {
        n = numbersToDisplay;  // numberToDisplayの値を書き換えないために変数にコピー
    }
    digitalWrite(digitPins[digit], DIGIT_ON);   // ディジットをオンにする
    displayNumber((n / div[digit]) % 10);       // ディジットに対応する数字を表示する
}

// 表示する数字をセットする
void setNumbers(int n) {
  numbersToDisplay = n;
}

// setup() は,最初に一度だけ実行される
void setup() {
  for (int i = 0; i < numberOfDigitPins; i++) {
    pinMode(digitPins[i], OUTPUT);  // digitPinsを出力モードに設定する
    digitalWrite(digitPins[i], DIGIT_OFF);
  }
  for (int i = 0; i < numberOfSegmentPins; i++) {
    pinMode(segmentPins[i], OUTPUT);  // segmentPinsを出力モードに設定する
  }

  MsTimer2::set(1, displayNumbers); // displayNumbers()を1ミリ秒ごとに呼び出す
  MsTimer2::start(); // タイマを開始する
}

void loop() {
  for (int i = 0; i < 10000; i++) {
    setNumbers(i);
    delay(1000);
  }
}

スケッチ

ここで利用したスケッチは、GitHubに置いてあります。

バージョン

Hardware:Arduino Uno
Software:Arduino 1.8.13

最終更新日

November 1, 2022

inserted by FC2 system