Raspberry Pi – シフトレジスター~ドットマトリックスLED

概要

Raspberry Piで8×8のドットマトリックスLEDを表示させてみた。

  • 2つのシフトレジスターを使って、任意のパターンを表示させるようにした
  • パターンを周期的に流れるように表示させた

ドットマトリックスLEDの仕様

キットに同梱されていたドットマトリックスLEDはピン配置や極性の手掛かりになる表示などがなかったので、1つずつ通電しながらピン配置と点灯の確認を行った。

内部の接続は以下のようになっている。RmをHIGHに、CnをLOWにすると、m行目n列目のLEDが点灯する。

回路図

キットのマニュアルの回路は2つのシフトレジスター74HC595を使っていたが、それらをカスケードに接続しているため、行と列のパターンを同時にしか設定できない。

そこで、2つのシフトレジスターを切り離して独立して制御するようにした。1行ずつスキャンしながら行ごとの点灯パターンを設定し、8×8の64個のLEDを任意のパターンで点灯させることができる。

ブレッドボードで回路を組んだら、かなり配線がごちゃごちゃになった。LEDのRとCから8本ずつのケーブルが出て、保護抵抗も8本使っているので仕方がないか。LEDのサイズがそれなりに大きいので、ブレッドボードの穴が覆われてしまう。

表示は平仮名の「お」で、後述のサイネージコードで「おはよう」と流して表示しているときの一部。

コード例

試験点灯

まず、8×8のドットパターンを2秒間だけ表示させてみる。

ドットパターンはサブディレクトリーlibの下にled_dotmatrix_data.pyとして以下のモジュールを準備した。

パターンをスペースとスペース以外の文字(ここでは*)を使って準備し、get_bit_pattern関数で8ビット×8行のリストとする。

最初のコードで、まずGPIOピンなどの定数を定義している。ROWSはドットマトリックスの各行を単独で指定する際に、シフトレジスターに与えるビットパターン。

shift関数は、SERとSRCLK、HIGH/LOWのレベルを指定してシフトレジスターを1回シフトする。

read関数は、RCLKをHIGHにしてストレージレジスタ―の出力を取り出し、LOWに戻す。

clear関数はドットマトリックスのR1~R8をLOWにしてLEDを消灯させる。念のためC1~C8もLOWにしている。

display関数は、8ビット×8行のビットパターンを与えてsleepの秒数だけLEDを点灯させる。

DisplayThreadはビットパターンを与えて生成し、startメソッドを呼ぶと内部でrunメソッドが実行される。stopメソッドによりスレッドが停止する。

試験点灯用のコードは以下の通りで、表示パターンを与えてスレッドを生成し、2秒間点灯させた後にスレッドを停止させている。

ここで、stopメソッドの後に僅かな時間の間をおかないと、「setmodeが必要」というランタイムエラーが出る。destroyメソッドでcleanupを実行するタイミングがstopで表示が停止するよりも早いからかもしれない。

CTRL-Cでの表示停止

次に、実行後表示を続けてCTRL-Cのキーボード入力で停止するようにした。

このとき、キーボード割込を待ち受けるwhileループの内容を単にpassとすると目に見えるくらいのちらつきが生じたため、sleepを入れている。

アニメーション表示

2つのパターンを準備して、一定時間で交互に表示を切り替えてみた。

まず、led_dotmatrix_dataモジュールで準備した表示パターンは以下の通り。

2つのパターンを交互に切り替える実行部分は以下の通り。

流れる表示

8×8のパターンを横に流れるように表示してみた。led_dotmatrix_dataモジュールに以下を追加。

get_cycle_pattern関数は、8×8のパターンの表示開始位置を与えてサイクリックに表示をずらすためのパターンを返す関数。

以下は、この関数を使って流れる表示をさせるコード。データのリストの最後まで来たら、表示させる桁数を先頭に戻している。この結果、正弦波のような波が流れて表示される。

サイネージ

複数の文字が横に流れるように表示させるようにしてみた。まず、led_dotmatrix_dataモジュールに以下の関数とパターンを追加。

combine_patterns関数は複数のパターンを引数にとり、それらを横に連ねた2次元リストを返す。

