panda's tech note

Advent Calendar 2020: ソフトウェア無線

注意LimeSDR などのソフトウェア無線は技術基準適合証明(通称,技適)を受けていないため,これらの機器を日本国内で無線機として利用することは電波法により禁じられています。無線機として使用するためには,実験試験局免許を取得するか電波暗室・シールドボックスなどの設備を使用する必要があります。または,アンテナの代わりにケーブルとアッテネータを用い有線接続をし電波を発しないようにすることで,無線通信ではなく有線通信とはなりますが実験することができます(この場合も電波が漏れないように注意してください)。このページを参照される方は,実験される国や地域の法令などを遵守するようにご注意ください。また,実験等はご自身の責任でお願いします。

Day 3: LimeSDRプログラミング

今日はソフトウェア無線機であるLimeSDRをPythonから触っていきます。1日目 にインストールした SoapySDR の Python binding を使います。そのため,今回のアドベントカレンダーでは,

import SoapySDR

で SoapySDR ライブラリをインポートした状態を前提とします。

SDRデバイス一覧の取得

SoapySDR.Device.enumerate() でSoapySDRから使用できるSDRデバイス一覧が取得できます。後に説明する SoapySDR.Device() でSDRデバイスのインスタンスを生成する際,使用するSDRデバイスを指定しなくても自動でデバイスを発見できますが,複数のSDRデバイスが接続されている場合,ここで取得した一覧からドライバやシリアル番号により使用するデバイスを指定できます。

コード: device_list.py

import SoapySDR

# Enumerate all SDR devices
print("Obtain the recognized SDR devices...")
results = SoapySDR.Device.enumerate()
print("Found {} devices:".format(len(results)))
for result in results: print(result)

LimeSDR-USBが接続されている機器でこれを実行すると以下のような出力が得られます(シリアル番号は伏せ字にしました)。

$ python3 device_list.py
Obtain the recognized SDR devices...
linux; GNU C++ version 7.3.0; Boost_106501; UHD_003.010.003.000-0-unknown

Found 1 devices:
{addr=1d50:6108, driver=lime, label=LimeSDR-USB [USB 3.0] XXXXXXXXXXXXX, media=USB 3.0, module=FX3, name=LimeSDR-USB, serial=XXXXXXXXXXXXXXXX}

SDRデバイスの初期化

SDRデバイスの初期化は以下のように SoapySDR.Device() で行うことができます。

args = dict(driver="lime")
sdr = SoapySDR.Device(args)

引数に dict(driver="lime") を指定していますが,特定のデバイスを検出して初期化するためには driver 以外にシリアル番号 serial で指定できます。

SoapySDR.Device() はSDRデバイスの初期化をし,インスタンスを返すので,返り値に対して操作をすることで,搬送波周波数やゲインなどアンテナの設定やストリームを作成して信号の送受信を行うとができます。今日は基本的な操作として,アンテナ関係の情報を取得します。具体的には,送信(TX)と受信(RX)アンテナのチャネル数、アンテナの種類、ゲインと設定可能な範囲、周波数(搬送波とアップコンバージョン・ダウンコンバージョンして内部で使用するベースバンドの周波数)と設定可能な範囲を取得します。

このコードは以下の通りです。

コード: device_init.py

import SoapySDR
import sys

# Create an SDR device instance
args = dict(driver="lime")
try:
    sdr = SoapySDR.Device(args)
except:
    sys.stderr.write("Failed to create an SDR device instance.\n")
    sys.exit(1)

if not sdr:
    sys.stderr.write("Could not find any SDR devices.\n")
    sys.exit(1)

# Get the number of channels for TX/RX
n_tx_ch = sdr.getNumChannels(SoapySDR.SOAPY_SDR_TX)
n_rx_ch = sdr.getNumChannels(SoapySDR.SOAPY_SDR_TX)
print("# of channels: (TX, RX) = ({}, {})".format(n_tx_ch, n_rx_ch))

print("---")

print("Antennas:")
for i in range(n_tx_ch):
    ants = sdr.listAntennas(SoapySDR.SOAPY_SDR_TX, i)
    print("\tTX ch{}: {}".format(i, ants))
for i in range(n_rx_ch):
    ants = sdr.listAntennas(SoapySDR.SOAPY_SDR_RX, i)
    print("\tRX ch{}: {}".format(i, ants))

print("---")

