Imported Upstream version 3.2.2
[debian/gnuradio] / gnuradio-examples / python / usrp / usrp_wfm_rcv_sca.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2006,2007 Free Software Foundation, Inc.
4
5 # This file is part of GNU Radio
6
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)
10 # any later version.
11
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.
16
17 # You should have received a copy of the GNU General Public License along
18 # with this program; if not, write to the Free Software Foundation, Inc.,
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #
21
22 """
23 Here is a bit of code that will receive SCA analog subcarriers of FM
24 Broadcast Stations using the USRP.  It is a modified version of
25 usrp_wfm_rcv.py.
26
27 Common SCA frequencies are 67 kHz and 92 kHz.  SCA is used for Reading
28 Services for the Blind, Background Music, Foreign Language Services, and
29 other services.  Remember you may hear static when tuned to a FM station
30 because this code only outputs SCA audio.
31
32 The USRP gain is critical for good decoding.  Adjust for minimum noise.
33  I use the Post FM Demod FFT to check for SCA subcarriers and to adjust
34 the USRP gain for the lowest noise floor.  The stereo pilot at 19 KHz,
35 the stereo difference signal around 38 KHz, and RDS at 57 KHz are also
36 displayed on the Post FM Demod FFT if present.
37
38 The range below 67 kHz is used for SCA only when Stereo is not used.
39
40 The SCA recieve range is not as far as the main FM carrier receive range
41 so tune in strong local stations first.
42
43 I tried to comment the code with the various parameters.  There seems to
44 be several choices for a couple of them.  I coded the common ones I see
45 here.
46
47 In the local area there are a couple of stations using digital SCA.
48 These look similar to narrow DRM signals and I wonder if they are using
49 OFDM.
50 """
51
52
53 from gnuradio import gr, gru, eng_notation, optfir
54 from gnuradio import audio
55 from gnuradio import usrp
56 from gnuradio.blks2impl.fm_emph import fm_deemph
57 from gnuradio.eng_option import eng_option
58 from gnuradio.wxgui import slider, powermate
59 from gnuradio.wxgui import stdgui2, fftsink2, form
60 from optparse import OptionParser
61 from usrpm import usrp_dbid
62 import sys
63 import math
64 import wx
65
66 def pick_subdevice(u):
67     """
68     The user didn't specify a subdevice on the command line.
69     Try for one of these, in order: TV_RX, BASIC_RX, whatever is on side A.
70
71     @return a subdev_spec
72     """
73     return usrp.pick_subdev(u, (usrp_dbid.TV_RX,
74                                 usrp_dbid.TV_RX_REV_2,
75                                 usrp_dbid.BASIC_RX))
76
77
78 class wfm_rx_sca_block (stdgui2.std_top_block):
79     def __init__(self,frame,panel,vbox,argv):
80         stdgui2.std_top_block.__init__ (self,frame,panel,vbox,argv)
81
82         parser=OptionParser(option_class=eng_option)
83         parser.add_option("-R", "--rx-subdev-spec", type="subdev", default=None,
84                           help="select USRP Rx side A or B (default=A)")
85         parser.add_option("-f", "--freq", type="eng_float", default=100.1e6,
86                           help="set frequency to FREQ", metavar="FREQ")
87         parser.add_option("-g", "--gain", type="eng_float", default=40,
88                           help="set gain in dB (default is midpoint)")
89         parser.add_option("-V", "--volume", type="eng_float", default=None,
90                           help="set volume (default is midpoint)")
91         parser.add_option("-O", "--audio-output", type="string", default="",
92                           help="pcm device name.  E.g., hw:0,0 or surround51 or /dev/dsp")
93
94         (options, args) = parser.parse_args()
95         if len(args) != 0:
96             parser.print_help()
97             sys.exit(1)
98
99         self.frame = frame
100         self.panel = panel
101
102         self.vol = 0
103         self.state = "FREQ"
104         self.freq = 0
105
106         # build graph
107
108         self.u = usrp.source_c()                    # usrp is data source
109
110         adc_rate = self.u.adc_rate()                # 64 MS/s
111         usrp_decim = 200
112         self.u.set_decim_rate(usrp_decim)
113         usrp_rate = adc_rate / usrp_decim           # 320 kS/s
114         chanfilt_decim = 1
115         demod_rate = usrp_rate / chanfilt_decim
116         sca_chanfilt_decim = 5
117         sca_demod_rate = demod_rate / sca_chanfilt_decim  #64 kHz
118         audio_decimation = 2
119         audio_rate = sca_demod_rate / audio_decimation  # 32 kHz
120
121         if options.rx_subdev_spec is None:
122             options.rx_subdev_spec = pick_subdevice(self.u)
123
124         self.u.set_mux(usrp.determine_rx_mux_value(self.u, options.rx_subdev_spec))
125         self.subdev = usrp.selected_subdev(self.u, options.rx_subdev_spec)
126         print "Using RX d'board %s" % (self.subdev.side_and_name(),)
127
128         #Create filter to get main FM Channel we want
129         chan_filt_coeffs = optfir.low_pass (1,           # gain
130                                             usrp_rate,   # sampling rate
131                                             100e3,        # passband cutoff
132                                             140e3,       # stopband cutoff
133                                             0.1,         # passband ripple
134                                             60)          # stopband attenuation
135         #print len(chan_filt_coeffs)
136         chan_filt = gr.fir_filter_ccf (chanfilt_decim, chan_filt_coeffs)
137
138         #Create demodulator block for Main FM Channel
139         max_dev = 75e3
140         fm_demod_gain = demod_rate/(2*math.pi*max_dev)
141         self.fm_demod = gr.quadrature_demod_cf (fm_demod_gain)
142
143         # Note - deemphasis is not applied to the Main FM Channel as main audio is not decoded
144
145         # SCA Devation is 10% of carrier but some references say 20% if mono with one SCA (6 KHz seems typical)
146         max_sca_dev = 6e3
147
148         # Create filter to get SCA channel we want
149         sca_chan_coeffs = gr.firdes.low_pass (1.0,                # gain
150                                           demod_rate,       # sampling rate
151                                           max_sca_dev,      # low pass cutoff freq
152                                           max_sca_dev/3,    # width of trans. band
153                                           gr.firdes.WIN_HANN) # filter type
154
155         self.ddc = gr.freq_xlating_fir_filter_fcf(sca_chanfilt_decim,       # decimation rate
156                                                   sca_chan_coeffs,    # taps
157                                                   0,              # frequency translation amount (Gets set by the UI)
158                                                   demod_rate)   # input sample rate
159
160         #Create demodulator block for SCA Channel
161         sca_demod_gain = sca_demod_rate/(2*math.pi*max_sca_dev)
162         self.fm_demod_sca = gr.quadrature_demod_cf (sca_demod_gain)
163
164
165         # SCA analog audio is bandwidth limited to 5 KHz
166         max_sca_audio_freq = 5.0e3
167         # SCA analog deephasis is 150 uS (75 uS may be used)
168         sca_tau = 150e-6
169
170         # compute FIR filter taps for SCA audio filter
171         audio_coeffs = gr.firdes.low_pass (1.0,         # gain
172                                            sca_demod_rate,      # sampling rate
173                                            max_sca_audio_freq, # low pass cutoff freq
174                                            max_sca_audio_freq/2.5,             # width of trans. band
175                                            gr.firdes.WIN_HAMMING)
176
177         # input: float; output: float
178         self.audio_filter = gr.fir_filter_fff (audio_decimation, audio_coeffs)
179
180         # Create deemphasis block that is applied after SCA demodulation
181         self.deemph = fm_deemph (audio_rate, sca_tau)
182
183         self.volume_control = gr.multiply_const_ff(self.vol)
184
185         # sound card as final sink
186         audio_sink = audio.sink (int (audio_rate),
187                                  options.audio_output,
188                                  False)  # ok_to_block
189
190         # now wire it all together
191         self.connect (self.u, chan_filt, self.fm_demod, self.ddc, self.fm_demod_sca)
192         self.connect (self.fm_demod_sca, self.audio_filter, self.deemph, self.volume_control, audio_sink)
193
194         self._build_gui(vbox, usrp_rate, demod_rate, sca_demod_rate, audio_rate)
195
196         if options.gain is None:
197             # if no gain was specified, use the mid-point in dB
198             g = self.subdev.gain_range()
199             options.gain = float(g[0]+g[1])/2
200
201         if options.volume is None:
202             g = self.volume_range()
203             options.volume = float(g[0]+g[1])/2
204
205         if abs(options.freq) < 1e6:
206             options.freq *= 1e6
207
208         # set initial values
209
210         self.set_gain(options.gain)
211         self.set_vol(options.volume)
212         if not(self.set_freq(options.freq)):
213             self._set_status_msg("Failed to set initial frequency")
214         self.set_sca_freq(67000)  # A common SCA Frequency
215
216
217     def _set_status_msg(self, msg, which=0):
218         self.frame.GetStatusBar().SetStatusText(msg, which)
219
220
221     def _build_gui(self, vbox, usrp_rate, demod_rate, sca_demod_rate, audio_rate):
222
223         def _form_set_freq(kv):
224             return self.set_freq(kv['freq'])
225
226         def _form_set_sca_freq(kv):
227             return self.set_sca_freq(kv['sca_freq'])
228
229         if 1:
230             self.src_fft = fftsink2.fft_sink_c(self.panel, title="Data from USRP",
231                                                fft_size=512, sample_rate=usrp_rate,
232                                                ref_scale=32768.0, ref_level=0, y_divs=12)
233             self.connect (self.u, self.src_fft)
234             vbox.Add (self.src_fft.win, 4, wx.EXPAND)
235
236         if 1:
237             post_demod_fft = fftsink2.fft_sink_f(self.panel, title="Post FM Demod",
238                                                  fft_size=2048, sample_rate=demod_rate,
239                                                  y_per_div=10, ref_level=0)
240             self.connect (self.fm_demod, post_demod_fft)
241             vbox.Add (post_demod_fft.win, 4, wx.EXPAND)
242
243         if 0:
244             post_demod_sca_fft = fftsink2.fft_sink_f(self.panel, title="Post SCA Demod",
245                                                 fft_size=1024, sample_rate=sca_demod_rate,
246                                                 y_per_div=10, ref_level=0)
247             self.connect (self.fm_demod_sca, post_demod_sca_fft)
248             vbox.Add (post_demod_sca_fft.win, 4, wx.EXPAND)
249
250         if 0:
251             post_deemph_fft = fftsink2.fft_sink_f (self.panel, title="Post SCA Deemph",
252                                                   fft_size=512, sample_rate=audio_rate,
253                                                   y_per_div=10, ref_level=-20)
254             self.connect (self.deemph, post_deemph_fft)
255             vbox.Add (post_deemph_fft.win, 4, wx.EXPAND)
256
257
258         # control area form at bottom
259         self.myform = myform = form.form()
260
261         hbox = wx.BoxSizer(wx.HORIZONTAL)
262         hbox.Add((5,0), 0)
263         myform['freq'] = form.float_field(
264             parent=self.panel, sizer=hbox, label="Freq", weight=1,
265             callback=myform.check_input_and_call(_form_set_freq, self._set_status_msg))
266
267         hbox.Add((5,0), 0)
268         myform['freq_slider'] = \
269             form.quantized_slider_field(parent=self.panel, sizer=hbox, weight=3,
270                                         range=(87.9e6, 108.1e6, 0.1e6),
271                                         callback=self.set_freq)
272         hbox.Add((5,0), 0)
273         vbox.Add(hbox, 0, wx.EXPAND)
274
275         hbox = wx.BoxSizer(wx.HORIZONTAL)
276         hbox.Add((5,0), 0)
277         myform['sca_freq'] = form.float_field(
278             parent=self.panel, sizer=hbox, label="SCA", weight=1,
279             callback=myform.check_input_and_call(_form_set_sca_freq, self._set_status_msg))
280
281         hbox.Add((5,0), 0)
282         myform['sca_freq_slider'] = \
283             form.quantized_slider_field(parent=self.panel, sizer=hbox, weight=3,
284                                         range=(38e3, 100e3, 1.0e3),
285                                         callback=self.set_sca_freq)
286         hbox.Add((5,0), 0)
287         vbox.Add(hbox, 0, wx.EXPAND)
288
289         hbox = wx.BoxSizer(wx.HORIZONTAL)
290         hbox.Add((5,0), 0)
291
292         myform['volume'] = \
293             form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Volume",
294                                         weight=3, range=self.volume_range(),
295                                         callback=self.set_vol)
296         hbox.Add((5,0), 1)
297
298         myform['gain'] = \
299             form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Gain",
300                                         weight=3, range=self.subdev.gain_range(),
301                                         callback=self.set_gain)
302         hbox.Add((5,0), 0)
303         vbox.Add(hbox, 0, wx.EXPAND)
304
305         try:
306             self.knob = powermate.powermate(self.frame)
307             self.rot = 0
308             powermate.EVT_POWERMATE_ROTATE (self.frame, self.on_rotate)
309             powermate.EVT_POWERMATE_BUTTON (self.frame, self.on_button)
310         except:
311             print "FYI: No Powermate or Contour Knob found"
312
313
314     def on_rotate (self, event):
315         self.rot += event.delta
316         if (self.state == "FREQ"):
317             if self.rot >= 3:
318                 self.set_freq(self.freq + .1e6)
319                 self.rot -= 3
320             elif self.rot <=-3:
321                 self.set_freq(self.freq - .1e6)
322                 self.rot += 3
323         else:
324             step = self.volume_range()[2]
325             if self.rot >= 3:
326                 self.set_vol(self.vol + step)
327                 self.rot -= 3
328             elif self.rot <=-3:
329                 self.set_vol(self.vol - step)
330                 self.rot += 3
331
332     def on_button (self, event):
333         if event.value == 0:        # button up
334             return
335         self.rot = 0
336         if self.state == "FREQ":
337             self.state = "VOL"
338         else:
339             self.state = "FREQ"
340         self.update_status_bar ()
341
342
343     def set_vol (self, vol):
344         g = self.volume_range()
345         self.vol = max(g[0], min(g[1], vol))
346         self.volume_control.set_k(10**(self.vol/10))
347         self.myform['volume'].set_value(self.vol)
348         self.update_status_bar ()
349
350     def set_freq(self, target_freq):
351         """
352         Set the center frequency we're interested in.
353
354         @param target_freq: frequency in Hz
355         @rypte: bool
356
357         Tuning is a two step process.  First we ask the front-end to
358         tune as close to the desired frequency as it can.  Then we use
359         the result of that operation and our target_frequency to
360         determine the value for the digital down converter.
361         """
362         r = usrp.tune(self.u, 0, self.subdev, target_freq)
363
364         if r:
365             self.freq = target_freq
366             self.myform['freq'].set_value(target_freq)         # update displayed value
367             self.myform['freq_slider'].set_value(target_freq)  # update displayed value
368             self.update_status_bar()
369             self._set_status_msg("OK", 0)
370             return True
371
372         self._set_status_msg("Failed", 0)
373         return False
374
375     def set_sca_freq(self, target_sca_freq):
376
377         self.ddc.set_center_freq(-target_sca_freq)
378         self.myform['sca_freq'].set_value(target_sca_freq)         # update displayed value
379         self.myform['sca_freq_slider'].set_value(target_sca_freq)  # update displayed value
380         self.update_status_bar()
381         self._set_status_msg("OK", 0)
382         return True
383
384     def set_gain(self, gain):
385         self.myform['gain'].set_value(gain)     # update displayed value
386         self.subdev.set_gain(gain)
387
388     def update_status_bar (self):
389         msg = "Volume:%r  Setting:%s" % (self.vol, self.state)
390         self._set_status_msg(msg, 1)
391         self.src_fft.set_baseband_freq(self.freq)
392
393     def volume_range(self):
394         return (-20.0, 0.0, 0.5)
395
396
397 if __name__ == '__main__':
398     app = stdgui2.stdapp (wfm_rx_sca_block, "USRP WFM SCA RX")
399     app.MainLoop ()