はじめに
自分自身pioasmを勉強し始めたばかりでコード書き方が分からない。ということで人のコードを読んで勉強しよう。今回は以下のロータリーエンコーダを読むプログラムを読んでいく。
読んでみる
https://github.com/GitJer/Rotary_encoder/blob/master/pio_rotary_encoder.pio よりプログラムを引用する。コメントアウトは https://www.deepl.com/ja/translator にて翻訳した。
.program pio_rotary_encoder
.wrap_target
.origin 0 ; ジャンプテーブルは0から始まらなければならない。
; A'B'ABの4ビットの組み合わせの16ビットのそれぞれに対して正しいジャンプを含む
; A'B'ABで形成される4ビットの組み合わせ
; A = ロータリーエンコーダのピン_A の現在の読み取り値
; A' = ロータリーエンコーダのpin_Aの前回の読み値
; B = ロータリーエンコーダのpin_Bの現在の読み取り値
; B' = ロータリーエンコーダのpin_Bの前回の読み取り値
jmp read ; 0000 = 00から00へ = 読み取り値の変化なし
jmp CW ; 0001 = 00から01 = 時計回り回転
jmp CCW ; 0010 = 00から10まで = 反時計回りの回転
jmp read ; 0011 = 00から11まで = エラー
jmp CCW ; 0100 = 01から00まで = 反時計回り回転
jmp read ; 0101 = 01から01 = 読み取り値の変化なし
jmp read ; 0110 = 01から10 = エラー
jmp CW ; 0111 = 01から11 = 時計回り回転
jmp CW ; 1000 = 10から00まで = 時計回りに回転
jmp read ; 1001 = 10から01 = エラー
jmp read ; 1010 = 10から10 = 読み取り値の変化なし
jmp CCW ; 1011 = 10から11 = 反時計回りの回転
jmp read ; 1100 = 11から00 = エラー
jmp CCW ; 1101 = 11から01 = 時計と反対方向の回転
jmp CW ; 1110 = 11から10 = 時計回りに回転
jmp read ; 1111 = 11から11 = 読み取り値に変化なし
pc_start: ;プログラムのエントリポイントです。
in pins 2 ; AとBの現在値を読み出し、それを使って以前の値を初期化する。
; 前の値(A'B')を初期化するために使用する。
read:
mov OSR ISR ; OSRは(次の命令の後に)前の値を持つ2つのビットをシフトするために使用されます。
; 前の値を持つ2ビットをISRにシフトする。
out ISR 2 ; 前の値をISRにシフトする。これはまた
; ISRの他のすべてのビットを0にします。
in pins 2 ; 現在の値をISRにシフトする。
; ISRの16個のLSBは現在、0000000000A'B'ABを含みます。
; これは、A'B'AB番地へのjmp命令を意味します。
mov exec ISR ; ISRにエンコードされたjmpを実行する
CW: ; 時計回りの回転が検出された
irq 0 ; IRQで時計回りの回転を知らせる
jmp read ; AとBの現在値の読み出しにジャンプする
CCW: ; 反時計回りの回転を検出した
irq 1 ; IRQで逆時計回りの回転を知らせる
; jmp read ; AとBの現在値を読みに行く。
; jmpは.wrapのため必要ない。
; プログラムの最初の文がjmp readになる。
.wrap
PIOには8つの命令(しかない)がある。それぞれの命令について以下のページを参考にした。
read
概要
- 現在の GPIO の状態を読み取り、
0000 0000 0000 A'B'AB
に並び変える。 -
0 A'B'A B
にジャンプし、C言語で言うswitch文のように機能する。
mov OSR ISR
: OSR(レジスタ) に ISR(レジスタ) の値をコピーする。
out ISR 2
: ISR をクリア。同時に ISR に OSR の最下位 2bit をシフトする。
in pins 2
: GPIO の状態を ISR に左シフトする。1
mov exec ISR
: ISR の値を命令として実行する
このとき ISR の中は0000 0000 0000 A'B'AB
であり、表より JMP 命令であることがわかる。また0 A'B'A B
のアドレスに JMP することもわかる。
引用元 : https://hanya-orz.hatenablog.com/entry/2021/07/28/191910#JMP
ここで./build/pio_rotary_encoder.pio.h
を参照する。このファイルは一度プログラムをコンパイルすると生成されるファイルである。このファイルを参照することで、それぞれの命令のアドレスを確認できる。
// -------------------------------------------------- //
(中略)
static const uint16_t pio_rotary_encoder_program_instructions[] = {
// .wrap_target
0x0011, // 0: jmp 17
0x0015, // 1: jmp 21
0x0017, // 2: jmp 23
0x0011, // 3: jmp 17
0x0017, // 4: jmp 23
0x0011, // 5: jmp 17
0x0011, // 6: jmp 17
0x0015, // 7: jmp 21
0x0015, // 8: jmp 21
0x0011, // 9: jmp 17
0x0011, // 10: jmp 17
0x0017, // 11: jmp 23
0x0011, // 12: jmp 17
0x0017, // 13: jmp 23
0x0015, // 14: jmp 21
0x0011, // 15: jmp 17
0x4002, // 16: in pins, 2
0xa0e6, // 17: mov osr, isr
0x60c2, // 18: out isr, 2
0x4002, // 19: in pins, 2
0xa086, // 20: mov exec, isr
0xc000, // 21: irq nowait 0
0x0011, // 22: jmp 17
0xc001, // 23: irq nowait 1
// .wrap
};
(以下略)
pio_rotary_encoder.pio
内のラベルpc_start
より上のjmp read
jmp CW
は、アドレスとして0 A'B'A B
の取る値を網羅しており、C言語で言うswitch文のように機能している。アドレスとそのアドレスの命令を下表にまとめる。
アドレス | 命令 | アドレス | 命令 | アドレス | 命令 | アドレス | 命令 |
---|---|---|---|---|---|---|---|
0 0000 | jmp read | 0 0100 | jmp CCW | 0 1000 | jmp CW | 0 1100 | jmp read |
0 0001 | jmp CW | 0 0101 | jmp read | 0 1001 | jmp read | 0 1101 | jmp CCW |
0 0010 | jmp CCW | 0 0110 | jmp read | 0 1010 | jmp read | 0 1110 | jmp CW |
0 0011 | jmp read | 0 0111 | jmp CW | 0 1011 | jmp CCW | 0 1111 | jmp read |
CW
irq 0
: IRQ 0 のフラグを立てる。
Cファイル内で IRQ 0 の割り込み処理をする。
jmp read
: ラベルread
にジャンプする。
CCW
irq 1
: IRQ 1 のフラグを立てる。
Cファイル内で IRQ 1 の割り込み処理をする。
jmp read
: ラベルread
にジャンプする。
感想
mov exec <レジスタ>
で条件分岐ができることに感動した。条件分岐の方法としては JMP 命令に条件を追加する方法があるが、分岐の数によってはmov exec <レジスタ>
を用いたほうが良いだろう。
-
Cファイル内で
sm_config_set_in_shift(&c, false, false, 0);
より左シフトで入れる設定にされている。RaspberryPi SDK ↩