PIO

PIO は I2C,SPI,UART のようなインタフェースをプログラムできる多目的ハードウェア。 ステートマシンとマイクロコードによって、高速なインターフェースをプログラムできる。

2つの PIO があり、各 PIO は 4 つのステートマシンを持っている。ステートマシンは 最大 32bit(それでも GPIO より多い)のデータバスと, 最大 5 本の制御信号(side set)を持つことができる。データバス、制御信号は指定した GPIO から連番で取得される(飛び飛びには取れない)。

ステートマシンは最大 32 命令を格納できる。X と Y のスクラッチパッド(レジスタ/変数)を保有する。 命令は遅延を指定できる(最大 5bit。ただし side set を使うと、その分減る)。 シーケンサの実行速度を指定する(落とす)事ができる。最速は 125MHz(8ns)/step である。

制御信号は「side set」と呼ばれる。各ステートでの値を一括して指定する。 特定の信号だけを選択的に変更することはできない。 また、値をレジスタ値などで間接的に設定することはできない(即値指定)。 side set と命令の遅延指定は合わせて 5bit になる。そのため例えば、制御信号を 2 本使うと、遅延指定は 3bit(0~7)になる。 制御信号は全てのステートで指定するが、opt を指定する(そのために 5bit のうちの最上位 1bit がさらに消費される)と、そのステートで制御信号値を指定するかどうかをステート毎に設定することができる(opt を使えば、異なった制御信号状態から、それを保持したまま JMP で同じコードに合流できる?)。

(side set 未指定/未使用)
	nop	   ; side 値は不要(書けない)

.side_set 1
	nop side 1 ; side 値を省略できない
	nop side 0 ;
	nop side 0 ;
	nop side 1 ;

.side_set 1 opt
	nop side 1 ;
	nop side 0 ;
	nop        ; side 値を省略できる(0 を維持)
	nop side 1 ;

.side_set 2
	nop side 0 ; <side-set-base-pin +0>L : <side-set-base-pin +1>L
	nop side 2 ; <side-set-base-pin +0>L : <side-set-base-pin +1>H
	nop side 3 ; side set をバスと見立てて値で指定する(H : H)

.side_set 3
	nop side 7 [3] ; ok
	nop side 7 [4] ; NG … 遅延 bit は 5 - 3 = 2bit(0~3)

.side_set 0   	   … 指定できる遅延は 0~31 (5bit)
.side_set 1	   … 指定できる遅延は 0~15 (4bit)
.side_set 1 opt	   … 指定できる遅延は 0~7  (3bit)

FIFO は 32bit。32bit 単位でシフトレジスタ(OSR,ISR)に(から)移動する。移動は PULL,PUSH で手動で行うか、自動で行わせる。 自動で行わせる場合、何 bit 使ったら移動させるかを指定する。 例えば 8bit を指定した場合、8bit を in/out すると次のデータに移動する(上位 24bit は使われない)。 自動で行わせた場合、手動の 2 倍の速度が出せる(最高 125MHz)。

sm_config_set_out_shift(&conf,  // state-machine config
			true,	// shift right
			true,	// auto-pull
			8) ;	// pull threshold

auto-pull が false の時(2 命令)
	pull		; 手動で pull して OSR に
	out pins, 8	; OSR から 8bit 出力

auto-pull が true の時(1 命令)
	out pins, 8	; OSR が empty なら OSR に自動で pull してから 8bit 出力
例えば FIFO に入れる bit 数が一定ではない時に手動で行う(とか?)
	uint8_t *data ;

    	pio_sm_put(pio,sm,(uint32_t)1023u)	; 32bit
    	pio_sm_put(pio,sm,(uint32_t)*data++)	; 8bit
	
	pull		;
	out  x, 32	; ループ回数(32bit)
loop:	
	pull 		;
	out  pins, 8	; 8bit 出力
	jmp  x-- loop   ;

割り込みは IRQ0 と IRQ1 があり、C のプログラムで受けることができる。 PIO 用の割り込み制御用の C 関数は「なぜか」ない(2021/02/07 現在)。

static inline void pio_irq_clear(PIO pio, uint sm) {
       pio->irq = 1u << sm ;
}
static inline void pio_irq0_set_enabled(PIO pio, uint sm, bool enabled) {
       pio->inte0 = (pio->inte0 & ~(1u << (sm + 8))) | (!!enabled << (sm + 8)) ;
}
static inline void pio_irq1_set_enabled(PIO pio, uint sm, bool enabled) {
       pio->inte1 = (pio->inte1 & ~(1u << (sm + 8))) | (!!enabled << (sm + 8)) ;
}

