9ピンで、9桁の7セグLEDを点灯する

更新履歴

はじめに

本情報のネタは、rockhillさんの掲示板「ワンチップマイ コンでGO!」のNo.494にあった、ノンノさんの発言「9桁7SEG-LED表 示」が発端です。恥ずかしながら、私の知識ではノンノさんの解説が理解できず、 自分で考えることにしました。ノンノさんの説明を理解していないので、ノンノ さん(もしくはオリジナル)の方法とは、異なるかもしれません。

謝辞

こんなに面白く、実用的なネタを提供してくれたノンノさん、その情報共有の場 を提供してくれたrockhillさんに感謝いたします。また、このページの図を書く ために使ったNcEditorの作者である、NCTOOLさんに感謝致します。最後に、この ページを見てくださった全ての皆様に感謝いたします。

免責

これは、思考の結果であり、実際の回路を作成して確認したわけでは有りません。 したがって、うまくいかないかもしれません。うまくいったら、是非ご一報下さ い。

最初のステップ

当たり前の話ですが、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と無接続で消灯」の組み合わせをうまく使いこなすこと が、重要なヒントとなります。

4ピンで12個のLEDを点灯する

いきなり9ピンで9桁の7セグLEDの光らせ方を説明するのは難しいので、4ピン で12個のLEDを自由に光らせます。4ピンでは、普通にマトリックを組んでも、2× 2=4つしか点灯できません。しかし、12個のLEDを光らせることができます。

IOを正多角形上に配置する

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つの回路図を重ねてみる

頭の中で、さきほどの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個分の電流しか流れ込まず、点灯数に関係なく同じ明る さになります。

いよいよ9ピンで9桁の7セグLEDを点灯する

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)ですね。