#-------------------------------------------------------
# 特設 ５章　PIOプログラミング　ソース一式 (2021/6/13)
#-------------------------------------------------------


#--------------------------------------
# リスト１  NOP命令だけを実行するプログラム
#--------------------------------------
import rp2

#----------------------------
# ステートマシン用コードの定義とアセンブル
#----------------------------
@rp2.asm_pio()                     # デコレータasm_pioを設定することで
def nop_prog():                    # アセンブラ（asm_pio）を呼び出し
    nop()                          # NOP命令だけからなるソースコード
                                   # (補足：NOP命令はMOV命令で代用されます)
#----------------------------

# ステートマシンの設定とオブジェクト生成
#  PIO[0]のStateMachine[0]を指定、バイナリコードはnop_progを指定
sm = rp2.StateMachine(0, nop_prog)

# ステートマシンの起動
sm.active(1)

#--------------------------------------




#--------------------------------------
# リスト２  SET命令を用いてLEDを点滅させるプログラム
#--------------------------------------
import rp2
from machine import Pin

# ステートマシン用コードの定義とアセンブル
#----------------------------
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink_LED():
    set(pins, 1)   # SET Pin  <- H
    set(pins, 0)   # SET Pin  <- L
#----------------------------

# LED用Pinの生成
led_pin = Pin(25, Pin.OUT)

# ステートマシンの設定とオブジェクト生成
# PIO_0のStateMachine_0を利用、命令はblink_LEDを指定、クロック2000Hz指定
sm = rp2.StateMachine(0, blink_LED, set_base=led_pin, freq=2000)

# ステートマシンの起動
sm.active(1)

#------------------------------------------------




#--------------------------------------
# リスト３ MicroPythonを利用してLED の点灯を制御するプログラム
#--------------------------------------
import utime
import rp2
from machine import Pin

# ステートマシン用コードの定義とアセンブル
#----------------------------
@rp2.asm_pio(out_init=rp2.PIO.OUT_LOW,out_shiftdir=rp2.PIO.SHIFT_RIGHT)
def control_LED():
    pull()          # FIFOからOSRへのデータ読み込み
    out(pins, 1)    # OSR から Pinへの1bitデータ書き込み(シフト方向は右)
#----------------------------

# LED用Pinの生成
led_pin = Pin(25, Pin.OUT)

# ステートマシンの設定とオブジェクト生成
# PIO[0]のStateMachine[0]を利用、命令はcontrol_LEDを指定
sm = rp2.StateMachine(0, control_LED, out_base=led_pin)

# ステートマシンの起動
sm.active(1)

# FIFOへの書き込み
while True:
    sm.put(1)  # LEDを点灯
    utime.sleep(1)
    sm.put(0)  # LEDを消灯
    utime.sleep(1)
#----------------------------------------------------------


#--------------------------------------
# リスト４　SW のON/OFF 状態をPIO から取得するプログラム
#           SWはGP0に接続(基板のビン番号1)
#--------------------------------------
import rp2
from machine import Pin
#
# ステートマシン用コードの定義とアセンブル
#----------------------------
@rp2.asm_pio(in_shiftdir=rp2.PIO.SHIFT_LEFT)
def sw_status():
    in_(pins, 1)    # Pinからデータを取得してISRに格納 (左シフト指定)
    push()          # ISRの値をRX FIFOに書き込み
#----------------------------

# SW入力用Pinオブジェクトの生成
sw_pin = Pin(0, Pin.IN, Pin.PULL_UP)                 # 入力としてPin0を選択、PULL_UPを設定

# ステートマシンの設定とオブジェクト生成
sm = rp2.StateMachine(0, sw_status, in_base=sw_pin)  # 入力Pinのベースを指定

# ステートマシンの起動
sm.active(1)

# PIO(RX FIFO)からSW状態を取得、表示ループ
prev_val = None
while True:
    val = sm.get()             # SWの状態をRX FIFOから取得
    if val != prev_val:
        print(val,end="")
        prev_val = val
#------------------------------------------------



#--------------------------------------
# リスト５　スイッチが押された際にIRQ を発行して
#           コールバック関数を呼び出すサンプル
#           SWはGP0に接続(基板のビン番号1)
#--------------------------------------
import utime
import rp2
from machine import Pin

