Renesas Synergy™

FAQ 1006588 : LEDのドライブ方法(78K0/Kx2,78K0S/Kx1+)

[はじめに]
マイコンの最も簡単な出力手段としてはLEDによる表示があります。LEDによる表示はLCDやVFD(蛍光表示管)に比べて専用の表示コントローラが不 要で比較的簡単に使用でき、マイコンの動作電源程度で動作し、小型で小規模なものでも使えることから幅広く使われています。

 

コーヒーブレイク

最近は、青色も出回り、3原色が出揃ったことでいろんなところで見かけるようになったLED。昨年のクリスマスの電飾では発光がきれいだ、寿命が長いとか省エネである等の理由でLEDの比率がかなり高くなったのではないかと思われます。
このように、急速に身の回りに増えてきたLEDですが、発光する色によって駆動電圧が異なります。古くからある赤色LEDは比較的低い電圧(1.5~1.8V)で発光します。それに対して、青色LEDは3V前後の電圧が必要です。


[ポートによる基本的なドライブ方法]
マイコンでLEDをドライブする際には普通はポート機能を使用します。基本的な接続の方法としては、下の図の左に示すようにポートとグランドの間に接続 し、ポートから電流を吐き出してLEDドライブする方法と右に示すようにポートと正電源の間に接続して、ポートが電流を吸い込んでドライブする方法があり ます。どちらを使用するかはポートのドライブ能力と要求される輝度を得るために必要な電流で判断します。

 

コーヒーブレイク

  上の接続方法では、LEDとシリーズに抵抗が接続されていますが、ポートを過電流から保護するための働きをします。LEDは電圧に対して流れる電流が大き く変化し、ある電圧までは殆ど電流は流れず、それ以上になると急激に電流が大きくなります。その傾向を右側のグラフに示します。
マイコンのポートは基本的に0/1のデジタル信号を出力するので、例えば電流を5mA流してもポートでの電圧降下は1V以下と規定しています。特性がこの 半分程度まで変動したとして、0.5Vしか電圧が下がらなかった場合にはその分がLEDにかかり、電流が10倍程度になってしまいます。これを避けるため に抵抗で電流制限します。

 


[プログラムによる制御]
上記の接続でポート40を使用したとすると、LEDを点灯するには左側の接続例ではポートに1を右側の接続例ではポートに0を出力します。1個だけの制御を行なう場合には、ビット操作命令を使うのが簡単です。

  • 左側の接続例での点灯処理    SET1  P4.0 (Cでは P4.0=1;)
  • 右側の接続例での点灯処理    CLR1  P4.0 (Cでは P4.0=0;)

このように、ポートを操作することでLEDを点灯させることができます。(下記の図のLED駆動例を参照)

 
[ドライブ電流の増大法]
LEDを点灯させるのに必要な電流が大きくて、マイコンで直接ドライブできない場合にはトランジスタを追加します。通常のLEDの電流は大きくても100mA以下で十分なので、低周波増幅用の小信号用トランジスタで十分です。

 
この接続例ではトランジスタにより信号が反転されるので、ポートを1にすることで、LEDを点灯します。

[多くのLEDのドライブ例]
上の例では、1個のLEDを点灯するためにポートを1つ使用しました。しかし、使用するLEDが多くなると各LEDにポートを割り当てることができなくな ります。そこで、マトリクスを組んでダイナミック表示を行なう方法が考えられます。LEDの電流が2mA以下で同時には4個までしか点灯しないとすると下 記の構成が考えられます。

ちょっと注意

ポートのドライブ能力は78K0S/Kx1+ではハイ側は5mA、ロウ側は10mAとハイ側よりロウ側が大きくなっています。78K0/Kxでも値は異な りますが、同じようにロウ側のドライブ能力が大きくなっています。LEDに流す電流は弱い方のドライブ能力で決定します。


[プログラム例1]
この接続では、点灯させたいLEDに対して上側のポートに1を設定し、下側のポートに0を設定します。
例えば、L1のLEDを点灯させる場合には、P40を1、P41~P43を0に設定し、P44を0、P45~P47を1に設定します。

        MOV     P4,# 11100001b  (Cでは  P4=0b11100001;)

一つひとつのLEDについてイミディエート(即値)で値を設定するのは面倒なので、通常は点灯させるLED(L1~L16)に対応したテーブルを準備しておき、テーブル参照で制御します。以下のテーブルは0なら消灯、1~16なら対応したLEDを点灯します。

LEDDATA:
DB 11110000b ; 消灯
DB 11100001b ; LED1点灯データ
DB 11100010b ; LED2点灯データ
DB 11100100b ; LED3点灯データ
DB 11101000b ; LED4点灯データ
DB 11010001b ; LED5点灯データ
DB 11010010b ; LED6点灯データ
DB 11010100b ; LED7点灯データ
DB 11011000b ; LED8点灯データ
DB 10110001b ; LED9点灯データ
DB 10110010b ; LED10点灯データ
DB 10110100b ; LED11点灯データ
DB 10111000b ; LED12点灯データ
DB 01110001b ; LED13点灯データ
DB 01110010b ; LED14点灯データ
DB 01110100b ; LED15点灯データ
DB 01111000b ; LED16点灯データ

このテーブルを用いて、LEDのどれかひとつを点灯させるプログラムの例を以下に示します。このプログラムでは点灯させたいLEDをXレジスタで指定します。