print("Gains:")
for i in range(n_tx_ch):
    g = sdr.listGains(SoapySDR.SOAPY_SDR_TX, i)
    print("\tTX ch{}:".format(i))
    for e in g:
        r = sdr.getGainRange(SoapySDR.SOAPY_SDR_TX, i, e)
        print("\t\t{}: [{}]".format(e, r))
for i in range(n_rx_ch):
    g = sdr.listGains(SoapySDR.SOAPY_SDR_RX, i)
    print("\tRX ch{}:".format(i))
    for e in g:
        r = sdr.getGainRange(SoapySDR.SOAPY_SDR_RX, i, e)
        print("\t\t{}: [{}]".format(e, r))

print("---")

print("Frequencies:")
for i in range(n_tx_ch):
    f = sdr.listFrequencies(SoapySDR.SOAPY_SDR_TX, i)
    print("\tTX ch{}:".format(i))
    for e in f:
        r = sdr.getFrequencyRange(SoapySDR.SOAPY_SDR_TX, i, e)
        print("\t\t{}: [{}]".format(e, r[0]))
for i in range(n_rx_ch):
    f = sdr.listFrequencies(SoapySDR.SOAPY_SDR_RX, i)
    print("\tRX ch{}:".format(i))
    for e in f:
        r = sdr.getFrequencyRange(SoapySDR.SOAPY_SDR_RX, i, e)
        print("\t\t{}: [{}]".format(e, r[0]))

このコードを実行すると以下のような出力となります。

$ python3 device_init.py
linux; GNU C++ version 7.3.0; Boost_106501; UHD_003.010.003.000-0-unknown

[INFO] Make connection: 'LimeSDR-USB [USB 3.0] XXXXXXXXXXXXX'
[INFO] Reference clock 30.72 MHz
[INFO] Device name: LimeSDR-USB
[INFO] Reference: 30.72 MHz
[INFO] LMS7002M register cache: Disabled
# of channels: (TX, RX) = (2, 2)
---
Antennas:
        TX ch0: ('NONE', 'BAND1', 'BAND2')
        TX ch1: ('NONE', 'BAND1', 'BAND2')
        RX ch0: ('NONE', 'LNAH', 'LNAL', 'LNAW', 'LB1', 'LB2')
        RX ch1: ('NONE', 'LNAH', 'LNAL', 'LNAW', 'LB1', 'LB2')
---
Gains:
        TX ch0:
                PAD: [0, 52]
                IAMP: [-12, 12]
        TX ch1:
                PAD: [0, 52]
                IAMP: [-12, 12]
        RX ch0:
                TIA: [0, 12]
                LNA: [0, 30]
                PGA: [-12, 19]
        RX ch1:
                TIA: [0, 12]
                LNA: [0, 30]
                PGA: [-12, 19]
---
Frequencies:
        TX ch0:
                RF: [3e+07, 3.8e+09]
                BB: [-1e+07, 1e+07]
        TX ch1:
                RF: [3e+07, 3.8e+09]
                BB: [-1e+07, 1e+07]
        RX ch0:
                RF: [3e+07, 3.8e+09]
                BB: [-1e+07, 1e+07]
        RX ch1:
                RF: [3e+07, 3.8e+09]
                BB: [-1e+07, 1e+07]

この出力から,送信アンテナは BAND1BAND2 の2種類があり,受信アンテナは LNAHLNALLNAWLB1LB2 の5種類があることがわかります。受信アンテナの LNA は Low-Noise Amplifier の略で,H, L, W は LimeSDR ボードのアンテナコネクタ(コネクタによってフィルタが異なるため最適な周波数範囲が異なります)に対応しています。

ゲインは各種アンプ(PAD: RF amplifier,IAMP: Current amplifier,TIA: Transimpedance amplifier,PGA: Programmable amplifier)の設定可能範囲が出力されています。

周波数については,搬送波周波数(RF)とアップコンバージョン・ダウンコンバージョンして内部で使用するベースバンド周波数(BB)の設定可能範囲が出力されています。なお,内部で使用するベースバンドの周波数はサンプル数によって変わるので、ここで取得したものはデフォルトのサンプル数に対するものです。スペックシート通り,30 MHzから3.8 GHzの周波数に対応していることがわかります。

今日のまとめと明日の予定

今日はSDRデバイスの初期化と簡単な操作を確認しました。明日は実際に信号を送ってみようと思います。