#コールバック用関数の定義(PIOオブジェクトに登録前提)
def intr(pio):
    print("InterruptHandler")
    flags = pio.irq().flags()           # IRQ フラグを取得
    print("{:04x}: ".format(flags),end="")
    if flags & rp2.PIO.IRQ_SM0:         # IRQフラグの特定(bitフィールドでの比較)
        print("catch irq(0)")
    else:
        print("not irq(0)")
#
# ステートマシン用コードの定義とアセンブル
#----------------------------
@rp2.asm_pio(in_shiftdir=rp2.PIO.SHIFT_LEFT)
def sw_status():
    label("top")
    in_(pins, 1)          # PinからSWの状態を取得してISRに格納 (SHIFT _LEFT)
    mov(x,isr)            # ISRの値を汎用レジスタX にコピー
    jmp(not_x,"trigger")  # if x == 0  then goto "trigger" 
    jmp("top")            #            else goto "top"
    label("trigger")
    irq(0)                # IRQフラグ：0を指定してIRQを発生
    jmp("top")            # topのラベルにJMP（この行が無くても先頭行に戻ります）
#----------------------------

# SW入力用Pinの生成
sw_pin = Pin(0, Pin.IN, Pin.PULL_UP)   # 入力としてPin0を選択、PULL_UPを設定

# ステートマシンの設定とオブジェクト生成
# PIO[0]のStateMachine[0]
sm = rp2.StateMachine(0, sw_status, in_base=sw_pin, freq=2000)

# PIO_0にコールバック関数(intr)を登録
rp2.PIO(0).irq(intr)

# ステートマシンの起動
sm.active(1)

# MicroPythonメインループ
while True:
    utime.sleep(1)
    print("zzz...")
#------------------------------------------------




#--------------------------------------
# リスト６　IRQ によりステート・マシンの実行を同期させるサンプル
#--------------------------------------
import rp2
import utime

# コールバック関数の定義
def hdlr0(arg):
    print("called Hander0")

def hdlr1(arg):
    print("called Hander1")

def hdlr2(arg):
    print("called Hander2")

# 
# ステートマシン用コードの定義とアセンブル
@rp2.asm_pio()
def timer1sec():
     #  (1 + 7) + ((30 + 1) + (30 + 1)) * 32 + (1 + 7) (= 2000)
     set(x, 31)              [7]  #  x <- 1f
     label("wait_loop")
     nop()                   [30]
     jmp(x_dec,"wait_loop")  [30]
     irq(4)                  [7]    # IRQフラグ4のIRQを発行（＊）

@rp2.asm_pio()
def consumer():
     wait(1,irq,4)  # IRQフラグ4が1になるまで待つ
     irq(2)         # IRGフラグ2のIRQを発行

@rp2.asm_pio()
def asm_nop():
     nop

# ステートマシンの設定とオブジェクト生成
# PIO[0]のStateMachine[0]
sm0 = rp2.StateMachine(0, timer1sec, freq = 2000) 
sm0.irq(hdlr0)

# PIO[0]のStateMachine[1]
sm1 = rp2.StateMachine(1, consumer, freq = 2000) 
sm1.irq(hdlr1)

# PIO[0]のStateMachine[2]
sm2 = rp2.StateMachine(2,asm_nop, freq = 2000) 
sm2.irq(hdlr2) 

sm0.active(1)
sm1.active(1)
sm2.active(1)
utime.sleep(5)
sm0.active(0)
sm1.active(0)
sm2.active(0)
#--------------------------------------------------------------





#--------------------------------------
# リスト７　PIO を用いたロータリ・エンコーダ用ドライバ（encoder_pio.py）
#--------------------------------------
from rp2 import PIO
from rp2 import StateMachine
from rp2 import asm_pio

