3
# Copyright 2005,2006,2007 Free Software Foundation, Inc.
5
# This file is part of GNU Radio
7
# GNU Radio is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3, or (at your option)
12
# GNU Radio is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public License
18
# along with GNU Radio; see the file COPYING. If not, write to
19
# the Free Software Foundation, Inc., 51 Franklin Street,
20
# Boston, MA 02110-1301, USA.
23
from gnuradio import gr, gru, eng_notation, optfir
24
from gnuradio import audio
25
from gnuradio import usrp
26
from gnuradio import blks2
27
from gnuradio.eng_option import eng_option
28
from gnuradio.wxgui import slider, powermate
29
from gnuradio.wxgui import stdgui2, fftsink2, form, scopesink2
30
from optparse import OptionParser
31
from usrpm import usrp_dbid
36
def pick_subdevice(u):
38
The user didn't specify a subdevice on the command line.
39
Try for one of these, in order: TV_RX, BASIC_RX, whatever is on side A.
43
return usrp.pick_subdev(u, (usrp_dbid.TV_RX,
44
usrp_dbid.TV_RX_REV_2,
45
usrp_dbid.TV_RX_REV_3,
48
class wfm_rx_block (stdgui2.std_top_block):
49
def __init__(self,frame,panel,vbox,argv):
50
stdgui2.std_top_block.__init__ (self,frame,panel,vbox,argv)
52
parser=OptionParser(option_class=eng_option)
53
parser.add_option("-R", "--rx-subdev-spec", type="subdev", default=None,
54
help="select USRP Rx side A or B (default=A)")
55
parser.add_option("-f", "--freq", type="eng_float", default=100.1e6,
56
help="set frequency to FREQ", metavar="FREQ")
57
parser.add_option("-g", "--gain", type="eng_float", default=65,
58
help="set gain in dB (default is midpoint)")
59
parser.add_option("-s", "--squelch", type="eng_float", default=0,
60
help="set squelch level (default is 0)")
61
parser.add_option("-V", "--volume", type="eng_float", default=None,
62
help="set volume (default is midpoint)")
63
parser.add_option("-O", "--audio-output", type="string", default="",
64
help="pcm device name. E.g., hw:0,0 or surround51 or /dev/dsp")
67
(options, args) = parser.parse_args()
81
self.u = usrp.source_c() # usrp is data source
83
adc_rate = self.u.adc_rate() # 64 MS/s
85
self.u.set_decim_rate(usrp_decim)
86
usrp_rate = adc_rate / usrp_decim # 320 kS/s
88
demod_rate = usrp_rate / chanfilt_decim
90
audio_rate = 3*demod_rate / audio_decimation/2 # 48 kHz
92
if options.rx_subdev_spec is None:
93
options.rx_subdev_spec = pick_subdevice(self.u)
95
self.u.set_mux(usrp.determine_rx_mux_value(self.u, options.rx_subdev_spec))
96
self.subdev = usrp.selected_subdev(self.u, options.rx_subdev_spec)
99
chan_filt_coeffs = gr.firdes.low_pass_2 (1, # gain
100
usrp_rate, # sampling rate
101
90e3, # passband cutoff
102
30e3, # transition bandwidth
103
70, # stopband attenuation
104
gr.firdes.WIN_BLACKMAN)
105
print len(chan_filt_coeffs)
106
chan_filt = gr.fir_filter_ccf (chanfilt_decim, chan_filt_coeffs)
108
self.rchan_sample = blks2.rational_resampler_fff(3,2)
109
self.lchan_sample = blks2.rational_resampler_fff(3,2)
112
#self.guts = blks2.wfm_rcv (demod_rate, audio_decimation)
113
self.guts = blks2.wfm_rcv_pll (demod_rate, audio_decimation)
115
# FIXME rework {add,multiply}_const_* to handle multiple streams
116
self.volume_control_l = gr.multiply_const_ff(self.vol)
117
self.volume_control_r = gr.multiply_const_ff(self.vol)
119
# sound card as final sink
120
audio_sink = audio.sink (int (audio_rate),
121
options.audio_output,
124
# now wire it all together
125
self.connect (self.u, chan_filt, self.guts)
126
self.connect((self.guts, 0), self.lchan_sample,self.volume_control_l,(audio_sink,0))
127
self.connect((self.guts, 1), self.rchan_sample,self.volume_control_r,(audio_sink,1))
130
self.guts.stereo_carrier_pll_recovery.squelch_enable(True)
132
print "FYI: This implementation of the stereo_carrier_pll_recovery has no squelch implementation yet"
135
self._build_gui(vbox, usrp_rate, demod_rate, audio_rate)
137
if options.gain is None:
138
# if no gain was specified, use the mid-point in dB
139
g = self.subdev.gain_range()
140
options.gain = float(g[0]+g[1])/2
142
if options.volume is None:
143
g = self.volume_range()
144
options.volume = float(g[0]+g[1])/2
146
if abs(options.freq) < 1e6:
151
self.set_gain(options.gain)
152
self.set_vol(options.volume)
154
self.guts.stereo_carrier_pll_recovery.set_lock_threshold(options.squelch)
156
print "FYI: This implementation of the stereo_carrier_pll_recovery has no squelch implementation yet"
158
if not(self.set_freq(options.freq)):
159
self._set_status_msg("Failed to set initial frequency")
162
def _set_status_msg(self, msg, which=0):
163
self.frame.GetStatusBar().SetStatusText(msg, which)
166
def _build_gui(self, vbox, usrp_rate, demod_rate, audio_rate):
168
def _form_set_freq(kv):
169
return self.set_freq(kv['freq'])
173
self.src_fft = fftsink2.fft_sink_c(self.panel, title="Data from USRP",
174
fft_size=512, sample_rate=usrp_rate,
175
ref_scale=32768.0, ref_level=0, y_divs=12)
176
self.connect (self.u, self.src_fft)
177
vbox.Add (self.src_fft.win, 4, wx.EXPAND)
180
post_fm_demod_fft = fftsink2.fft_sink_f(self.panel, title="Post FM Demod",
181
fft_size=512, sample_rate=demod_rate,
182
y_per_div=10, ref_level=0)
183
self.connect (self.guts.fm_demod, post_fm_demod_fft)
184
vbox.Add (post_fm_demod_fft.win, 4, wx.EXPAND)
187
post_stereo_carrier_generator_fft = fftsink2.fft_sink_c (self.panel, title="Post Stereo_carrier_generator",
188
fft_size=512, sample_rate=audio_rate,
189
y_per_div=10, ref_level=0)
190
self.connect (self.guts.stereo_carrier_generator, post_stereo_carrier_generator_fft)
191
vbox.Add (post_stereo_carrier_generator_fft.win, 4, wx.EXPAND)
194
post_deemphasis_left = fftsink2.fft_sink_f (self.panel, title="Post_Deemphasis_Left",
195
fft_size=512, sample_rate=audio_rate,
196
y_per_div=10, ref_level=0)
197
self.connect (self.guts.deemph_Left, post_deemphasis_left)
198
vbox.Add (post_deemphasis_left.win, 4, wx.EXPAND)
201
post_deemphasis_right = fftsink2.fft_sink_f(self.panel, title="Post_Deemphasis_Right",
202
fft_size=512, sample_rate=audio_rate,
203
y_per_div=10, ref_level=-20)
204
self.connect (self.guts.deemph_Left, post_deemphasis_right)
205
vbox.Add (post_deemphasis_right.win, 4, wx.EXPAND)
209
LmR_fft = fftsink2.fft_sink_f(self.panel, title="LmR",
210
fft_size=512, sample_rate=audio_rate,
211
y_per_div=10, ref_level=-20)
212
self.connect (self.guts.LmR_real,LmR_fft)
213
vbox.Add (LmR_fft.win, 4, wx.EXPAND)
216
self.scope = scopesink2.scope_sink_f(self.panel, sample_rate=demod_rate)
217
self.connect (self.guts.fm_demod,self.scope)
218
vbox.Add (self.scope.win,4,wx.EXPAND)
220
# control area form at bottom
221
self.myform = myform = form.form()
223
hbox = wx.BoxSizer(wx.HORIZONTAL)
225
myform['freq'] = form.float_field(
226
parent=self.panel, sizer=hbox, label="Freq", weight=1,
227
callback=myform.check_input_and_call(_form_set_freq, self._set_status_msg))
230
myform['freq_slider'] = \
231
form.quantized_slider_field(parent=self.panel, sizer=hbox, weight=3,
232
range=(87.9e6, 108.1e6, 0.1e6),
233
callback=self.set_freq)
235
vbox.Add(hbox, 0, wx.EXPAND)
237
hbox = wx.BoxSizer(wx.HORIZONTAL)
241
form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Volume",
242
weight=3, range=self.volume_range(),
243
callback=self.set_vol)
247
form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Gain",
248
weight=3, range=self.subdev.gain_range(),
249
callback=self.set_gain)
252
myform['sqlch_thrsh'] = \
253
form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Stereo Squelch Threshold",
254
weight=3, range=(0.0,1.0,0.01),
255
callback=self.set_squelch)
257
vbox.Add(hbox, 0, wx.EXPAND)
260
self.knob = powermate.powermate(self.frame)
262
powermate.EVT_POWERMATE_ROTATE (self.frame, self.on_rotate)
263
powermate.EVT_POWERMATE_BUTTON (self.frame, self.on_button)
265
print "FYI: No Powermate or Contour Knob found"
268
def on_rotate (self, event):
269
self.rot += event.delta
270
if (self.state == "FREQ"):
272
self.set_freq(self.freq + .1e6)
275
self.set_freq(self.freq - .1e6)
278
step = self.volume_range()[2]
280
self.set_vol(self.vol + step)
283
self.set_vol(self.vol - step)
286
def on_button (self, event):
287
if event.value == 0: # button up
290
if self.state == "FREQ":
294
self.update_status_bar ()
297
def set_vol (self, vol):
298
g = self.volume_range()
299
self.vol = max(g[0], min(g[1], vol))
300
self.volume_control_l.set_k(10**(self.vol/10))
301
self.volume_control_r.set_k(10**(self.vol/10))
302
self.myform['volume'].set_value(self.vol)
303
self.update_status_bar ()
305
def set_squelch(self,squelch_threshold):
307
self.guts.stereo_carrier_pll_recovery.set_lock_threshold(squelch_threshold);
309
print "FYI: This implementation of the stereo_carrier_pll_recovery has no squelch implementation yet"
311
def set_freq(self, target_freq):
313
Set the center frequency we're interested in.
315
@param target_freq: frequency in Hz
318
Tuning is a two step process. First we ask the front-end to
319
tune as close to the desired frequency as it can. Then we use
320
the result of that operation and our target_frequency to
321
determine the value for the digital down converter.
323
r = usrp.tune(self.u, 0, self.subdev, target_freq)
326
self.freq = target_freq
327
self.myform['freq'].set_value(target_freq) # update displayed value
328
self.myform['freq_slider'].set_value(target_freq) # update displayed value
329
self.update_status_bar()
330
self._set_status_msg("OK", 0)
333
self._set_status_msg("Failed", 0)
336
def set_gain(self, gain):
337
self.myform['gain'].set_value(gain) # update displayed value
338
self.subdev.set_gain(gain)
340
def update_status_bar (self):
341
msg = "Volume:%r Setting:%s" % (self.vol, self.state)
342
self._set_status_msg(msg, 1)
343
self.src_fft.set_baseband_freq(self.freq)
345
def volume_range(self):
346
return (-20.0, 0.0, 0.5)
349
if __name__ == '__main__':
350
app = stdgui2.stdapp (wfm_rx_block, "USRP WFM RX")