MicroPython的午睡(16) ラズパイPico、PIOで74HC595制御、簡単

Joseph Halfmoon

前回は、Raspberry Pi PicoのProgramable IO(PIO)が可能性無限大、みたいな話を書きましたが、オシロで波形を観察したにとどまりました。今回はその威力を実地に確かめるべく、前々回の7セグLED4桁駆動回路に適用してみます。一撃で任意の波形を作れるPIOがあると本当に楽だ。

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

まずは、テスト時の様子を撮ったアイキャッチ画像を御覧ください。前回と同じじゃないか、ということなかれ、であります。Digilent AnalogDicovery2の接続

  • 前回はオシロ
  • 今回はロジアナ

AnalogDiscovery2の2現象のオシロでは見たい信号を取り切れなくなり、ロジアナ機能を使った、ということであります。それでとらえた波形はこちらです。

PIO_74HC595_LOGA

Name欄に74HC595の端子名を書いて置きました。上から

  • DS、シリアル入力データ端子。シフトINするデータを載せる
  • OE#、アウトプットイネーブル。Lowのとき74HC595の出力がドライブされる
  • ST_CP、シフトレジスタから出力(ストレージ)レジスタへのラッチ
  • SH_CP、シフトレジスタへのクロック

であります。まあOE#信号は常にイネーブルでも良いのですが、殊更に制御してみました。上記から分かることは、最低でも4端子を制御しないと74HC595は所望の動作をしてくれない、ということであります。前回はデータ1端子、クロック1端子を制御しただけした。さて複数端子を制御する方法は? Raspberry Pi Pico Python SDK をくまなく真面目?に読んでいたら分かります(本当か?)結論から書きましょう。

PIOは、ある端子から始まる連続した「端子群」を1度に操作できる

のであります。これは、データの出力に使用するOUT命令であっても、今回クロックなど制御端子の操作に使っている sideset 機能であっても同様です。今回は、制御端子をGP19番から「並び」の3端子、GP19, GP20, GP21 に割り当てています。そこで以下のようなパラメータをSM(ステートマシン)に教えてやれば、GP19番からの並びの端子を一気に操作してくれます。とっても楽。

sideset_base=Pin(19)
4桁7セグLED駆動プログラムの改造

前々回の回路構成に戻し、前々回のMicroPythonプログラムをPIO利用のコードで書き換えてみます。ソースコード全文は末尾にあります。まず、嬉しいのが、

前々回ソフトウエアでパルス幅を確保するのに使ったソフトループ

が無くせた、ということであります。当然、信号をON/OFFするための処理やら、シフトするための処理などCPUパワーを使ってソフトで始末していた部分は、SM側に「オフロード」されています。前々回のsend8bitData() 関数と比べるとその差が明らかです。今回は、パターンを反転し(セグメント端子がカソード側なので)、データをSMに渡すだけになっています。

SM自体の起動は、最初に1回

sm.active(1)

でおしまいです。SMはFIFOにデータが来るのをずっと「待って」おり、put()関数でデータが書きこまれるとPIOアセンブラで書かれたコード ”send74HC595″ を実行します。この関数自体はMicroPythonの中に埋め込まれていますが、メインのArmプロセッサが実行するわけでなく、実行はSMによります。

こうしてSMは 「74HC595 専用のインタフェース回路」として動作しつづけます。

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

MicroPython的午睡(17) ラズパイPico、I2CでシリアルEEPROM接続 へ進む

74HC595制御をPIOで行った7セグLED4桁表示プログラム
from rp2 import PIO, asm_pio
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 send8bitData(dat):
    tmp = ~dat & 0xFF
    sm.put(tmp)

@asm_pio(sideset_init=(rp2.PIO.OUT_LOW,rp2.PIO.OUT_LOW,rp2.PIO.OUT_LOW), out_init=rp2.PIO.OUT_LOW, out_shiftdir=PIO.SHIFT_RIGHT)
def send74HC595():
    pull()
    set(x, 7).side(0x4)  [1]
    label("loop")
    out(pins, 1)         [1]
    nop().side(0x5)      [1]
    nop().side(0x4)      [1]
    jmp(x_dec, "loop")
    nop().side(0x6)      [1]
    nop().side(0x4)      [1]
    nop().side(0x0)

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

sm = rp2.StateMachine(0, send74HC595, freq=400000, sideset_base=Pin(19), out_base=Pin(22))
sm.active(1)

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)

sm.active(0)