panda's tech note

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

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

Day 5: BPSKによる通信(受信)

今日はBPSKで変調されたディジタル信号を受信するところを実装します。BPSKの受信は,

  1. SDRデバイスの初期化
  2. 受信機・アンテナの設定(搬送波周波数・帯域幅・ゲイン・サンプルレート)
  3. 受信ストリームの有効化
  4. 信号サンプルの受信
  5. 信号サンプルから基準信号の位相推定
  6. 受信データの復調

という手順で行います。信号サンプルから基準信号の位相を推定する手順は少々複雑なので,まずは信号サンプルを受信するところまで進めます。

まずは昨日の送信のときと同様に,サンプルレートやシンボルあたりのサンプル数,帯域幅,搬送波周波数を引数で設定できるように以下のように書きます。

import SoapySDR
import sys
import argparse
import math
import numpy as np

# Arguments
parser = argparse.ArgumentParser()
parser.add_argument('--sample-rate', type=int, default=1e6)
parser.add_argument('--samples-per-symbol', type=int, default=10)
parser.add_argument('--bandwidth', type=int, default=5e6)
parser.add_argument('--rf', type=int, default=2420e6)

"""
Call the main routine
"""
if __name__ == "__main__":
    # Parse the arguments
    args = parser.parse_args()
    main(args)

SDRデバイスの初期化

3日目で扱ったように,まずはSDRデバイスを初期化し,インスタンスを取得します。ここまでは送信と変わりません。

"""
Main routine
"""
def main(args):
    # Create an SDR device instance
    try:
        sdr = SoapySDR.Device(dict(driver="lime"))
    except:
        sys.stderr.write("Failed to create an SDR device instance.\n")
        return False

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

受信機・アンテナの設定

次に受信機・アンテナの設定をします。サンプルレート,帯域幅,アンテナ,ゲイン,搬送波周波数を設定します。このうち,サンプルレート,帯域幅,搬送波周波数は引数で変更でき,そのパラメータを argparse を通じて取得しています。

    sdr.setSampleRate(SoapySDR.SOAPY_SDR_RX, 0, args.sample_rate)
    sdr.setBandwidth(SoapySDR.SOAPY_SDR_RX, 0, args.bandwidth)
    sdr.setAntenna(SoapySDR.SOAPY_SDR_RX, 0, "LNAW")
    sdr.setGain(SoapySDR.SOAPY_SDR_RX, 0, 50.0)
    sdr.setFrequency(SoapySDR.SOAPY_SDR_RX, 0, args.rf)

送信では第1引数に SoapySDR.SOAPY_SDR_TX を指定していましたが,受信では SoapySDR.SOAPY_SDR_RX を指定します。また,アンテナはボードの接続と合わせます。今回は RX1_W を使うので LNAW を指定しています(これも引数で指定できるようにしても良いと思います)。

受信ストリームの有効化

次に受信アンテナから信号サンプルを取得する受信ストリームを設定します。送信同様,今回は1つのアンテナ(シングルチャネル)のみを使うため,以下のようにシングルチャネルのストリームを準備しています。

    # Setup an Rx stream
    rxStream = sdr.setupStream(SoapySDR.SOAPY_SDR_RX, SoapySDR.SOAPY_SDR_CF32, [0])
    # Activate the stream
    sdr.activateStream(rxStream)

こちらの引数も,第1引数以外は送信ストリームと同じです。

信号サンプルの受信

信号サンプルを受信するために信号サンプル用の受信バッファを準備します。今回は以下の通り,ストリームの最大送信単位のバッファを作成しました。

    # Check the maximum transmit unit
    mtu = sdr.getStreamMTU(rxStream)

    # Prepare a receive buffer
    rxBuffer = np.zeros(mtu, np.complex64)

次に信号サンプルを受信します。電波を常に受信するために無限ループ内に受信プログラムを以下のように書きます。

    while True:
        # Receive samples
        status = sdr.readStream(rxStream, [rxBuffer], rxBuffer.size)
        if status.ret != rxBuffer.size:
            sys.stderr.write("Failed to receive samples in readStream(): {}\n".format(status.ret))
            return False
        # Process the received signal here

readStream() により,受信ストリームから受信バッファに信号サンプルを読み込みます。このループ内で受信したサンプルを処理するのですが,この処理は少々複雑なので明日実装していきます。なお,このループ内の処理が遅いと,受信ストリームへの電波信号の入力に対し readStream() による受信ストリームからの読み出しよりが間に合わず,信号サンプルの取りこぼしにつながります。そのため,受信した信号は高速に処理して readStream() を呼び出すか,別スレッドで信号処理をする必要があります。

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

今日は信号を受信し,受信ストリームから信号サンプルを取り出す部分を実装しました。明日は復調のために基準信号の位相推定を実装します。