前の記事でちょっとはマシになったが、モータはずっと動きっぱなしでステッピングモータの使い方とは言い難いものがある。
ステッピングモータに望まれるのは、決められたステップ数を動かすことだろうと思う。
なので本記事では、PIOでCPUと並列に決められたステップ数だけ動かすことを試みる。
PIOの制御構文
決められたステップ数だけ動かすためには以下の処理が必要となる。
- CPUからカウンタ値をPIOに渡すこと
- ステップ数に達するまでカウンタ値をデクリメント(-1すること)しながらループして繰り返すこと
- カウンタが0になったら停止すること
以下で各処理について説明する。
データ渡し
CPUからPIOにデータを渡すためには1bit単位の32bit FIFO(First In First Out=先入れ先出し方式)を用いる。
何故FIFOなのか…は恐らくだが以下のような理由だと思う。
- 単純にFIFOを用いる処理(SPI/I2C/UART etc...)が多い
- ハードウェアコストが安い(DPRAMに比べて)
FIFOは送信側と受信側が必要だが、今回はCPUが送信側、PIOが受信側となる。
データの流れはRP2040 Datasheetより次のブロック図参照
まずは -System→ の部分。CPU側の処理になる。
sm.put(data)
sm.put(data, length)
dataの部分には32bit以下の値を入れる。
FIFOが一杯の場合はputしてもブロックするようだ。
(実際にやった訳ではないが、コードを見る限り)
次に -PULL→ の部分。ここからPIO側の処理。
pull()
pull(noblock)
pull(ifempty)
データをTXFIFOからOSR(Output Shift Register)に1ワード(32bit)分ロードする。
標準でブロック(TXFIFOが入っていなければ待つ)する。
noblockでブロックされなくなる。TXFIFOが空の場合スクラッチレジスタxがOSRにコピーされる。
※PIOでは各ピンでフリーに使えるレジスタ(スクラッチレジスタ)がxとyの2つある
ifemptyは出力シフトカウンタがpull_threshに達しない限り何もしない。
ここを説明しておいて何だが、autopull=true,pull_thresh=32とすると、空になったら自動で32bit補充するのでpullする必要は無かったりする…
次に -OUT→ の部分
out(dist, bit_count)
distに対してbit_countの数だけ書き込む。
ループと条件分岐
ループと条件分岐は古き良き(?)labelとjmpを使う。
まぁlabelは良いとして、jmpは下記構文となる。
jmp(cnd, label)
labelはjmpするlabelということで良いとして、cndは把握しておく必要がある。
cnd | 説明 |
---|---|
not_x | xが0になったらjmp |
x_dec | xをデクリメントして0でなければjmp |
not_y | xが0になったらjmp |
y_dec | yをデクリメントして0でなければjmp |
x_not_y | xがyでなければjmp |
pin | StateMachineのコンストラクタでjmp_pin=で指定したピンが1ならばjmp(多分) |
not_osre | OSRが空でなければ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=True, pull_thresh=32)
def pulse_motor_2phase():
# wrap_target()~wrap()までが無限ループになる
wrap_target()
# osrからxに1ワード(32bit)コピーする
out(x, 32)
label("start_step")
# 1stepに4clock=2msかける
set(pins, 0b1001)[3]
set(pins, 0b0011)[3]
set(pins, 0b0110)[3]
set(pins, 0b1100)[3]
jmp(x_dec, "start_step")
set(pins, 0b0000)
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(0x100)
time.sleep(10)
実行するとある程度回ってから停止する。
sm.put(0x100)でステップ数を指定しており、ここの値を変えると回る量が変化する。
2021/5/8 修正
jmp(x_dec, "start_step")はxが0になるとjmpしないということに気づいたため、別途not_xで条件判定していた部分を削除しコードをシンプルにしたただし、現時点では4step毎になっているため分解能が1/4になっているのが難点。
次回はこの辺りを直してみようと思う。
参考情報
-
rp2.py
PIO側の定義が載っているソースコード。どんな引数が取れるかが分かる。重要。 -
rp2_pio.c
CPU側の実処理が分かるソースコード。重要。 -
Raspberry Pi Pico SDK documentation
C/C++のSDK documentation。こいつのMicroPython版があれば‥!!