前回、遅ればせながら入手のソフトウエア無線受信機RTL-SDRをPythonで読み取りできました。順当な線ではPythonで後段処理、ということになりますが、このところやっていたScilab様をお見限り、というのは寂しい限り。どうせリアルタイム処理は狙わないのでRTL-SDRのデータをScilabへもお裾分けっと。
※「手習ひデジタル信号処理」投稿順 Indexはこちら
案ずるより産むがやすしということで、長年逡巡していたRTL-SDRも使ってみたらば今のところ順調です。pyrtlsdrモジュールを使えば、PythonでIQ信号を取り出せるので、Pythonには各種の実績あるモジュールあり、それらを駆使しての後段処理に夢が広がるというわけです。
しかし度々お世話になってきたScilabを止めてしまうのも寂しいということで、今回はPythonからScilabへRTL-SDRのIQ信号をファイルで送る(勿論リアルタイム性はまったく無。バッチです。)ルートを開拓。
python側のコード
python側のコードは以下のようです。RTL-SDRを駆動して所定の分量の信号を取得し、それをScilab可読のフォーマットのテキストファイルに落とすもの。
$ python pyrtlsdr2sci.py -h
上記のように -h オプションつければ使用方法が表示されます。といっても引数に値をセットして呼び出すだけ。全てにデフォルト値が与えられているので、何も引数にセットしなければ、Tokyo FM(80MHz)を中心とした2MHz幅くらいの領域のIQ信号を262144点取得し、toScilab.csv というファイル名にてカレントフォルダに書き出します。
なお、以下のコードは暫定版っす。当然バグあり、エラーチェックなど無のもの。
#!/usr/bin/python # coding: utf-8 ### @file pyrtlsdr2sci.py ### @brief save RTL-SDR IQ signal for scilab ### ### @author: Joseph Halfmoon ### @date: June 6, 2023 """ Save RTL-SDR IQ signal for scilab v0.1 input ===== --FS sample rate (default=2.048[MHz]) --FC center_freq (defalut=80[MHz] --N number of samples (defalut=256*1024) --FNAME File name to save (defalut=toScilab.csv) -V VERSION """ import argparse import csv import numpy as np import sys from rtlsdr import RtlSdr versionSTR = "pyrtlsdr2sci.py v0.1" def parse2float(st): """parse string to float Returns at Fail: None """ try: work = float(st) except: return None return work def chkVal(arg, dval): vl = parse2float(arg) if vl is None: nLen = len(arg) if nLen > 1: bdy = arg[0:nLen-1] lastChar = arg[nLen-1] vl = parse2float(bdy) if vl is None: vl = dval else: lastChar = "0" return (vl, lastChar) def freqIN(arg, dval): prefixes = 'mkMG' vl, lastChar = chkVal(arg, dval) pfx = prefixes.find(lastChar) if pfx == 0: ex = 1e-3 elif pfx == 1: ex = 1e3 elif pfx == 2: ex = 1e6 elif pfx == 3: ex = 1e9 else: ex = 1 return vl * ex def parse2int(st): """parse string to int Returns at Fail: None """ try: work = int(st) except: return None return work class rtlsdrBuffer(object): """rtlsdr buffer class read & save RTL-SDR IQ signal Fs : sampling Freq[Hz] Fc : center Freq[Hz] """ def __init__(self, Fs, Fc, N): self.sdr = RtlSdr() self.sdr.sample_rate = Fs self.sdr.center_freq = Fc self.sdr.freq_correction = 60 self.sdr.gain = 'auto' self.N = N self.buffer = np.zeros(self.N, dtype=np.complex128) def read(self): self.buffer = self.sdr.read_samples(self.N) def writeSCI(self, fnam): self.fnam = fnam np.savetxt(self.fnam, self.buffer, fmt='%.18e %+.18ej') def main(): """main program. process options """ parser = argparse.ArgumentParser(description=versionSTR) parser.add_argument('--FS', nargs=1, help='sample rate (default=2.048[MHz])') parser.add_argument('--FC', nargs=1, help='center_freq (defalut=80[MHz])') parser.add_argument('--N', nargs=1, help='number of samples (defalut=256*1024)') parser.add_argument('--FNAME', nargs=1, help='File name to save (defalut=toScilab.csv)') parser.add_argument('-V', dest='VERSION', help='Show Version, then exit', action='store_true', default=False) args = parser.parse_args() if args.VERSION: print( versionSTR ) sys.exit(0) Fs = 2.048e6 if args.FS is not None: Fs = freqIN(args.FS[0], Fs) Fc = 80e6 if args.FC is not None: Fc = freqIN(args.FC[0], Fc) N = 256*1024 if args.N is not None: tmpN = parse2int(args.N[0]) if tmpN is not None: N = tmpN FNAME = "toScilab.csv" if args.FNAME is not None: FNAME = args.FNAME[0] sdr = rtlsdrBuffer(Fs, Fc, N) sdr.read() sdr.writeSCI(FNAME) sys.exit(0) if __name__ == "__main__": main()
上記コードを書くにあたって、numpyのndarray(複素数が詰まっている)のファイル出力の方法が分かってなかったので、以下のページにお世話になりました。
以下のようなコマンドラインで信号データが、–FNAMEで指定のファイルに出力されます。
出力ファイルとScilabでの読み込み
出力されたcsvファイルをテキストエディタで開くとこんな感じです。1行毎に複素数ひとつが書き出されてます。ポイントは虚数単位が “j” と表現されているところですかね。Python(numpy)側のデフォルトは 電気電子風に j みたいです。
一方、Scilabではキホンは i を使っている(コンソールからの手入力は%iだがファイル上では i で良い)のですが、試したところファイル上では j でも虚数単位と解釈して読み込んでくれました。読み込んだ後は勿論 i に統一されてます。心が広いなScilab。
Scilba上で、Python出力のファイルを読み込む処理シーケンスが以下に。
よめとるみたいやね。