chakokuのブログ(rev4)

日々のごった煮ブログです

RP2040のPIOを引き続き調べる

PIOでロータリエンコーダのドライバを作ってみたいと思い調査
使い慣れているMicroPythonでPIOを叩きたい。やりたいのは、エンコーダからの2本のパラレル信号を受けたいのだが。。簡単に処理するには、2つのIOを束ねてバス幅2bitとして扱いたい。英語表現では、multiple consecutive pins (複数の連続するピン)となって、RP2040のSDKではIOのbaseに加え、バス幅を指定できるようなのだが、、MicroPythonからバス幅を指定できるかどうか。。
MicroPython版のPIOライブラリはあまり仕様の説明がないような。。。
ソースを見ながら推測する(CによるAPI仕様があまり分かっておらず)
Gitのソース*1を参考にしながらrp2.StateMachineで指定可能な引数は以下だろう

prog, freq, jmp_pin, 
in_base, out_base,set_base, sideset_base,
in_shiftdir, out_shiftdir,
push_thresh, pull_thresh,    

かなりできなさそうな印象。

間違っているかもしれんが、Pi Pico C/C++SDKに、sm_config_set_out_pinsの仕様が書かれていて、out_baseがPin番号で、out_countがビット幅と思ってるのだが。。それとも、out_countとは、単にout_baseからのオフセット値??

4.1.15.3.13. sm_config_set_out_pins

static void sm_config_set_out_pins (pio_sm_config *c,
uint out_base, uint out_count)

Picoをロジアナにしてしまうサンプルがあり、ここでもバス幅をcountで指定しているので、入力のIOを束ねることができるようだ。

uint16_t capture_prog_instr = pio_encode_in(pio_pins, pin_count);

rp2040-logic-analyzer/rp2040-logic-analyzer.c at main · gamblor21/rp2040-logic-analyzer · GitHub

これをMicroPythonから設定したいのだが、、

多分無理そうなのでこれ以上は追わない。忘れるので覚書

smオブジェクトへの操作

put FIFO(TX FIFO)への書き込み
get FIFO(RX FIFO)からの読み込み

PIOアセンブラ内で使える命令(主に出力)

pull FIFO(TX FIFO)からシフトレジスタ(OSR)への読み込み
out シフトレジスタ(OSR)から任意のリソースへのシフト

PIOアセンブラ内で使える命令(主に入力)

in シフトレジスタ(ISR)をシフトしてから任意のリソースから読み込み
push シフトレジスタ(ISR)からFIFO(RX FIFO)への書き込み

データ操作

set 任意のリソースへの任意の値の書き込み
mov 任意のリソース間の値の移動

実行制御

jmp 条件付きjump

jmp命令で使える条件

"not_x" (!x) ,  "x_dec"  (x--),    "not_y" (!y),  "y_dec" (y--), "x_not_y"  ( x != y),
"pin":,  "not_osre"  (OSR),

