MicroPython的午睡(14) ラズパイPico、7セグ4桁、とりあえずタイマ駆動

Joseph Halfmoon

前回までLEDの4個ばかりでラズパイPicoのマルチコアの動作とかを確かめてきました。今回、Picoの機能を活かしても少しIOを触ってみるために外付けのハードを追加しました。とはいえLEDに変わりありません。が、4桁の7セグLEDなのでダイナミック駆動必須です。今回から時間縛りがあって多ピンの連動が必要なIO操作をやって行く予定です。

※「MicroPython的午睡」投稿順 Indexはこちら

(今回使用のMicroPythonコードの全文は末尾にあります)

以前4桁の7セグLEDを動的に駆動してみるのは以下の投稿で行っています。ただし、マイコンは RISC-V搭載 GD32VF103、GigaDevice社のSDK利用にて、Cでのプログラミングでした。カソード側の駆動には「怪しい」74HC595を使い、GD32VF103のタイマ5の割り込みを使っての駆動です。

部品屋根性(9) 7セグLED、割り込みでダイナミック駆動

後でRaspberry Pi Picoならではの機能を使って行きたいと思いつつ、今回は上記とほぼ同等の「タイマ」によるダイナミック駆動をやってみたいと思います。ラズパイPicoのMicroPythonは、タイマ使うのも大変「お楽」です。どちらかというと所望の配線をする方が面倒。。。

まずは回路図を掲げておきます。下に掲げた回路は、ココの7セグLED駆動回路とココのラズパイPicoの回路をガチャンコと接続しただけの回路であります。

RPi Pico 7SEG Schematic

アノードコモンの7セグLEDの各桁のアノードはPMOSトランジスタBSS84で駆動するようになっています。トランジスタのON/OFFはラズパイPicoのGP2、3,4,5の4本に割り当ててあります。PMOSなので、ラズパイPicoがLOWを出力すると電流が流れます(セグメントがLOWならば。)

どのセグメントを点灯させるかを決める側は74HC595「シフトレジスタ」で制御しています。(74HC595の「純正」データシートはこちら。「怪しくない」純正の74HC595も購入してあるのです。が、表面実装品。ブレークアウトボードに半田付けしておらず、今回はDIPの「怪しい」デバイス使用。)出力Q0からQ6をa-gまでの7セグメント、Q7がdp(小数点)に電流制限抵抗を介して接続しています。74HC595の制御信号については以下のように割り当てています。

  • DS GP22
  • OE# GP21
  • ST GP20
  • SH GP19
  • MR# GP18

今回、MR#は使用しないので吊り上げておいても良かったのですが、とりあえず端子に接続しています。74HC595は裏側で動作するシフトレジスタと出力をラッチしておくための表のレジスタの2段構えになっています。DSにシフトレジスタへ入力する値を載せて置いてSHの立ち上がりでシフトインします。8ビットのレジスタなのでこれを8回繰り返すと全ビットを入力できます。その後、ST信号の立ち上がりで裏から表にデータがラッチされます。そしてOE#信号をLOWにするとラッチされた値でQ0~Q7端子がドライブされます。

この辺の74HC595の制御をラズパイPicoの機能を使ってカッコよくしたい、という希望があるのですが、今回は全てソフトウエアで上記の制御をやっています。

各桁の駆動は、ラズパイPicoのMicroPythonのTimerオブジェクトを使って行います。Timerオブジェクトを使うと、メインの処理の裏側で、タイマコールバック関数を呼び出せます。周期的な実行や、ワンショットのタイムアウト処理などいろいろ応用が利きそうです。今回はコールバック関数呼び出し毎に以下の処理を行っています。

  1. 前回駆動していた桁のアノードドライバをディセーブルにする
  2. 74HC595の出力をディセーブル(HiZ)にしておく
  3. 今回駆動の桁に表示するセグメントパターンを74HC595にシリアル送信した後、出力FFにラッチ
  4. 74HC595の出力をイネーブルにする
  5. 今回桁のアノードドライバをイネーブルにする

