aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorDimitri Stolnikov <horiz0n@gmx.net>2013-04-28 12:41:35 +0200
committerDimitri Stolnikov <horiz0n@gmx.net>2013-04-28 12:46:03 +0200
commita31ea525fb8439b3708b8f4124ac99994ff7a978 (patch)
tree4906b0256fb03e5a02dbb310b77d311027e11bae /apps
parenta5bdb272402fb012c0a97556cf50a69128e4c573 (diff)
apps: add spectrum browser and signal generator
usage examples: osmocom_fft -a "rtl=0" -f 100e6 -s 2.4e6 -g 15 osmocom_siggen_gui -a "hackrf=0" -s 5e6 -f 100e6 --sine osmocom_siggen_gui -a "hackrf=0" -s 5e6 -f 100e6 --sweep -x 2M -y 1 -c34 known issues: - switching between siggen modes is broken at the moment (WIP) and has to be made via cli switches only. - filter bandwidth controls have no effect for TX (this has to be investigated)
Diffstat (limited to 'apps')
-rw-r--r--apps/CMakeLists.txt11
-rwxr-xr-xapps/osmocom_fft270
-rwxr-xr-xapps/osmocom_siggen51
-rw-r--r--apps/osmocom_siggen_base.py397
-rwxr-xr-xapps/osmocom_siggen_gui374
5 files changed, 1102 insertions, 1 deletions
diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt
index c837d77..88e5d4a 100644
--- a/apps/CMakeLists.txt
+++ b/apps/CMakeLists.txt
@@ -20,6 +20,15 @@
include(GrPython)
GR_PYTHON_INSTALL(
+ FILES
+ osmocom_siggen_base.py
+ DESTINATION ${GR_PYTHON_DIR}/osmosdr
+)
+
+GR_PYTHON_INSTALL(
PROGRAMS
- DESTINATION bin
+ osmocom_fft
+ osmocom_siggen
+ osmocom_siggen_gui
+ DESTINATION ${GR_RUNTIME_DIR}
)
diff --git a/apps/osmocom_fft b/apps/osmocom_fft
new file mode 100755
index 0000000..5449354
--- /dev/null
+++ b/apps/osmocom_fft
@@ -0,0 +1,270 @@
+#!/usr/bin/env python
+#
+# Copyright 2012 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import osmosdr
+from gnuradio import gr, gru
+from gnuradio import eng_notation
+from gnuradio.eng_option import eng_option
+from optparse import OptionParser
+
+import sys
+import numpy
+
+try:
+ from gnuradio.wxgui import stdgui2, form, slider
+ from gnuradio.wxgui import forms
+ from gnuradio.wxgui import fftsink2, waterfallsink2, scopesink2
+ import wx
+except ImportError:
+ sys.stderr.write("Error importing GNU Radio's wxgui. Please make sure gr-wxgui is installed.\n")
+ sys.exit(1)
+
+class app_top_block(stdgui2.std_top_block):
+ def __init__(self, frame, panel, vbox, argv):
+ stdgui2.std_top_block.__init__(self, frame, panel, vbox, argv)
+
+ self.frame = frame
+ self.panel = panel
+
+ parser = OptionParser(option_class=eng_option)
+ parser.add_option("-a", "--args", type="string", default="",
+ help="device args, [default=%default]")
+ parser.add_option("-A", "--antenna", type="string", default=None,
+ help="select Rx Antenna where appropriate")
+ parser.add_option("-s", "--samp-rate", type="eng_float", default=1e6,
+ help="set sample rate (bandwidth) [default=%default]")
+ parser.add_option("-f", "--freq", type="eng_float", default=None,
+ help="set frequency to FREQ", metavar="FREQ")
+ parser.add_option("-g", "--gain", type="eng_float", default=None,
+ help="set gain in dB (default is midpoint)")
+ parser.add_option("-W", "--waterfall", action="store_true", default=False,
+ help="Enable waterfall display")
+ parser.add_option("-S", "--oscilloscope", action="store_true", default=False,
+ help="Enable oscilloscope display")
+ parser.add_option("", "--avg-alpha", type="eng_float", default=1e-1,
+ help="Set fftsink averaging factor, default=[%default]")
+ parser.add_option ("", "--averaging", action="store_true", default=False,
+ help="Enable fftsink averaging, default=[%default]")
+ parser.add_option("", "--ref-scale", type="eng_float", default=1.0,
+ help="Set dBFS=0dB input value, default=[%default]")
+ parser.add_option("", "--fft-size", type="int", default=1024,
+ help="Set number of FFT bins [default=%default]")
+ parser.add_option("", "--fft-rate", type="int", default=30,
+ help="Set FFT update rate, [default=%default]")
+
+ (options, args) = parser.parse_args()
+ if len(args) != 0:
+ parser.print_help()
+ sys.exit(1)
+ self.options = options
+ self.show_debug_info = True
+
+ self.src = osmosdr.source_c(options.args)
+
+ # Set the antenna
+ if(options.antenna):
+ self.src.set_antenna(options.antenna, 0)
+
+ self.src.set_sample_rate(options.samp_rate)
+ input_rate = self.src.get_sample_rate()
+
+ if options.waterfall:
+ self.scope = \
+ waterfallsink2.waterfall_sink_c (panel, fft_size=1024,
+ sample_rate=input_rate)
+ self.frame.SetMinSize((800, 420))
+ elif options.oscilloscope:
+ self.scope = scopesink2.scope_sink_c(panel, sample_rate=input_rate)
+ self.frame.SetMinSize((800, 600))
+ else:
+ self.scope = fftsink2.fft_sink_c (panel,
+ fft_size=options.fft_size,
+ sample_rate=input_rate,
+ ref_scale=options.ref_scale,
+ ref_level=20.0,
+ y_divs = 12,
+ average=options.averaging,
+ avg_alpha=options.avg_alpha,
+ fft_rate=options.fft_rate)
+ def fftsink_callback(x, y):
+ self.set_freq(x)
+
+ self.scope.set_callback(fftsink_callback)
+ self.frame.SetMinSize((800, 420))
+
+ self.connect(self.src, self.scope)
+
+ self._build_gui(vbox)
+ self._setup_events()
+
+
+ # set initial values
+
+ if options.gain is None:
+ # if no gain was specified, use the mid-point in dB
+ g = self.src.get_gain_range()
+ options.gain = float(g.start()+g.stop())/2
+
+ if options.freq is None:
+ # if no freq was specified, use the mid-point
+ r = self.src.get_freq_range()
+ options.freq = float(r.start()+r.stop())/2
+
+ self.set_gain(options.gain)
+
+ if self.show_debug_info:
+ self.myform['samprate'].set_value(self.src.get_sample_rate())
+
+ if not(self.set_freq(options.freq)):
+ self._set_status_msg("Failed to set initial frequency")
+
+ def _set_status_msg(self, msg):
+ self.frame.GetStatusBar().SetStatusText(msg, 0)
+
+ def _build_gui(self, vbox):
+
+ def _form_set_freq(kv):
+ return self.set_freq(kv['freq'])
+
+ vbox.Add(self.scope.win, 10, wx.EXPAND)
+
+ # add control area at the bottom
+ self.myform = myform = form.form()
+ hbox = wx.BoxSizer(wx.HORIZONTAL)
+ hbox.Add((4,0), 0, 0)
+ myform['freq'] = form.float_field(
+ parent=self.panel, sizer=hbox, label="Center freq", weight=1,
+ callback=myform.check_input_and_call(_form_set_freq,
+ self._set_status_msg))
+
+ hbox.Add((4,0), 0, 0)
+ g = self.src.get_gain_range()
+
+ # some configurations don't have gain control
+ if g.stop() <= g.start():
+ glow = 0.0
+ ghigh = 1.0
+
+ else:
+ glow = g.start()
+ ghigh = g.stop()
+
+ myform['gain'] = form.slider_field(parent=self.panel,
+ sizer=hbox, label="Gain",
+ weight=3,
+ min=int(glow), max=int(ghigh),
+ callback=self.set_gain)
+ hbox.Add((4,0), 0, 0)
+ vbox.Add(hbox, 0, wx.EXPAND)
+
+ self._build_subpanel(vbox)
+
+ def _build_subpanel(self, vbox_arg):
+ # build a secondary information panel (sometimes hidden)
+
+ # FIXME figure out how to have this be a subpanel that is always
+ # created, but has its visibility controlled by foo.Show(True/False)
+
+ def _form_set_sample_rate(kv):
+ return self.set_sample_rate(kv['samprate'])
+
+ if not(self.show_debug_info):
+ return
+
+ panel = self.panel
+ vbox = vbox_arg
+ myform = self.myform
+
+ hbox = wx.BoxSizer(wx.HORIZONTAL)
+
+ hbox.Add((4,0), 0)
+ myform['samprate'] = form.float_field(
+ parent=panel, sizer=hbox, label="Sample Rate",
+ callback=myform.check_input_and_call(_form_set_sample_rate,
+ self._set_status_msg))
+ vbox.AddSpacer(5)
+
+ vbox.Add(hbox, 0, wx.EXPAND)
+ vbox.AddSpacer(5)
+
+ def set_freq(self, target_freq):
+ """
+ Set the center frequency we're interested in.
+
+ @param target_freq: frequency in Hz
+ @rypte: bool
+ """
+ actual_freq = self.src.set_center_freq(target_freq, 0)
+ self.myform['freq'].set_value(actual_freq)
+
+ if not self.options.oscilloscope:
+ self.scope.set_baseband_freq(actual_freq)
+
+ return True
+
+ def set_gain(self, gain):
+ if self.myform.has_key('gain'):
+ self.myform['gain'].set_value(gain) # update displayed value
+ self.src.set_gain(gain, 0)
+
+ def set_sample_rate(self, samp_rate):
+ ok = self.src.set_sample_rate(samp_rate)
+ input_rate = self.src.get_sample_rate()
+ self.scope.set_sample_rate(input_rate)
+ if self.show_debug_info: # update displayed values
+ self.myform['samprate'].set_value(self.src.get_sample_rate())
+
+ # set_sample_rate never fails; always falls back to closest requested.
+ return True
+
+ def _setup_events(self):
+ if not self.options.waterfall and not self.options.oscilloscope:
+ self.scope.win.Bind(wx.EVT_LEFT_DCLICK, self.evt_left_dclick)
+
+ def evt_left_dclick(self, event):
+ (ux, uy) = self.scope.win.GetXY(event)
+ if event.CmdDown():
+ # Re-center on maximum power
+ points = self.scope.win._points
+ if self.scope.win.peak_hold:
+ if self.scope.win.peak_vals is not None:
+ ind = numpy.argmax(self.scope.win.peak_vals)
+ else:
+ ind = int(points.shape()[0]/2)
+ else:
+ ind = numpy.argmax(points[:,1])
+
+ (freq, pwr) = points[ind]
+ target_freq = freq/self.scope.win._scale_factor
+ print ind, freq, pwr
+ self.set_freq(target_freq)
+ else:
+ # Re-center on clicked frequency
+ target_freq = ux/self.scope.win._scale_factor
+ self.set_freq(target_freq)
+
+def main ():
+ app = stdgui2.stdapp(app_top_block, "OSMOCOM Spectrum Browser", nstatus=1)
+ app.MainLoop()
+
+if __name__ == '__main__':
+ main ()
diff --git a/apps/osmocom_siggen b/apps/osmocom_siggen
new file mode 100755
index 0000000..0283fcf
--- /dev/null
+++ b/apps/osmocom_siggen
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+#
+# Copyright 2008,2009,2011,2012 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr
+from osmosdr import osmocom_siggen_base as osmocom_siggen
+import sys
+
+def main():
+ if gr.enable_realtime_scheduling() != gr.RT_OK:
+ print "Note: failed to enable realtime scheduling, continuing"
+
+ # Grab command line options and create top block
+ try:
+ (options, args) = osmocom_siggen.get_options()
+ tb = osmocom_siggen.top_block(options, args)
+
+ except RuntimeError, e:
+ print e
+ sys.exit(1)
+
+ tb.start()
+ raw_input('Press Enter to quit: ')
+ tb.stop()
+ tb.wait()
+
+# Make sure to create the top block (tb) within a function:
+# That code in main will allow tb to go out of scope on return,
+# which will call the decontructor on usrp and stop transmit.
+# Whats odd is that grc works fine with tb in the __main__,
+# perhaps its because the try/except clauses around tb.
+if __name__ == "__main__":
+ main()
diff --git a/apps/osmocom_siggen_base.py b/apps/osmocom_siggen_base.py
new file mode 100644
index 0000000..3b5d1d7
--- /dev/null
+++ b/apps/osmocom_siggen_base.py
@@ -0,0 +1,397 @@
+#!/usr/bin/env python
+#
+# Copyright 2008,2009,2011,2012 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+SAMP_RATE_KEY = 'samp_rate'
+LINK_RATE_KEY = 'link_rate'
+GAIN_KEY = 'gain'
+IF_GAIN_KEY = 'if_gain'
+BWIDTH_KEY = 'bwidth'
+TX_FREQ_KEY = 'tx_freq'
+FREQ_CORR_KEY = 'freq_corr'
+AMPLITUDE_KEY = 'amplitude'
+AMPL_RANGE_KEY = 'ampl_range'
+WAVEFORM_FREQ_KEY = 'waveform_freq'
+WAVEFORM_OFFSET_KEY = 'waveform_offset'
+WAVEFORM2_FREQ_KEY = 'waveform2_freq'
+FREQ_RANGE_KEY = 'freq_range'
+GAIN_RANGE_KEY = 'gain_range'
+IF_GAIN_RANGE_KEY = 'if_gain_range'
+BWIDTH_RANGE_KEY = 'bwidth_range'
+TYPE_KEY = 'type'
+
+def setter(ps, key, val): ps[key] = val
+
+import osmosdr
+from gnuradio import gr, gru, eng_notation
+from gnuradio.gr.pubsub import pubsub
+from gnuradio.eng_option import eng_option
+from optparse import OptionParser
+import sys
+import math
+
+n2s = eng_notation.num_to_str
+
+waveforms = { gr.GR_SIN_WAVE : "Complex Sinusoid",
+ gr.GR_CONST_WAVE : "Constant",
+ gr.GR_GAUSSIAN : "Gaussian Noise",
+ gr.GR_UNIFORM : "Uniform Noise",
+ "2tone" : "Two Tone",
+ "sweep" : "Sweep" }
+
+#
+# GUI-unaware GNU Radio flowgraph. This may be used either with command
+# line applications or GUI applications.
+#
+class top_block(gr.top_block, pubsub):
+ def __init__(self, options, args):
+ gr.top_block.__init__(self)
+ pubsub.__init__(self)
+ self._verbose = options.verbose
+
+ #initialize values from options
+ self._setup_osmosdr(options)
+ self[SAMP_RATE_KEY] = options.samp_rate
+ self[TX_FREQ_KEY] = options.tx_freq
+ self[FREQ_CORR_KEY] = options.freq_corr
+ self[AMPLITUDE_KEY] = options.amplitude
+ self[WAVEFORM_FREQ_KEY] = options.waveform_freq
+ self[WAVEFORM_OFFSET_KEY] = options.offset
+ self[WAVEFORM2_FREQ_KEY] = options.waveform2_freq
+
+ #subscribe set methods
+ self.subscribe(SAMP_RATE_KEY, self.set_samp_rate)
+ self.subscribe(GAIN_KEY, self.set_gain)
+ self.subscribe(IF_GAIN_KEY, self.set_if_gain)
+ self.subscribe(BWIDTH_KEY, self.set_bandwidth)
+ self.subscribe(TX_FREQ_KEY, self.set_freq)
+ self.subscribe(FREQ_CORR_KEY, self.set_freq_corr)
+ self.subscribe(AMPLITUDE_KEY, self.set_amplitude)
+ self.subscribe(WAVEFORM_FREQ_KEY, self.set_waveform_freq)
+ self.subscribe(WAVEFORM2_FREQ_KEY, self.set_waveform2_freq)
+ self.subscribe(TYPE_KEY, self.set_waveform)
+
+ #force update on pubsub keys
+ for key in (SAMP_RATE_KEY, GAIN_KEY, IF_GAIN_KEY, BWIDTH_KEY,
+ TX_FREQ_KEY, FREQ_CORR_KEY, AMPLITUDE_KEY,
+ WAVEFORM_FREQ_KEY, WAVEFORM_OFFSET_KEY, WAVEFORM2_FREQ_KEY):
+# print "key: ", key, "=", self[key]
+ self[key] = self[key]
+ self[TYPE_KEY] = options.type #set type last
+
+ def _setup_osmosdr(self, options):
+ self._sink = osmosdr.sink_c(options.args)
+ self._sink.set_sample_rate(options.samp_rate)
+
+ # Set the gain from options
+ if(options.gain):
+ self._sink.set_gain(options.gain)
+
+ # Set the antenna
+ if(options.antenna):
+ self._sink.set_antenna(options.antenna, 0)
+
+ self.publish(FREQ_RANGE_KEY, self._sink.get_freq_range)
+ self.publish(GAIN_RANGE_KEY, self._get_rf_gain_range)
+ self.publish(IF_GAIN_RANGE_KEY, self._get_if_gain_range)
+ self.publish(BWIDTH_RANGE_KEY, self._sink.get_bandwidth_range)
+ self.publish(GAIN_KEY, self._get_rf_gain)
+ self.publish(IF_GAIN_KEY, self._get_if_gain)
+ self.publish(BWIDTH_KEY, self._sink.get_bandwidth)
+
+ def _get_rf_gain_range(self):
+ return self._sink.get_gain_range("RF")
+
+ def _get_if_gain_range(self):
+ return self._sink.get_gain_range("IF")
+
+ def _get_rf_gain(self):
+ return self._sink.get_gain("RF")
+
+ def _get_if_gain(self):
+ return self._sink.get_gain("IF")
+
+ def _set_tx_amplitude(self, ampl):
+ """
+ Sets the transmit amplitude
+ @param ampl the amplitude or None for automatic
+ """
+ ampl_range = self[AMPL_RANGE_KEY]
+ if ampl is None:
+ ampl = (ampl_range[1] - ampl_range[0])*0.3 + ampl_range[0]
+ self[AMPLITUDE_KEY] = max(ampl_range[0], min(ampl, ampl_range[1]))
+
+ def set_samp_rate(self, sr):
+ self._sink.set_sample_rate(sr)
+ sr = self._sink.get_sample_rate()
+
+ if self[TYPE_KEY] in (gr.GR_SIN_WAVE, gr.GR_CONST_WAVE):
+ self._src.set_sampling_freq(self[SAMP_RATE_KEY])
+ elif self[TYPE_KEY] == "2tone":
+ self._src1.set_sampling_freq(self[SAMP_RATE_KEY])
+ self._src2.set_sampling_freq(self[SAMP_RATE_KEY])
+ elif self[TYPE_KEY] == "sweep":
+ self._src1.set_sampling_freq(self[SAMP_RATE_KEY])
+ self._src2.set_sampling_freq(self[WAVEFORM_FREQ_KEY]*2*math.pi/self[SAMP_RATE_KEY])
+ else:
+ return True # Waveform not yet set
+
+ if self._verbose:
+ print "Set sample rate to:", sr
+
+ return True
+
+ def set_gain(self, gain):
+ if gain is None:
+ g = self[GAIN_RANGE_KEY]
+ gain = float(g.start()+g.stop())/2
+ if self._verbose:
+ print "Using auto-calculated mid-point RF gain"
+ self[GAIN_KEY] = gain
+ return
+ gain = self._sink.set_gain(gain, "RF")
+ if self._verbose:
+ print "Set RF gain to:", gain
+
+ def set_if_gain(self, gain):
+ if gain is None:
+ g = self[IF_GAIN_RANGE_KEY]
+ gain = float(g.start()+g.stop())/2
+ if self._verbose:
+ print "Using auto-calculated mid-point IF gain"
+ self[IF_GAIN_KEY] = gain
+ return
+ gain = self._sink.set_gain(gain, "IF")
+ if self._verbose:
+ print "Set IF gain to:", gain
+
+ def set_bandwidth(self, bw):
+ bw = self._sink.set_bandwidth(bw)
+ if self._verbose:
+ print "Set bandwidth to:", bw
+
+ def set_freq(self, target_freq):
+
+ if target_freq is None:
+ f = self[FREQ_RANGE_KEY]
+ target_freq = float(f.start()+f.stop())/2.0
+ if self._verbose:
+ print "Using auto-calculated mid-point frequency"
+ self[TX_FREQ_KEY] = target_freq
+ return
+
+ tr = self._sink.set_center_freq(target_freq)
+ if tr is not None:
+ self._freq = tr
+ if self._verbose:
+ print "Set center frequency to", tr
+ elif self._verbose:
+ print "Failed to set freq."
+ return tr
+
+ def set_freq_corr(self, ppm):
+ if ppm is None:
+ if self._verbose:
+ print "Setting freq corrrection to 0"
+ self[FREQ_CORR_KEY] = 0
+ return
+
+ ppm = self._sink.set_freq_corr(ppm)
+ if self._verbose:
+ print "Set freq correction to:", ppm
+
+ def set_waveform_freq(self, freq):
+ if self[TYPE_KEY] == gr.GR_SIN_WAVE:
+ self._src.set_frequency(freq)
+ elif self[TYPE_KEY] == "2tone":
+ self._src1.set_frequency(freq)
+ elif self[TYPE_KEY] == 'sweep':
+ #there is no set sensitivity, redo fg
+ self[TYPE_KEY] = self[TYPE_KEY]
+ return True
+
+ def set_waveform2_freq(self, freq):
+ if freq is None:
+ self[WAVEFORM2_FREQ_KEY] = -self[WAVEFORM_FREQ_KEY]
+ return
+ if self[TYPE_KEY] == "2tone":
+ self._src2.set_frequency(freq)
+ elif self[TYPE_KEY] == "sweep":
+ self._src1.set_frequency(freq)
+ return True
+
+ def set_waveform(self, type):
+ self.lock()
+ self.disconnect_all()
+ if type == gr.GR_SIN_WAVE or type == gr.GR_CONST_WAVE:
+ self._src = gr.sig_source_c(self[SAMP_RATE_KEY], # Sample rate
+ type, # Waveform type
+ self[WAVEFORM_FREQ_KEY], # Waveform frequency
+ self[AMPLITUDE_KEY], # Waveform amplitude
+ self[WAVEFORM_OFFSET_KEY]) # Waveform offset
+ elif type == gr.GR_GAUSSIAN or type == gr.GR_UNIFORM:
+ self._src = gr.noise_source_c(type, self[AMPLITUDE_KEY])
+ elif type == "2tone":
+ self._src1 = gr.sig_source_c(self[SAMP_RATE_KEY],
+ gr.GR_SIN_WAVE,
+ self[WAVEFORM_FREQ_KEY],
+ self[AMPLITUDE_KEY]/2.0,
+ 0)
+ if(self[WAVEFORM2_FREQ_KEY] is None):
+ self[WAVEFORM2_FREQ_KEY] = -self[WAVEFORM_FREQ_KEY]
+
+ self._src2 = gr.sig_source_c(self[SAMP_RATE_KEY],
+ gr.GR_SIN_WAVE,
+ self[WAVEFORM2_FREQ_KEY],
+ self[AMPLITUDE_KEY]/2.0,
+ 0)
+ self._src = gr.add_cc()
+ self.connect(self._src1,(self._src,0))
+ self.connect(self._src2,(self._src,1))
+ elif type == "sweep":
+ # rf freq is center frequency
+ # waveform_freq is total swept width
+ # waveform2_freq is sweep rate
+ # will sweep from (rf_freq-waveform_freq/2) to (rf_freq+waveform_freq/2)
+ if self[WAVEFORM2_FREQ_KEY] is None:
+ self[WAVEFORM2_FREQ_KEY] = 0.1
+
+ self._src1 = gr.sig_source_f(self[SAMP_RATE_KEY],
+ gr.GR_TRI_WAVE,
+ self[WAVEFORM2_FREQ_KEY],
+ 1.0,
+ -0.5)
+ self._src2 = gr.frequency_modulator_fc(self[WAVEFORM_FREQ_KEY]*2*math.pi/self[SAMP_RATE_KEY])
+ self._src = gr.multiply_const_cc(self[AMPLITUDE_KEY])
+ self.connect(self._src1,self._src2,self._src)
+ else:
+ raise RuntimeError("Unknown waveform type")
+
+ self.connect(self._src, self._sink)
+ self.unlock()
+
+ if self._verbose:
+ print "Set baseband modulation to:", waveforms[type]
+ if type == gr.GR_SIN_WAVE:
+ print "Modulation frequency: %sHz" % (n2s(self[WAVEFORM_FREQ_KEY]),)
+ print "Initial phase:", self[WAVEFORM_OFFSET_KEY]
+ elif type == "2tone":
+ print "Tone 1: %sHz" % (n2s(self[WAVEFORM_FREQ_KEY]),)
+ print "Tone 2: %sHz" % (n2s(self[WAVEFORM2_FREQ_KEY]),)
+ elif type == "sweep":
+ print "Sweeping across %sHz to %sHz" % (n2s(-self[WAVEFORM_FREQ_KEY]/2.0),n2s(self[WAVEFORM_FREQ_KEY]/2.0))
+ print "Sweep rate: %sHz" % (n2s(self[WAVEFORM2_FREQ_KEY]),)
+ print "TX amplitude:", self[AMPLITUDE_KEY]
+
+
+ def set_amplitude(self, amplitude):
+ if amplitude < 0.0 or amplitude > 1.0:
+ if self._verbose:
+ print "Amplitude out of range:", amplitude
+ return False
+
+ if self[TYPE_KEY] in (gr.GR_SIN_WAVE, gr.GR_CONST_WAVE, gr.GR_GAUSSIAN, gr.GR_UNIFORM):
+ self._src.set_amplitude(amplitude)
+ elif self[TYPE_KEY] == "2tone":
+ self._src1.set_amplitude(amplitude/2.0)
+ self._src2.set_amplitude(amplitude/2.0)
+ elif self[TYPE_KEY] == "sweep":
+ self._src.set_k(amplitude)
+ else:
+ return True # Waveform not yet set
+
+ if self._verbose:
+ print "Set amplitude to:", amplitude
+ return True
+
+def get_options():
+ usage="%prog: [options]"
+
+ parser = OptionParser(option_class=eng_option, usage=usage)
+ parser.add_option("-a", "--args", type="string", default="",
+ help="Device args, [default=%default]")
+ parser.add_option("-A", "--antenna", type="string", default=None,
+ help="Select Rx Antenna where appropriate")
+ parser.add_option("-s", "--samp-rate", type="eng_float", default=1e6,
+ help="Set sample rate (bandwidth) [default=%default]")
+ parser.add_option("-g", "--gain", type="eng_float", default=None,
+ help="Set gain in dB (default is midpoint)")
+ parser.add_option("-f", "--tx-freq", type="eng_float", default=None,
+ help="Set carrier frequency to FREQ [default=mid-point]",
+ metavar="FREQ")
+ parser.add_option("-c", "--freq-corr", type="int", default=None,
+ help="Set carrier frequency correction [default=0]")
+ parser.add_option("-x", "--waveform-freq", type="eng_float", default=0,
+ help="Set baseband waveform frequency to FREQ [default=%default]")
+ parser.add_option("-y", "--waveform2-freq", type="eng_float", default=None,
+ help="Set 2nd waveform frequency to FREQ [default=%default]")
+ parser.add_option("--sine", dest="type", action="store_const", const=gr.GR_SIN_WAVE,
+ help="Generate a carrier modulated by a complex sine wave",
+ default=gr.GR_SIN_WAVE)
+ parser.add_option("--const", dest="type", action="store_const", const=gr.GR_CONST_WAVE,
+ help="Generate a constant carrier")
+ parser.add_option("--offset", type="eng_float", default=0,
+ help="Set waveform phase offset to OFFSET [default=%default]")
+ parser.add_option("--gaussian", dest="type", action="store_const", const=gr.GR_GAUSSIAN,
+ help="Generate Gaussian random output")
+ parser.add_option("--uniform", dest="type", action="store_const", const=gr.GR_UNIFORM,
+ help="Generate Uniform random output")
+ parser.add_option("--2tone", dest="type", action="store_const", const="2tone",
+ help="Generate Two Tone signal for IMD testing")
+ parser.add_option("--sweep", dest="type", action="store_const", const="sweep",
+ help="Generate a swept sine wave")
+ parser.add_option("", "--amplitude", type="eng_float", default=0.3,
+ help="Set output amplitude to AMPL (0.0-1.0) [default=%default]",
+ metavar="AMPL")
+ parser.add_option("-v", "--verbose", action="store_true", default=False,
+ help="Use verbose console output [default=%default]")
+
+ (options, args) = parser.parse_args()
+
+ return (options, args)
+
+# If this script is executed, the following runs. If it is imported,
+# the below does not run.
+def test_main():
+ if gr.enable_realtime_scheduling() != gr.RT_OK:
+ print "Note: failed to enable realtime scheduling, continuing"
+
+ # Grab command line options and create top block
+ try:
+ (options, args) = get_options()
+ tb = top_block(options, args)
+
+ except RuntimeError, e:
+ print e
+ sys.exit(1)
+
+ tb.start()
+ raw_input('Press Enter to quit: ')
+ tb.stop()
+ tb.wait()
+
+# Make sure to create the top block (tb) within a function:
+# That code in main will allow tb to go out of scope on return,
+# which will call the decontructor on radio and stop transmit.
+# Whats odd is that grc works fine with tb in the __main__,
+# perhaps its because the try/except clauses around tb.
+if __name__ == "__main__":
+ test_main()
diff --git a/apps/osmocom_siggen_gui b/apps/osmocom_siggen_gui
new file mode 100755
index 0000000..86003c4
--- /dev/null
+++ b/apps/osmocom_siggen_gui
@@ -0,0 +1,374 @@
+#!/usr/bin/env python
+#
+# Copyright 2009,2011,2012 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr
+from gnuradio.gr.pubsub import pubsub
+from osmosdr import osmocom_siggen_base as osmocom_siggen
+import sys, math
+
+try:
+ from gnuradio.wxgui import gui, forms
+ import wx
+except ImportError:
+ sys.stderr.write("Error importing GNU Radio's wxgui. Please make sure gr-wxgui is installed.\n")
+ sys.exit(1)
+
+class app_gui(pubsub):
+ def __init__(self, frame, panel, vbox, top_block, options, args):
+ pubsub.__init__(self)
+ self.frame = frame # Use for top-level application window frame
+ self.panel = panel # Use as parent class for created windows
+ self.vbox = vbox # Use as sizer for created windows
+ self.tb = top_block # GUI-unaware flowgraph class
+ self.options = options # Supplied command-line options
+ self.args = args # Supplied command-line arguments
+ self.build_gui()
+
+ # Event response handlers
+ def evt_set_status_msg(self, msg):
+ self.frame.SetStatusText(msg, 0)
+
+ # GUI construction
+ def build_gui(self):
+ self.vbox.AddSpacer(3)
+ self.vbox.AddStretchSpacer()
+ ##################################################
+ # Baseband controls
+ ##################################################
+ bb_vbox = forms.static_box_sizer(parent=self.panel, label="Baseband Modulation", orient=wx.VERTICAL, bold=True)
+ self.vbox.Add(bb_vbox, 0, wx.EXPAND)
+ sine_bb_hbox = wx.BoxSizer(wx.HORIZONTAL)
+ sweep_bb_hbox = wx.BoxSizer(wx.HORIZONTAL)
+ tone_bb_hbox = wx.BoxSizer(wx.HORIZONTAL)
+ self.vbox.AddSpacer(5)
+ self.vbox.AddStretchSpacer()
+ #callback to show/hide forms
+ def set_type(type):
+ sine_bb_hbox.ShowItems(type == gr.GR_SIN_WAVE)
+ sweep_bb_hbox.ShowItems(type == 'sweep')
+ tone_bb_hbox.ShowItems(type == '2tone')
+ self.vbox.Layout()
+ self.tb.subscribe(osmocom_siggen.TYPE_KEY, set_type)
+ #create sine forms
+ sine_bb_hbox.AddSpacer(5)
+ forms.text_box(
+ parent=self.panel, sizer=sine_bb_hbox,
+ label='Frequency (Hz)',
+ ps=self.tb,
+ key=osmocom_siggen.WAVEFORM_FREQ_KEY,
+ converter=forms.float_converter(),
+ )
+ sine_bb_hbox.AddStretchSpacer()
+ #create sweep forms
+ sweep_bb_hbox.AddSpacer(5)
+ forms.text_box(
+ parent=self.panel, sizer=sweep_bb_hbox,
+ label='Sweep Width (Hz)',
+ ps=self.tb,
+ key=osmocom_siggen.WAVEFORM_FREQ_KEY,
+ converter=forms.float_converter(),
+ )
+ sweep_bb_hbox.AddStretchSpacer()
+ forms.text_box(
+ parent=self.panel, sizer=sweep_bb_hbox,
+ label='Sweep Rate (Hz)',
+ ps=self.tb,
+ key=osmocom_siggen.WAVEFORM2_FREQ_KEY,
+ converter=forms.float_converter(),
+ )
+ sweep_bb_hbox.AddStretchSpacer()
+ #create 2tone forms
+ tone_bb_hbox.AddSpacer(5)
+ forms.text_box(
+ parent=self.panel, sizer=tone_bb_hbox,
+ label='Tone 1 (Hz)',
+ ps=self.tb,
+ key=osmocom_siggen.WAVEFORM_FREQ_KEY,
+ converter=forms.float_converter(),
+ )
+ tone_bb_hbox.AddStretchSpacer()
+ forms.text_box(
+ parent=self.panel, sizer=tone_bb_hbox,
+ label='Tone 2 (Hz)',
+ ps=self.tb,
+ key=osmocom_siggen.WAVEFORM2_FREQ_KEY,
+ converter=forms.float_converter(),
+ )
+ tone_bb_hbox.AddStretchSpacer()
+ forms.radio_buttons(
+ parent=self.panel, sizer=bb_vbox,
+ choices=osmocom_siggen.waveforms.keys(),
+ labels=osmocom_siggen.waveforms.values(),
+ ps=self.tb,
+ key=osmocom_siggen.TYPE_KEY,
+ style=wx.NO_BORDER | wx.RA_HORIZONTAL,
+ )
+ bb_vbox.AddSpacer(5)
+ bb_vbox.Add(sine_bb_hbox, 0, wx.EXPAND)
+ bb_vbox.Add(sweep_bb_hbox, 0, wx.EXPAND)
+ bb_vbox.Add(tone_bb_hbox, 0, wx.EXPAND)
+ set_type(self.tb[osmocom_siggen.TYPE_KEY])
+
+ ##################################################
+ # Frequency controls
+ ##################################################
+ fc_vbox = forms.static_box_sizer(parent=self.panel,
+ label="Center Frequency",
+ orient=wx.VERTICAL,
+ bold=True)
+ fc_vbox.AddSpacer(3)
+ # First row of frequency controls (center frequency)
+ freq_hbox = wx.BoxSizer(wx.HORIZONTAL)
+ fc_vbox.Add(freq_hbox, 0, wx.EXPAND)
+ fc_vbox.AddSpacer(5)
+ # Second row of frequency controls (freq. correction)
+ corr_hbox = wx.BoxSizer(wx.HORIZONTAL)
+ fc_vbox.Add(corr_hbox, 0, wx.EXPAND)
+ fc_vbox.AddSpacer(3)
+ # Add frequency controls to top window sizer
+ self.vbox.Add(fc_vbox, 0, wx.EXPAND)
+ self.vbox.AddSpacer(5)
+ self.vbox.AddStretchSpacer()
+
+ freq_hbox.AddSpacer(3)
+ forms.text_box(
+ parent=self.panel, sizer=freq_hbox,
+ label='Center Frequency (Hz)',
+ proportion=1,
+ converter=forms.float_converter(),
+ ps=self.tb,
+ key=osmocom_siggen.TX_FREQ_KEY,
+ )
+ freq_hbox.AddSpacer(5)
+
+ forms.slider(
+ parent=self.panel, sizer=freq_hbox,
+ proportion=2,
+ ps=self.tb,
+ key=osmocom_siggen.TX_FREQ_KEY,
+ minimum=self.tb[osmocom_siggen.FREQ_RANGE_KEY].start(),
+ maximum=self.tb[osmocom_siggen.FREQ_RANGE_KEY].stop(),
+ num_steps=100,
+ )
+ freq_hbox.AddSpacer(3)
+
+ corr_hbox.AddSpacer(3)
+ forms.text_box(
+ parent=self.panel, sizer=corr_hbox,
+ label='Frequency Correction (ppm)',
+ proportion=1,
+ converter=forms.int_converter(),
+ ps=self.tb,
+ key=osmocom_siggen.FREQ_CORR_KEY,
+ )
+ corr_hbox.AddSpacer(5)
+
+ forms.slider(
+ parent=self.panel, sizer=corr_hbox,
+ proportion=2,
+ ps=self.tb,
+ key=osmocom_siggen.FREQ_CORR_KEY,
+ minimum=-100,
+ maximum=+100,
+ num_steps=201,
+ )
+ corr_hbox.AddSpacer(3)
+
+ ##################################################
+ # Amplitude controls
+ ##################################################
+ amp_vbox = forms.static_box_sizer(parent=self.panel,
+ label="Amplitude",
+ orient=wx.VERTICAL,
+ bold=True)
+ amp_vbox.AddSpacer(3)
+ # First row of amp controls (ampl)
+ lvl_hbox = wx.BoxSizer(wx.HORIZONTAL)
+ amp_vbox.Add(lvl_hbox, 0, wx.EXPAND)
+ amp_vbox.AddSpacer(5)
+ # Second row of amp controls (tx gain)
+ gain_hbox = wx.BoxSizer(wx.HORIZONTAL)
+ amp_vbox.Add(gain_hbox, 0, wx.EXPAND)
+ amp_vbox.AddSpacer(3)
+ if_gain_hbox = wx.BoxSizer(wx.HORIZONTAL)
+ amp_vbox.Add(if_gain_hbox, 0, wx.EXPAND)
+ amp_vbox.AddSpacer(3)
+
+ self.vbox.Add(amp_vbox, 0, wx.EXPAND)
+ self.vbox.AddSpacer(5)
+ self.vbox.AddStretchSpacer()
+
+ lvl_hbox.AddSpacer(3)
+ forms.text_box(
+ parent=self.panel, sizer=lvl_hbox,
+ proportion=1,
+ converter=forms.float_converter(),
+ ps=self.tb,
+ key=osmocom_siggen.AMPLITUDE_KEY,
+ label="Level (0.0-1.0)",
+ )
+ lvl_hbox.AddSpacer(5)
+ forms.log_slider(
+ parent=self.panel, sizer=lvl_hbox,
+ proportion=2,
+ ps=self.tb,
+ key=osmocom_siggen.AMPLITUDE_KEY,
+ min_exp=-6,
+ max_exp=0,
+ base=10,
+ num_steps=100,
+ )
+ lvl_hbox.AddSpacer(3)
+
+ if self.tb[osmocom_siggen.GAIN_RANGE_KEY].start() < self.tb[osmocom_siggen.GAIN_RANGE_KEY].stop():
+ gain_hbox.AddSpacer(3)
+ forms.text_box(
+ parent=self.panel, sizer=gain_hbox,
+ proportion=1,
+ converter=forms.float_converter(),
+ ps=self.tb,
+ key=osmocom_siggen.GAIN_KEY,
+ label="RF Gain (dB)",
+ )
+ gain_hbox.AddSpacer(5)
+ forms.slider(
+ parent=self.panel, sizer=gain_hbox,
+ proportion=2,
+ ps=self.tb,
+ key=osmocom_siggen.GAIN_KEY,
+ minimum=self.tb[osmocom_siggen.GAIN_RANGE_KEY].start(),
+ maximum=self.tb[osmocom_siggen.GAIN_RANGE_KEY].stop(),
+ step_size=self.tb[osmocom_siggen.GAIN_RANGE_KEY].step(),
+ )
+ gain_hbox.AddSpacer(3)
+
+ if self.tb[osmocom_siggen.IF_GAIN_RANGE_KEY].start() < self.tb[osmocom_siggen.IF_GAIN_RANGE_KEY].stop():
+ if_gain_hbox.AddSpacer(3)
+ forms.text_box(
+ parent=self.panel, sizer=if_gain_hbox,
+ proportion=1,
+ converter=forms.float_converter(),
+ ps=self.tb,
+ key=osmocom_siggen.IF_GAIN_KEY,
+ label="IF Gain (dB)",
+ )
+ if_gain_hbox.AddSpacer(5)
+ forms.slider(
+ parent=self.panel, sizer=if_gain_hbox,
+ proportion=2,
+ ps=self.tb,
+ key=osmocom_siggen.IF_GAIN_KEY,
+ minimum=self.tb[osmocom_siggen.IF_GAIN_RANGE_KEY].start(),
+ maximum=self.tb[osmocom_siggen.IF_GAIN_RANGE_KEY].stop(),
+ step_size=self.tb[osmocom_siggen.IF_GAIN_RANGE_KEY].step(),
+ )
+ if_gain_hbox.AddSpacer(3)
+
+ ##################################################
+ # Bandiwdth controls
+ ##################################################
+ bwidth_vbox = forms.static_box_sizer(parent=self.panel,
+ label="Bandwidth",
+ orient=wx.VERTICAL,
+ bold=True)
+ bwidth_vbox.AddSpacer(3)
+ bwidth_hbox = wx.BoxSizer(wx.HORIZONTAL)
+ bwidth_vbox.Add(bwidth_hbox, 0, wx.EXPAND)
+ bwidth_vbox.AddSpacer(3)
+
+ self.vbox.Add(bwidth_vbox, 0, wx.EXPAND)
+ self.vbox.AddSpacer(5)
+ self.vbox.AddStretchSpacer()
+
+ if self.tb[osmocom_siggen.BWIDTH_RANGE_KEY].start() < self.tb[osmocom_siggen.BWIDTH_RANGE_KEY].stop():
+ bwidth_hbox.AddSpacer(3)
+ forms.text_box(
+ parent=self.panel, sizer=bwidth_hbox,
+ proportion=1,
+ converter=forms.float_converter(),
+ ps=self.tb,
+ key=osmocom_siggen.BWIDTH_KEY,
+ label="Bandwidth (Hz)",
+ )
+ bwidth_hbox.AddSpacer(5)
+ forms.slider(
+ parent=self.panel, sizer=bwidth_hbox,
+ proportion=2,
+ ps=self.tb,
+ key=osmocom_siggen.BWIDTH_KEY,
+ minimum=self.tb[osmocom_siggen.BWIDTH_RANGE_KEY].start(),
+ maximum=self.tb[osmocom_siggen.BWIDTH_RANGE_KEY].stop(),
+ step_size=self.tb[osmocom_siggen.BWIDTH_RANGE_KEY].step(),
+ )
+ bwidth_hbox.AddSpacer(3)
+
+ ##################################################
+ # Sample Rate controls
+ ##################################################
+ sam_hbox = forms.static_box_sizer(parent=self.panel,
+ label="Sample Rate",
+ orient=wx.HORIZONTAL,
+ bold=True)
+ self.vbox.Add(sam_hbox, 0, wx.EXPAND)
+ self.vbox.AddSpacer(5)
+ self.vbox.AddStretchSpacer()
+ sam_hbox.AddStretchSpacer(20)
+ forms.static_text(
+ parent=self.panel, sizer=sam_hbox,
+ label='Sample Rate (sps)',
+ ps=self.tb,
+ key=osmocom_siggen.SAMP_RATE_KEY,
+ converter=forms.float_converter(),
+ )
+ sam_hbox.AddStretchSpacer(20)
+
+def main():
+ try:
+ # Get command line parameters
+ (options, args) = osmocom_siggen.get_options()
+
+ # Create the top block using these
+ tb = osmocom_siggen.top_block(options, args)
+
+ # Create the GUI application
+ app = gui.app(top_block=tb, # Constructed top block
+ gui=app_gui, # User interface class
+ options=options, # Command line options
+ args=args, # Command line args
+ title="OSMOCOM Signal Generator", # Top window title
+ nstatus=1, # Number of status lines
+ start=True, # Whether to start flowgraph
+ realtime=True) # Whether to set realtime priority
+
+ # And run it
+ app.MainLoop()
+
+ except RuntimeError, e:
+ print e
+ sys.exit(1)
+
+# Make sure to create the top block (tb) within a function: That code
+# in main will allow tb to go out of scope on return, which will call
+# the decontructor on radio device and stop transmit. Whats odd is that
+# grc works fine with tb in the __main__, perhaps its because the
+# try/except clauses around tb.
+if __name__ == "__main__": main()