Raspberry Pi Picoでモータを動かす⑤（PIOで動かす③）

[Raspberry Pi Picoでモータを動かす④（PIOで動かす②）](https://qiita.com/fmax/items/ed0b3c386da686ee221c)では停止が4step毎となっており、分解能が下がっていた。
今回はこれをどうにかしてみる。

### とりあえず止める
まずは単純に各ステップ毎にカウンタをデクリメントし、止められるようにしてみる。
といってもPIOではADD,SUB,MUL,DEVなどの四則演算命令は使えない。
expressions(+,-,*,/等)というのがあって、一見計算できそうだがこれは多分マクロ的なものかと思う。
使ってると思われる[サンプルプログラム](https://github.com/raspberrypi/pico-micropython-examples/blob/master/pio/pio_ws2812.py#L1-L52)の下記のコードを見てもそんな感じ。
まぁ演算命令が無いしね…。
（四則演算の方法を御存知の方がいらっしゃったら是非ご連絡を！）

```
def ws2812():
    T1 = 2
    T2 = 5
    T3 = 3
    wrap_target()
    label("bitloop")
    out(x, 1)               .side(0)    [T3 - 1]
    jmp(not_x, "do_zero")   .side(1)    [T1 - 1]
    jmp("bitloop")          .side(1)    [T2 - 1]
    label("do_zero")
    nop()                   .side(0)    [T2 - 1]
    wrap()
```


ということで気を取り直してどうやってデクリメントするか。
美しくは無いが、jmp命令で下記のように書いてみた。

```
from rp2 import PIO, StateMachine, asm_pio
import time
from machine import Pin

# シフト方向を指定しautopull有効、pull_thresh=32にする
@asm_pio(set_init=(PIO.OUT_LOW, PIO.OUT_LOW, PIO.OUT_LOW, PIO.OUT_LOW), out_shiftdir=PIO.SHIFT_LEFT, autopull=False)
def pulse_motor_2phase():
    # wrap_target()～wrap()までが無限ループになる
    wrap_target()
    label("start")
    # 非駆動時OFFにする場合
    #set(pins, 0b0000)
    pull()
    out(x, 32)

    label("loop")
    
    jmp(x_dec, "step1")
    jmp("start")
    label("step1")
    set(pins, 0b1001)[3]
    
    jmp(x_dec, "step2")
    jmp("start")
    label("step2")
    set(pins, 0b0011)[3]
    
    jmp(x_dec, "step3")
    jmp("start")
    label("step3")
    set(pins, 0b0110)[3]
    
    jmp(x_dec, "step4")
    jmp("start")
    label("step4")
    set(pins, 0b1100)[3]
    
    jmp("loop")

    wrap()
# 0番のpioを使用、2kHz(1Clock=500us)でGPIO2をベースにする
# 分周の関係でfreq >= 1908Hzらしい
sm = StateMachine(0, pulse_motor_2phase, freq=2000, set_base=Pin(2))
sm.active(1)
sm.put(0x1)
time.sleep(10)

```

単純にjmpコマンドで指定できるデクリメントを使用している。
jmp(x_dec, "stepx")は0のときは何もしないでスルーするので、label("stepx")との間にjmp("start")を入れ0になったらスタートに戻ってpull()待ちするようにした。なお、引数1つのjmpは無条件に引数のラベルに飛ぶ。
ループのどこでpull()待ちに入るのか分かりにくかったので、autopullから通常のpull()に変更している。

これで動作させてみるとちゃんと指定したステップで止まっていることが確認できた。

しかしコレ、大きな問題がありステップ途中で止まるにも関わらず**2回目の動作も最初のステップから始まる**のだ。
まぁまだ**使い物にならない**。
なので頑張ってなんとかする。

### 何とかしたバージョン
そして下記が頑張って何とかしたバージョン。

```
from rp2 import PIO, StateMachine, asm_pio
import time
from machine import Pin

# シフト方向を指定しautopull有効、pull_thresh=32にする
@asm_pio(set_init=(PIO.OUT_LOW, PIO.OUT_LOW, PIO.OUT_LOW, PIO.OUT_LOW), out_shiftdir=PIO.SHIFT_LEFT, autopull=False)
def pulse_motor_2phase():
    # wrap_target()～wrap()までが無限ループになる
    set(x, 0)
    wrap_target()
    
    label("loop")
    
    jmp(x_dec, "step1")
    pull()
    out(x, 32)
    label("step1")
    set(pins, 0b1001)[3]
    
    jmp(x_dec, "step2")
    pull()
    out(x, 32)
    label("step2")
    set(pins, 0b0011)[3]
    
    jmp(x_dec, "step3")
    pull()
    out(x, 32)
    label("step3")
    set(pins, 0b0110)[3]
    
    jmp(x_dec, "step4")
    pull()
    out(x, 32)
    label("step4")
    set(pins, 0b1100)[3]
    
    jmp("loop")

    wrap()
# 0番のpioを使用、2kHz(1Clock=500us)でGPIO2をベースにする
# 分周の関係でfreq >= 1908Hzらしい
sm = StateMachine(0, pulse_motor_2phase, freq=2000, set_base=Pin(2))
sm.active(1)
sm.put(0x1)
time.sleep(1)
sm.put(0x2)
```

一応続きからステップを刻む。
sm.put(0x0)で1step,sm.put(0x1)で2step...となる。

今回はインストラクションメモリ(32ワード)の限界にぶち当たっていることに気づかずかなりの時間がかかった。
インストラクションメモリはオーバーすると**何の警告もなくオーバーした時点からループして戻る**という恐ろしい仕様だった。
PIOは実行前に命令数カウントする癖を付けた方が良いのかもしれない。

これで片側の回転指示は概ね出来た。次回はCW/CCWの選択切り替えでもやってみようかと思う。