4桁の7セグLEDをダイナミック点灯させてデジタル時計を自作
Raspberry Piを使って、4桁(4digits)の7セグLEDに時間を表示させてデジタル時計を自作してみます。
完成までのロードマップはこんな感じ。
- Pythonで現在時刻を取得
- GPIOでPNPトランジスタを制御してダイナミック点灯させる
- シフトレジスタを介して7セグLEDに時刻を表示
順番にやり方を説明していきます。
Pythonで現在の時刻を取得する方法
datetimeモジュールを使うと、日時を取得することができます。
import datetime
dt = datetime.datetime.now()
print(dt)
出力結果はこのようになります。
年月日と時分秒まで出力されています。
タイムゾーンを指定していないので、UTC時刻となってしまっています。
Python3.9からはZoneInfoが使えるようになっていて、簡単にJST(日本標準時)を指定できます。
import datetime
from zoneinfo import ZoneInfo
dt = datetime.datetime.now(ZoneInfo("Asia/Tokyo"))
print(dt)
出力結果はこのように9時間進んだ時間になります。
今回は4桁の7セグLEDに表示するので、必要なデータは時間と分だけになります。
hour、minuteを指定すれば、時間と分だけ取得できます。
import datetime
from zoneinfo import ZoneInfo
dt = datetime.datetime.now(ZoneInfo("Asia/Tokyo"))
print(dt.hour)
print(dt.minute)
出力結果はこのようになります。
8
4桁の7セグLEDの内部回路
続いて、4桁の7セグLEDの仕組みと構造を見ていきましょう。
7セグLEDの基本についてはこちらの記事をご覧ください。
内部回路と端子配列
このように8個のLEDが4組、合計32個内蔵されています。
各組の同じ点灯位置のLEDのカソードはショートされています。
今回使用した3461BSはアノードコモンなので、アノードは各組ごとにまとめられて、別々の端子として外に出ています。
端子配列は次の通りです。
点灯方法
4桁の7セグLEDは、各桁が同時に点灯することはありません。
- 点灯させる桁のアノードだけに電源を印加
- その桁に表示させる数字の部分のLEDを点灯
- 電源を切断し、次の桁に電源を印加
- その桁に表示させる数字の部分のLEDを点灯
- 以降繰り返し・・・
このように、各桁に順番にLEDを点灯させていきます。
この流れを1周20ms以下という短時間で繰り返すようにします。
20ms以下の点滅であれば、人間の目には点滅が認識できず、ずっと点灯しているように見えるのです。
この点灯方式をダイナミック点灯といいます。
ダイナミック点灯のやり方
Raspberry Piを使ってダイナミック点灯をするための回路図とプログラムを紹介します。
回路図
回路図はこのようになります。
PNPトランジスタのオン・オフで点灯させる桁を切り替えます。
ざっくりした説明ですが、PNPトランジスタは、ベースの電圧が低ければオン、高ければオフします。
したがって、点灯させたい桁に接続されているトランジスタのベースをLOW、その他をHIGHとなるようにGPIOを制御します。
なぜトランジスタを使うかというと、アノード側にはLED8個分の電流が流れるため、GPIOから直接制御すると電流が不足するためです。
トランジスタを介することで、GPIOからの電流を数百倍に増幅することができるのです。
トランジスタの基本動作についてはこちらの記事をご覧ください。
また、シフトレジスタ(74HC595)の使い方はこちらの記事をご覧ください。
プログラム
一番上の桁から順番に【1 2 3 4】と表示させるプログラムです。
import RPi.GPIO as GPIO
import time
import sys
dataPin = 17
latchPin = 27
clockPin = 22
digitPin = (12,16,20,21)
def setup():
GPIO.setmode(GPIO.BCM)
GPIO.setup(dataPin, GPIO.OUT)
GPIO.setup(latchPin, GPIO.OUT)
GPIO.setup(clockPin, GPIO.OUT)
for pin in digitPin:
GPIO.setup(pin,GPIO.OUT)
def setRegister(dPin,cPin,val):
for i in range(0,8):
GPIO.output(cPin,GPIO.LOW); # クロックをLOWに
GPIO.output(dPin,val&(0x80>>i)) # 最上位bitから順にデータを入力
GPIO.output(cPin,GPIO.HIGH); # クロックをHIGHに
# 選択された桁のアノード側トランジスタをオンさせる
def selectDigit(digit):
GPIO.output(digitPin[0],GPIO.LOW if (digit == 0x01) else GPIO.HIGH) # 1桁目
GPIO.output(digitPin[1],GPIO.LOW if (digit == 0x02) else GPIO.HIGH) # 2桁目
GPIO.output(digitPin[2],GPIO.LOW if (digit == 0x04) else GPIO.HIGH) # 3桁目
GPIO.output(digitPin[3],GPIO.LOW if (digit == 0x08) else GPIO.HIGH) # 4桁目
def loop():
while True:
selectDigit(0x08) # 4桁目のdigitを選択
GPIO.output(latchPin,GPIO.LOW) # ラッチピンをLOW
setRegister(dataPin,clockPin,0xf9) # 4桁目に1を表示
GPIO.output(latchPin,GPIO.HIGH) # ラッチピンをHIGHにしてLEDの表示を更新
time.sleep(0.003)
selectDigit(0x04) # 3桁目の位のdigitを選択
GPIO.output(latchPin,GPIO.LOW)
setRegister(dataPin,clockPin,0xa4) # 3桁目に2を表示
GPIO.output(latchPin,GPIO.HIGH)
time.sleep(0.003)
selectDigit(0x02) # 2桁目のdigitを選択
GPIO.output(latchPin,GPIO.LOW)
setRegister(dataPin,clockPin,0xb0) # 2桁目に3を表示
GPIO.output(latchPin,GPIO.HIGH)
time.sleep(0.003)
selectDigit(0x01) # 1桁目のdigitを選択
GPIO.output(latchPin,GPIO.LOW)
setRegister(dataPin,clockPin,0x99) # 1桁目に4を表示
GPIO.output(latchPin,GPIO.HIGH)
time.sleep(0.003)
def destroy():
GPIO.cleanup()
sys.exit()
if __name__ == '__main__':
print ('Program is starting ... ')
setup()
try:
loop()
except KeyboardInterrupt:
destroy()
34行目で4桁目(一番左)の桁を選択しています。
これは29行目に該当し、digitPin[3]、つまりGPIO21をLOWにして、それ以外をHIGHにするという動きになります。
35行目でラッチピンをLOWにしてから36行目で表示させる数字を指定してシフトレジスタへ送信しています。
0xf9は2進数で11111001なので、7セグLEDのBとCの位置をLOWにして(点灯させて)数字の1を表示させます。
37行目でラッチピンをHIGHにして出力を更新、38行目でsleepを3msとしているので、3ms間点灯が継続します。
以降、これを順番に繰り返します。
sleepが3msなので、1桁目から4桁目まで繰り返す時間は12msと短時間なので、【1234】がずっと点灯しているように見えます。
試しにsleepを100msとすると、このように各桁が順番に点灯しているのが分かります。
4桁の7セグLEDをダイナミック点灯させてみました。
ゆっくり切り替えるとこんな感じ。
1周期を20ms以下くらいにすると全部の桁が点灯しているように見えます。 pic.twitter.com/3rYzNUZr7Q
— りょうのすけ (@ryo_analog) 2022年7月22日
デジタル時計表示プログラム
これまで説明した、現在時刻の取得方法と7セグLEDの点灯方法を使って、7セグLEDに時刻を表示させてみたいと思います。
回路はダイナミック点灯の説明で使ったものと同じです。
プログラムは次のようにしました。
import RPi.GPIO as GPIO
import time
import datetime
import sys
dataPin = 17
latchPin = 27
clockPin = 22
digitPin = (12,16,20,21)
num = [0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90]
t_delta = datetime.timedelta(hours=9) # 9時間
JST = datetime.timezone(t_delta, 'JST') # UTCから9時間差の「JST」タイムゾーン
tm1 = 0xff
tm2 = 0xff
th1 = 0xff
th2 = 0xff
def setup():
GPIO.setmode(GPIO.BCM)
GPIO.setup(dataPin, GPIO.OUT)
GPIO.setup(latchPin, GPIO.OUT)
GPIO.setup(clockPin, GPIO.OUT)
for pin in digitPin:
GPIO.setup(pin,GPIO.OUT)
def setRegister(dPin,cPin,val):
for i in range(0,8):
GPIO.output(cPin,GPIO.LOW); # クロックをLOWに
GPIO.output(dPin,val&(0x80>>i)) # 最上位bitから順にデータを入力
GPIO.output(cPin,GPIO.HIGH); # クロックをHIGHに
# 選択された桁のアノード側トランジスタをオンさせる
def selectDigit(digit):
GPIO.output(digitPin[0],GPIO.LOW if (digit == 0x01) else GPIO.HIGH) # 小数第一位
GPIO.output(digitPin[1],GPIO.LOW if (digit == 0x02) else GPIO.HIGH) # 一の位
GPIO.output(digitPin[2],GPIO.LOW if (digit == 0x04) else GPIO.HIGH) # 十の位
GPIO.output(digitPin[3],GPIO.LOW if (digit == 0x08) else GPIO.HIGH) # 百の位
def loop():
global t_delta, JST
while True:
dt = datetime.datetime.now(JST)
thz = str(dt.hour).rjust(2, '0') # 取得した「時」に十の位の値が無い場合は0にする
tmz = str(dt.minute).rjust(2, '0') # 取得した「分」に十の位の値が無い場合は0にする
tm1 = num[int(tmz[1])] # 取得した「分」の一の位に該当するものをnumのリストから選択
tm2 = num[int(tmz[0])] # 取得した「分」の十の位に該当するものをnumのリストから選択
th1 = num[int(thz[1])] # 取得した「時」の一の位に該当するものをnumのリストから選択
th2 = num[int(thz[0])] # 取得した「時」の十の位に該当するものをnumのリストから選択
selectDigit(0x08) # 「時」の十の位のdigitを選択
GPIO.output(latchPin,GPIO.LOW) # ラッチピンをLOW
setRegister(dataPin,clockPin,th2) # 「時」の十の位のデータをレジスタへ送信
GPIO.output(latchPin,GPIO.HIGH) # ラッチピンをHIGHにしてLEDの表示を更新
time.sleep(0.003)
selectDigit(0x04) # 「時」の一の位のdigitを選択
GPIO.output(latchPin,GPIO.LOW)
setRegister(dataPin,clockPin,th1 & 0x7f) # 「時」の一の位のデータをレジスタへ送信、DPをオン(LOWにする)
GPIO.output(latchPin,GPIO.HIGH)
time.sleep(0.003)
selectDigit(0x02) # 「分」の十の位のdigitを選択
GPIO.output(latchPin,GPIO.LOW)
setRegister(dataPin,clockPin,tm2) # 「分」の十の位のデータをレジスタへ送信
GPIO.output(latchPin,GPIO.HIGH)
time.sleep(0.003)
selectDigit(0x01) # 「分」の一の位のdigitを選択
GPIO.output(latchPin,GPIO.LOW)
setRegister(dataPin,clockPin,tm1) # 「分」の一の位のデータをレジスタへ送信
GPIO.output(latchPin,GPIO.HIGH)
time.sleep(0.003)
def destroy():
GPIO.cleanup()
sys.exit()
if __name__ == '__main__':
print ('Program is starting ... ')
setup()
try:
loop()
except KeyboardInterrupt:
destroy()
取得した時刻をそれぞれの桁で分割し、該当する7セグLEDの桁に割り当てます。
45行目、46行目ではゼロ埋めをしています。
例えば、取得した時間が1時1分の場合、01:01とはならず、4桁目と2桁目のデータは無しとなります。
rjustメソッドを使うと、指定した文字数となるように右側を指定した文字で埋めます。
今回は文字数を2文字、埋める文字は0として、データ無しになることを防いでいます。
文字型にする必要があるので、strに変換しています。
48行目~51行目で、取得した各桁の数字と一致するものをnum[]のリスト内から選んでいます。
「時」と「分」の間にドットが常に点灯するようにするため、61行目で0x7f(01111111)と&を取っています。
これで3桁目が選択された時はDPが常に点灯(シフトレジスタのQ7がLOW)になります。
その他は、現在時刻の取得、ダイナミック点灯で説明した内容と同じ動きです。
表示結果
電波時計と同じ時刻を示しているので問題なさそうです!