Skip to content

Commit ff4dd8d

Browse files
committed
feat: add python script and design files
1 parent d2c279b commit ff4dd8d

7 files changed

+1493
-1
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
.dep
33
build
44
TAGS
5-
*.cap
5+
*.cap
6+
.ipynb_checkpoints

doc/plot-waveform.png

95.7 KB
Loading

python/CW-Filter-Design.ipynb

+457
Large diffs are not rendered by default.

python/README.md

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# Python materials for CentSDR
2+
3+
python関連のファイルです。
4+
5+
## DSPデザインファイル
6+
7+
フィルタ係数の設計に使用したJupyter notebookです。
8+
9+
- [SSB-Filter-Design.ipynb](SSB-Filter-Design.ipynb) : SSBモード用の1300Hz LPF IIRフィルタ設計
10+
- [CW-Filter-Design.ipynb](CW-Filter-Design.ipynb) : CWモード用の150Hz LPF IIRフィルタ設計
11+
- [TLV320AIC3204-1st-IIR-HPF.ipynb](TLV320AIC3204-1st-IIR-HPF.ipynb) : Codec DSP用DCリジェクトHPFフィルタ
12+
13+
## centsdr.py
14+
15+
CentSDRをスクリプトやコマンドラインから制御するための、モジュール兼コマンドです。
16+
シリアル(USB-CDC)のコマンドを、Pythonスクリプトで行うものです。内部バッファの波形をプロットすることもできます。
17+
18+
## Requirement
19+
20+
- python 2.7
21+
- pyserial
22+
23+
波形表示をするにはオプションで下記が必要です
24+
25+
- numpy
26+
- matplotlib
27+
28+
## デバイスの指定
29+
30+
シリアルポートを指定する必要があります。環境変数`CENTSDR_DEVICE`、または`-d`オプションで指定します。スクリプトの冒頭の`DEFAULT_DEVICE`を修正してもOKです。
31+
32+
- `$ export CENTSDR_DEVICE=/dev/ttyACM1`
33+
- `$ ./centsdr.py -d /dev/cu.usbmodem401 -p 0`
34+
- `$ CENTSDR_DEVICE=/dev/cu.usbmodem401 ./centsdr.py -p 0`
35+
36+
## Usage
37+
38+
```
39+
$ ./centsdr.py -h
40+
Usage: centsdr.py [options]
41+
42+
Options:
43+
-h, --help show this help message and exit
44+
-d DEV, --dev=DEV device node (default from env var CENTSDR_DEVICE)
45+
-F FREQ, --freqeucy=FREQ
46+
set tuning frequency
47+
-G GAIN, --gain=GAIN gain (0-95)
48+
-V VOLUME, --volume=VOLUME
49+
set volume
50+
-A AGC, --agc=AGC set agc
51+
-M MODE, --mode=MODE set modulation
52+
-C MODE, --channel=MODE
53+
set channel
54+
-P, --power show power
55+
-s, --show show current setting
56+
-S SHOWARG, --show-arg=SHOWARG
57+
show specific setting
58+
-p BUFFER, --plot=BUFFER
59+
plot buffer
60+
-l, --loop loop continuously
61+
```
62+
63+
## 制御
64+
65+
コマンドラインからの制御例を示します。オプションは複数同時に指定できます。
66+
67+
### 周波数を設定する
68+
69+
```
70+
$ ./centsdr.py -F 27500000
71+
```
72+
73+
### 復調モードを設定する
74+
75+
```
76+
$ ./centsdr.py -M fm
77+
```
78+
79+
### ボリュームを設定する
80+
81+
```
82+
$ ./centsdr.py -V 10
83+
```
84+
85+
### AGCを設定する
86+
87+
```
88+
$ ./centsdr.py -A mid
89+
```
90+
91+
### RFゲインを設定する
92+
93+
```
94+
$ ./centsdr.py -G 40
95+
```
96+
97+
## データ取得
98+
99+
### 現在状態を取得する
100+
101+
```
102+
$ ./centsdr.py -s
103+
tune: 27500300
104+
volume: 10
105+
mode: fm
106+
gain: 60
107+
channel: 8
108+
agc: manual
109+
```
110+
111+
### 電力を取得する
112+
113+
```
114+
$ ./centsdr.py -P
115+
-73.1
116+
```
117+
118+
### 波形を表示する
119+
120+
波形データが保存されているバッファ番号を指定します。
121+
122+
- 0: Capture buffer
123+
- 1: Audio play buffer
124+
- 2: intermediate buffer 1
125+
- 3: intermediate buffer 2
126+
127+
```
128+
$ ./centsdr.py -p 0
129+
```
130+
131+
クローズは、ウィンドウのクローズボタンです。Ctrl-Cでは消えません。
132+
133+
`-l`を指定すると連続して表示を行います。
134+
135+
```
136+
$ ./centsdr.py -p 0 -l
137+
```
138+
139+
こちらの停止はCtrl-Cです。クローズボタンでは消えません。
140+
141+
<div align="center">
142+
<img src="/doc/plot-waveform.png" width="480px">
143+
</div>
144+
145+
## スクリプトからの使用例
146+
147+
モジュールとしてimportしてスクリプトで使用することができます。
148+
149+
```
150+
from centsdr import CentSDR
151+
sdr = CentSDR()
152+
sdr.set_tune(27500000)
153+
sdr.set_mode('fm')
154+
sdr.set_volume(20)
155+
```

