3 # Copyright 2005,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.
26 from optparse import OptionParser
28 from gnuradio import gr, gru, eng_notation
29 from gnuradio import usrp
30 from gnuradio import audio
31 from gnuradio import blks2
32 from gnuradio.eng_option import eng_option
33 from gnuradio.wxgui import stdgui2, fftsink2, scopesink2, slider, form
34 from usrpm import usrp_dbid
36 from numpy import convolve, array
39 #print "pid =", os.getpid()
40 #raw_input('Press Enter to continue: ')
42 # ////////////////////////////////////////////////////////////////////////
44 # ////////////////////////////////////////////////////////////////////////
46 class ptt_block(stdgui2.std_top_block):
47 def __init__(self, frame, panel, vbox, argv):
48 stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv)
51 self.space_bar_pressed = False
53 parser = OptionParser (option_class=eng_option)
54 parser.add_option("-R", "--rx-subdev-spec", type="subdev", default=None,
55 help="select USRP Rx side A or B")
56 parser.add_option("-T", "--tx-subdev-spec", type="subdev", default=None,
57 help="select USRP Tx side A or B")
58 parser.add_option ("-f", "--freq", type="eng_float", default=442.1e6,
59 help="set Tx and Rx frequency to FREQ", metavar="FREQ")
60 parser.add_option ("-g", "--rx-gain", type="eng_float", default=None,
61 help="set rx gain [default=midpoint in dB]")
62 parser.add_option("-I", "--audio-input", type="string", default="",
63 help="pcm input device name. E.g., hw:0,0 or /dev/dsp")
64 parser.add_option("-O", "--audio-output", type="string", default="",
65 help="pcm output device name. E.g., hw:0,0 or /dev/dsp")
66 parser.add_option ("-N", "--no-gui", action="store_true", default=False)
67 (options, args) = parser.parse_args ()
73 if options.freq < 1e6:
76 self.txpath = transmit_path(options.tx_subdev_spec, options.audio_input)
77 self.rxpath = receive_path(options.rx_subdev_spec, options.rx_gain, options.audio_output)
78 self.connect(self.txpath)
79 self.connect(self.rxpath)
81 self._build_gui(frame, panel, vbox, argv, options.no_gui)
83 self.set_transmit(False)
84 self.set_freq(options.freq)
85 self.set_rx_gain(self.rxpath.gain) # update gui
86 self.set_volume(self.rxpath.volume) # update gui
87 self.set_squelch(self.rxpath.threshold()) # update gui
90 def set_transmit(self, enabled):
91 self.txpath.set_enable(enabled)
92 self.rxpath.set_enable(not(enabled))
94 self.frame.SetStatusText ("Transmitter ON", 1)
96 self.frame.SetStatusText ("Receiver ON", 1)
99 def set_rx_gain(self, gain):
100 self.myform['rx_gain'].set_value(gain) # update displayed value
101 self.rxpath.set_gain(gain)
103 def set_tx_gain(self, gain):
104 self.txpath.set_gain(gain)
106 def set_squelch(self, threshold):
107 self.rxpath.set_squelch(threshold)
108 self.myform['squelch'].set_value(self.rxpath.threshold())
110 def set_volume (self, vol):
111 self.rxpath.set_volume(vol)
112 self.myform['volume'].set_value(self.rxpath.volume)
113 #self.update_status_bar ()
115 def set_freq(self, freq):
116 r1 = self.txpath.set_freq(freq)
117 r2 = self.rxpath.set_freq(freq)
118 #print "txpath.set_freq =", r1
119 #print "rxpath.set_freq =", r2
121 self.myform['freq'].set_value(freq) # update displayed value
124 def _build_gui(self, frame, panel, vbox, argv, no_gui):
126 def _form_set_freq(kv):
127 return self.set_freq(kv['freq'])
131 # FIXME This REALLY needs to be replaced with a hand-crafted button
132 # that sends both button down and button up events
133 hbox = wx.BoxSizer(wx.HORIZONTAL)
135 self.status_msg = wx.StaticText(panel, -1, "Press Space Bar to Transmit")
136 of = self.status_msg.GetFont()
137 self.status_msg.SetFont(wx.Font(15, of.GetFamily(), of.GetStyle(), of.GetWeight()))
138 hbox.Add(self.status_msg, 0, wx.ALIGN_CENTER)
140 vbox.Add(hbox, 0, wx.EXPAND | wx.ALIGN_CENTER)
142 panel.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
143 panel.Bind(wx.EVT_KEY_UP, self._on_key_up)
144 panel.Bind(wx.EVT_KILL_FOCUS, self._on_kill_focus)
147 if 1 and not(no_gui):
148 rx_fft = fftsink2.fft_sink_c(panel, title="Rx Input", fft_size=512,
149 sample_rate=self.rxpath.if_rate,
150 ref_level=80, y_per_div=20)
151 self.connect (self.rxpath.u, rx_fft)
152 vbox.Add (rx_fft.win, 1, wx.EXPAND)
154 if 1 and not(no_gui):
155 rx_fft = fftsink2.fft_sink_c(panel, title="Post s/w DDC",
156 fft_size=512, sample_rate=self.rxpath.quad_rate,
157 ref_level=80, y_per_div=20)
158 self.connect (self.rxpath.ddc, rx_fft)
159 vbox.Add (rx_fft.win, 1, wx.EXPAND)
161 if 0 and not(no_gui):
162 foo = scopesink2.scope_sink_f(panel, title="Squelch",
164 self.connect (self.rxpath.fmrx.div, (foo,0))
165 self.connect (self.rxpath.fmrx.gate, (foo,1))
166 self.connect (self.rxpath.fmrx.squelch_lpf, (foo,2))
167 vbox.Add (foo.win, 1, wx.EXPAND)
169 if 0 and not(no_gui):
170 tx_fft = fftsink2.fft_sink_c(panel, title="Tx Output",
171 fft_size=512, sample_rate=self.txpath.usrp_rate)
172 self.connect (self.txpath.amp, tx_fft)
173 vbox.Add (tx_fft.win, 1, wx.EXPAND)
176 # add control area at the bottom
178 self.myform = myform = form.form()
181 hbox = wx.BoxSizer(wx.HORIZONTAL)
182 hbox.Add((5,0), 0, 0)
183 myform['freq'] = form.float_field(
184 parent=panel, sizer=hbox, label="Freq", weight=1,
185 callback=myform.check_input_and_call(_form_set_freq, self._set_status_msg))
187 hbox.Add((5,0), 0, 0)
188 vbox.Add(hbox, 0, wx.EXPAND)
192 hbox = wx.BoxSizer(wx.HORIZONTAL)
194 form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Volume",
195 weight=3, range=self.rxpath.volume_range(),
196 callback=self.set_volume)
198 myform['squelch'] = \
199 form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Squelch",
200 weight=3, range=self.rxpath.squelch_range(),
201 callback=self.set_squelch)
203 myform['rx_gain'] = \
204 form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Rx Gain",
205 weight=3, range=self.rxpath.subdev.gain_range(),
206 callback=self.set_rx_gain)
208 vbox.Add(hbox, 0, wx.EXPAND)
211 self._build_subpanel(vbox)
213 def _build_subpanel(self, vbox_arg):
214 # build a secondary information panel (sometimes hidden)
216 # FIXME figure out how to have this be a subpanel that is always
217 # created, but has its visibility controlled by foo.Show(True/False)
219 #if not(self.show_debug_info):
226 #panel = wx.Panel(self.panel, -1)
227 #vbox = wx.BoxSizer(wx.VERTICAL)
229 hbox = wx.BoxSizer(wx.HORIZONTAL)
231 #myform['decim'] = form.static_float_field(
232 # parent=panel, sizer=hbox, label="Decim")
235 #myform['fs@usb'] = form.static_float_field(
236 # parent=panel, sizer=hbox, label="Fs@USB")
239 #myform['dbname'] = form.static_text_field(
240 # parent=panel, sizer=hbox)
243 vbox.Add(hbox, 0, wx.EXPAND)
246 def _set_status_msg(self, msg, which=0):
247 self.frame.GetStatusBar().SetStatusText(msg, which)
249 def _on_key_down(self, evt):
250 # print "key_down:", evt.m_keyCode
251 if evt.m_keyCode == wx.WXK_SPACE and not(self.space_bar_pressed):
252 self.space_bar_pressed = True
253 self.set_transmit(True)
255 def _on_key_up(self, evt):
256 # print "key_up", evt.m_keyCode
257 if evt.m_keyCode == wx.WXK_SPACE:
258 self.space_bar_pressed = False
259 self.set_transmit(False)
261 def _on_kill_focus(self, evt):
262 # if we lose the keyboard focus, turn off the transmitter
263 self.space_bar_pressed = False
264 self.set_transmit(False)
267 # ////////////////////////////////////////////////////////////////////////
269 # ////////////////////////////////////////////////////////////////////////
271 class transmit_path(gr.hier_block2):
272 def __init__(self, subdev_spec, audio_input):
273 gr.hier_block2.__init__(self, "transmit_path",
274 gr.io_signature(0, 0, 0), # Input signature
275 gr.io_signature(0, 0, 0)) # Output signature
277 self.u = usrp.sink_c ()
279 dac_rate = self.u.dac_rate();
280 self.if_rate = 320e3 # 320 kS/s
281 self.usrp_interp = int(dac_rate // self.if_rate)
282 self.u.set_interp_rate(self.usrp_interp)
284 self.audio_rate = self.if_rate // self.sw_interp # 32 kS/s
287 self.normal_gain = 32000
289 self.audio = audio.source(int(self.audio_rate), audio_input)
290 self.audio_amp = gr.multiply_const_ff(self.audio_gain)
292 lpf = gr.firdes.low_pass (1, # gain
293 self.audio_rate, # sampling rate
294 3800, # low pass cutoff freq
295 300, # width of trans. band
296 gr.firdes.WIN_HANN) # filter type
298 hpf = gr.firdes.high_pass (1, # gain
299 self.audio_rate, # sampling rate
300 325, # low pass cutoff freq
301 50, # width of trans. band
302 gr.firdes.WIN_HANN) # filter type
304 audio_taps = convolve(array(lpf),array(hpf))
305 self.audio_filt = gr.fir_filter_fff(1,audio_taps)
307 self.pl = blks2.ctcss_gen_f(self.audio_rate,123.0)
308 self.add_pl = gr.add_ff()
309 self.connect(self.pl,(self.add_pl,1))
311 self.fmtx = blks2.nbfm_tx(self.audio_rate, self.if_rate)
312 self.amp = gr.multiply_const_cc (self.normal_gain)
314 # determine the daughterboard subdevice we're using
315 if subdev_spec is None:
316 subdev_spec = usrp.pick_tx_subdevice(self.u)
317 self.u.set_mux(usrp.determine_tx_mux_value(self.u, subdev_spec))
318 self.subdev = usrp.selected_subdev(self.u, subdev_spec)
319 print "TX using", self.subdev.name()
321 self.connect(self.audio, self.audio_amp, self.audio_filt,
322 (self.add_pl,0), self.fmtx, self.amp, self.u)
324 self.set_gain(self.subdev.gain_range()[1]) # set max Tx gain
327 def set_freq(self, target_freq):
329 Set the center frequency we're interested in.
331 @param target_freq: frequency in Hz
334 Tuning is a two step process. First we ask the front-end to
335 tune as close to the desired frequency as it can. Then we use
336 the result of that operation and our target_frequency to
337 determine the value for the digital up converter. Finally, we feed
338 any residual_freq to the s/w freq translater.
340 r = self.u.tune(self.subdev.which(), self.subdev, target_freq)
342 # Use residual_freq in s/w freq translator
347 def set_gain(self, gain):
349 self.subdev.set_gain(gain)
351 def set_enable(self, enable):
352 self.subdev.set_enable(enable) # set H/W Tx enable
354 self.amp.set_k (self.normal_gain)
360 # ////////////////////////////////////////////////////////////////////////
362 # ////////////////////////////////////////////////////////////////////////
364 class receive_path(gr.hier_block2):
365 def __init__(self, subdev_spec, gain, audio_output):
366 gr.hier_block2.__init__(self, "receive_path",
367 gr.io_signature(0, 0, 0), # Input signature
368 gr.io_signature(0, 0, 0)) # Output signature
370 self.u = usrp.source_c ()
371 adc_rate = self.u.adc_rate()
373 self.if_rate = 256e3 # 256 kS/s
374 usrp_decim = int(adc_rate // self.if_rate)
376 self.u.set_decim_rate(usrp_decim)
377 self.quad_rate = self.if_rate // if_decim # 64 kS/s
379 audio_rate = self.quad_rate // audio_decim # 32 kS/s
381 if subdev_spec is None:
382 subdev_spec = usrp.pick_rx_subdevice(self.u)
383 self.subdev = usrp.selected_subdev(self.u, subdev_spec)
384 print "RX using", self.subdev.name()
386 self.u.set_mux(usrp.determine_rx_mux_value(self.u, subdev_spec))
388 # Create filter to get actual channel we want
389 chan_coeffs = gr.firdes.low_pass (1.0, # gain
390 self.if_rate, # sampling rate
391 13e3, # low pass cutoff freq
392 4e3, # width of trans. band
393 gr.firdes.WIN_HANN) # filter type
395 print "len(rx_chan_coeffs) =", len(chan_coeffs)
397 # Decimating Channel filter with frequency translation
398 # complex in and out, float taps
399 self.ddc = gr.freq_xlating_fir_filter_ccf(if_decim, # decimation rate
401 0, # frequency translation amount
402 self.if_rate) # input sample rate
404 # instantiate the guts of the single channel receiver
405 self.fmrx = blks2.nbfm_rx(audio_rate, self.quad_rate)
407 # standard squelch block
408 self.squelch = blks2.standard_squelch(audio_rate)
410 # audio gain / mute block
411 self._audio_gain = gr.multiply_const_ff(1.0)
413 # sound card as final sink
414 audio_sink = audio.sink (int(audio_rate), audio_output)
416 # now wire it all together
417 self.connect (self.u, self.ddc, self.fmrx, self.squelch, self._audio_gain, audio_sink)
420 # if no gain was specified, use the mid-point in dB
421 g = self.subdev.gain_range()
422 gain = float(g[0]+g[1])/2
426 v = self.volume_range()
427 self.set_volume((v[0]+v[1])/2)
428 s = self.squelch_range()
429 self.set_squelch((s[0]+s[1])/2)
432 def volume_range(self):
433 return (-20.0, 0.0, 0.5)
435 def set_volume (self, vol):
436 g = self.volume_range()
437 self.volume = max(g[0], min(g[1], vol))
438 self._update_audio_gain()
440 def set_enable(self, enable):
441 self.enabled = enable
442 self._update_audio_gain()
444 def _update_audio_gain(self):
446 self._audio_gain.set_k(10**(self.volume/10))
448 self._audio_gain.set_k(0)
450 def squelch_range(self):
451 return self.squelch.squelch_range()
453 def set_squelch(self, threshold):
454 print "SQL =", threshold
455 self.squelch.set_threshold(threshold)
458 return self.squelch.threshold()
460 def set_freq(self, target_freq):
462 Set the center frequency we're interested in.
464 @param target_freq: frequency in Hz
467 Tuning is a two step process. First we ask the front-end to
468 tune as close to the desired frequency as it can. Then we use
469 the result of that operation and our target_frequency to
470 determine the value for the digital down converter in the
471 FPGA. Finally, we feed any residual_freq to the s/w freq
474 r = self.u.tune(0, self.subdev, target_freq)
476 # Use residual_freq in s/w freq translater
477 # print "residual_freq =", r.residual_freq
478 self.ddc.set_center_freq(-r.residual_freq)
483 def set_gain(self, gain):
485 self.subdev.set_gain(gain)
488 # ////////////////////////////////////////////////////////////////////////
490 # ////////////////////////////////////////////////////////////////////////
493 app = stdgui2.stdapp(ptt_block, "NBFM Push to Talk")
496 if __name__ == '__main__':