これまでの実験結果から、2つの実用的なパルス検出手法の特徴は以下のようになる。
・2系統GPIO
・PIO
4nsecパルス検出、検出時間4.2usec、2つのGPIOが必要
12nsecパルス検出、検出時間0.4usec、1つのGPIOで対応
どちらの手法を選択するかは、アプリケーションから決まってくるだろう。ここで、目的を「一瞬の変化を捉え、さらに、その時間を測定する」とした場合を検討したい。
まず、2系統GPIO割込みでは、LOWエッジとHIGHエッジの割込みの時間差をナノ秒単位で計測することは困難である。一方のPIO割込みでは、レジスタのカウントダウンを用いることで測定が可能となる。その一例を記載する。
.program pioTIME
irq clear 0 ; 割込みフラグ0番クリア
.wrap_target ; ラップ開始
set x, 31 ; Xレジスタ最大値設定
set y, 31 ; Yレジスタ最大値設定
wait 0 gpio 2 ; GPIO2 LOW状態待ち
low_1st: ; 前段 16nsec x 32 = 512nsec未満
jmp pin high ; GPIO2 HIGH検出
jmp y-- low_1st ; Yレジスタのカウントダウン
low_2nd: ; 後段 16nsec x 32 = 512nsec未満
jmp pin high ; GPIO2 HIGH検出
jmp x-- low_2nd ; Xレジスタのカウントダウン
set x, 0 ; 1024nsec以上パルス対応
set y, 0 ; 1024nsec以上パルス対応
high: ; パルス検出(もしくは1024nsec以上経過)
in y, 32 ; 左シフト32bit
in x, 5 ; 左シフト5bit
push noblock ; YレジスタとXレジスタの内容をCPUへプッシュ
irq 0 ; 割込みフラグ0番セット
wait 1 gpio 2 ; GPIO2 High状態待ち(1024nsec以上パルス対応)
.wrap ; .wrap_targetへ自動ジャンプ
以下の項目がポイントとなる。
・2命令でHIGHエッジを検出するため、測定の基本単位は16nsec
・(32 + 32) x 16 = 1024nsec未満まで測定が可能
・ジャンプ命令”jmp pin high”で用いるpinのピン番号は、別途設定が必要
・YとXのレジスタの内容を受け取ったCPUは、デコードしてナノ秒に換算
・1024nsecを超えるパルスに対する例外処理が必要
C言語からアセンブラを制御するための初期化関数。
% c-sdk {
// パルス時間測定初期化関数(アセンブラ・プログラム名pioTIMEと整合)
// pio: pio0 or pio1, sm: ステート・マシン 0-4
// offset: ロードされるプログラムのメモリオフセット, pin: GPIOピン番号
void pioTIME_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);
// プログラム名pioTIMEと整合した関数名を定義
// 生成された関数からデフォルト構成を取得 (.wrap構成などを含む)
pio_sm_config c = pioTIME_program_get_default_config(offset);
// ステート・マシン構成から入力ピンとジャンプピンを設定
sm_config_set_in_pins(&c, pin);
sm_config_set_jmp_pin(&c, pin);
// ステート・マシン構成から入力時のISRシフト動作を設定
// 2番目の引数 true:右シフト, false:左シフト
// 3番目の引数 true:自動PUSH有効, false:自動PUSH無効
// 4番目の引数 自動PUSH有効時の閾値
sm_config_set_in_shift(&c, false, false, 0);
// 設定した構成をステート・マシンへロードし開始アドレスへ移動
pio_sm_init(pio, sm, offset, &c);
}
%}
以下は、パルス時間測定ステート・マシンを制御する関数群。
#define PICO_PULSE_IN_PIN 2 // パルス入力ピン
bool pioTimeFlag = false; // パルス検出フラグ
// パルス測定PIOハンドラー
void time_handler() {
pio_interrupt_clear(pio1, 0); // 割込み0番クリア
pioTimeFlag = true; // パルス検出フラグ設定
}
// パルス測定割込み設定
void setirq_TIME(PIO pio) {
// 割込みフラグPIO1_IRQ_0にハンドラーtime_handler登録
irq_set_exclusive_handler(PIO1_IRQ_0, time_handler);
// 割込みフラグPIO1_IRQ_0有効化
irq_set_enabled(PIO1_IRQ_0, true);
// 割込みソースpis_interrupt0の有効化(関数名irq0)
pio_set_irq0_source_enabled(pio, pis_interrupt0, true);
}
// パルス測定割込み動作初期化関数
void init_TIME(PIO pio, uint sm, uint offset, uint pin, long freq) {
// 初期化関数(アセンブラgpio.pio内で定義)
pioTIME_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_TIME(PIO pio, uint sm, bool state) {
pio_sm_set_enabled(pio, sm, state);
}
以下は、PIO割込みによるパルス検出のコア部分。タイマー割り込みなどに関する部分は、これまでに説明した内容を参考にして欲しい。
// パルス時間デコード関数(表示更新関数から呼び出し)
uint16_t decodeTime(uint32_t time) {
uint16_t t = 0;
uint8_t hi5bit = (detect_time >> 5 & 0xFF);
uint8_t lo5bit = detect_time & 0x1F;
if (hi5bit != 0xFF && lo5bit == 0x1F) t = (31 - hi5bit) * 16; // 0nsec以上、512nsec未満
else if (hi5bit == 0xFF) t = (31 - lo5bit) * 16 + 512; // 512nsec以上、1024nsec未満
else if (hi5bit == 0x00 && lo5bit == 0x00) t = 1024; // 例外処理:1024nsec以上
if (t == 0) t = 8; // 例外処理:8nsec以下
return t;
}
PIO pi1 = pio1; // プログラマブルI/O - 1
uint smPULSE_TIME = 3; // ステート・マシン - 3
// PIOパルス時間割込み動作初期化
setirq_TIME(pi1);
uint offsetTIME = pio_add_program(pi1, &pioTIME_program);
init_TIME(pi1, smPULSE_TIME, offsetTIME, PICO_PULSE_IN_PIN, 125000000.0); // 125MHz
// PIO割込みによるパルス検出
detect_number = 0; // 1秒間のパルス検出回数初期化
control_TIME(pi1, smPULSE_TIME, true); // ステート・マシン有効化
while (true) {
if (pioTimeFlag) { // パルス検出
if (pio_sm_is_rx_fifo_empty(pi1, smPULSE_TIME) == false) { // RX FIFO の状態確認
detect_time = pio_sm_get_blocking(pi1, smPULSE_TIME); // 16nsec単位のパルス時間取得
}
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
pioTimeFlag = false; // パルス検出フラグクリア
}
}
以下に、パルス時間測定の実験結果を示す。有機LED上部左は、周期1msecの1秒間のパルス検出数。下部左はPWM出力ナノ秒パルス幅で、下部右は検出パルス幅である。上部右は検出手法を示す数値。1024nsecから16nsec単位でPWMのパルス幅を繰り返し減少し、正確にパルス幅を測定している。
「一瞬の変化を捉え、さらに、その時間を測定する」には、PIO割込みが有用であることが分かった。しかし、8nsec以下のパルスに対しては、取りこぼしが発生する。そこで、1系統GPIOのLOWエッジのみの割込みと組み合わせたハイブリッド型を用いれば、PIO割込みがなくGPIO割込みがある場合は8nsec以下のパルスを検出したとことになる。これにより1つのGPIOだけを使ったハイブリッド型でナノ秒単位のパルス検出と、その時間測定が可能となった。
最後にハイブリッド型の実験結果を掲載する。左手前がパルスを供給するラズパイで、その奥がラズピコ。バックには、SSHによりラズパイを制御するTeraTerm画面。画面では、ラズパイのPythonによる1024nsecから4nsecまでのパルス発生を示している。有機LEDの下部左は、GPIO割込みによるパルス検出数であり、それ以外の数値は、これまでと同様。
16nsec単位でパルス幅を算出するラズピコは、12nsecのパルスが入力されると、16nsecまたは8nsecのパルス幅を示す。そして、8nsec以下になると取りこぼしが発生するものの、GPIO割込みは検出パルス数1,000を維持している。