設定できる4系統の入出力

  • in_base(アセンブラ内ではout pinsとして指定(例:in_(pins, 1)),
  • out_base(アセンブラ内ではin pinsとして指定(例:out(pins, 1)),,
  • set_base, (set命令により操作されるPIN (例:set(pins, 0)))
  • sideset_base,(アセンブラ内でsideset操作するとsideset_baseで指定したPINから出力(例:.side(0x1))

分からないのは、、movの時はどの設定が使われる??
仕様書を確認すると、movでsourceがpinsの場合はin_base、destがpinsの場合はout_baseとあった。合理的な設計だ。。

さらに調べていると、、出力はバスとして束ねられるが、入力は束ねられのか?

  • static void sm_config_set_out_pins (pio_sm_config *c, uint out_base, uint out_count)
  • static void sm_config_set_in_pins (pio_sm_config *c, uint in_base)

PIOレジスタ仕様書にも、OUT_COUNTのフィールドがあるけど、IN_COUNTというのがない。だから、、2本の信号を受け取りたかったら、ステートマシンを2つ動かす必要がある??本当??しかし、、ロジアナサンプルでも入力を束ねていたから、それができないはずはない。暗黙で入力はバス幅32なのでは??とも思えるのだが。。そんなはずはないか。

ロジアナサンプルで入力バスを指定しているのは以下の関数

 inline static uint pio_encode_in(enum pio_src_dest src, uint value) {
     valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_IN_SRC));
     return _pio_encode_instr_and_src_dest(pio_instr_bits_in, src, value);
 }

inlineで展開されていて、最後は以下の関数のようだ

inline static uint _pio_encode_instr_and_args(enum pio_instr_bits instr_bits, uint arg1, uint arg2) {
    valid_params_if(PIO_INSTRUCTIONS, arg1 <= 0x7);
#if PARAM_ASSERTIONS_ENABLED(PIO_INSTRUCTIONS)
    uint32_t major = _pio_major_instr_bits(instr_bits);
    if (major == pio_instr_bits_in || major == pio_instr_bits_out) {
        assert(arg2 && arg2 <= 32);
    } else {
        assert(arg2 <= 31);
    }
#endif
    return instr_bits | (arg1 << 5u) | (arg2 & 0x1fu);
}

参照先
https://raspberrypi.github.io/pico-sdk-doxygen/pio__instructions_8h_source.html

これは、PIOアセンブラなのか??
あらためてソースを読み直すと、、下記となっており、PIO用の命令を生成して、.instructionsに渡している。だから、PIOの命令を使ってバス幅を指定しているようだ。そんな命令あったか?? それにしても興味深い。。PIO命令で入力のバス幅を指定する??そんなの可能か?

    uint16_t capture_prog_instr = pio_encode_in(pio_pins, pin_count);
    struct pio_program capture_prog = {
            .instructions = &capture_prog_instr,
            .length = 1,
            .origin = -1
    };
    uint offset = pio_add_program(pio, &capture_prog);

IN命令を読み直すと、Bit countフィールドを指定すると、in_baseで指定したPinから指定分をシフトしながら読み込んでくれると書かれていた。(in/outのbitってどういう意味かイマイチわからんと思って放置していた)結局そういうことか。

IN always uses the least significant Bit count bits of the source data. For example, if PINCTRL_IN_BASE is set to 5, the
instruction IN 3, PINS will take the values of pins 5, 6 and 7, and shift these into the ISR. First the ISR is shifted to the left
or right to make room for the new input data, then the input data is copied into the gap this leaves. The bit order of the
input data is not dependent on the shift direction.

これは実にありがたい仕様だ。。

IN命令は、常にソースデータの最下位ビットカウントビットを使用します。 たとえば、PINCTRL_IN_BASEが5に設定されている場合、IN 3, PINSの命令を実行すると、ピン5、6、および7の値を取得し、これらをISRにシフトします。 最初にISRが左または右にシフトされて新しい入力データ用のスペースが確保され、次に入力データがこの隙間にコピーされます。 入力データのビット順序はシフト方向に依存しません。

調べてないけど、outも同じような考え方だろうか。。あっちはOUT_COUNTがあるのだが。。
out命令はin命令とちょっと違うようだ。

A 32-bit value is written to Destination: the lower Bit count bits come from the OSR, and the remainder are zeroes.

初めにちゃんとマニュアル読まなかったから遠回りしたけど*2、なんとかドライバ開発ができそうな気がしてきた。応用の効く仕様で実装されていることに感謝!!

MicroPython PIOサンプル
https://github.com/micropython/micropython/tree/master/examples/rp2


安定版だと以下のエラーが出るので、unstable版で試す

=== @asm_pio(in_shiftdir=PIO.SHIFT_LEFT)
=== def ingo():
===     in_(pins, 2)
===     push()
===
===
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "rp2.py", line 228, in asm_pio
  File "rp2.py", line 36, in __init__
ImportError: no module named 'array'

*1:https://github.com/micropython/micropython/blob/master/ports/rp2/rp2_pio.c

*2:しかもなんとなく分からない仕様を放置していた