割り込みは C で割り込みハンドラ(引数、戻り値とも void な関数)を設定する。 割り込みの中で、割り込み要求をクリアする。 ステートマシンの <irq_num> や rel の要/不要は未調査(とりあえずこれで動く)。 割り込み要因を伝えるためで、何でも良いと思うのだが?(どうやって受け取る?)

割り込みハンドラ
static void irqPioHandler() {
        pio_irq_clear(pio0, 0) ;		// 割り込みクリア。pio,sm を即値指定(ハンドラに引数がないので)
}

初期化
	irq_set_exclusive_handler(PIO0_IRQ_0, irqPioHandler) ;	   // 割り込みハンドラ登録
	irq_set_priority(PIO0_IRQ_0, PICO_DEFAULT_IRQ_PRIORITY) ;  // プライオリティ設定
	irq_clear(PIO0_IRQ_0) ;	     				   // クリア
	irq_set_enabled(PIO0_IRQ_0, true) ;			   // 許可
	
	pio_irq0_set_enabled(pio0, 0, true) ;	// IRQ0 を enable
 	pio_irq1_set_enabled(pio0, 0, false) ;	// IRQ1 を disable
	pio_irq_clear(pio0, 0) ;              	// IRQ をクリア

割り込み発生
	irq 0 rel	; IRQ 発生

jmp 命令以外に .wrap_target と .wrap でプログラムをループさせることができる。 jmp 命令は 1 clock かかるが .warp はゼロ遅延で実行される。

loop:
	nop      side 0 ;
	jmp loop side 1 ;

.warp_target
	nop      side 0 ;
	nop      side 1 ;
.wrap

ストール(待ち)が発生する命令がある。 FIFO の空き/補充待ち、入力ピンの変化待ち、割り込み応答待ちがある。

in		; auto-push 設定時
out		; auto-pull 設定時
pull block
wait
irq wait

1 命令あたりの実行速度を設定できる(分周器)。(n + f/256) 分の 1 に設定できる。 n clock 毎に実行されるが、さらに 256 clock 中 f clock 実行が遅延する。 あくまでシステムクロック(125MHz = 8ns)単位の処理なので、f を設定するとジッタが発生する。
io_sm_set_clkdiv_int_frac(pio, sm, 2, 0)   ; … 8ns * 2 = 16ns 毎に実行される。ジッタは発生しない

io_sm_set_clkdiv_int_frac(pio, sm, 2, 128) ; … 8ns * 2 = 16ns と 8ns * 3 = 24ns の交互(128/256 = 1/2)毎に実行される
			       	      	       20ns 毎ではない(平均は 20ns)。ジッタが発生する

FIFO へは DMA で送受信できる。PIO を高速で動作させる場合で for ループによる転送では間に合わない場合は特に有効。 config 経由で設定する項目と、チャンネルに直接設定する項目がある。 CMakeLists.txt で hardware_dma のライブラリのリンクを指定する必要がある。

CMakeLists.txt
	target_link_libraries(${PROJECT_NAME}
		pico_stdlib
		hardware_pio
		hardware_dma
	)


設定(送信)… 固定設定を初期化時に 1 度だけセットする
	dma_channel_config conf = dma_channel_get_default_config(ch) ;	// DMA channel no.
    
	channel_config_set_read_increment(&conf,			// DMA config
					  true) ;			// is increment on read

	channel_config_set_write_increment(&conf,			// DMA config
					   false) ;			// is increment on write
				       
    	channel_config_set_transfer_data_size(&conf,			// DMA config
					      DMA_SIZE_8) ;		// size (uint8_t)

    	channel_config_set_dreq(&conf,					// DMA config
			        pio_get_dreq(pio,			// PIO no.
					     sm,		 	// state-machine no.
					     true)) ;			// is TX (true:TX, false:RX)

        dma_channel_set_config(ch,	 		// DMA channel no.
			       &conf,			// DMA config
			       false) ;			// start immediately

        dma_channel_set_write_addr(ch,			// DMA channel no.	   
        		           &pio->txf[sm],	// write address
			           false) ;		// start immediately
			       
転送開始
        dma_channel_transfer_from_buffer_now(ch,	// DMA channel no.
        		  	             data,	// read address (unit8_t*)
        		  	  	     size) ;	// number of transfers

記載情報の無断転載を禁止します。
間違いや追加すべき情報があれば twitter @boyahina まで是非。