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のみで実現できる。