ArduinoでのHello Worldは、LEDチカチカ(Lチカ)が定番です。少しひねくれて、4桁7セグメントLEDを使ってLチカを行います。
OSL40562-LBというカソードコモンのダイナミック接続4桁高輝度青色7セグメントLED(小数点があるのでLEDは8個)を使います。
Arduinoをどう使えばLチカできるのかを考えていきます。Lチカそのものというよりは、Arduinoの使い方に重点をおいています。
今回は4桁のダイナミック接続7セグメントLEDを使います。最終的にはダイナミック点灯制御の実験を行います。LED1個の制御→1桁の制御(数字の表示)→4桁の制御と順に拡張していきます。
アノードコモンタイプのLEDでは、HIGHとLOWを逆にすれば動作すると思います。
ちょっと長いので目次をつけておきます。
このページで必要なものは以下の通りです。
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番、9番、8番、6番です。例えば、12番をLOWにし、1番をHIGHにすれば、一番左の桁の左下のLED(E)が点灯します。
Arduinoは単なる電源として利用し、セグメントを一つ点灯します。Arduinoは単なる電源なので、スケッチはありません。Arduinoの5V出力を、抵抗を通じてLEDのアノードに、GNDをカソードに接続します。一番左の桁の左下(E)のセグメントを点灯させるには、1番に5V、12番にGNDを接続します。
Arduinoで制御を行う前に、制御対象が単体で動作することを確認しておくと、スケッチが悪いのか制御対象が悪いのかの切り分けが簡単になります。
ピンの接続は以下の通りです。
| Arduinoのピン番号 | OSL40562-LBのピン番号 |
|---|---|
| 5V | 1 |
| GND | 12 |
Aruinoのスケッチを使って、セグメントを一つ点灯します。
Arduinoのデジタル出力を使い、HIGH(5V)とLOW(0V)をピンに出力します。HIGHを出力したピンをLEDの1番に、LOWを出力したピンをLEDの12番に接続すると、一番左の桁の左下(E)のセグメントを点灯することができるはずです。
以下のピン接続を前提にスケッチを書いていきます。Arduinoのピン番号と7セグメントLEDのピン番号とが一つずれているので注意してください。異なるピンに接続する場合は、スケッチを変更してください。
| Arduinoのピン番号 | OSL40562-LBのピン番号 |
|---|---|
| 2 | 1 |
| 13 | 12 |
ArduinoでデジタルピンにHIGHやLOWを出力を行うには、以下の操作を行う必要があります。
デジタルピンを出力モードにするには、pinMode()という関数を利用します。また、デジタルピンに出力するには、digitalWrite()という関数を利用します。
Arduinoでは、通常、C++/C言語を用いて記述します。ただし、main()関数はArduinoソフトウェア自身が定義しているため、利用者はmain()関数を定義する必要はありません。Arduinoで定義しているmain()関数は、その中で、setup()という最初に一度だけ実行される関数と、その後無限に実行されるloop()という関数を呼び出しています。このため、setup()とloop()の2つの関数を定義します。双方の関数とも引数も戻り値も持たない関数です。つまり、以下の形の関数を定義します。
void setup() {
}
void loop() {
}
通常は、接続する機器の初期化などをsetup()で行い、接続する機器の制御をloop()で行います。関数が分かれているので、グローバル変数を多用することになります。
LEDを点灯するには以下を行います。
今回は、ただ単にセグメントを点灯させるだけですが、setup()の中でデジタルピンの出力モードの設定を、loop()の中でデジタルピンへの出力を行うことにします。今回はLEDはつけっぱなしなので、loop()に入れた処理をsetup()に入れても問題はありません。
const int anode_pin = 2; // アノードに接続するArduinoのピン
const int cathode_pin = 13; // カソードに接続するArduinoのピン
// setup() は、最初に一度だけ実行される
void setup() {
pinMode(anode_pin, OUTPUT); // anode_pinを出力モードに設定する
pinMode(cathode_pin, OUTPUT); // cathode_pinを出力モードに設定する
}
// loop() は、setup ()実行後、無限に実行される
void loop() {
digitalWrite(cathode_pin, LOW); // cathode_pinにLOWを出力する
digitalWrite(anode_pin, HIGH); // anode_pinにHIGHを出力する
}
セグメントを点滅させるスケッチを作成します。これでLチカ完成です(たぶん)。
セグメントを点灯する その2では、点灯だけを行いましたが、次は点滅させてみます。そのためには、点灯→消灯を繰り返します。
今回利用している7セグメントLEDは、カソードコモンなので、カソードの出力は変えずにアノードの出力を変えます。
以下のピン接続を前提にスケッチを書いていきます。Arduinoのピン番号と7セグメントLEDのピン番号とが一つずれているので注意してください。
| Arduinoのピン番号 | OSL40562-LBのピン番号 |
|---|---|
| 2 | 1 |
| 13 | 12 |
アノードを0V(LOW)にすれば、セグメントは消灯します。単純に点灯と消灯を繰り返すと、点滅速度が速すぎて人間の目には点滅しているようには見えません(この特性を後で利用することになります)。このため、点灯と消灯の間に少し待ち時間を入れます。
Arduinoでは、delay()という関数が用意されていて、ミリ秒単位で処理を中断することができます。マイクロ秒単位で中断するdelayMicroseconds()という関数も用意されています。今回は500ミリ秒間隔で点滅させてみます。
const int anode_pin = 2; // アノードに接続するArduinoのピン
const int cathode_pin = 13; // カソードに接続するArduinoのピン
// setup() は、最初に一度だけ実行される
void setup() {
pinMode(anode_pin, OUTPUT); // anode_pinを出力モードに設定する
pinMode(cathode_pin, OUTPUT); // cathode_pinを出力モードに設定する
}
// loop() は、setup ()実行後、無限に実行される
void loop() {
digitalWrite(cathode_pin, LOW); // cathode_pinにLOWを出力する
digitalWrite(anode_pin, HIGH); // anode_pinにHIGHを出力する
delay(500); // 500ミリ秒待つ
digitalWrite(anode_pin, LOW); // anode_pinにHIGHを出力する
delay(500); // 500ミリ秒待つ
}
7セグメントLEDの一つのセグメントだけをチカチカさせているだけではもったいないので、次は全部のセグメント(小数点含む)をチカチカさせます。一度に点灯するセグメントは一つで、どのセグメントを点灯するのかは、ランダムに決めることにします。
今までは同じセグメントだけを点灯したり、点滅したりさせましたが、今回は複数のセグメントを点灯します。
以下のピン接続を前提にスケッチを書いていきます。カソードピンの一部は使っていないので注意してください。
| Arduinoのピン番号 | OSL40562-LBのピン番号 |
|---|---|
| 2 | 1 |
| 3 | 2 |
| 4 | 3 |
| 5 | 4 |
| 6 | 5 |
| 8 | 7 |
| 11 | 10 |
| 12 | 11 |
| 13 | 12 |
今回はピンをたくさん使います。しかも、アノードのピンは、1/2/3/4/5/7/10/11と少し跳んでいます。このため、単純にfor文などでループしても、目的のピン番号を得ることはできません。このため、アノードのピン番号を配列に格納します。配列の名前をanode_pinsとし、以下のように格納します。
| anode_pinsの添え字 | Arduinoのピン番号 |
|---|---|
| 0 | 2 |
| 1 | 3 |
| 2 | 4 |
| 3 | 5 |
| 4 | 6 |
| 5 | 8 |
| 6 | 11 |
| 7 | 12 |
こうすることで、配列の添え字を0から7まで変化させると、添え字に対応したピン番号を得ることができ、スケッチが簡単になります。
配列の要素数は、配列全体の大きさを配列の要素1個の大きさで割ることで求めることができます。
ランダムな数字を取得するには、random()という関数を利用します。今回は、上記の通り、0から7までの数字が必要なので、random(8)を呼び出します。
後は、選択したピンをしばらく点灯して、消灯するということを繰り返すだけです。
const int anode_pins[] = {2, 3, 4, 5, 6, 8, 11, 12}; // アノードに接続するArduinoのピン
const int cathode_pin = 13; // カソードに接続するArduinoのピン
const int number_of_anode_pins = sizeof(anode_pins) / sizeof(anode_pins[0]);
// setup() は、最初に一度だけ実行される
void setup() {
for (int i = 0; i < number_of_anode_pins; i++) {
pinMode(anode_pins[i], OUTPUT); // anode_pinsを出力モードに設定する
}
pinMode(cathode_pin, OUTPUT); // cathode_pinを出力モードに設定する
}
// loop() は、setup ()実行後、無限に実行される
void loop() {
int anode_pin = anode_pins[random(number_of_anode_pins)]; // 点灯するピンを選択する
digitalWrite(cathode_pin, LOW); // cathode_pinにLOWを出力する
digitalWrite(anode_pin, HIGH); // 選択したピンにHIGHを出力する
delay(100); // 少し待つ
digitalWrite(anode_pin, LOW); // 選択したピンにLOWを出力する
}
今までは、各セグメントを一つずつ点灯しただけでしたが、今回は、1桁の数字を表示させます。
4桁ありますが、まだ1桁だけを使います。
まずは、0から9の数字をどのように点灯するかを考えます。今回は、以下のようにします。6、7、9は好みが分かれるかもしれません。
上記で示した、表示する数字と点灯させるセグメントとの関係を整理します。一番左が表示させる数字で、右側が点灯させるセグメントです。
| 数字 | A | B | C | D | E | F | G |
|---|---|---|---|---|---|---|---|
| 0 | ● | ● | ● | ● | ● | ● | |
| 1 | ● | ● | |||||
| 2 | ● | ● | ● | ● | ● | ||
| 3 | ● | ● | ● | ● | ● | ||
| 4 | ● | ● | ● | ● | |||
| 5 | ● | ● | ● | ● | ● | ||
| 6 | ● | ● | ● | ● | ● | ● | |
| 7 | ● | ● | ● | ● | |||
| 8 | ● | ● | ● | ● | ● | ● | ● |
| 9 | ● | ● | ● | ● | ● | ● |
上記の表に従って、同時にセグメントを点灯します。
以下のピン接続を前提にスケッチを書いていきます。
| Arduinoのピン番号 | OSL40562-LBのピン番号 |
|---|---|
| 2 | 1 |
| 3 | 2 |
| 4 | 3 |
| 6 | 5 |
| 8 | 7 |
| 11 | 10 |
| 12 | 11 |
| 13 | 12 |
0から9までを、上記の表に従ってただひたすら点灯します。
const int anode_a = 12; //アノードに接続するArduinoのピン
const int anode_b = 8;
const int anode_c = 5;
const int anode_d = 3;
const int anode_e = 2;
const int anode_f = 11;
const int anode_g = 6;
const int cathode_pin = 13; // カソードに接続するArduinoのピン
// setup() は、最初に一度だけ実行される
void setup () {
// ピンを出力モードに設定する
pinMode(anode_a, OUTPUT);
pinMode(anode_b, OUTPUT);
pinMode(anode_c, OUTPUT);
pinMode(anode_d, OUTPUT);
pinMode(anode_e, OUTPUT);
pinMode(anode_f, OUTPUT);
pinMode(anode_g, OUTPUT);
pinMode(cathode_pin, OUTPUT);
}
// loop() は、setup ()実行後、無限に実行される
void loop() {
// cathode_pinにLOWを出力する
digitalWrite(cathode_pin, LOW);
// 各数字で点灯するピンにHIGHを、消灯するピンにLOWを出力する
/* 0 */
digitalWrite(anode_a, HIGH);
digitalWrite(anode_b, HIGH);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, HIGH);
digitalWrite(anode_e, HIGH);
digitalWrite(anode_f, HIGH);
digitalWrite(anode_g, LOW);
delay(500);
/* 1 */
digitalWrite(anode_a, LOW);
digitalWrite(anode_b, HIGH);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, LOW);
digitalWrite(anode_e, LOW);
digitalWrite(anode_f, LOW);
digitalWrite(anode_g, LOW);
delay(500);
/* 2 */
digitalWrite(anode_a, HIGH);
digitalWrite(anode_b, HIGH);
digitalWrite(anode_c, LOW);
digitalWrite(anode_d, HIGH);
digitalWrite(anode_e, HIGH);
digitalWrite(anode_f, LOW);
digitalWrite(anode_g, HIGH);
delay(500);
/* 3 */
digitalWrite(anode_a, HIGH);
digitalWrite(anode_b, HIGH);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, HIGH);
digitalWrite(anode_e, LOW);
digitalWrite(anode_f, LOW);
digitalWrite(anode_g, HIGH);
delay(500);
/* 4 */
digitalWrite(anode_a, LOW);
digitalWrite(anode_b, HIGH);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, LOW);
digitalWrite(anode_e, LOW);
digitalWrite(anode_f, HIGH);
digitalWrite(anode_g, HIGH);
delay(500);
/* 5 */
digitalWrite(anode_a, HIGH);
digitalWrite(anode_b, LOW);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, HIGH);
digitalWrite(anode_e, LOW);
digitalWrite(anode_f, HIGH);
digitalWrite(anode_g, HIGH);
delay(500);
/* 6 */
digitalWrite(anode_a, HIGH);
digitalWrite(anode_b, LOW);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, HIGH);
digitalWrite(anode_e, HIGH);
digitalWrite(anode_f, HIGH);
digitalWrite(anode_g, HIGH);
delay(500);
/* 7 */
digitalWrite(anode_a, HIGH);
digitalWrite(anode_b, HIGH);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, LOW);
digitalWrite(anode_e, LOW);
digitalWrite(anode_f, HIGH);
digitalWrite(anode_g, LOW);
delay(500);
/* 8 */
digitalWrite(anode_a, HIGH);
digitalWrite(anode_b, HIGH);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, HIGH);
digitalWrite(anode_e, HIGH);
digitalWrite(anode_f, HIGH);
digitalWrite(anode_g, HIGH);
delay(500);
/* 9 */
digitalWrite(anode_a, HIGH);
digitalWrite(anode_b, HIGH);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, HIGH);
digitalWrite(anode_e, LOW);
digitalWrite(anode_f, HIGH);
digitalWrite(anode_g, HIGH);
delay(500);
}
前記のプログラムでも問題なく数字を表示できますが、数字を表示するたびに毎回すべてのセグメントの点灯・消灯をコーディングしていては、スケッチが大きくなりすぎるし、間違いが混入する原因になります。このため、各数字を表示させる部分を関数化します。まずは、0から9までそれぞれの数字の表示を個別の関数にしてみます。
具体的には、display_0()からdisplay_9()を定義しました。これにより、0を表示したいときは、display_0()を呼び出すだけですむようになります。このような小さいスケッチではありがたみは少ないですが、複雑なスケッチになってくると、同じ処理をひとまとめにして再利用できる関数は有効です。
const int anode_a = 12; //アノードに接続するArduinoのピン
const int anode_b = 8;
const int anode_c = 5;
const int anode_d = 3;
const int anode_e = 2;
const int anode_f = 11;
const int anode_g = 6;
const int cathode_pin = 13; // カソードに接続するArduinoのピン
void display_0() {
digitalWrite(anode_a, HIGH);
digitalWrite(anode_b, HIGH);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, HIGH);
digitalWrite(anode_e, HIGH);
digitalWrite(anode_f, HIGH);
digitalWrite(anode_g, LOW);
}
void display_1() {
digitalWrite(anode_a, LOW);
digitalWrite(anode_b, HIGH);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, LOW);
digitalWrite(anode_e, LOW);
digitalWrite(anode_f, LOW);
digitalWrite(anode_g, LOW);
}
void display_2() {
digitalWrite(anode_a, HIGH);
digitalWrite(anode_b, HIGH);
digitalWrite(anode_c, LOW);
digitalWrite(anode_d, HIGH);
digitalWrite(anode_e, HIGH);
digitalWrite(anode_f, LOW);
digitalWrite(anode_g, HIGH);
}
void display_3() {
digitalWrite(anode_a, HIGH);
digitalWrite(anode_b, HIGH);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, HIGH);
digitalWrite(anode_e, LOW);
digitalWrite(anode_f, LOW);
digitalWrite(anode_g, HIGH);
}
void display_4() {
digitalWrite(anode_a, LOW);
digitalWrite(anode_b, HIGH);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, LOW);
digitalWrite(anode_e, LOW);
digitalWrite(anode_f, HIGH);
digitalWrite(anode_g, HIGH);
}
void display_5() {
digitalWrite(anode_a, HIGH);
digitalWrite(anode_b, LOW);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, HIGH);
digitalWrite(anode_e, LOW);
digitalWrite(anode_f, HIGH);
digitalWrite(anode_g, HIGH);
}
void display_6() {
digitalWrite(anode_a, HIGH);
digitalWrite(anode_b, LOW);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, HIGH);
digitalWrite(anode_e, HIGH);
digitalWrite(anode_f, HIGH);
digitalWrite(anode_g, HIGH);
}
void display_7() {
digitalWrite(anode_a, HIGH);
digitalWrite(anode_b, HIGH);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, LOW);
digitalWrite(anode_e, LOW);
digitalWrite(anode_f, HIGH);
digitalWrite(anode_g, LOW);
}
void display_8() {
digitalWrite(anode_a, HIGH);
digitalWrite(anode_b, HIGH);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, HIGH);
digitalWrite(anode_e, HIGH);
digitalWrite(anode_f, HIGH);
digitalWrite(anode_g, HIGH);
}
void display_9() {
digitalWrite(anode_a, HIGH);
digitalWrite(anode_b, HIGH);
digitalWrite(anode_c, HIGH);
digitalWrite(anode_d, HIGH);
digitalWrite(anode_e, LOW);
digitalWrite(anode_f, HIGH);
digitalWrite(anode_g, HIGH);
}
// setup() は、最初に一度だけ実行される
void setup () {
// ピンを出力モードに設定する
pinMode(anode_a, OUTPUT);
pinMode(anode_b, OUTPUT);
pinMode(anode_c, OUTPUT);
pinMode(anode_d, OUTPUT);
pinMode(anode_e, OUTPUT);
pinMode(anode_f, OUTPUT);
pinMode(anode_g, OUTPUT);
pinMode(cathode_pin, OUTPUT);
}
// loop() は、setup ()実行後、無限に実行される
void loop() {
// cathode_pinにLOWを出力する
digitalWrite(cathode_pin, LOW);
display_0();
delay(500);
display_1();
delay(500);
display_2();
delay(500);
display_3();
delay(500);
display_4();
delay(500);
display_5();
delay(500);
display_6();
delay(500);
display_7();
delay(500);
display_8();
delay(500);
display_9();
delay(500);
}
先ほど関数化は行いましたが、似たような処理がたくさん書いてあり無駄が多いようにも思えます。今度は、関数に引数を与えて、その数字を表示するようにしてみます。引数はLEDに表示する一桁の整数とします。
引数を与えて数字を表示するためには、引数により表示するセグメントを変更する必要があります。switch文を使って引数により処理を切り替えても構いませんが、ここでは、配列を利用してみます。前述の数字と点灯させるセグメントの関係をほぼそのまま利用します。
まず、表のAからGまでを逆に並べます(逆に並べるのは単に趣味の問題です)。その後、●を1、空欄を0と置き換えると、以下のような表になります。GからAまでを2進数7ケタの数字とみなすと、表示する数字に対応する数値ができあがります。
Arduinoソフトウェアが利用しているavr-gccでは、2進数を表記するための拡張が通常のC言語に施されています。0bという接頭辞をつけると、2進数とみなされます。
| 入力 | 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であれば消灯します。
const int anode_a = 12; //アノードに接続するArduinoのピン
const int anode_b = 8;
const int anode_c = 5;
const int anode_d = 3;
const int anode_e = 2;
const int anode_f = 11;
const int anode_g = 6;
const int cathode_pin = 13; // カソードに接続するArduinoのピン
// 数字と表示させるセグメントの関係
const int digits[] = {
0b00111111, // 0
0b00000110, // 1
0b01011011, // 2
0b01001111, // 3
0b01100110, // 4
0b01101101, // 5
0b01111101, // 6
0b00100111, // 7
0b01111111, // 8
0b01101111, // 9
};
// 数字を表示する
void display_number (int n) {
// digits[n]の各ビットを調べて対応するセグメントを点灯・消灯する
digitalWrite(anode_a, digits[n] & 0b00000001 ? HIGH : LOW);
digitalWrite(anode_b, digits[n] & 0b00000010 ? HIGH : LOW);
digitalWrite(anode_c, digits[n] & 0b00000100 ? HIGH : LOW);
digitalWrite(anode_d, digits[n] & 0b00001000 ? HIGH : LOW);
digitalWrite(anode_e, digits[n] & 0b00010000 ? HIGH : LOW);
digitalWrite(anode_f, digits[n] & 0b00100000 ? HIGH : LOW);
digitalWrite(anode_g, digits[n] & 0b01000000 ? HIGH : LOW);
}
void setup () {
pinMode(anode_a, OUTPUT);
pinMode(anode_b, OUTPUT);
pinMode(anode_c, OUTPUT);
pinMode(anode_d, OUTPUT);
pinMode(anode_e, OUTPUT);
pinMode(anode_f, OUTPUT);
pinMode(anode_g, OUTPUT);
pinMode(cathode_pin, OUTPUT);
}
void loop () {
digitalWrite(cathode_pin, LOW);
display_number(0);
delay(500);
display_number(1);
delay(500);
display_number(2);
delay(500);
display_number(3);
delay(500);
display_number(4);
delay(500);
display_number(5);
delay(500);
display_number(6);
delay(500);
display_number(7);
delay(500);
display_number(8);
delay(500);
display_number(9);
delay(500);
}
「もっと関数化する」のスケッチでも構いませんが、display_number()の中に、たくさんdigitalWrite()がならんでいて、もう少し短くできそうです。
まずは、「セグメントをランダムに点灯する」で行ったように、アノードピンを配列に格納します。ただし、今回は、セグメントAから順に格納するので、並び順が異なることに気を付けてください。
| anode_pins[] | 格納されているピン番号 |
|---|---|
| 0 | セグメントAに接続しているArduinoのピン番号 |
| 1 | セグメントBに接続しているArduinoのピン番号 |
| 2 | セグメントCに接続しているArduinoのピン番号 |
| 3 | セグメントDに接続しているArduinoのピン番号 |
| 4 | セグメントEに接続しているArduinoのピン番号 |
| 5 | セグメントFに接続しているArduinoのピン番号 |
| 6 | セグメントGに接続しているArduinoのピン番号 |
次に、「もっと関数化する」のdisplay_number()を眺めるとわかりますが、segment_a、segment_bと進んでいくたびに、論理積をとる数値の1の位置が左に1ビットずつ移動していることがわかります。
表示したい数字をnとしたとき、各セグメントを点灯させるかどうかは、下の表のように決めることができます。「<<」は、左辺の数値を左にnビットシフトするための演算子です。例えば、0b00000100 は1 << 2 と同じです。
| anode_pins[] | 対応するセグメント | 点灯・消灯の判断(その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 |
ここで、一番左の数字(anode_pins[]のインデックス)と一番右の数字が同じことが重要です。これにより、数字のnを表示する際に、anode_pins[i]は、以下のように制御することができます。
digitalWrite(anode_pins[i], digits[n] & (1 << i) ? HIGH : LOW);
最後に、スケッチを示します。
const int anode_pins[] = {12, 8, 5, 3, 2, 11, 6}; // アノードに接続するArduinoのピン
const int cathode_pin = 13; // カソードに接続するArduinoのピン
const int number_of_anode_pins = sizeof(anode_pins) / sizeof(anode_pins[0]);
const int digits[] = {
0b00111111, // 0
0b00000110, // 1
0b01011011, // 2
0b01001111, // 3
0b01100110, // 4
0b01101101, // 5
0b01111101, // 6
0b00100111, // 7
0b01111111, // 8
0b01101111, // 9
};
// 数字(n)を表示する
void display_number (int n) {
for (int i = 0; i < number_of_anode_pins; i++) {
digitalWrite(anode_pins[i], digits[n] & (1 << i) ? HIGH : LOW);
}
}
// setup() は、最初に一度だけ実行される
void setup() {
for (int i = 0; i < number_of_anode_pins; i++) {
pinMode(anode_pins[i], OUTPUT); // anode_pinsを出力モードに設定する
}
pinMode(cathode_pin, OUTPUT); // cathode_pinを出力モードに設定する
}
void loop () {
digitalWrite(cathode_pin, LOW);
display_number(0);
delay(500);
display_number(1);
delay(500);
display_number(2);
delay(500);
display_number(3);
delay(500);
display_number(4);
delay(500);
display_number(5);
delay(500);
display_number(6);
delay(500);
display_number(7);
delay(500);
display_number(8);
delay(500);
display_number(9);
delay(500);
}
[関数化する」[もっと関数化する」[もっともっと関数化する」のスケッチのサイズは以下の通りでした。intをuint8_tに替えるともう少し減るかもしれません。
| スケッチ | コンパイル後のサイズ |
|---|---|
| 関数化する | 1840バイト |
| もっと関数化する | 1438バイト |
| もっともっと関数化する | 1392バイト |
今までは1桁しか利用していませんでしたが、今回は4桁全てを対象に、ランダムにチカチカさせます。ちょっと派手になります。
以下のピン接続を前提にスケッチを書いていきます。小数点のピンも含めて全部使います。
| Arduinoのピン番号 | OSL40562-LBのピン番号 |
|---|---|
| 2 | 1 |
| 3 | 2 |
| 4 | 3 |
| 5 | 4 |
| 6 | 5 |
| 7 | 6 |
| 8 | 7 |
| 9 | 8 |
| 10 | 9 |
| 11 | 10 |
| 12 | 11 |
| 13 | 12 |
「セグメント(1桁)をランダムに点灯する」を4桁の拡張します。カソードを選択する処理を追加するだけです。ただし、点灯しないカソードはHIGHにしておく必要があります(そうしないと、目的とは異なる桁のセグメントも点灯します)。このため、setup()の中で、カソードをHIGHに設定しておき、loop()の中で、選択したアノードをHIGHに、カソードをLOWにします。
const int anode_pins[] = {12, 8, 5, 3, 2, 11, 6, 4}; // アノードに接続するArduinoのピン
const int cathode_pins[] = {7, 9, 10, 13}; // カソードに接続するArduinoのピン
const int number_of_anode_pins = sizeof(anode_pins) / sizeof(anode_pins[0]);
const int number_of_cathode_pins = sizeof(cathode_pins) / sizeof(cathode_pins[0]);
// setup() は、最初に一度だけ実行される
void setup() {
for (int i = 0; i < number_of_anode_pins; i++) {
pinMode(anode_pins[i], OUTPUT); // anode_pinsを出力モードに設定する
}
for (int i = 0; i < number_of_cathode_pins; i++) {
pinMode(cathode_pins[i], OUTPUT); // cathode_pinを出力モードに設定する
digitalWrite(cathode_pins[i], HIGH);
}
}
void loop () {
int anode_pin = anode_pins[random(number_of_anode_pins)]; // 点灯するピンを選択する
int cathode_pin = cathode_pins[random(number_of_cathode_pins)]; // 点灯するピンを選択する
digitalWrite(cathode_pin, LOW);
digitalWrite(anode_pin, HIGH);
delay(10);
digitalWrite(cathode_pin, HIGH);
digitalWrite(anode_pin, LOW);
}
ひとつのセグメントの点灯時間は少し短めにしてみました。
今までは同時にはひとつのセグメントしか点灯させていませんでした。しかし、「セグメント(4桁)をランダムに点灯する」のスケッチを動作させると、同時には一つのセグメントしか点灯させていないにもかかわらず、複数のセグメントが同時に点灯しているように見えたと思います。ここでは、ダイナミック点灯の原理を簡単に説明した後、以下の図のように各桁の異なるセグメントを同時に点灯させてみます。
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のピン番号 | OSL40562-LBのピン番号 |
|---|---|
| 2 | 1 |
| 3 | 2 |
| 4 | 3 |
| 5 | 4 |
| 6 | 5 |
| 7 | 6 |
| 8 | 7 |
| 9 | 8 |
| 10 | 9 |
| 11 | 10 |
| 12 | 11 |
| 13 | 12 |
各桁で表示させるセグメントは一つなので、turn_on_pins[]という配列に、各桁で点灯させるピンに接続しているArduinoのピン番号を書いてあります。
const int anode_pins[] = {12, 8, 5, 3, 2, 11, 6, 4}; // アノードに接続するArduinoのピン
const int cathode_pins[] = {7, 9, 10, 13}; // カソードに接続するArduinoのピン
const int number_of_anode_pins = sizeof(anode_pins) / sizeof(anode_pins[0]);
const int number_of_cathode_pins = sizeof(cathode_pins) / sizeof(cathode_pins[0]);
// 各桁で点灯するセグメント。右の桁から。
const int turn_on_pins[] = {2, 11, 12, 3};
// setup() は、最初に一度だけ実行される
void setup() {
for (int i = 0; i < number_of_anode_pins; i++) {
pinMode(anode_pins[i], OUTPUT); // anode_pinsを出力モードに設定する
}
for (int i = 0; i < number_of_cathode_pins; i++) {
pinMode(cathode_pins[i], OUTPUT); // cathode_pinを出力モードに設定する
digitalWrite(cathode_pins[i], HIGH);
}
}
void loop () {
for (int i = 0; i < number_of_cathode_pins; i++) {
digitalWrite(cathode_pins[i], LOW); // 表示桁を選択
digitalWrite(turn_on_pins[i], HIGH); // 表示するセグメントのアノードをHIGHにする
delay(3); // 明るさ調整用。点滅しない程度に。
digitalWrite(turn_on_pins[i], LOW); // アノードをすべてLOWにする
digitalWrite(cathode_pins[i], HIGH); // 選択したカソードをHIGHにする
}
}
0から9999までの数字を順に表示させてみます。
「1桁の数字を表示する」と「4桁を同時に点灯する」の考え方を融合します。
以下のピン接続を前提にスケッチを書いていきます。小数点のピンも含めて全部書いてありますが、全てのピンを使っているわけではありません。
| Arduinoのピン番号 | OSL40562-LBのピン番号 |
|---|---|
| 2 | 1 |
| 3 | 2 |
| 4 | 3 |
| 5 | 4 |
| 6 | 5 |
| 7 | 6 |
| 8 | 7 |
| 9 | 8 |
| 10 | 9 |
| 11 | 10 |
| 12 | 11 |
| 13 | 12 |
const int anode_pins[] = {12, 8, 5, 3, 2, 11, 6}; // アノードに接続するArduinoのピン
const int cathode_pins[] = {7, 9, 10, 13}; // カソードに接続するArduinoのピン
const int number_of_anode_pins = sizeof(anode_pins) / sizeof(anode_pins[0]);
const int number_of_cathode_pins = sizeof(cathode_pins) / sizeof(cathode_pins[0]);
const int digits[] = {
0b00111111, // 0
0b00000110, // 1
0b01011011, // 2
0b01001111, // 3
0b01100110, // 4
0b01101101, // 5
0b01111101, // 6
0b00100111, // 7
0b01111111, // 8
0b01101111, // 9
};
// 1桁の数字(n)を表示する
void display_number (int n) {
for (int i = 0; i < number_of_anode_pins; i++) {
digitalWrite(anode_pins[i], digits[n] & (1 << i) ? HIGH : LOW);
}
}
// アノードをすべてLOWにする
void clear_segments() {
for (int j = 0; j < number_of_anode_pins; j++) {
digitalWrite(anode_pins[j], LOW);
}
}
void display_numbers (int n) {
for (int i = 0; i < number_of_cathode_pins; i++) {
digitalWrite(cathode_pins[i], LOW);
display_number(n % 10); // 最後の一桁を表示する
delay(1);
clear_segments();
digitalWrite(cathode_pins[i], HIGH);
n = n / 10; // 10で割る
}
}
// setup() は、最初に一度だけ実行される
void setup() {
for (int i = 0; i < number_of_anode_pins; i++) {
pinMode(anode_pins[i], OUTPUT); // anode_pinsを出力モードに設定する
}
for (int i = 0; i < number_of_cathode_pins; i++) {
pinMode(cathode_pins[i], OUTPUT); // cathode_pinを出力モードに設定する
digitalWrite(cathode_pins[i], HIGH);
}
}
void loop () {
for (int i = 0; i < 10000; i++) {
display_numbers(i);
}
}
「4桁の数字を表示する(カウンタ)」では、4桁の数字を表示しましたが、実は、あまりいい構造になっていません。ダイナミック点灯では常時LEDの表示を行う必要がありますが、このプログラムでは、数字を表示する(display_numbers())を呼び出したときにしか表示していません。このため、たとえば、loop()の中でdelay()を入れてしまうと、一瞬数字が表示されてその後消えてしまいます。delay()を使うなという話もありますが…
ここでは、表示する数字の設定と数字の表示を分離します。数字の設定は任意のタイミングで実施し、表示はloop()とは独立に割り込みベースで行います。
表示方法は同じなので、割り込みの設定部分を以下に示します。
割り込みベクタについては、tone()の内部構造のページを参照してください。
以下のピン接続を前提にスケッチを書いていきます。小数点のピンも含めて全部書いてありますが、全てのピンを使っているわけではありません。
| Arduinoのピン番号 | OSL40562-LBのピン番号 |
|---|---|
| 2 | 1 |
| 3 | 2 |
| 4 | 3 |
| 5 | 4 |
| 6 | 5 |
| 7 | 6 |
| 8 | 7 |
| 9 | 8 |
| 10 | 9 |
| 11 | 10 |
| 12 | 11 |
| 13 | 12 |
TIMER2を利用したスケッチです。tone()とデジタルピンの3番と11番へのPWM出力に干渉してしまいます。
このスケッチはレジスタを直接触っているので、機種依存です。Arduino Unoでの動作は確認しましたが、他のArduinoでは動かないこともあります。Arduinoはプロセッサの違いを隠してくれているので通常はこのようなことは起こりません。レジスタを直接触るなどは、あまりよくない方法かもしれません。タイマ割り込みを扱うMsTimer2などのライブラリがあるのでそちらを使うのがいいと思います。
set_numbers()は、異なるコンテキストで共有する変数への書き込みなので、本質的には割り込みを禁止にしておくのがいいと思います。
const int anode_pins[] = {12, 8, 5, 3, 2, 11, 6}; // アノードに接続するArduinoのピン
const int cathode_pins[] = {7, 9, 10, 13}; // カソードに接続するArduinoのピン
const int number_of_anode_pins = sizeof(anode_pins) / sizeof(anode_pins[0]);
const int number_of_cathode_pins = sizeof(cathode_pins) / sizeof(cathode_pins[0]);
int numbers_to_display = 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
};
// 1桁の数字(n)を表示する
void display_number (int n) {
for (int i = 0; i < number_of_anode_pins; i++) {
digitalWrite(anode_pins[i], digits[n] & (1 << i) ? HIGH : LOW);
}
}
// アノードをすべてLOWにする
void clear_segments() {
for (int j = 0; j < number_of_anode_pins; j++) {
digitalWrite(anode_pins[j], LOW);
}
}
void display_numbers () {
int n = numbers_to_display; // number_to_displayの値を書き換えないために変数にコピー
for (int i = 0; i < number_of_cathode_pins; i++) {
digitalWrite(cathode_pins[i], LOW);
display_number(n % 10); // 最後の一桁を表示する
delayMicroseconds(100);
clear_segments();
digitalWrite(cathode_pins[i], HIGH);
n = n / 10; // 10で割る
}
}
void set_numbers(int n) {
numbers_to_display = n;
}
// setup() は、最初に一度だけ実行される
void setup() {
for (int i = 0; i < number_of_anode_pins; i++) {
pinMode(anode_pins[i], OUTPUT); // anode_pinsを出力モードに設定する
}
for (int i = 0; i < number_of_cathode_pins; i++) {
pinMode(cathode_pins[i], OUTPUT); // cathode_pinを出力モードに設定する
digitalWrite(cathode_pins[i], HIGH);
}
// f = クロック周波数 / ( 2 * 分周比 * ( 1 + 比較レジスタの値))
// 分周比=32, 比較レジスタの1値=255 -> f = 16000000 / (2 * 32 * 256) = 976 Hz
OCR2A = 255; // 255クロックごとに割り込みをかける
TCCR2B = 0b100; // 分周比を32に設定する
bitWrite(TIMSK2, OCIE2A, 1); // TIMER2を許可する
}
void loop () {
for (int i = 0; i < 10000; i++) {
set_numbers(i);
delay(1000);
}
}
ISR(TIMER2_COMPA_vect) {
display_numbers();
}
写りが悪いですが、最後のプログラムが動作している様子です。
Arduino Uno/Arduino 1.6.5
メニューを表示するためにJavaScriptを有効にしてください。