本情報のネタは、rockhillさんの掲示板「ワンチップマイ コンでGO!」のNo.494にあった、ノンノさんの発言「9桁7SEG-LED表 示」が発端です。恥ずかしながら、私の知識ではノンノさんの解説が理解できず、 自分で考えることにしました。ノンノさんの説明を理解していないので、ノンノ さん(もしくはオリジナル)の方法とは、異なるかもしれません。
当たり前の話ですが、LEDを光らすためには、アノードからカソードに電流を 流します。簡単に言えばアノードに+、カソードに−をつなげば光ります(普通 は、電流制限抵抗を直列につなぎますが、説明がややこしくなるので、以降の説 明では省きます)。ここで、下記の図のように、2本のマイコンのIO端子(IO-0, IO-1)に逆方向につないだ二つのLED(LED01, LED10)を考えます。
まず、LED01を点灯させるには、IO-0をHi、IO-1をLowにすればよいことが判 ります。逆に、LED10を点灯させるには、IO-1をHi、IO-0をLowにします。 マイコンの端子にはHi、Low、そして無接続状態(ハイインピーダンス)の 3種類の出力があります。この全ての組み合わせを表にすると、以下の通りです。
| IO-0がLow | IO-0がHi | IO-0が 無接続 | |
| IO-1がLow | 消灯 | LED01が点灯 | 消灯 |
| IO-1がHi | LED10が点灯 | 消灯 | 消灯 |
| IO-1が 無接続 | 消灯 | 消灯 | 消灯 |
2ピンで2個のLEDを光らすことができましたが、まったくうれしくありません ね。でも、この表の「Hiと無接続で消灯」の組み合わせをうまく使いこなすこと が、重要なヒントとなります。
いきなり9ピンで9桁の7セグLEDの光らせ方を説明するのは難しいので、4ピン で12個のLEDを自由に光らせます。4ピンでは、普通にマトリックを組んでも、2× 2=4つしか点灯できません。しかし、12個のLEDを光らせることができます。
4ピン(IO-0, IO-1, IO-2, IO-3)を正方形に配置し、IO-0から3つの発光ダイオー ドを他の3つのピンに向けて結線します(下記の図を参照)
ここで、IO-0をHiにします。残りの3ピン(IO-1、IO-2、IO-3)をLowにするか 無接続にするかで、3つのLED(LED01, LED02, LED03)を点灯もしくは消灯させるこ とができます。ここでのポイントは、「Hiと無接続で消灯」です。無接続にする ことで、この後で記述するほかの9個のLEDへの影響をなくしています(Hiにでも消 灯するが他のLEDが光る)。また、上記の図は回路図っぽくない図ですが、正多角 形上にI/Oピンを配置することが、私の理解の第一歩でした。
今度は、IO-1に着目してから3つの発光ダイオードを他の3つのピンに向けて 結線します(下記の図を参照)。
同じように、IO-1をHiにします。残りの3ピン(IO-0、IO-2、IO-3)を Lowにするか無接続にするかで、3つのLED(LED10, LED12, LED13)を点灯もしくは 消灯させることができます。
あとは、IO-2とIO-3も同じように考えて、各々3つのLEDを点灯もしくは消灯 させることができます。
LEDの番号は、最初アノードに繋がっているIOピンの番号、次がカソードに繋 がっているIOピンの番号です。例えばLED12は、IOピン1番にアノードが、IOピン 2番にカソードに繋がっています。なんとなく、12個点灯・消灯制御が見えてきま したか?
頭の中で、さきほどの4つの回路図を重ねてみてください。図を書くのはしん どいので、下記の図ではちょうど最初の図の二つのダイオードペアを赤い線で表して います。
また、2つ以上のLEDが直列して電流が流れる(回り込む)ことはありません。 図を書けば判るのですが。ある端子がLowになっていると、そこに直接繋がってい るLEDの電圧降下が、2つ以上のLEDを介して流れる電流の電圧降下より小さいため、 必ず一つのLEDしか光りません。例えば、IO-0がHiでIO-1がLowの場合、LED02から LED21に電流が流れそうですが、並列に繋がっているLED01の電圧降下の方が小さ いため、LED02とLED21の点灯に必要な電圧に達しません(ただし同じ特性のLEDの 場合)。
実際の回路では電流制限抵抗が必要なので、各頂点はこんな感じになると思い ます。
各IO端子から電流制限抵抗を入れてLEDのアノードに繋ぎ、カソードはIO端子
に直結します(逆でも良い)。ここでは、IO-0だけ書いてありますが、他も同じ
です。点灯する数によって明るさが変化するので、ちょっと工夫が必要です。
一回につき、ひとつのLEDしか点灯させないか、点灯間隔をLEDの点灯数に応じて
変化させます。
2008/04/07追記:カソード側に抵抗を入 れれば、点灯個数に関係なく同じ明るさになることにきがつきましたので、訂 正します。 各IO端子からLEDのアノードに繋ぎ、カソードはほかの 端子から結線されるLEDをまとめて、電流制限抵抗を介してIO端子に直結しま す。こうすると、IOピンがLowになったとき、ひとつだけのピンがHiになって いるはずなので、LED1個分の電流しか流れ込まず、点灯数に関係なく同じ明る さになります。
4ピンで12個のLEDを点灯する方法を一般化すると、以下のようになります。
後は、IO0からIOnまでのピンを順にHiにしていくダイナミック点灯を行うこ とで、Nピンで、N×(N-1)のLEDを、任意に点灯・消灯できます 。
9桁の7セグLEDには、8個×9桁=72個のLEDがあるので、9ピンあればよいこと になります。一般的な7セグLEDは、「アノードコモン」もしくは「カソードコモ ン」のように、どちらかの電極が共通配線になっています。これを、ちょうど正 多角形の頂点に見立てればよいわけです。例えば、アノードコモンの場合、
すると、こんな感じです。LEDは省略していますが、勘弁してください。
IO-0から出ている赤い直線が、0桁目の7セグLEDに相当します。IO-0側がアノー ド、他方がカソードです。同様にして、1桁目、2桁目...8桁目の7セグLEDのアノー ドをIO-1,IO-2,...IO-8に結線し、カソードはアノードと結線していないIOピ ンに結線します。
これで、9ピンで9桁の7セグLEDを点灯できました。ふう、疲れた。
今までは、LEDを正多角形の辺と対角線上に並べて考えてきました。これでは、 実際の配線にそぐいません。何とか、長方形に並べる必要があります。そのため には、先に示した LED01, LED02, ... LED98を、長方形にマッピングする必要が あります。この良い方法は、私もわかりませんでした。
答えは、ノンノさんより教えていただいた、元ねた にありました。
この配線を良く見ると、先に示した以下のルール、
が守られているのがわかると思います。
ポイントは、斜めの「float bit」と書かれた赤いラインです。長方形状に並 べ、縦と横をそれぞれIO-0, IO-1,IO-2,...IO-8に結線(原典ではRA0, RA1,...RA7)に結線すると、どうしてもアノードとカソードが同じIOピンにつなが るところが出てきてしまいます。これを、float bitと呼ばれる線で繋ぎ、 IO-8(RB0)に結線しているのです。このように結線すると、同じパターンで処理することができます。
avr_gccだとこんな感じたと思います。コンパイルはしてみましたが、実行は していません。あくまでも、説明のためのコードです。 本当は、ちらつきを抑えるために、完全に消灯させる時間が必要です。 PICのアセンブラは忘れてしまいま したので、原典のソースはまったく読んでいません。不具合があってもご勘弁を。
#include <stdint.h>
#include <stdlib.h>
#include <avr/io.h>
#include <util/delay_basic.h>
const uint8_t SEG_LED_PATTERN [] = {
0b11000000,
0b11111001,
0b10100100,
0b10110000,
0b10011001,
0b10010010,
0b10000010,
0b11111000,
0b10000000,
0b10010000,
0b10001000,
0b10000011,
0b11000110,
0b10100001,
0b10000110,
0b10001110,
};
uint8_t segdata[9]; // 8セグメント9桁を表示させるためのデータ
void display(){
uint8_t digit;
DDRB &= 0xFE; // float lineを入力、つまり無接続(ハイインピーダンス)に設定する。
PORTB &= 0xFE; // float lineをlowにする。
// 0-7桁の出力
digit = 1;
int i;
for(i=0;i<8;i++){
PORTA = digit; // アノード側をHiに、残りをLowにする。
// Hiにするポートを出力に設定する。、
// 点灯する(Lowにする)ポートを出力に設定する。
// 消灯するポートを入力、つまり無接続(ハイインピーダンス)に設定する。
uint8_t out_data = SEG_LED_PATTERN[segdata[i]] ;
DDRA = digit| out_data;
// 必要ならfloat lineをLowにする。
if(out_data & digit){
DDRB |= 1; // float lineを出力にする。
} else {
DDRB &= 0xFE; // float lineを入力、つまり無接続(ハイインピーダンス)に設定する。
}
_delay_loop_2(10000); //ちょっと待つ
digit = digit << 1;
}
// 8桁目を出力する。
PORTB |= 1; // 8桁目のアノードをHiにする。
DDRB |= 1; //
// 8桁目はfloating lineをつかわない。
PORTA = 0;
// 点灯する(Lowにする)ポートを出力に設定する。
// 消灯するポートを入力、つまり無接続(ハイインピーダンス)に設定する。
DDRA = SEG_LED_PATTERN[segdata[8]];
_delay_loop_2(10000); //ちょっと待つ
//全部消灯
DDRA = 0;
DDRB &= 0xFE;
PORTB &= 0xFE;
}
この方法を思いついたきっかけは、「ダイナミック点灯で、遊んでいるピンを 有効活用できないか?」でした。N本のI/Oピンをアノードコモンに結線した場合、 1ピンだけがHiで、残りはlowで遊んでいます。この遊んでいるピンをカソードに 接続できないか考えました。
もう一つのヒントは、むかし師匠に教わった「1ピンで2つのLEDを点灯する 方法」でした。この方法については、ちょっと書く気力がありません...
もっと数学的に言うと、「IOピンがN本ある。各IOピンは、Hi、Low、無接続 の3状態を取りうる。この時、HiとLowを1ピンづつ、残りを無接続とする選び方 (ダイオードは方向性があるので)は何通りか。」という問題となります。答え は、nP2 = N×(N-1)ですね。