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 wait1 命令あたりの実行速度を設定できる(分周器)。(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 まで是非。