2台の周波数カウンタを同期!最大8チャンネルの1pps位相比較を実現する方法
本記事では、2台のマルチチャネル周波数アナライザ「CNT-104S」を同期させ、最大8チャンネルの1pps位相比較を実現する手法を解説します 。通常は独立してしまう2台のタイムスケールを、「ARMING on block」機能を用いて3ns未満の不確かさで同期させる具体的なセットアップ手順を詳しく紹介します 。さらに、外部UTC基準を用いたリアルタイム測定や、チャンネル数を6つに絞ることで標準50psの超高精度を実現する応用設定も網羅しています 。従来機4台分を代替する費用対効果の高いテスト環境の構築ノウハウに加え、実践的なPythonスクリプトのサンプルも公開しています 。
背景
CNT-104Sは、4つの並列入力信号を同時に測定できるマルチチャネル周波数アナライザ / タイムインターバルアナライザです。 この多機能な特徴は、同じタイムスケールで動作する4つの並列タイムスタンプを使用して、4つの1pps信号を位相比較するために使用できます。しかし、8つの異なる1pps信号を比較したい場合、単にCNT-104Sを2台重ねたり、19インチ2Uラックに2台並べてマウントしたりするだけでは不十分です。
- 1台のCNT-104Sでは、4つの1ppsパルスの位相比較が可能です。
- 2台のCNT-104Sを使用すれば8つの1ppsパルスを接続できますが、2台のCNTのタイムスケールは独立しており、同期されません。
- 以下の手順により、2台のCNT-104Sを8つの入力を備えた共通のタイムスケールに同期させることができます。
図2:8チャンネル位相比較のセットアップ
基本原理
共通のタイムスケールを生成するために、CNT-104Sの「ARMING on block」機能を使用します。このArming機能が設定されると、タイムスケールがゼロに設定されるArmingイベントが発生するまで、あらゆる入力トリガーイベントがブロックされます。2台のCNT-104Sは全く同じ瞬間にアーミングされ、両方のユニットで同時にタイムスケールがゼロにリセットされます。その後、8つのDUT(被測定デバイス)は、アーミングのタイムスタンプを基準にタイムスタンプが付与されます。「Arming on block」を選択すると、後続のArmingトリガーイベントは無視されます。
両方のユニットから測定データをダウンロードすると、同じタイムスケールを持つ8つのタイムスタンプを入手することができます。2つのユニット間のアーミング回路の遅延差や、2本の同じ長さのケーブル間の遅延差も含め、3ns未満の不確かさで得られます。
SCPIコマンドを使用してコントローラーからこの8チャネルの並列タイムスタンプを設定する場合、推奨される測定関数は「Function TIMESTAMPS A, B, D, E」であり、これは各チャネルの各トリガーイベントのタイムスタンプを単に返します。GUIまたはWebインターフェースを使用する場合、推奨される測定関数は「Time Interval Accumulated A, B, D, E」であり、測定データのファイルを保存してMS Excelにエクスポートし、後処理を行います。
このアプリケーションノートでは1ppsパルス間の位相を測定する方法を説明していますが、同じハードウェア構成を使用して最大300 MHzまでのRF信号を位相比較することも可能です。
- 入力周波数が20MHz未満の場合、すべてのトリガーイベントにタイムスタンプを付けることができます。
- 入力周波数が20MHzを超える場合、位相サンプルは50nsまでの設定されたサンプル間隔で取得されます。
詳細なセットアップ
図2に示すように接続します。
- 両方のユニットで「Recall Default(デフォルトの呼び出し)」を選択します(SETTINGS -> USER OPTIONS -> RECALL DEFAULTS -> YES)。
- 両方の周波数カウンタ/アナライザを同期測定用にセットアップします。
- すべての入力で、手動(Manual)トリガーレベルをパルス振幅の50%に設定します(SETTINGS -> INPUTS -> X -> TRIGGER LEVEL = MANUAL, ABSOLUTE TRIGGER LEVEL X = <任意のトリガーレベル値>、※Xは特定の入力)。
- 1ppsパルスに対して入力インピーダンスを50オームに設定します (SETTINGS -> INPUTS -> X -> IMPEDANCE = 50 OHM、※Xは特定の入力)。
- 1ppsパルスに対してDCカップリングを設定します (SETTINGS -> INPUTS -> X -> COUPLING = DC、※Xは特定の入力)。
- 測定関数(Measurement function)をTime Interval Accumulated A,B,D,Eに設定します(SETTINGS -> MEASUREMENT -> FUNCTION -> TIME/PHASE -> TIME INTERVAL ACC. A, B, D, E)。
- サンプル間隔(Sample Interval)を0に設定します (SETTINGS -> MEASUREMENT -> SAMPLE INTERVAL = 0)。
- 任意のサンプル数(Sample Count)を設定します (SETTINGS -> MEASUREMENT -> SAMPLE COUNT = <任意のサンプル数値>)。例:86401サンプルは1日間の記録、604801は1週間の記録、2592001サンプルは1ヶ月(30日)の記録を意味します。
- ブロックアーミングを設定します。(SETTINGS -> ARMING -> SOURCE = EA, ARM ON -> Block)。
- CNT-104Sのパルス出力を1秒パルス用に設定します。(SETTINGS -> PULSE OUTPUT -> ON, 1s period)。
- HOLDモードに入ります。
- SINGLEモードで測定を開始します(最大1000万個の1pps値をチャネルあたり115日間サンプリング可能)。
- これにより、相対タイムスケールが両方のカウンターで同時に0から開始されます(アーミングイベント)。
- 測定開始後、パルス出力をオフにします(これは測定には影響せず、設定したサンプル数が終了するまで中断することなく継続されます)。
- 結果ファイルをMS Excelなどに保存/ストリーミングし、結果を処理します。
- 結果ファイルには2つの列があります。Aのスタートトリガーのタイムスタンプと、タイムインターバルの結果値TI(A-X)です。
- アーミングイベント(タイムスケールの開始点)はタイムスタンプT0=0となります。
- 各サンプルにおいて、DUT 1はタイムスタンプT1を持ちます。DUT 2、3、4はそれぞれT1+TI(A-B)、T1+TI(A-D)、T1+TI(A-E)のタイムスタンプを持ちます(TI(x-y)はタイムインターバル値)。
- DUT 5はタイムスタンプT5を持ちます。DUT 6、7、8はそれぞれT5+TI(A-B)、T5+TI(A-D)、T5+TI(A-E)のタイムスタンプを持ちます。
CNT-104Sユニット間のタイムスケールの不確かさは3ns未満(標準的には1ns)です。入力のアーミングエッジからいずれかのユニットが「トリガー準備完了」になるまでの時間は5ns未満ですが、ユニット間の差は3ns未満です。(各CNT-104Sの個別のチャネルオフセットの不確かさは25ps rms未満であり、これはアーミングの不確かさに比べて無視できるレベルです )。
拡張機能 – 不確かさ50ps(標準)の6チャネルタイムスケール
前述の8チャネルの例における2台のCNT-104S間のタイムスケールの不確かさは3ns未満です。しかし、Ext Arm入力の代わりにCNT-104SユニットのA、B、D、E入力のいずれかをアーミングに使用すると、タイムスケールの不確かさを3ns未満から標準で50psに大幅に減らすことができます。これはDUTの数を犠牲にして行われるものであり、この場合DUTは8個ではなく最大6個となります。
ハードウェアのセットアップは前のものとよく似ていますが、以下の例では、カウンター/アナライザ#1の入力Eと、カウンター/アナライザ#2の入力Aをアーミング入力として使用します。 これにより、アーミングから測定入力までのタイムスキューは、各CNT-104S内で25ps rms未満に減少します。
図4:6チャンネル並行タイムスタンプ測定のセットアップ
(タイムスケールの不確かさ:典型値 50 ps、8チャンネル時の典型値 1 ns に対して向上)
結論
CNT-104S マルチチャネル周波数アナライザは、MS Excel、Matlab、Stable32などのデータファイルの後処理の有無にかかわらず、同期テストに共通するいくつかの測定に使用できます。
- 4つまたは8つの同期ソースの位相比較
- 周期/パルス幅(1pps)の検証、キャリブレーション、調整
- ワンダー・パラメータ(TIE)
- 短期安定度テスト(ADEV)
- 周波数/周期の安定性の検証(マイクログリッチの発見)
- PLLパラメータテスト
CNT-104S タイマー/カウンター/アナライザの特徴
- 4つのDUTの並列テスト用のマルチチャネル周波数アナライザ
- 高分解能と高スピード
- TIEおよびADEV測定機能の内蔵
プログラミングサンプル(Pyrhon script)
# This example demonstartes how to timestamp up to 8 x 1 PPS signals in parallel
# using 2 synchronized CNT-104S
# The setup is:
#
# Common 10 MHz reference -> CNT-104S #1 Ext Ref IN
# -> CNT-104S #2 Ext Ref IN
#
# CNT-104S #1 Pulse Output -> power splitter -> CNT-104S #1 Ext Arm IN
# -> CNT-104S #2 Ext Arm IN
#
# DUT1 -> CNT-104S #1 input A
# DUT2 -> CNT-104S #1 input B
# DUT3 -> CNT-104S #1 input D
# DUT4 -> CNT-104S #1 input E
# DUT5 -> CNT-104S #2 input A
# DUT6 -> CNT-104S #2 input B
# DUT7 -> CNT-104S #2 input D
# DUT8 -> CNT-104S #2 input E
import pyvisa as visa
from time import sleep
# helpers
def cnt_connect_and_reset(resource_manager, ipv4: str):
cnt = resource_manager.open_resource(f’TCPIP::{ipv4}::hislip0′)
cnt_id = cnt.query(‘*IDN?’)
print(f’Connected to: {cnt_id}’)
# reset to the known default state
cnt.write(‘*RST;*CLS’)
return cnt
def cnt_check_errors(cnt, operation_name: str):
error = cnt.query(‘:SYST:ERR?’)
error_code = int(error.split(‘,’)[0])
if error_code != 0:
raise RuntimeError(f'{operation_name} failed: {error}’)
def cnt_configure(cnt, configuration: str):
cnt.write(f’:SYST:CONF “{configuration}”‘)
cnt_check_errors(cnt, ‘Configuration’)
def cnt_set_format(cnt, configuration: str):
cnt.write(f’:SYST:CONF “{configuration}”‘)
cnt_check_errors(cnt, ‘Configuration’)
def cnt_start_measurement(cnt):
# enable operation complete (OPC) bit in the status register
cnt.write(f’*ESE 1′)
# start the measurement
cnt.write(‘:INIT;*OPC’)
def cnt_operation_complete(cnts):
complete = True
for cnt in cnts:
stb = cnt.read_stb()
if (stb & 32) == 0:
complete = False
return complete
def cnt_extract_timestamps_from_ascii(response: str):
# ASCii format with timestamps is:
# <VAL0>,<TS0>,<VAL1>,<TS1>,…
# so all odd elements in the list are timestamps
float_array = response.split(‘,’)
timestamps = float_array[1::2]
return [float(t) for t in timestamps]
def cnt_cleanup(cnts):
for cnt in cnts:
cnt.close()
# main script
if __name__ == ‘__main__’:
# Create VISA resource manager. In some cases you may need to specify dll/so
# file of VISA implementation, for example:
# rm = visa.ResourceManager(‘librsvisa.so’)
rm = visa.ResourceManager()
# CNT-104S that will provide common arming via Pulse Output
cnt_1 = cnt_connect_and_reset(resource_manager=rm, ipv4=”10.40.15.23″)
# 2nd CNT-104S
cnt_2 = cnt_connect_and_reset(resource_manager=rm, ipv4=”10.40.15.26″)
print(‘Configuring measurement…’)
# configure common arming signal
cnt_configure(
cnt_1,
‘PulseOutputMode=PulseGenerator;’
‘PulseOutputPeriod=1.0;’
‘PulseOutputWidth=0.01’
)
cnts = [cnt_1, cnt_2]
inputs_to_measure = [‘A’,’B’,’D’,’E’]
sample_count = 100
for cnt in cnts:
# measurement configuration
cnt_configure(
cnt,
f’Function=Timestamps {“,”.join(inputs_to_measure)};’
f’SampleCount={sample_count};’
‘SampleInterval=0.0’
)
# arming configuration
cnt_configure(cnt, f’StartArmingSource=EA; ArmOn=Block’)
# inputs configuration
for input in inputs_to_measure:
cnt_configure(
cnt,
f’TriggerMode{input}=Manual;’
f’AbsoluteTriggerLevel{input}=0.3;’
f’Coupling{input}=DC;’
f’Impedance{input}=50 Ohm’
)
# set ASCii format (for example code simplicity) with timestamps
# note: in real life binary form (PACKed) is preferable most of the time
cnt.write(‘:FORM ASC;:FORM:TINF ON’)
print(‘Measurement configuration completed’)
# start measurement
for cnt in cnts:
cnt_start_measurement(cnt)
print(‘Measurement started’)
# prepare timestamp storage
series = []
for _ in range(0, len(cnts) * len(inputs_to_measure)):
series.append([])
# fetch data in real time
measurement_complete = False
while True:
if not measurement_complete:
measurement_complete = cnt_operation_complete(cnts)
# fetch all series one by one
nothing_fetched = True
dut_index = 0
for cnt in cnts:
for input in inputs_to_measure:
samples=cnt.query(f’:FETCH:ARR? MAX, {input}’).strip()
if len(samples) > 0:
timestamps = cnt_extract_timestamps_from_ascii(samples)
series[dut_index].extend(timestamps)
dut_index += 1
if nothing_fetched:
if measurement_complete:
print(‘Measurement completed’)
break;
# pace FETCh queries if no data is ready yet
sleep(1.0)
# print results
print(‘Results:’)
separator = ‘, ‘
series_names = [f’DUT #{i + 1}’ for i in range(0, len(series))]
header = separator.join(series_names)
print(header)
data = list(zip(*series))
for row in data:
print(separator.join([f'{t:0.17g}’ for t in row]))