@asm_pio(in_shiftdir=PIO.SHIFT_LEFT)
def encoder():

    CLOCK_WISE = 1
    COUNTER_CLOCK_WISE = 2
 
    set(x, CLOCK_WISE)           # Xに右回転(CLOCK_SIZE)の定数を設定
    set(y, COUNTER_CLOCK_WISE)   # Yに左回転(COUNTER_CLOCK_SIZE)の定数を設定
 
    label("start")
    wait(0, pin, 0)        # クロックがLになるまで待つ
    jmp(pin, "on_cw")      # データ信号がHの場合、on_cw(右回転中)に分岐
  
    # Counter CW           # そうでないなら、CCW（左回転中）
    wait(1, pin, 0)        # クロックがHになるまで待つ
    jmp(pin, "ccw_ok")     # データ信号がHの場合、CCW OK（左回転OK）に分岐
    jmp("start")           # そうでないなら、左回転NGと判断して先頭に戻る
    label("ccw_ok")        # --- 左回転OKのフロー  ----
    mov(isr, y)            # ISRにY(COUNTER_CLOCK_WISE)を設定
    push()                 # ISRの値をRX FIFO に設定
    irq(0)                 # IRQフラグ：０でIRQを発生、コールバック関数呼び出し
    jmp("start")         
 
    # CW                   # --- 右回転中のフロー ---
    label("on_cw")         #
    wait(1, pin, 0)        # クロックがHになるまで待つ
    jmp(pin, "start")      # データ信号がHの場合、右回転はNGと判断して先頭に戻る
                           # そうでないなら右回転はOK
    mov(isr, x)            # ISR にX(CLOCK_WISE)を設定
    push()                 # ISRの値をRX FIFO に設定
    irq(0)                 # IRQフラグ：０でIRQを発生、コールバック関数呼び出し
    jmp("start")         
 
 
CLOCK_WISE = 1
COUNTER_CLOCK_WISE = 2

class RotaryEncoder:

    def __init__(self, clock, signal):
        self.counter = 0
        self.sm = StateMachine(0, encoder, freq = 2000, in_base = clock, jmp_pin = signal)
        self.sm.irq(self._handler)                  # コールバック関数の登録
 
    def set_active(self):
        self.sm.active(1)
 
    def set_inactive(self):
        self.sm.active(0)
 
    def get_value(self):
        return self.counter

    # コールバック関数の定義(軸が回転した時にIRQにより呼び出される)
    def _handler(self, ev):
        val = self.sm.get()   
        if val == CLOCK_WISE:
            #print("->")
            self.counter += 1
        elif val == COUNTER_CLOCK_WISE:
            #print("<-")
            self.counter -= 1
#--------------------------------------------------------------



#--------------------------------------
# リスト８  ロータリ・エンコーダ用ドライバを用いたテスト・プログラム
#--------------------------------------
from machine import Pin
from machine import PWM
import utime
import encoder_pio                                 # PIO版ロータリエンコーダドライバの読み込み

ENC_CLK_PIN = 2
ENC_SIG_PIN = 3
LED_PIN = 25

enc_clk  = Pin(ENC_CLK_PIN, Pin.IN, Pin.PULL_UP)   # クロック信号用入力の設定 (プルアップ有効化)
enc_sig  = Pin(ENC_SIG_PIN, Pin.IN, Pin.PULL_UP)   #  データ信号用入力の設定 (プルアップ有効化)
led = Pin(LED_PIN, Pin.OUT)                        # LED用出力の設定 
pwm = PWM(led)                                     # LEDの明るさ調整はPWMを利用
duty = 0xff
pwm.duty_u16(duty)

enc = encoder_pio.RotaryEncoder(enc_clk, enc_sig)  # ロータリエンコーダドライバの初期化
enc.set_active()                                   # ロータリエンコーダドライバの起動

prev_val = 0
while True:
    utime.sleep(0.1)
    val = enc.get_value()                    # ドライバから値を取得 
    print("diff:{:d}({:d}->{:d})".format(val - prev_val, val,prev_val))
    if val == prev_val:
        continue
    elif val > prev_val:                     # エンコーダの値が前回よりも大きくなっていたら
        if duty < 0x7fff:                    # dutyを２倍にする
            duty <<=  1
            duty += 1
    elif val < prev_val:                     # エンコーダの値が前回よりも小さくなっていたら
        if duty > 0:                         # dutyを1/2にする
            duty >>=  1

    print("duty:{:04x}".format(duty))
    pwm.duty_u16(duty)                       # dutyの値を設定してLEDの明るさを変える
    prev_val = val

enc.set_inactive()                           # ロータリエンコーダドライバの停止
#--------------------------------------------------------------