python/SSB-Filter-Design.ipynb

+435
Large diffs are not rendered by default.

python/TLV320AIC3204-1st-IIR-HPF.ipynb

+255
Large diffs are not rendered by default.

python/centsdr.py

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
#!/usr/bin/env python
2+
import numpy as np
3+
import serial
4+
import struct
5+
import os
6+
import re
7+
8+
DEFAULT_DEVICE='/dev/cu.usbmodem401'
9+
10+
class CentSDR():
11+
def __init__(self, dev = None):
12+
self.dev = dev or os.getenv('CENTSDR_DEVICE') or DEFAULT_DEVICE
13+
self.serial = None
14+
15+
def __enter__(self):
16+
self.open()
17+
return self
18+
19+
def __exit__(self, exc_type, exc_value, traceback):
20+
self.close()
21+
22+
def open(self):
23+
if self.serial is None:
24+
self.serial = serial.Serial(self.dev)
25+
26+
def close(self):
27+
if self.serial:
28+
self.serial.close()
29+
self.serial = None
30+
31+
def send_command(self, cmd):
32+
self.open()
33+
self.serial.write(cmd)
34+
self.serial.readline() # discard empty line
35+
36+
def set_frequency(self, freq):
37+
self.send_command("freq %d\r" % freq)
38+
39+
def set_tune(self, freq):
40+
self.send_command("tune %d\r" % freq)
41+
42+
def set_gain(self, gain):
43+
self.send_command("gain %d\r" % gain)
44+
45+
def set_volume(self, gain):
46+
self.send_command("volume %d\r" % gain)
47+
48+
def set_fs(self, fs):
49+
self.send_command("fs %d\r" % fs)
50+
51+
def set_mode(self, mode):
52+
self.send_command("mode %s\r" % mode)
53+
54+
def set_channel(self, chan):
55+
self.send_command("channel %d\r" % chan)
56+
57+
def set_agc(self, agc):
58+
self.send_command("agc %s\r" % agc)
59+
60+
def fetch_data(self):
61+
result = ''
62+
line = ''
63+
while True:
64+
c = self.serial.read()
65+
if c == chr(13):
66+
next # ignore CR
67+
line += c
68+
if c == chr(10):
69+
result += line
70+
line = ''
71+
next
72+
if line.endswith('ch>'):
73+
# stop on prompt
74+
break
75+
return result
76+
77+
def fetch_array(self, sel):
78+
def hex16(h):
79+
return struct.unpack('>h', h.decode('hex'))[0]
80+
self.send_command("data %d\r" % sel)
81+
data = self.fetch_data()
82+
x = []
83+
for line in data.split('\n'):
84+
if line:
85+
x.extend([hex16(d) for d in line.strip().split(' ')])
86+
return np.array(x)
87+
88+
def data(self, sel = 0):
89+
self.send_command("data %d\r" % sel)
90+
data = self.fetch_data()
91+
x = []
92+
for line in data.split('\n'):
93+
if line:
94+
d = line.strip().split(' ')
95+
x.append(float(d[0])+float(d[1])*1.j)
96+
return np.array(x)
97+
98+
def read_power(self):
99+
self.send_command("power\r")
100+
resp = self.fetch_data()
101+
m = re.match(r"power: ([\d.-]+)dBm", resp)
102+
return float(m.group(1))
103+
104+
def read_status(self, arg = ''):
105+
self.send_command("show %s\r" % arg)
106+
return self.fetch_data()
107+
108+
def flush_data(self):
109+
self.send_command("\r")
110+
self.fetch_data()
111+
112+
def run_as_command():
113+
from optparse import OptionParser
114+
parser = OptionParser(usage="%prog [options]")
115+
parser.add_option("-d", "--dev", dest="device",
116+
help="device node (default from env var CENTSDR_DEVICE)", metavar="DEV")
117+
parser.add_option("-F", "--freqeucy", type="int", dest="freq",
118+
help="set tuning frequency", metavar="FREQ")
119+
parser.add_option("-G", "--gain", type="int", dest="gain",
120+
help="gain (0-95)", metavar="GAIN")
121+
parser.add_option("-V", "--volume", type="string", dest="volume",
122+
help="set volume", metavar="VOLUME")
123+
parser.add_option("-A", "--agc", type="string", dest="agc",
124+
help="set agc", metavar="AGC")
125+
parser.add_option("-M", "--mode", type="string", dest="mode",
126+
help="set modulation", metavar="MODE")
127+
parser.add_option("-C", "--channel", type="string", dest="channel",
128+
help="set channel", metavar="MODE")
129+
parser.add_option("-P", "--power", action="store_true", dest="power",
130+
help="show power")
131+
parser.add_option("-s", "--show", action="store_true", dest="show",
132+
help="show current setting")
133+
parser.add_option("-S", "--show-arg", type='string', dest="showarg", default='',
134+
help="show specific setting")
135+
parser.add_option("-p", "--plot", dest="buffer",
136+
help="plot buffer", metavar="BUFFER")
137+
parser.add_option("-l", "--loop", action="store_true", dest="loop", default=False,
138+
help="loop continuously")
139+
(opt, args) = parser.parse_args()
140+
sdr = CentSDR(opt.device)
141+
if opt.freq:
142+
sdr.set_tune(opt.freq)
143+
if opt.gain:
144+
sdr.set_gain(opt.gain)
145+
if opt.mode:
146+
sdr.set_mode(opt.mode)
147+
if opt.volume:
148+
sdr.set_volume(int(opt.volume))
149+
if opt.channel:
150+
sdr.set_channel(int(opt.channel))
151+
if opt.agc:
152+
sdr.set_agc(opt.agc)
153+
if opt.show:
154+
print sdr.read_status(opt.showarg)
155+
if opt.buffer:
156+
import pylab as pl
157+
sdr.flush_data()
158+
opt.buffer = int(opt.buffer)
159+
samp = sdr.fetch_array(opt.buffer)
160+
if opt.buffer == 0:
161+
samp = np.array(samp[0::2]) + np.array(samp[1::2])*1j
162+
x = range(len(samp))
163+
if opt.loop:
164+
from signal import signal, SIGINT
165+
def sigint_handler(signum, frame):
166+
opt.loop = False
167+
signal(SIGINT, sigint_handler)
168+
pl.ion()
169+
g1 = pl.plot(x, np.real(samp))
170+
g2 = pl.plot(x, np.imag(samp))
171+
pl.ylim(-10000,10000)
172+
pl.xlim(0, len(samp))
173+
while opt.loop:
174+
samp = sdr.fetch_array(opt.buffer)
175+
if opt.buffer == 0:
176+
samp = np.array(samp[0::2]) + np.array(samp[1::2])*1j
177+
x = range(len(samp))
178+
g1[0].set_data(x, np.real(samp))
179+
g2[0].set_data(x, np.imag(samp))
180+
pl.draw()
181+
pl.pause(0.01)
182+
pl.show()
183+
exit(0)
184+
if opt.power:
185+
print sdr.read_power()
186+
exit(0)
187+
188+
if __name__ == '__main__':
189+
run_as_command()

0 commit comments

Comments
 (0)