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.
23 from gnuradio import gr, gru, eng_notation, optfir, window
24 from gnuradio import audio
25 from gnuradio import usrp
26 from gnuradio.eng_option import eng_option
27 from optparse import OptionParser
28 from usrpm import usrp_dbid
34 class tune(gr.feval_dd):
36 This class allows C++ code to callback into python.
38 def __init__(self, tb):
39 gr.feval_dd.__init__(self)
42 def eval(self, ignore):
44 This method is called from gr.bin_statistics_f when it wants to change
45 the center frequency. This method tunes the front end to the new center
46 frequency, and returns the new frequency as its result.
49 # We use this try block so that if something goes wrong from here
50 # down, at least we'll have a prayer of knowing what went wrong.
51 # Without this, you get a very mysterious:
53 # terminate called after throwing an instance of 'Swig::DirectorMethodException'
56 # message on stderr. Not exactly helpful ;)
58 new_freq = self.tb.set_next_freq()
62 print "tune: Exception: ", e
65 class parse_msg(object):
66 def __init__(self, msg):
67 self.center_freq = msg.arg1()
68 self.vlen = int(msg.arg2())
69 assert(msg.length() == self.vlen * gr.sizeof_float)
71 # FIXME consider using Numarray or NumPy vector
74 self.data = struct.unpack('%df' % (self.vlen,), t)
77 class my_top_block(gr.top_block):
80 gr.top_block.__init__(self)
82 usage = "usage: %prog [options] min_freq max_freq"
83 parser = OptionParser(option_class=eng_option, usage=usage)
84 parser.add_option("-R", "--rx-subdev-spec", type="subdev", default=(0,0),
85 help="select USRP Rx side A or B (default=A)")
86 parser.add_option("-g", "--gain", type="eng_float", default=None,
87 help="set gain in dB (default is midpoint)")
88 parser.add_option("", "--tune-delay", type="eng_float", default=1e-3, metavar="SECS",
89 help="time to delay (in seconds) after changing frequency [default=%default]")
90 parser.add_option("", "--dwell-delay", type="eng_float", default=10e-3, metavar="SECS",
91 help="time to dwell (in seconds) at a given frequncy [default=%default]")
92 parser.add_option("-F", "--fft-size", type="int", default=256,
93 help="specify number of FFT bins [default=%default]")
94 parser.add_option("-d", "--decim", type="intx", default=16,
95 help="set decimation to DECIM [default=%default]")
96 parser.add_option("", "--real-time", action="store_true", default=False,
97 help="Attempt to enable real-time scheduling")
98 parser.add_option("-B", "--fusb-block-size", type="int", default=0,
99 help="specify fast usb block size [default=%default]")
100 parser.add_option("-N", "--fusb-nblocks", type="int", default=0,
101 help="specify number of fast usb blocks [default=%default]")
103 (options, args) = parser.parse_args()
108 self.min_freq = eng_notation.str_to_num(args[0])
109 self.max_freq = eng_notation.str_to_num(args[1])
111 if self.min_freq > self.max_freq:
112 self.min_freq, self.max_freq = self.max_freq, self.min_freq # swap them
114 self.fft_size = options.fft_size
117 if not options.real_time:
120 # Attempt to enable realtime scheduling
121 r = gr.enable_realtime_scheduling()
126 print "Note: failed to enable realtime scheduling"
128 # If the user hasn't set the fusb_* parameters on the command line,
129 # pick some values that will reduce latency.
132 if options.fusb_block_size == 0 and options.fusb_nblocks == 0:
133 if realtime: # be more aggressive
134 options.fusb_block_size = gr.prefs().get_long('fusb', 'rt_block_size', 1024)
135 options.fusb_nblocks = gr.prefs().get_long('fusb', 'rt_nblocks', 16)
137 options.fusb_block_size = gr.prefs().get_long('fusb', 'block_size', 4096)
138 options.fusb_nblocks = gr.prefs().get_long('fusb', 'nblocks', 16)
140 #print "fusb_block_size =", options.fusb_block_size
141 #print "fusb_nblocks =", options.fusb_nblocks
145 self.u = usrp.source_c(fusb_block_size=options.fusb_block_size,
146 fusb_nblocks=options.fusb_nblocks)
149 adc_rate = self.u.adc_rate() # 64 MS/s
150 usrp_decim = options.decim
151 self.u.set_decim_rate(usrp_decim)
152 usrp_rate = adc_rate / usrp_decim
154 self.u.set_mux(usrp.determine_rx_mux_value(self.u, options.rx_subdev_spec))
155 self.subdev = usrp.selected_subdev(self.u, options.rx_subdev_spec)
156 print "Using RX d'board %s" % (self.subdev.side_and_name(),)
159 s2v = gr.stream_to_vector(gr.sizeof_gr_complex, self.fft_size)
161 mywindow = window.blackmanharris(self.fft_size)
162 fft = gr.fft_vcc(self.fft_size, True, mywindow)
167 c2mag = gr.complex_to_mag_squared(self.fft_size)
169 # FIXME the log10 primitive is dog slow
170 log = gr.nlog10_ff(10, self.fft_size,
171 -20*math.log10(self.fft_size)-10*math.log10(power/self.fft_size))
173 # Set the freq_step to 75% of the actual data throughput.
174 # This allows us to discard the bins on both ends of the spectrum.
176 self.freq_step = 0.75 * usrp_rate
177 self.min_center_freq = self.min_freq + self.freq_step/2
178 nsteps = math.ceil((self.max_freq - self.min_freq) / self.freq_step)
179 self.max_center_freq = self.min_center_freq + (nsteps * self.freq_step)
181 self.next_freq = self.min_center_freq
183 tune_delay = max(0, int(round(options.tune_delay * usrp_rate / self.fft_size))) # in fft_frames
184 dwell_delay = max(1, int(round(options.dwell_delay * usrp_rate / self.fft_size))) # in fft_frames
186 self.msgq = gr.msg_queue(16)
187 self._tune_callback = tune(self) # hang on to this to keep it from being GC'd
188 stats = gr.bin_statistics_f(self.fft_size, self.msgq,
189 self._tune_callback, tune_delay, dwell_delay)
191 # FIXME leave out the log10 until we speed it up
192 #self.connect(self.u, s2v, fft, c2mag, log, stats)
193 self.connect(self.u, s2v, fft, c2mag, stats)
195 if options.gain is None:
196 # if no gain was specified, use the mid-point in dB
197 g = self.subdev.gain_range()
198 options.gain = float(g[0]+g[1])/2
200 self.set_gain(options.gain)
201 print "gain =", options.gain
204 def set_next_freq(self):
205 target_freq = self.next_freq
206 self.next_freq = self.next_freq + self.freq_step
207 if self.next_freq >= self.max_center_freq:
208 self.next_freq = self.min_center_freq
210 if not self.set_freq(target_freq):
211 print "Failed to set frequency to", target_freq
216 def set_freq(self, target_freq):
218 Set the center frequency we're interested in.
220 @param target_freq: frequency in Hz
223 Tuning is a two step process. First we ask the front-end to
224 tune as close to the desired frequency as it can. Then we use
225 the result of that operation and our target_frequency to
226 determine the value for the digital down converter.
228 return self.u.tune(0, self.subdev, target_freq)
231 def set_gain(self, gain):
232 self.subdev.set_gain(gain)
238 # Get the next message sent from the C++ code (blocking call).
239 # It contains the center frequency and the mag squared of the fft
240 m = parse_msg(tb.msgq.delete_head())
242 # Print center freq so we know that something is happening...
245 # FIXME do something useful with the data...
247 # m.data are the mag_squared of the fft output (they are in the
248 # standard order. I.e., bin 0 == DC.)
249 # You'll probably want to do the equivalent of "fftshift" on them
250 # m.raw_data is a string that contains the binary floats.
251 # You could write this as binary to a file.
254 if __name__ == '__main__':
257 tb.start() # start executing flow graph in another thread...
260 except KeyboardInterrupt: