PocketSDR APにてbladeRFを使いたい

category: gnss
tags: pocketsdr qzss sdr

PocketSDR AP

GitHubにて公開されているPocketSDRは、ソフトウェア無線(SDR: software defined radio)にて測位衛星信号の受信と処理を行うパッケージであり、Maxim MAX2771を用いたハードウェアと、Pythonにて記述されたソフトウェア群(AP: application)からなります。PocketSDRには、このハードウェアにて観測したサンプルデータが添付されていて、実際の信号処理を容易に体験できます。

PocketSDRハードウェア

PocketSDRには、CAD(computer aided design)ソフトウェアAutodesk EAGLE用の回路図エディタファイル(拡張子sch)と、配線パターンファイル(拡張子brd)が添付されています。このEAGLEは一定条件にて無償で利用できます。EAGLEにて、schファイルとbrdファイルを読み込めば、基板発注に必要なガーバーデータやメタルマスクデータを出力できます。

私も、リフロー炉(基板ヒーター)を準備して、PocketSDRハードウェア作成を目指していますが、現在、そのMAX2771が入手できないのです。末尾に何もつかないMAX2769にも興味がありますが、こちらも入手できない状況です。

しかしながら、PocketSDR APは、特定ハードウェアに依存しないように作成されています。そこで、手持ちSDRハードウェアのbladeRF x40に測位衛星アンテナを接続して信号を観測し、そのPocketSDR APでの処理を試みます。

結果は微妙ですが、最低限の確認はできました。いずれはPocketSDRハードウェアを利用したいです。

機器構成

ここで使用したハードウェア機材は次の通りです。信号スプリッタの1ポートを利用して、今回の実験を行います。残りポートには、bynav C1-FS受信機と、u-blox F9P受信機・Drogger VRSC受信機を接続しています。

  • Nuand bladeRF x40 (300 MHz-3.8 GHz, 12-bit 40MSPS, 40KLE Altera Cyclone 4E FPGA)
  • DFRobot LattePanda Alpha 800s (Intel Core m3-8100y, 8GB LPDDR3) + NVMe SSD 1TB
  • Beitian BT-200(L1帯・L2帯・L5帯・L6帯対応アンテナ)
  • 同軸ケーブル20 m
  • InStock Wireless GPS410(4分岐スプリッタ)

PocketSDR and bladeRF

LattePandaには、Ubuntu 20.04.4 LTSをインストールしています。ここで使用したbladeRFは古い製品ですが、現行製品のbladeRF 2.0 microでも同様に利用できると思います。また、bladeRF-cli -iにて確認したbladeRFのバージョンは次の通りです。オプション-iは対話モード利用を表します。

bladeRF> version

  bladeRF-cli version:        1.8.0-git-c8320f71-ppafocal
  libbladeRF version:         2.2.1-git-c8320f71-ppafocal

  Firmware version:           2.3.2
  FPGA version:               0.11.0 (configured by USB host)

GPS L1 C/A信号の観測

GPS L1 C/A信号の中心周波数は、1.57542 GHzです。はじめに、bladeRFに設定する標本化周波数と帯域幅を決めなければなりません。PocketSDRのサンプルには、ベースバンド標本化を行ったものと、Low-IF(intermediate frequency)標本化を行ったものとがあります。ここでは、ベースバンド標本化を行ったサンプルL1_20211202_084700_4MHz_IQ.binの諸元(標本化周波数4 MHz、帯域幅2.5 MHz)を参考にして、標本化周波数を4 MHzに、また、帯域幅を3 MHzに設定します。bladeRFにて設定できる帯域幅は、1.5 MHz、2.5 MHz、2.75 MHz、3 MHz、3.84 MHz、5 MHz、5.5 MHz、6 MHz、7 MHz、8.75 MHz、10 MHz、12 MHz、14 MHz、20 MHz、または、28 MHzです。

実際には、AGC(automatic gain control)をオフにした上で、利得を最大の60 dBに設定して、直流オフセット補正を行い、0.1秒間の記録を行います。ここではbladeRF-cli -iにより対話的にbladeRFを設定しますが、これをファイルに保存して-sオプションでバッチ実行することも可能です。

set frequency rx 1.57542g
set bandwidth rx 3m
set samplerate rx 4m
set agc rx off
set gain rx 60
cal lms
cal dc rx
rx config format=bin n=400k file=ch.bin
rx start
rx wait