実行部分は以下の通りで、複数の文字パターンを関数で1つの2次元リストにまとめ、それを上の流れる表示と同じように処理している。

 

2次元リストの結合

概要

2次元リスト同士の結合について整理。

  • 縦方向の結合
    • appendはループが必要
    • extendは破壊的に結合
    • +演算子は非破壊的に結合
  • 横方向の結合
    • extendでループが必要
    • +演算子はループの方法に注意

縦方向の結合

appendは要注意

1次元のリストの場合と同じく、appendは引数全体を要素として追加してしまう。

追加されるリストの各行を取り出して追加すれば可能。ただし破壊的。

extendはok(破壊的)

extendは引数のリストの内容をそのまま追加してくれる。ただし破壊的。

+演算子もok(非破壊的)

+演算子もextendと同じ挙動だが非破壊的。

横方向の結合

extendで一手間必要

extendで各要素に追加する場合、各要素をループで取出して追加する必要がある。

カウンターではなく要素を直接取り出してもok。

+演算子は要注意

+演算子で結合する場合は、結果のリストを準備しておく必要がある。このとき、結合するリストと同じ要素数(行数)の空のリストを要素に持つリストを準備しておく必要がある。

また、ループはカウンターを使って添字でリスト要素を指定しなければならない。

要素を直接取り出して演算した場合、変数が新たに作成されるがその結果は元のリストには反映されないので注意。

 

リストの追加・結合

概要

リストに対する要素の追加、リストの結合について整理。

  • リストへの1つの要素の追加
    • appendが簡明、ただし破壊的
    • extendも使えるがリストとして追加、破壊的
    • +演算子もリストとして追加、非破壊的
  • リスト同士の結合
    • appendは使えない
    • extendは破壊的にリストを追加
    • +演算子は非破壊的にリストを追加

リストへの要素の追加

appendは破壊的

appendで1つの要素をリストに追加できる。appendは破壊的で元のリストを改変する。戻り値はNone

appendは1つの引数しか持てない(append(4, 5)とはできない)。

extendはリストを追加(破壊的)

extendでも要素を追加できるが、要素そのものではなく、追加したい要素を含むリストとして追加する。extendは破壊的で元のリストを改変する。戻り値はNone

+演算子もリストの演算(非破壊的)

+演算子もextendと同じくリストを追加する。extendと違って非破壊的で、元のリストは改変されず、結果は戻り値で返される。

リストの結合

appendは使えない

appendは引数をリストの要素として追加するので、リストを引数に与えるとそのリストが要素として追加されてしまう。

extendはOk(破壊的)

extendは引数に与えたリストで元のリストを拡張する。ただし破壊的で元のリストが改変される。

+演算子もOk(非破壊的)

+演算子もextendと同じくリストを拡張するが非破壊的。拡張と言うよりも、くっつける/追加するというイメージ。

 

Raspberry Pi – シフトレジスタ―~4桁7セグメントLED

概要

  • Raspberry Piでシフトレジスターを介して4桁7セグメントLEDをコントロール
  • 各桁を短時間で明滅させて、視覚的に全桁が同時発光しているように見せる
  • おまけでカウントダウンタイマーにしてみた

4桁7セグメントLEDのピン配置と回路

キットに同梱されていたLEDのピン配置は下図の通り。

D1~D4のピンは左から順に各桁の7セグメントLEDに対応している。これらのピンがHIGHのLEDにa~dpのピンへの入力が反映されて表示される。LOWの時にはLEDは消灯する。

a~dpは7セグメントLEDのピン配置に対応している。

内部の回路は以下の通りで、カソードコモンとなっている。たとえば左から2桁目のLEDを点灯させたいときは、D2をHIGHにしてa~dpのうち点灯させたいセグメントのピンをLOWに、消灯させたいセグメントのピンをHIGHにする。

4桁の同時発光

セグメントの明滅を制御するa~dpのピンが1組しかないので、ある桁を特定のパターンで点灯させたいときは、他の桁は消灯させる(でなければ、DnがHIGHになっている桁の全てが同じパターンで点灯する)。

