Charlieplexingの話(2012/12/28)でマイコンの3つのポートで12個のLEDを駆動する方法について書いたので、Chipiplexing 方式を使ったバイナリクロックを作ってみた。
バイナリクロックは時刻を2進数で表示する時計で、例えば10:39分は
|
○ |
|
● |
|
○ |
○ |
○ |
|
○ |
● |
○ |
● |
○ |
● |
● |
1 |
0 |
3 |
9 |
のように表示する。
LEDで表示しようとすると12個のLEDが必要である。これをATTiny45でCharlieplexで3つのI/Oを使って実現しようというもの。
回路図はこんな感じ

最初にAVRでI/Oポートを3つ持つ品種AT90S2323を使おうと考えたが、AT90Sシリーズのデータシートを見るとソース(電流吐出し)とシンク(電流吸込み)のドライブ能力が対象でない。と
いうことでATTiny45を使うことにした。
ATTiny45はRESET#、XTAL1,XTAL2をI/Oポートとして使用できるのだが、
RESET#はドライブ能力がかなり低いのでピークで10数mAの出力が必要な今回のような用途には使えないし、外部X'talを使うのでXTAL1、XTAL2も使えないということでI/O
ポートは3つである。
(水晶発振器出力を#2PIN (CLKI)に入れて#3PIN(XTAL2)をI/Oに使えば、い~じゃん。というツッコミは無しである!)
R1、R2、R3にマイコンの出力ポートの内部抵抗が使えないかと思い、ATTiny45の内部抵抗をデータシートで確認すると30数Ωくらい
だったので、R4=R6=510Ω、R5=5.6kΩで作ったら、P0=H、P1=LにしたときD1、D8、D9が点灯してしまう。
SPICEで確認してみたら

SPICEさんはD1点灯(緑)のときD3(青)には200uA流れるとおっしゃる。200uAでは明々とは点灯しないような気がするのだけれど...
D1、D8、D9に関係する部分だけ抜き出してVcc=3.3V、D1,D8,D9のVf=2V、Q1,Q2のVbe=0.7として計算した各ポイントの電圧は↓のとおりである。

D1のアノード電圧=2.65V、Q2のエミッタ電圧=0.95Vだから、D9のA-K間の電圧=1.7Vである。これはD9のVfより小さいのでD9は点灯しないはずだ。(D8も同様)
2日くらい考えたところ、
昔のLEDは10m以上流さないと点灯しなかったが、最近のLEDは高輝度と謳っていなくても1mAも流すと点灯する。物によっては0.1mAでも点灯する。
!よく考えたら、Vf=2VはおおよそIf=20mAの場合のVfである。(D1のA-K間は2V)
ところがD8,D9に少ない電流が流れているとすると当然Vfも小さい値になる。
普通の赤色LEDで電流が流れ始める電圧は1.7V付近だから点灯するくらいの電流が流れているのではないか。
D8、D9が完全にOFFになるようにQ1のエミッタ電圧を下げ、Q2のエミッタ電圧を上げることにした。
アルカリ電池×2を使った場合を考えてVcc=3.4V、D8、D9が完全にOFFになる電圧を1.5Vとして計算すると、R4=R5=1.2kにしたときに1.49VになるのでD8,D9は完全に消えるだろう。
出力ポートの内部抵抗をあてにすると、I/Oピン入出力電流の絶対最大規格40mAに引っ掛かる恐れがあるのだけれど、R1、R2を大きくするとD8、D9を単独で点灯したときの電流が減るので痛し痒しだ。
D1~D8を点灯した時とD9~D12を点灯したときの電流の差があるのでLEDによって明暗の差が出てしまう。明らかに分かるようならPWMで補正しよう。
LED表示のソースはこんな感じ
/* bit ON/OFF */ #define ON 1
#define OFF 0
/* * DDRB PORTD * xxxx xxxxx : 出力データ */
#define DDR(d) ((d)<<4)
#define PD(d) ((d)&0x0F)
/* ポートの状態 */
#define HI PD(ON)|DDR(ON)
#define LOW PD(ON)|DDR(OFF)
#define HIZ PD(OFF)|DDR(OFF)
/* PB0,PB1,PB2の状態 * xxxx xxxx * ||| |||+--- PORTB(0) * ||| ||+---- PORTB(1) * ||| |+----- PORTB(2) * ||+-------- DDRB(0) * |+--------- DDRB(1) * +---------- DDRB(2) */
#define P0H HI
#define P0L LOW
#define P0Z HIZ
#define P1H (HI<<1)
#define P1L (LOW<<1)
#define P1Z (HIZ<<1)
#define P2H (HI<<2)
#define P2L (LOW<<2)
#define P2Z (HIZ<<2)
/**/
#define MIN1_1 P2Z | P1Z | P0L
#define MIN1_2 P2Z | P1Z | P0H
#define MIN1_4 P2Z | P1H | P0L
#define MIN1_8 P2Z | P1L | P0H
#define MIN10_1 P2Z | P1L | P0Z
#define MIN10_2 P2Z | P1H | P0Z
#define MIN10_4 P2H | P1L | P0Z
#define HOUR1_1 P2L | P1Z | P0Z
#define HOUR1_2 P2H | P1Z | P0Z
#define HOUR1_4 P2L | P1Z | P0H
#define HOUR1_8 P2H | P1Z | P0L
#define HOUR10_1 P2L | P1H | P0Z #define ALL_OFF 0x00
#define LED_OFF() PORTB = DDRB = 0x00
#define LED_ON(d) do { register uint8_t pd, dd; \
dd = pd = (d); \
dd &= 0x0F; \
pd >>= 4; \
} while(0)
/* output data */
const uint8_t port_data[] =
{
ALL_OFF, MIN1_1, MIN1_2, MIN1_4, MIN1_8,
MIN10_1, MIN10_2, MIN10_4,
HOUR1_1, HOUR1_2, HOUR1_4, HOUR1_8,
HOUR10_1
};
/* display LED * 指定したLEDを時刻に応じてON/OFFする * void disp(uint8_t pos)
* arg : uint8_t pos : LED number
* uint8_t data : 0: off, 1: on
* ret : void * global: uint8_t min, hour : 時分BCD */
void disp(uint8_t pos, uint8_t data)
uint8_t p;
/* all off */
LED_OFF();
if (pos < 4) { /* 分 */ if (BCD_L(min) & _BV(pos)) p = port_data[pos+1]; } else if (pos < 7) { /* 10分 */ if (BCD_H(min) & _BV(pos-4)) p = port_data[pos+1]; } else if (pos < 11) { /* 時 */ if (BCD_L(hour) & _BV(pos-7)) p = port_data[pos+1]; } else if (pos < 12) { /* 10時 */ if (BCD_H(hour) & _BV(pos-11)) p = port_data[pos+1]; } LED_ON(p); }
|
ポート(PB(x))をHにするときはDDR(x)←1、PORTB(x)←1
ポート(PB(x))をLにするときはDDR(x)←1、PORTB(x)←0
ポート(PB(x))をHizにするときはDDR(x)←0、PORTB(x)←0
このデータをLED0-LED11毎にテーブルで用意しておいて、表示するLED番号で出力データを検索して、上位4bitをDDRBに、下位4bitをPORTBに出力する。
そうしているうちに完成
↑11:54を表示
後ろは学研のArduino、もっぱらISPに使っている。
動いているところ
←リンク先にAVI
リセット時には、全てのLEDが右下から順に点灯し、11:40からカウントが始まる。
時分しか表示できないので撮影用に1秒毎にカウントアップするようにしている。
まあ動いたから良しとしよう。
I/Oポートがもう一個あって4個あるなら普通にCharlieplexingで駆動するのが簡単だと思う。
本当に時計として使うならC3をトリマに変えて発振周波数を合わせる必要がある。
最近のコメント