7 セグメント LED には、各セグメントの点灯/消灯を制御するのに夫々 1 本の制御線が必要で、セグメント全体でキャラクタ一桁を表示するには合計 7 本の制御線が必要になります。仮にデジタル温度計の温度表示を、整数部 2 桁、小数点以下 1 桁の 0.0 ℃ から 99.9 ℃ まで表示可能にしたいのであれば、普通に考えると 7 セグメント × 3 桁 = 21 本もの制御線が必要になってしまいます(Fig.1 )。ちょっとしたデジタル温度計を作りたいだけなのに、28 ピンなんてごついチップを使うのはガッカリ(´・ω・`)ですよね。
少ない制御線でいかにして 7 セグメント LED を駆動するのか? 電子工作の先人たちは様々な方法でこれを実現してきました(大袈裟)
この記事で登場する 7 セグ LED は、カソード コモン品(=GND がパッケージ内で共通線になっている)としています。電源が共通線のアノード コモン品もあるので、パーツ屋さんで購入する際には注意しましょう。また、解説のため、電流制限抵抗などの部品は盛大に省略しています。実装される際には、各部品の定格内で動作するよう、適当な追加を行ってください。
全桁の全セグメントについて、それぞれ点灯/消灯の制御をおこなった場合、点灯すべきセグメントは常に点灯するようになっていますが、この表示方法はスタティック(static; 静的)点灯と呼ばれます。表示すべきセグメントは常に点灯状態のまま変化しない、つまり静的だからです。
ここで、最初に 1 桁目だけを点灯し、次に 2 桁目だけを点灯、最後に 3 桁目だけを点灯して、そしてまた 1 桁目だけを点灯する...という動作を高速に行うと、人間の目には残像によって、全ての桁が点灯しているように見えます。数値を表示している桁は常に一つで、他の桁は全て消灯しています。これがダイナミック(dynamic; 動的)点灯と呼ばれる方法です。この方法では、表示すべきセグメントを制御する 7 本の制御線と、どの桁を表示するのか制御する桁数分だけの制御線があればよいことになります(Fig.2 )。つまり、3 桁の数値を表示したいのであれば、7 + 3 = 10 本の制御線があればよいのです。素直に全部結線した場合の半分になりました!
桁数 n の 7 セグ LED を制御したい場合、スタティック点灯では 7n 本の制御線が必要になりますが、ダイナミック点灯では 7+n 本の制御線で済むので、必要なポート数を劇的に減らすことができます。特に、表示したい桁数が多いときにはこうか は ばつぐん だ!
ダイナミック点灯では、桁数が増えても必要な信号線の増加を抑えられるメリットがありますが、表示がチラつくという問題が出てきます。特に桁数が多い場合に顕著で、表示が波打っているように見えたり、各桁の実質の点灯時間が短くなることから、表示が暗くなるデメリットがあります。桁数が多すぎる場合には、上位桁と下位桁で制御回路を更に分けるなどの対策が必要になってきますが、これらのデメリットを無視できるのなら非常に有用なテクニックには違いありません。
7 セグメント LED では、各セグメントを各個に自由に点灯/消灯させることができますが*1、人間にとって意味のある点灯パターンというのは限られています。特に、今回の温度計のような例であれば、7 セグメント LED に 0 から 9 までの数字シンボルを表示できれば十分ですから、点灯パターンは高々 10 通りしかありません。ここで、24=16
であることから、0 から 9 までの数値を表示したいのであれば、高々 4 本の制御線があれば事足りることがわかります。
この制御線のオン/オフの組み合わせに応じて、点灯すべきセグメントを適切に制御してくれるのがドライバ IC と呼ばれるものです。ドライバ IC には、74HC4511 や 74LS47 などがあります。いずれも、4 本の信号線で 7 セグメント LED に 0 から 9 までの数字を表示できます(Fig.3 )。
ダイナミック点灯方式を用いて、例えば 8 桁の数値を表示したい場合を考えます。そのまま考えると、追加で 8 本の制御線があればよさそうですが、もっと減らせないでしょうか? ダイナミック点灯では、ある瞬間において点灯している桁は基本的には常に一つだけですので、先のドライバ IC のように、制御線のオン/オフの組み合わせで 1 桁目から 8 桁目を指定できればよいと考えられます。そんな場合、3-8 デコーダ IC というピッタリな IC があります。これを利用すれば、3 本の信号線で 8 通りの出力を制御できるので、8 桁の数値表示でも、たった 3 本の制御線を追加すればよいことになります(Fig.4 )。
説明のため簡略化してありますが、3-8 デコーダとセグメント LED のコモン間にはトランジスタなどのドライブ回路が必要です。直結した場合、3-8 デコーダ IC のシンク電流の制限に掛かります。
…と、このように、必要な制御線の本数を節約する方法が色々とあるのですが、一方で、マイコンの外に追加で IC が増えてしまう、というデメリットもあります。マイコンの出力ポートが足りない!→ポート数を増やしたいけど、マイコンのランクを上げるとお値段が…→ドライバ IC/デコーダ IC を使って出力ポートを節約しよう→ドライバ IC の追加コストの方が高かった…なんてありそうな気もします。そこら辺はコストやら、基盤の空きスペースやらと相談で。
さっそくダイナミック点灯による 3 桁の 7 セグメント LED の制御をやってみます。手持ちがないので、ドライバ IC もデコーダ IC も使わず、PIC16F819 だけを使用しました。実行すると、000、001、012、123、234、...、DEF、EF0、F01、...と表示を繰り返します。
#include <htc.h> __CONFIG ( FOSC_INTOSCIO // 内蔵オシレータ & WDTE_OFF // WDT 無効 & PWRTE_OFF // PWRT 無効 & MCLRE_OFF // MCLR 使用しない & BOREN_OFF // BOD 無効 & LVP_OFF // Low-Voltage Programming 使用しない & CPD_OFF // Data memory code protection 無効 & DEBUG_OFF // In-Circuit Debugger 使用しない & CP_OFF // Program Memory code protection 無効 ); // 7 セグメント LED の点灯パターンを定義 static int pattern_7seg [] = { //abcdefg. 0b11111100, // 0 0b01100000, // 1 0b11011010, // 2 0b11110010, // 3 //abcdefg. 0b01100110, // 4 0b10110110, // 5 0b10111110, // 6 0b11100100, // 7 //abcdefg. 0b11111110, // 8 0b11110110, // 9 0b11101110, // A 0b00111110, // B //abcdefg. 0b10011100, // C 0b01111010, // D 0b10011110, // E 0b10001110, // F }; static void ResetTimer0 () { TMR0 = -200; } static int timeout; // Timer0 のタイムアウトをカウントする static void ResetTimer0_TimeoutCount () { timeout = -900; } static int seg_num = 0; // 表示する値 static int seg_idx = 1; // 表示中のセグメント // メインルーチン void main () { // PORTA の入出力設定 TRISA = 0b00000001; ADCON1bits.PCFG = 0b1110; // PORTB の入出力設定 TRISB = 0b00000000; // Timer0 の準備 OSCCONbits.IRCF = 0b111; // 内蔵クロックを 8MHz に設定 // OPTION REGISTER OPTION_REGbits.T0CS = 0b0; // Timer0 は CLKO(=Fosc/4) 基準で動作 OPTION_REGbits.PSA = 0b0; // プリスケーラを Timer0 にアサイン OPTION_REGbits.PS = 0b001; // プリスケーラ比を 1:4 に設定 TMR0 = 0; // Timer0 を一応クリア INTCONbits.TMR0IE = 0b1; // Timer0 の割り込みを許可 INTCONbits.GIE = 0b1; // グローバル割り込み許可 // メインループ ResetTimer0 (); ResetTimer0_TimeoutCount (); while (1) { switch (seg_idx) { case 1: RA3 = 0; PORTB = pattern_7seg [seg_num & 0xf]; RA1 = 1; break; case 2: RA1 = 0; PORTB = pattern_7seg [(seg_num >> 4) & 0xf]; RA2 = 1; break; case 3: RA2 = 0; PORTB = pattern_7seg [(seg_num >> 8) & 0xf]; RA3 = 1; break; } } } // 割り込みルーチン static void interrupt isr (void) { // Timer0 による割り込み if (INTCONbits.TMR0IF) { // 表示するセグメントを切り替える seg_idx = (seg_idx % 3) + 1; // タイムアウトしたので表示する数値を更新 if (!timeout++) { seg_num = (seg_num << 4) | (seg_num + 1) & 0xf; ResetTimer0_TimeoutCount (); } INTCONbits.TMR0IF = 0; // 割り込みフラグをクリアするのはプログラムの仕事 ResetTimer0 (); // タイマを再スタート } }
...と、こんな感じです。内蔵クロックなので正確な時間はアレですが、計算上は 400 マイクロ秒ごとに桁をくるくる回してダイナミック表示をしています。この状態ではチラツキを感じることはありません。
R = (5.0V - 2.1V) ÷ 20mA = 145 Ω
。手持ちが無かったので 1kΩ で。セグメントあたり 3mA。VBE ÷ 10kΩ = 70μA
。(5.0V - VBE) ÷ 10kΩ = 430μA
。TR の増幅率を 200 とすれば、IC = ( 430uA - 70uA )×200 = 72mA
。RB<7:0> で点灯させたいセグメントを H レベルにして制御しています。また、RA<3:1> で、どの桁を表示するかトランジスタ ドライバを駆動しています。たった、これだけの回路ですが、なかなかアツイですね。
寄せられたコメント (全 2 件中、最新 5 件まで表示しています)
Vdd と Vss の件と、カソード コモンであること(と、回路図を追加したり色々)を追記しておきました
ツッコミありがとうございます(=゚ω゚)ノ
VDDの位置が逆になってはいませんか?
ところで7セグは回路的にカソードコモンだと思いますが初心者的にはその辺も明示
してほしいと思います。
プログラムはの公開は大変参考になります。ありがとうございます。
これからも見させて頂いて勉強させていただきますので宜しくお願いします。