Raspberry Pi Picoでモータを動かす②(ユニポーラ2相,1-2相励磁)でとりあえずステッピングモータを動かしたが、あのコードではモータを動かしている間はCPUが専有されてしまう。
そのためRaspberry Pi Picoの特徴的な機能であるPIO(Programmable IO)を使用して並列処理を行うことにする。
ベースとなる動きはユニポーラ2相励磁。
PIO(Programmable IO)とは
PIOとは一言でいうとIO用コプロセッサ(補助処理装置)。
Raspberry Pi Pico C/C++ SDKの「3.1.1 Background」を流し読みして超ざっくり意訳すると
普通のPCではハード直に叩かない。(というかSPI/UART/I2C/GPIOみたいなハード直叩きのピンが出てない)
ラズパイ(4Bとか)や普通のマイコンはIOついてて概ね上手くいくけど、選択肢が限られる(特定のIOは特定のピンしか駄目とか)し要らんIOまで一杯付いてくるので高くなりがち。
じゃあソフトウェアでGPIO叩いてプロトコル実現する(Bit-Banging)にも、プロトコルが低速なものじゃないと無理だし、IOの種類が多くなると割り込みやタイミング制御で訳わかんなくなる。
FPGA/CPLDでやると並列に動くし機能は完璧なんだけど、コスト高になるしプログラミングが独特で大変。
という訳でRaspberry Pi PicoにはIO用にすごく小さくて安いステートマシン(コプロセッサ)を8個も載せてやったのさ!
とかなんとか
実際インストラクションメモリが32、命令の種類は9個ととてもシンプルで小さいが、効果は絶大。
Raspberry Pi Picoで最も特徴的な機能であると思う。
PIOをMicroPythonから使う
C/C++の場合にはアセンブラで.pioファイルとやらを作ってメイン側から呼び出したコードを作ってリンクする(やったことないので適当)らしいがMicroPythonではasm_pioというのを通して書く。
で、出来たコードがこちら
from rp2 import PIO, StateMachine, asm_pio
import time
from machine import Pin
@asm_pio(set_init=(PIO.OUT_LOW, PIO.OUT_LOW, PIO.OUT_LOW, PIO.OUT_LOW))
def pulse_motor_2phase():
# wrap_target()~wrap()までが無限ループになる
wrap_target()
# 1stepに4clock=2msかける
set(pins, 0b1001)[3]
set(pins, 0b0011)[3]
set(pins, 0b0110)[3]
set(pins, 0b1100)[3]
wrap()
# 0番のpioを使用、2kHz(1Clock=500us)でGPIO2をベースにする
# 分周の関係でfreq > 1907Hzらしい
sm = StateMachine(0, pulse_motor_2phase, freq=2000, set_base=Pin(2))
sm.active(1)
time.sleep(10)
sm.active(0)
time.sleepしてるところに他の処理を書くとモータ動かしながら他のことができる。
つまりコンソールからモータ始動/停止を指示したりできる。
これで少し実用的になった。
キモはこの行
set(pins, 0b1001)[3]
意味はpinsつまりPin(2)を始まりにして
3bit=Pin(5),2bit=Pin(4),1bit=Pin(3),0bit=Pin(2)
となっているのを0b1001で1clock=500usで全ビット同時にセットしている。
その後[3]で3clock=1500usのディレイを入れており合計2msになって、前に作ったものと等価っぽくなる。
(厳密には以前のものは各出力ごとに1clockのディレイがあったはずなので、完全な等価ではない)
注意点
注意点はset命令が16bitであること。一見ナンノコッチャだがまぁ下を見て欲しい。
set命令は以下のような構造になっている。
Bit: | 15-13 | 12-8 | 7-5 | 4-0 |
---|---|---|---|---|
set | 0b111 | Delay/side-set | Destination | Data |
15-13bitの0b111はsetを意味するOpcode、Destinationはpinsの場合0b000となる。
ここで注意したいのはDelay/side-setが共用かつ5bitしかないこと
つまりDelayは最大でも31まで、しかもsidesetが入っている場合はその分減るようだ。
sidesetが使われているか使われていないかの判定は初期化時にされている。
@asm_pio(sideset_init=(PIO.OUT_LOW, PIO.OUT_LOW, PIO.OUT_LOW, PIO.OUT_LOW))
と記載すると4bit分予約される。それもjmpやnopに至るまでの全ての命令のsidesetに。
つまり、上記だとdelayは全く使えなくなるということになってしまう。事実上の排他機能だなこりゃ。
sidesetは全ての命令で使えて便利だが、インストラクションメモリを切り詰める必要がない場合は使わないほうが自由度が高い。
自分も最初は全部sidesetで書けば良いじゃん!とか思っていたときがありました…。
使うsidesetのbit数と使えるdelay数の関係はRaspberry Pi Pico(RP2040)のPIOについて備忘録(MicroPython)の「sidesetとdelay」の項で検証しているのでありがたく参照させて頂く。
食い合うのは食い合うのだが単純な使用ビット数では無いようだ。
また、Dataも5bitしかないので6bit以上は使えない。
今回のコードで0bxxxxのところの頭にビットを足していくと2bit足した段階で動かなくなる
ビットが多い場合の処理は今後の課題行き。
sideset使えば増やせるけど…ねぇ。
参考資料
正直言ってPIO(特にMicroPython)は資料が少なくかなり大変だった。
備忘録的な意味で数少ない参考資料を列挙しておく。
-
Raspberry Pi Python SDK
本家本元のPython SDKの資料。コレの記載が少ないばっかりに苦労する、が最低限目を通さないと始まらない。 -
Raspberry Pi Pico C/C++ SDK
事実上これも読まないと訳が解らなくなるところがある。命令セットの部分とか。悲しい。 -
Raspberry Pi Picoの仕様書を読んでみる(3)
非常に分かりやすい。PIOで何かやろうとするなら最初に読んでおくのが良いかも -
Raspberry Pi Pico(RP2040)のPIOについて備忘録(MicroPython)
情報量が多い。「設定できる動作周波数の下限」の部分は割とハマる所。
でもCPUはMax133MHzなのにデフォルト125MHzなのね。
仮に設定いじって133MHz動作にすると下限が約2029Hzとなるので注意が必要かも。 -
MicroPython的午睡(15) ラズパイPico、プログラマブルIOの威力
ベーシックなコードでClock周りの挙動が分かる -
ラズパイマガジン 2021年夏号 Raspberry Pi Picoを大解剖 非同期制御「PIO」で距離を測る p48~p51
雑誌だけあって分かりやすい。が、もーちょっと情報が欲しかった。4ページのために¥2,453使ってしまったよ…。