2系統GPIO割込みによって、「一瞬の変化を捉える」という目的は達成できたものの、複数信号の変化を捉える場合は、その倍のGPIOを確保しなければならない。そこで、ラズピコの最大の特長である「プログラマブルI/O」(以下、”PIO”)を使ったパルス検出の検討を進める。

PIOのアセンブラには「待機命令WAIT」が用意されている。”wait 0 gpio 2″と記述すれば、GPIO2がLOWになるまで待機する。この命令によって、パルス検出は容易に実現できる。以下は、パルス検出のアセンブラ。ここでは、ファイル名をgpio.pioとしている。

.program pioPULSE
    irq clear 1      ; 割込みフラグ1番クリア
.wrap_target         ; ラップ開始
    wait 0 gpio 2    ; GPIO2 LOW状態待ち
    wait 1 gpio 2    ; GPIO2 HIGH状態待ち 
    irq 1            ; パルス検出(割込みフラグ1番セット)
.wrap                ; .wrap_targetへ自動ジャンプ

続いて、gpio.pioの中にC言語からアセンブラを制御するための初期化関数を定義する。”% c-sdk {〈出力情報〉%}”と記述することで、アセンブラと出力情報がヘッダ・ファイルgpio.pio.hに書き込まれる。

関数名pioPULSE_program_initとpioPULSE_program_get_default_configをアセンブラのプログラム名pioPULSEと整合させることが、ここでは重要である。

% c-sdk {
// パルス検出初期化関数(アセンブラ・プログラム名pioPULSEと整合)
// pio: pio0 or pio1, sm: ステート・マシン 0-4
// offset: ロードされるプログラムのメモリオフセット, pin: GPIOピン番号
void pioPULSE_program_init(PIO pio, uint sm, uint offset, uint pin) {
   // GPIOピンpinをpioに割り当てる
   pio_gpio_init(pio, pin);
   // pio, sm, pin, 連続ピン数1で定義されたピンの入出力設定(true:出力, false:入力)
   pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
   // プログラム名pioPULSEと整合した関数名を定義
   // 生成された関数からデフォルト構成を取得 (.wrap構成などを含む)
   pio_sm_config c = pioPULSE_program_get_default_config(offset);
   // ステート・マシン構成から入力ピンを設定
   sm_config_set_in_pins(&c, pin);
   // 設定した構成をステート・マシンへロードし開始アドレスへ移動
   pio_sm_init(pio, sm, offset, &c);
}
%}

以下は、パルス検出ステート・マシンを制御する関数群。

#define PICO_PULSE_IN_PIN 2 // パルス入力ピン
bool pioPulseFlag = false;  // パルス検出フラグ

// パルス発生PIOハンドラー
void pulse_handler() {
    pio_interrupt_clear(pio0, 1); // 割込み1番クリア
    pioPulseFlag = true;          // パルス検出フラグ設定
}

// パルス検出割込み設定
void setirq_PULSE(PIO pio) {
    // 割込みフラグPIO0_IRQ_1にハンドラーpulse_handler登録
    irq_set_exclusive_handler(PIO0_IRQ_1, pulse_handler);
    // 割込みフラグPIO0_IRQ_1有効化
    irq_set_enabled(PIO0_IRQ_1, true);
    // 割込みソースpis_interrupt1の有効化(関数名irq1)
    pio_set_irq1_source_enabled(pio, pis_interrupt1, true);
}

// パルス検出割込み動作初期化関数
void init_PULSE(PIO pio, uint sm, uint offset, uint pin, long freq) {
    // 初期化関数(アセンブラgpio.pio内で定義)
    pioPULSE_program_init(pio, sm, offset, pin);
    // ステート・マシンのクロック設定
    pio_sm_set_clkdiv(pio, sm, clock_get_hz(clk_sys) / freq);
    // パルス入力・プルアップ
    gpio_pull_up(PICO_PULSE_IN_PIN);
}

// パルス検出ステート・マシン有効化・無効化
void control_PULSE(PIO pio, uint sm, bool state) {
    pio_sm_set_enabled(pio, sm, state);
}

以下は、PIO割込みによるパルス検出のコア部分。ステート・マシンを最大の125MHzで起動している。タイマー割り込みなどに関する部分は、前項の「ポーリング手法」や「GPIO割り込み」を参考にして欲しい。

PIO pi0 = pio0;      // プログラマブルI/O - 0
uint smPULSE_IN = 2; // ステート・マシン - 2

// PIOパルス検出割込み動作初期化
setirq_PULSE(pi0);
uint offsetPULSE = pio_add_program(pi0, &pioPULSE_program);
init_PULSE(pi0, smPULSE_IN, offsetPULSE, PICO_PULSE_IN_PIN, 125000000.0); // 125MHz

// PIO割込みによるパルス検出
detect_number = 0; // 1秒間のパルス検出回数初期化
control_PULSE(pi0, smPULSE_IN, true); // ステート・マシン有効化

while (true) {
    if (pioPulseFlag) { // パルス検出
        gpio_put(PICO_PULSE_DETECT_PIN, HIGH); // パルス検出ピンHIGH
        detect_number++; // パルス検出数インクリメント
        if (oneSecFlag) { // 1秒経過
            multicore_fifo_push_blocking((uint32_t)detect_number); // 表示更新(実測150nsec)
            detect_number = 0; // パルス検出数クリア
            oneSecFlag = false; // 1秒経過フラグクリア
            update_PWM(PICO_PULSE_OUT_PIN); // パルス幅更新(1024nsecから16nsec循環)
         }
         sleep_us(1); // 1usec休止(オシロスコープ確認用)
         gpio_put(PICO_PULSE_DETECT_PIN, LOW); // パルス検出ピンLOW
         pioPulseFlag = false; // パルス検出フラグクリア
    }
}

実験結果から、最小パルス幅16nsecでパルス検出数1,000となったため、PIO割込みの有用性を確認できた。2系統GPIO割込みに比べて、1つのGPIOのみで実現できる。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です