LEDON:
PUSH HL
MOV A,#LOW LEDDATA ; テーブルアドレスの下位
ADD A,X ; オフセットを加算
MOV L,A
MOV A,#HIGH LEDDATA ; テーブルアドレスの上位
ADDC A,#0 ; 上位への桁上げ
MOV H,A ; HLをテーブルの読み出しポインターに
MOV A,[HL] ; データを読み出し
MOV P4,A ; ポートに出力
POP HL
RET


[プログラム例2]
この処理と同じことをC言語で記述する場合には、LEDの点灯データを配列として準備しておき、点灯させたいLEDを配列の番号として配列から読み出すだけです。以下にそのための記述例を示します。
配列は17個の要素(unsigned char:バイトデータ)から構成されたLEDSTATUSで定義しています。この構造の配列をdispと言う名前で定義しています。

typedef unsigned char LEDSTATUS[17];
const LEDSTATUS disp = {
0b11110000, // 消灯
0b01111000, // LED1点灯データ
0b01110100, // LED2点灯データ
0b01110010, // LED3点灯データ
0b01110001, // LED4点灯データ
0b10111000, // LED5点灯データ
0b10110100, // LED6点灯データ
0b10110010, // LED7点灯データ
0b10110001, // LED8点灯データ
0b11011000, // LED9点灯データ
0b11010100, // LED10点灯データ
0b11010010, // LED11点灯データ
0b11010001, // LED12点灯データ
0b11101000, // LED13点灯データ
0b11100100, // LED14点灯データ
0b11100010, // LED15点灯データ
0b11100001, // LED16点灯データ
};

この配列を用いて実際のLEDを点灯する関数の例として下記のLEDONを示します。この関数は点灯したいLED(unsigned cha:バイト)を引数としてコールします。一旦、LEDを消灯した後で、配列からデータを読み出して、それをP4に書き込むことで、指定したLEDを点灯するようになります。

void    LEDON(unsigned char LEDNUMBER){
P4 = 0b11110000; /* Clear LED */
P4 = disp[LEDNUMBER]; /* Turn on specified LED */
}
解説

アセンブラでのテーブル参照はCでは配列の参照となります。プログラムそのものより配列(データテーブル)のデータ定義に手間がかかります。また、この配列はROMに入れておくことになるので、配列の宣言の先頭に定数であることを示すconstを宣言しておきます。


[プログラム例の使い方]
このプログラム例2は以下のようにして使用します。タイマH1を用いて、4MHz動作の場合には約1秒周期でLED1~LED16を順番に点灯します。

#pragma SFR
#pragma NOP
void main(void){
unsigned char work1;

P4 = 0b11110000; /* Clear All LED */
PM4 = 0x00; /* Port 4 is Output */

TMHMD1 = 0b01000000; /* set interval timer mode */
CMP01 = 60; /* Interval = 60ms */
IF0 = 0x00;
work1 = 0x01;
TMHE1 = 1;
while(1){
LEDON(work1++); /* LED on */
while(TMIFH1 == 0){
NOP(); /* wait timer interrupt */
}
TMIFH1 = 0;
if(work1 == 17) work1 = 1;
}
}
解説

ここでは、点灯するLEDの切り替えタイミングはタイマを利用して計測しています。タイマは動作モード(インターバルタイマ・モード)とカウントクロックをモードレジスタで設定し、カウントさせる値をコンペアレジスタ(CMP01)に設定してから起動(TMHE1に1をセット)しています。これでタイマは設定された時間ごとに割り込みを発生して割り込み要求フラグ(TMIFH1)をセットします。実際に割り込みを利用して処理を行なってもいいのですが、ここではwhile(TMIFH1 == 0)によるポーリングを行なっています。割り込み要求フラグがセットされるとループを抜けます。
ループを抜けたら、割り込み要求フラグをクリア(TMIFH1 = 0)します。通常のベクタ割り込みでは、割り込みを受け付けて、ベクタに分岐するときに割り込み要求フラグは自動的にクリアされるのですが、ポーリングではプログラムでクリアしないといけません。

ベクタ割り込みやここで使ったポーリング以外にもタイムアップを待つ方法はあります。それは、省電力機能(スタンバイ機能)を用いてCPUの実行を停止してしまう方法です。スタンバイ状態でタイマからの割り込み要求でCPUを再起動して処理を継続します。このためには、スタンバイ状態を解除するための割り込みのマスクを解除しておく必要があります。上記のmain関数を以下のように変更します。



スタンバイにはHALT以外にクロック発振も停止してしまうSTOPもあります。しかし、この場合にはタイマで時間を計測する必要があるので、STOPは使えません。HALTを使用するには、#pragma指令によりHALT命令を使用することを宣言する必要があります。そこで、最初に#pragma HALT宣言を追加します。また、割り込みのマスクを解除するために、TMMKH1 = 0;も追加します。なお、ベクタ割り込みは使用しないので、CPUは割り込み禁止状態にしておきます。
そのうえで、タイマ割り込みを待つために、while文をHALT()に変更します。

割り込みについてはFAQの「割り込みの動作の基本 [共通]」も参照してください。
また、スタンバイについてはFAQの「スタンバイ動作について」もご参照ください。
他にご質問がございましたら、リクエストを送信してください