そこで、ある桁を短い時間点灯させたあと消灯するという動作を各桁入れ替えながら実行していく。点灯・入れ替えの時間が十分短いと、残像減少によって人の目には全桁が同時発光しているように見える。

Raspberry Piによる制御回路

全体の考え方は以下の通り。

  • D1~D4の桁選択ピンは4つのGPIOピンでコントロールする
  • a~dpのセグメント選択ピンはシフトレジスター74HC595を介してコントロールする
  • 7セグメントLEDの保護抵抗はD1~D4に1つずつ繋ぎ、1つの7セグメントあたり1つとする

試験点灯

ソフトウェアによる制御の考え方は以下の通り。

  1. 初期設定ではD1~D4をLOWにする
  2. DnをD1~D4と順番に入れ替えて以下を繰り返す
    1. DnをHIGHにして選択する
    2. 選択したLEDに対してシフトレジスターを介して表示パターンを送る
      • 表示パターンはa→dpの順の8ビットで定義して、LSBから順にシフトレジスターに送り込む
    3. 一定時間sleepさせる
    4. HIGHにしたDnをLOWにする
  3. CTRL-Cが押されたらD1~D4をLOWにして終了処理を行う。

以下のPythonコードでは、いくつかの関数を定義している。

initialize
LEDとシフトレジスターの初期化を行う。
destroy
終了処理を行う。
display_a_digit
指定した桁のLEDを指定したパターンで一定時間点灯させる。桁の指定は左から0~3、パターンは0~9の数字に対応したビットパターン、点灯時間はミリ秒で与える。
display_digits
表示させる4桁の数値を文字列で与えて、一定時間点灯させる。ミリ秒で与えた点灯時間はdisplay_a_digit関数にそのまま渡される。

カウントダウンタイマー

time.perf_counter

4桁7セグメントLEDでカウントダウンタイマーを作ってみた。

  • 左2桁が分、右2桁が秒に対応する
  • 動作中は秒数でコントロールし、LED表示のために分と秒を分解する
  • 点灯時間をtime.perf_count関数で計り、約1秒ごとにカウントダウンする
  • カウントダウンを終えたら’0000’を表示して割り込みを待つ

なお、整数値の分・秒を2桁表示の文字列にする関数get_two_digits関数を定義している。

69-72行で分と秒をセットし、それを総秒数に計算。

77-93行がカウントダウン部分。

  • tartとendをtime.perf_counterで取得して経過秒数を計算
  • 表示秒数が1秒たつとカウントダウン
  • カウントダウン後と残りゼロ秒になったときに内側のループを抜ける

95-98行目では、残りゼロ秒になったときにオールゼロを表示して待機。

スレッド

実行部分をシンプルにするためにスレッドを使ってみた。

66-83行目で、4桁の内容を表示し続けるスレッドクラスを準備。

95-108行目で、1秒ごとにスレッドを発生させて現在の残り時間を表示。

回路の写真

74HC595の向きを逆にしてしまったのでコードがごちゃごちゃ。

Raspberry Pi – シフトレジスタ―~7セグメントLED

 

シフトレジスターには74HC595を使う。

ここでは、別々のGPIOから7セグメントLEDを点灯させたときと逆に、a→dpのビット順にしていて、コードではLSBから順に読みだしてシフトレジスターに送ることにした。

表示 a b c d e f g dp Hex
0 11111100 0xfc
1 01100000 0x60
2 11011010 0xda
3 11110010 0xf2
4 01100110 0x66
5 10110110 0xb6
6 10111110 0xbe
7 11100000 0xe0
8 11111110 0xfe
9 11110110 0xf6
A 11101110 0xee
B 00111110 0x3e
C 10011100 0x9c
D 01111010 0x7a
E 10011110 0x9e
F 10001110 0x8e

以下はRaspberry Piで7セグメントLEDを点灯させるコード。リストのpatternsに0~Fまでのセグメント点灯パターンを入れ、対応する桁数分シフトしてシフトレジスターに送っている。そして、各パターンを0.5秒ずつ表示させてカウントアップさせている。

キットのテキストのコードでは、time.sleep()を入れていたり入れていなかったりするが、特にこれがなくても問題なく動作する。

 