これを「素早く」行っていけば、チラつかず4ケタの値が同時に表示されているように見える、というのがダイナミック駆動であります。今回はタイマ周期を上げていって、250Hzくらいあれば、私の目にはチラついて見えなかったので、その値を採用しています。1桁あたり250Hzなので、「ディスプレイ全体」とすると62.5Hzの「スキャンレート」となります。なお、デバッグの際10Hzくらいで駆動していましたが、この速さだと1桁ずつしか駆動してないじゃん、というのが肉眼でも良く分かります。

とりあえず表示するパターンとして0から9までの十進数を表示できる定数を用意しておきました。末尾に添付のサンプルプログラムでは、メインルーチンの中で3秒に1回カウンタ値を0からカウントアップし、そのカウンタの値を7セグLEDで表示するようになっています(ラーメンカウンタ的?)

ここで1箇所注釈つけて置くとしたら、表示パターンはカソード側で制御しています。セグメント側をLOWで引いたときにLEDが点灯です。ソース内で定義しているパターンは一般的なもので点灯セグメントが1です。よって、74HC595に送信する手前のどこかで0/1を反転させておかないと上手く表示されません。例によって例のごとく、最初私はココを間違えていて、何やら不穏なパターンが表示されて慌てました。

冒頭のアイキャッチ画像を見ていただくと分かるとおり、小さいブレッドボードを4枚も使っています。中央下がラズパイPico、右側やや下の一つだけサイズの違うブレッドボードが74HC595です。左上がBSS84 PMOSトランジスタ4個とLED側の電源(ラズパイPicoからは電源もらっていない)、そして中央が7セグ4桁LEDです。

折角ね、作ったのでね、最低数回くらいはこの回路でいろいろやりたい希望です。どこまで何ができるのか。

MicroPython的午睡(13) ラズパイPico、マルチコアの排他制御など へ戻る

MicroPython的午睡(15) ラズパイPico、プログラマブルIOの威力 へ進む

4桁7セグメントLED、タイマによるダイナミック駆動
from machine import Pin, Timer
import time

LED7 = [0] * 10
#            ABCDEFGP
LED7[0] = (0b11111100)
LED7[1] = (0b01100000)
LED7[2] = (0b11011010)
LED7[3] = (0b11110010)
LED7[4] = (0b01100110)
LED7[5] = (0b10110110)
LED7[6] = (0b10111110)
LED7[7] = (0b11100000)
LED7[8] = (0b11111110)
LED7[9] = (0b11110110)

datIdx = 0
datPat = [0] * 4

dig = [None] * 4
dig[0] = Pin(2, Pin.OUT)
dig[1] = Pin(3, Pin.OUT)
dig[2] = Pin(4, Pin.OUT)
dig[3] = Pin(5, Pin.OUT)

for idx in range(4):
    dig[idx].value(1)

hc595DS = Pin(22, Pin.OUT)
hc595OEb = Pin(21, Pin.OUT)
hc595ST = Pin(20, Pin.OUT)
hc595SH = Pin(19, Pin.OUT)
hc595MRb = Pin(18, Pin.OUT)

hc595DS.value(0)
hc595OEb.value(1)
hc595ST.value(0)
hc595SH.value(0)
hc595MRb.value(1)

def softloop(n):
    sum = 0
    for i in range(n):
        sum += i
    return sum

def send8bitData(dat):
    for i in range(8):
        tmp = 1 if (dat & 0x01) == 0 else 0
        hc595DS.value(tmp)
        softloop(10)
        hc595SH.value(1)
        softloop(10)
        hc595SH.value(0)
        softloop(10)
        dat >>=1
    hc595ST.value(1)
    softloop(10)
    hc595ST.value(0)

def display1digit(timer):
    global datIdx
    dig[datIdx].value(1)
    datIdx = (datIdx + 1) if datIdx < 3 else 0
    hc595OEb.value(1)
    send8bitData(datPat[datIdx])
    hc595OEb.value(0)
    dig[datIdx].value(0)   

tim = Timer()
tim.init(freq=250, mode=Timer.PERIODIC, callback=display1digit)

for cnt in range(9999):
    datPat[3] = LED7[cnt % 10]
    datPat[2] = LED7[(cnt // 10) % 10]
    datPat[1] = LED7[(cnt // 100) % 10]
    datPat[0] = LED7[(cnt // 1000) % 10]
    time.sleep(3.0)