panda's tech note

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

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

Day 15: オリジナルプロトコルの実装 (5)

5日目6日目7日目 を参考にしながら,オリジナルプロトコルの受信機能を実装します。

5日目 で説明した通り,SoapySDRを使った受信処理は sdr.readStream() で読み込んだサンプル列を処理します。6日目 でも説明しましたが,読み出したサンプル列が送信されたサンプル列の先頭と同期していないため,複数回 sdr.readStream() を呼び出して読み込んだサンプル列を連結して処理することになります。

シンボルのエッジ検出

受信したサンプル列からシンボルのエッジを検出します。基本的には 7日目detectEdge() 関数と同様のアプローチを取りますが,今回はプリアンブルを長く取る代わりにプリアンブルの前の正弦波信号を送らないため,無信号を無視するための処理を入れます。無信号と判別するための閾値を以下のように定義します。

RECEIVE_SIGNAL_THRESHOLD = 0.02

この閾値を超えた信号に対して,位相の変化が90度を超える位相の変化点からサンプル列に対するシンボルのオフセットを検出します。 これを実現するための detectEdge() 関数を以下の通り実装します。

"""
Detect edge
"""
def detectEdge(samples, samples_per_symbol):
    # Amplitude
    amps = np.abs(samples)
    # Calculate the phase change points
    shifted = samples[1:]
    orig = samples[0:-1]
    orig = np.where(orig==0, orig + 1e-9, orig) # to avoid zero division
    x = shifted / orig
    diffAngles = np.arctan2(x.imag, x.real)
    # Find the change points of a phase (also checking the amplitude)
    changePoints = np.where( (amps[0:-1] > RECEIVE_SIGNAL_THRESHOLD)
        & (np.absolute(diffAngles) > math.pi / 2))[0]
    # Find the edge index
    if changePoints.size == 0:
        return False
    bc = np.bincount(changePoints % samples_per_symbol)
    return np.argmax(bc)

7日目 の実装との差は,上述の無信号部分を無視するようにしたことと,変化点が1つも見つからなかった場合に False を返すようにしたことです。

プリアンブルの検出

次にサンプル列からプリアンブルの検出を行います。7日目 ではプリアンブルの検出をするために主成分分析により同期検波を行いましたが,そこでも説明した通り,位相が徐々にずれていくドリフト減少が発生するため,長いサンプル列を処理すると位相が反転してしまう可能性があります。そのため,今回はDBPSK変調方式のように前のシンボルからの位相差で位相を判定します。

プリアンブルの検出はまず無信号でないシンボル列から位相の反転の繰り返しを探します。プリアンブルは128シンボル分と定義しましたが,プリアンブルの前半は検出できないこともあるため,16シンボル分の位相反転によりプリアンブルを検出します。プリアンブルの検出は以下のように実装しました。

"""
Detect preamble
"""
def detectPreamble(symbols):
    # Amplitude and angles
    amps = np.abs(symbols)
    # Calculate angles from previous symbols
    cur = symbols[1:] # current symbols
    prev = symbols[0:-1] # previous symbols
    prev = np.where(prev==0, prev + 1e-9, prev) # to avoid zero division
    diffAngles = np.angle(cur / prev)
    # Detect part of the preamble using alternating 16 symbols
    pattern = bitstring.BitArray(hex='ffff')
    binary = np.where((amps[0:-1] > RECEIVE_SIGNAL_THRESHOLD) & (np.absolute(diffAngles) > math.pi / 2), True, False)
    found = binary.find(pattern)
    if len(found) == 0:
        return False
    return found[0]

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

今日はシンボルのエッジとプリンブルの検出を実装しました。明日はこれを組み合わせてデータの信号を取り出す予定です。