7セグメントLEDの保護抵抗

キットの回路図で、7セグメントLEDの保護抵抗をコモンアノードに1本だけ接続している例があった。

一般には複数のLEDにはそれぞれに保護抵抗を繋ぐべきとされている。並列接続のパーツに同じ電圧がかかったとき、同じVfでも多く電流が流れる特性のLEDにより多くの電流が流れるから、というのは理解できる。

そこで、手持ちの7セグメントLEDの各LEDの特性値を調べてみた。回路図は大したことはないが、折角描いたので記録しておく。

抵抗Rをいろいろな値のものに変え、VrVfを計測してIfを算出した。その結果は以下のようなグラフになった。

dpを含めた8つのLEDのうち、fだけ特性が大きく違っている。この場合、1つのLEDに電流が集中するのではなく、同じ電圧でfだけが暗くなることになる。

もしこのfの特性が逆で他のLEDの特性曲線より左側に来ると、同じ電圧をかけた時にfだけ大きな電流が流れることになる。

ただ、一般的なLEDであまりばらつきが大きくないとすると、概ね規格値のVfとIfを実現するような抵抗1つでも大きな問題はなさそうだ。どのみち大きな影響を受けるLEDはデータシートから外れているので、このように一つ一つ特性曲線を確認せずに抵抗を繋ぐ限り、影響は変わらないといえる。

 

 

Raspberry Pi – シフトレジスター~LEDバーグラフ

LEDバーグラフ

LEDバーグラフの例はこちらで示したが、点滅させるLEDの数だけGPIOポートを使った。シフトレジスターを使えば、データに関してはシリアル信号1本だけで済む。以下はシフトレジスター74HC595を使った回路例。

各LEDの点滅の制御は以下の流れ。

  1. LEDの個数分以下を処理
    1. SERに点灯ならHIGH、消灯ならLOWを入力
    2. SRCLKにHIGHを入力してシフト後、LOWに
  2. RCLKをHIGHにして、QA~QHの出力をLEDに流す
  3. 全消灯するときはSRCLKをLOWにしてからHIGHに戻す

以下はこの回路をRuspberry PiのMicro Pythonで制御するコード例。

SRCLK/RCLKでHIGH/LOWに変化させた後、それぞれLOW/HIGH戻すまでに0.001秒待たせているが、これがなくても動作はする。

 

74HC595

概要

74HC595には以下の特徴がある。

  • 8bitのSIPOシフトレジスター
  • 1本のシリアル入力ピン(SER)と8本のパラレル出力ピン(QA~QH)がある
  • 非同期クリアピンがある
  • QH’ピンで複数の74HC595をカスケード接続できる

ピン配置

各ピンの機能は以下の通り。

VCC 電源電圧
GND グランド
SER シリアル入力端子(立ち上がり同期)
SRCLK シフトクロック入力(立ち上がり同期)
RCLK ストレージレジスタ書き込み(立ち上がり同期)
SRCLR HIGHでシフトレジスタの内容を保持、LOWでクリア(クロックと非同期)
OE HIGHで出力無効、LOWで出力有効
QA~QH 8ビットのパラレル出力(RCLKで読み出す)
QH’ QHの状態を常に出力

データシート

絶対最大定格

電源電圧 VCC −0.5 ~ 7 V
入力電圧 VIN −0.5 ~  VCC + 0.5 V
出力電圧 VOUT −0.5 ~  VCC + 0.5 V
入力保護ダイオード電流 IIK ±20 mA
出力寄生ダイオード電流 IOK ±20 mA
出力電流 IOUT QH’: ±25, QA~QH: ±35 mA
VCC/GND電流 ICC ±75 mA
許容損失 PD 500 (DIP) / 180 (SOP) mW

動作範囲

電源電圧 VCC 2 ~ 6 V
入力電圧 VIN 0 ~ VCC V
出力電圧 VOUT 0 ~ VCC V
動作温度 TOPR −40 ~ 85
入力上昇・下降時間 tr, tf 0 ~ 1000 (VCC = 2.0V)
0 ~ 500 (VCC = 4.5V)
0 ~ 400 (VCC = 6.0V)
ns

 