すぐにbladeRF>のプロンプトが出て、観測ファイルch.binが保存されます。

bladeRF観測データの読み込み

この観測ファイルをPocketSDR APに読み込むために、pythonディレクトリにあるコードsdr_func.pypocket_trk.pyを修正します。修正する関数はread_data()です。

sdr_func.py

def read_data(file, fs, IQ, T, toff=0.0, sdrname='pocketsdr'):
    off = int(fs * toff * IQ)
    cnt = int(fs * T * IQ) if T > 0.0 else -1 # all if T=0.0

    if sdrname == 'pocketsdr':
        dt   = 'int8'  # data type
        div  =  1      # divisor of data
        qsgn = -1      # Q sign inverted in MAX2771
    elif sdrname == 'bladerf':
        dt   = 'int16' # data type
        div  = 256     # divisor of data
        qsgn = 1       # Q sign
    else:
        raise ValueError('Please specify appropriate SDR.')

    raw = np.fromfile(file, dtype=dt, offset=off, count=cnt)

    if len(raw) < cnt:
        return np.array([], dtype='complex64')
    elif IQ == 1: # I-sampling
        return np.array(raw/div, dtype='complex64')
    else: # IQ-sampling
        return np.array(raw[0::2]/div + raw[1::2] * 1j*qsgn/div, dtype='complex64')

pocket_trk.py

def read_data(fp, N, IQ, buff, ix, sdrname = 'pocketsdr'):
    if sdrname == 'pocketsdr':
        dt   ='int8'   # data type
        ds   =  1      # data size
        div  =  1      # divisor of data
        qsgn = -1      # Q sign inverted in MAX2771
    elif sdrname == 'bladerf':
        dt   = 'int16' # data type
        ds   =   2     # data size
        div  = 256     # divisor of data
        qsgn =   1     # Q sign
    else:
        raise ValueError('Please specify appropriate SDR.')

    if fp == None:
        raw = np.frombuffer(sys.stdin.buffer.read(N * IQ * ds), dtype=dt)
    else:
        raw = np.frombuffer(fp.read(N * IQ * ds), dtype=dt)

    if len(raw) < N * IQ:
        return False
    elif IQ == 1: # I
        buff[ix:ix+N] = np.array(raw/div, dtype='complex64')
    else: # IQ
        buff[ix:ix+N] = np.array(raw[0::2]/div + raw[1::2]*1j*qsgn/div, dtype='complex64')
    return True

PocketSDRはIQ(in-phase, quadrature)成分をそれぞれ2ビットで標本化する一方、bladeRFは12ビットで標本化します。しかし、bladeRFでの観測では、必ずしも全ビットが活かされてはいませんでした。

PocketSDR APは観測データをcomplex64にて読み込みますので、ここで読み込んだ整数値をそのままbuffに設定しても構いません。しかし、PocketSDR APユーザインターフェースにてきれいに表示するために、bladeRFの観測データを256で割ってbuffに格納します。

そして、-sdrオプションにてbladeRFをSDRとして選択できるよう、pocket_*.pyを修正します。

GPS L1 C/A信号の処理

このようにして得た観測ファイルに対して、電力スペクトル密度確認、信号捕捉、信号追尾の処理を行います。

はじめに、この電力スペクトル密度とヒストグラムを確認します。PocketSDRのsampleディレクトリにて./pocket_psd.py -f 4 -IQ -h -sdr bladerf ch.binを実行します。

bladeRF L1 C/A power spectrum density and histogram

下記のPocketSDRでのスペクトルと比較すると、このスペクトルには明確な信号部分がありませんでした。

PocketSDR L1 C/A power spectrum density and histogram

bladeRFにて観測した信号のIQ(in-phase, quadrature)成分ともにガウス形をしていて、良好に動作しているように見えます。しかし、このヒストグラムにおいては、信号があればゼロ成分が現れない方が望ましくて、PocketSDRのヒストグラムにもゼロ成分はありませんでした。Nuandのフォーラムでは、bladeRFのRX VGA2(reception variable gain amplifier 2)の利得を10 dBにまで低下させて、LNA(low noise amplifier)を付加することが議論されています。私の環境では、VGA2の利得を10 dBまで下げるとヒストグラムが直流のみになったので、このVGA2利得を最大値の28 dBに設定しました。衛星信号観測にbladeRFを用いるには、LNAの付加が必要なようです。

bladeRFでは、特に、直流オフセット補正が重要です。前述のcal lmscal dc rxを実行しないと、下図のように、ヒストグラム上に顕著な直流オフセットが現れます。MacにbladeRFを接続して、データ取得を試みましたが、この直流オフセット補正コマンド実行時にSegmentation Faultが発生して、補正できませんでした。

PocketSDR L1 C/A power spectrum density and histogram

次に、L1 C/A信号の信号捕捉を行います。GPS PRN(pseudo random noise)番号1から32を対象にして、pocket_acq.pyを実行します。

./pocket_acq.py -f 4 -prn 1-32 -sdr bladerf ch.bin
SIG= L1CA, PRN=   1, COFF=  0.92000 ms, DOP=  3115 Hz, C/N0= 33.8 dB-Hz
SIG= L1CA, PRN=   2, COFF=  0.43575 ms, DOP=  4107 Hz, C/N0= 46.2 dB-Hz
SIG= L1CA, PRN=   3, COFF=  0.87875 ms, DOP=  -424 Hz, C/N0= 34.0 dB-Hz
SIG= L1CA, PRN=   4, COFF=  0.32325 ms, DOP=  2525 Hz, C/N0= 33.5 dB-Hz
SIG= L1CA, PRN=   5, COFF=  0.00100 ms, DOP=  5000 Hz, C/N0= 39.7 dB-Hz
SIG= L1CA, PRN=   6, COFF=  0.01025 ms, DOP=  1701 Hz, C/N0= 47.6 dB-Hz
SIG= L1CA, PRN=   7, COFF=  0.98725 ms, DOP=  4268 Hz, C/N0= 38.1 dB-Hz
SIG= L1CA, PRN=   8, COFF=  0.97525 ms, DOP=  4034 Hz, C/N0= 34.0 dB-Hz
SIG= L1CA, PRN=   9, COFF=  0.33500 ms, DOP=   330 Hz, C/N0= 41.3 dB-Hz
SIG= L1CA, PRN=  10, COFF=  0.61250 ms, DOP=  4454 Hz, C/N0= 33.4 dB-Hz
SIG= L1CA, PRN=  11, COFF=  0.42475 ms, DOP=  3940 Hz, C/N0= 46.4 dB-Hz
SIG= L1CA, PRN=  12, COFF=  0.68625 ms, DOP=   527 Hz, C/N0= 38.8 dB-Hz
SIG= L1CA, PRN=  13, COFF=  0.94275 ms, DOP= -5000 Hz, C/N0= 34.2 dB-Hz
SIG= L1CA, PRN=  14, COFF=  0.00600 ms, DOP=  3484 Hz, C/N0= 34.5 dB-Hz
SIG= L1CA, PRN=  15, COFF=  0.22575 ms, DOP= -4452 Hz, C/N0= 34.2 dB-Hz
SIG= L1CA, PRN=  16, COFF=  0.75900 ms, DOP=  1574 Hz, C/N0= 35.4 dB-Hz
SIG= L1CA, PRN=  17, COFF=  0.63100 ms, DOP= -1015 Hz, C/N0= 38.5 dB-Hz
SIG= L1CA, PRN=  18, COFF=  0.92475 ms, DOP=  1880 Hz, C/N0= 34.4 dB-Hz
SIG= L1CA, PRN=  19, COFF=  0.01675 ms, DOP=  -933 Hz, C/N0= 42.7 dB-Hz
SIG= L1CA, PRN=  20, COFF=  0.55075 ms, DOP=  3894 Hz, C/N0= 44.1 dB-Hz
SIG= L1CA, PRN=  21, COFF=  0.98650 ms, DOP=  5000 Hz, C/N0= 34.2 dB-Hz
SIG= L1CA, PRN=  22, COFF=  0.31575 ms, DOP=  2504 Hz, C/N0= 34.6 dB-Hz
SIG= L1CA, PRN=  23, COFF=  0.18825 ms, DOP=  3006 Hz, C/N0= 33.8 dB-Hz
SIG= L1CA, PRN=  24, COFF=  0.77775 ms, DOP=  5000 Hz, C/N0= 34.2 dB-Hz
SIG= L1CA, PRN=  25, COFF=  0.47925 ms, DOP=  3984 Hz, C/N0= 33.8 dB-Hz
SIG= L1CA, PRN=  26, COFF=  0.04125 ms, DOP=  -508 Hz, C/N0= 34.4 dB-Hz
SIG= L1CA, PRN=  27, COFF=  0.17050 ms, DOP=  2453 Hz, C/N0= 33.8 dB-Hz
SIG= L1CA, PRN=  28, COFF=  0.52325 ms, DOP=   496 Hz, C/N0= 34.1 dB-Hz
SIG= L1CA, PRN=  29, COFF=  0.13950 ms, DOP=   507 Hz, C/N0= 34.3 dB-Hz
SIG= L1CA, PRN=  30, COFF=  0.28775 ms, DOP=  3535 Hz, C/N0= 34.3 dB-Hz
SIG= L1CA, PRN=  31, COFF=  0.32300 ms, DOP= -1984 Hz, C/N0= 34.1 dB-Hz
SIG= L1CA, PRN=  32, COFF=  0.06225 ms, DOP= -1980 Hz, C/N0= 34.4 dB-Hz
TIME = 2.778 s