シフトレジスター

直列入力並列出力(SIPO)

直列入力並列出力(SIPO: Serial-in Parallel-out)のシフトレジスターについて。複数のDフリップフロップを出力と入力で数珠つなぎにするとSIPOのシフトレジスターを構成できる。

以下は4ビットのシフトレジスターの簡単な例。

タイミングチャートで表すと、以下の様にCLKの立ち上がりのたびにSIN→Q0、Q0→Q1、Q1→Q2、Q2→Q3と1ビットの値がシフトしていく。

ここで、CLKの立ち上がり時にSIN→Q0→Q1→Q2→Q3と一斉に値が反映されてしまわないかということが気になる。

以下はクロックパルスの部分を誇張して描いたタイミングチャート。ここで、各Dフリップフロップに入力が与えられてから、それが出力に反映されるまで、僅かな時間だが遅延があることを表現している。

このため、CLKの立ち上がりが各Dフリップフロップに一斉に届いたとしても、SINの信号が使われるのはQ0のみになることがわかる。

実際のシフトレジスターには、内容をクリアする端子や、パラレル信号を取り出すための端子などが備えられている。

 

Dフリップフロップ

概要

Dラッチは、入力Gが1になっている間入力D(データ)の値を出力Qに出し続ける。一方入力Gにクロックパルスが与えられる場合、入力データの値をクロックパルスの立ち上がりで一度だけ記憶・出力して、次のクロックの立ち上がりまでその出力を保持したいときには、Dラッチではうまくいかない。

Dフリップフロップはこのような場合に使う。Dフリップフロップ回路は、以下の様にDラッチを2つ連ねた形をしている。

この回路は、CLKの入力が0→1に立ち上がった時のDの値を保持し、次に再びCLKが0→1に立ち上がるまでその値を保持し続ける。

基本的なDフリップフロップは以下のようなシンボルで表される。

仕組み

前段・後段それぞれのラッチのGに与える信号を反転させているのがミソで、以下のような流れになる。

  • クロックレベルが0のときは前段のラッチが入力状態、後段のラッチが保持状態となる
    • 前段のラッチは入力の値を出力し続ける
    • 後段のラッチは前段の値に関わらず保持内容を出力し続ける
  • クロックレベルが1になると、前段のラッチが保持状態、後段のラッチが出力状態になる
    • 前段のラッチはそれまでの入力内容を保持する
    • 後段のラッチは前段の保持内容を出力し始める
  • 再びクロックレベルが0になっても、後段のラッチは前のステップで入力された値を保持し続ける

イメージで図にすると以下の様になるだろうか。

回路図での確認

Dフリップフロップの動作を回路図で確認すると以下の流れになる。

初期状態として、すべての端子の信号レベルが0の状態とする。このとき前段のG1への入力のみ反転して1となり、前段のラッチは出力状態、後段のラッチは保持状態となっている。

その状態で入力が1となった場合。前段が出力状態となり1を出力するが、後段は保持状態のため、最後の出力は変化しない。

ここでクロックが1となると、前段が保持状態、後段が出力状態となり、最後の出力が1に変わる。クロックが1の間に入力値が変化しても、立ち上がり時の値が前段で保持されているので、出力は変わらない。

その後クロックが0となると、後段が保持状態となり、最後の出力が維持される。

その後入力信号が0となっても、後段で保持されている値が出力され続ける。

タイミングチャート

Dフリップフロップの動作をタイミングチャートで見てみた。下図の緑/オレンジの色がついている部分が前段/後段がSetの状態、色がついていない部分がそれぞれのHoldの状態。

各ステップでの状態は以下の様に推移する。

Step CLK 入力 前段の状態 前段の出力 後段の状態 後段の出力
a 0 0 Set 0 Hold 0
b 0 1 Set 1 Hold 0
c 1 1 Hold 1 Set 1
d 0 1 Set 1 Hold 1
e 0 0 Set 0 Hold 1
f 1 0 Hold 0 Set 0
g 1 1 Hold 0 Set 0
h 0 1 Set 1 Hold 0