PocketSDR L1 C/A acquisition with bladeRF

複数GPS衛星からの信号捕捉ができました。

次に、信号追尾を行うために、観測時間を30秒程度にまで延ばした別データch1.binを用意しました。しかし、ここで数秒から数十秒に1回の割合で、大きくヒストグラムが崩れる課題に気づきました。高須先生は、Ubuntu 20.04 LTSでは信号取りこぼしがあるので、Ubuntu 18.04 LTSを使用されています

OS入れ替えは次の機会にして、ここではUbuntu 20.04 LTSを用います。./pocket_trk.py -f 4 -prn 32 -sdr bladerf ch1.bin -pにて、L1 C/A信号を追尾します。

PocketSDR L1 C/A tracking with bladeRF

なんとか航法データを復号できました。

次に、この観測データch.binを用いてスナップショット測位を行います。この観測は、2022-03-04 14:31 UTC(世界協定時: coordinated universal time、日本時間では23:31)頃に行いました。ここで必要になる航法データ20220304o.nav以前と同様の手順で得ました。

$ ./pocket_snap.py -f 4 -nav 20220304o.nav ch.bin -ts 2022-03-04-14:31:00 -sdr bladerf
2022/03/04 14:31:00.000   -90.000000000    0.000000000 -6378137.000    5    0
TIME (s) = 4.938

残念ながら、失敗です。時刻を微調整しても、また、広島の大まかな座標を与えても、座標を得られませんでした。

みちびきL6D信号の抽出

みちびきL6帯信号の中心周波数は、1.17645 GHzです。ここでは、標本化周波数を12 MHzに、帯域幅を10 MHzにそれぞれ設定して、L6帯信号を標本化し、その結果を観測ファイルl6.binに保存します。bladeRF-cli -iにて入力するコマンドは次の通りです。

set frequency rx 1.17645g
set bandwidth rx 10m
set samplerate rx 12m
set agc rx off
set gain rx 60
cal lms
cal dc rx
rx config format=bin n=100m file=l6.bin
rx start
rx wait

この電力スペクトル密度とヒストグラムを求めます。

./pocket_psd.py -f 12 -IQ -h -sdr bladerf l6.bin

PocketSDR L6-band spectrum density with bladeRF

次に、このL6D信号の捕捉を行います。

PocketSDR L6-band signal acquisition with bladeRF

この結果からは、L6D信号を見つけられませんでした。

まとめ

GPSやみちびきの信号観測に汎用ソフトウェア無線機bladeRFを使用しました。その過程で、利得不足、信号取り込みビットが有効に活用されていない、直流オフセット補正が求められる、などの課題がありました。データ取りこぼしについては、データ収集パソコンのOSによるものかもしれません。

GPS L1 C/A信号の捕捉と追尾はでき、また航法データの一部を取得できましたが、測位には至りませんでした。

今後の追加実験として、データ収集パソコンのOSダウングレード、LNA追加とbladeRFのRX VGA2の利得調整、GPSDO(disciplined oscillator)による安定クロック供給、他SDRの利用、などを考えています。しかしながら、PocketSDRなどの測位専用SDRを使ってみたいです。


関連記事