[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Discuss-gnuradio] "Click to tune" prototype USRP Scanner (files attache
From: |
Johnathan Corgan |
Subject: |
[Discuss-gnuradio] "Click to tune" prototype USRP Scanner (files attached) |
Date: |
Sun, 04 Jun 2006 20:18:20 -0700 |
User-agent: |
Thunderbird 1.5.0.2 (X11/20060522) |
Attached is snapshot of my current progress towards a 'click to tune'
scanner using a USRP. It doesn't scan yet, though.
The application will bring up a waterfall sink display based on a 1024
point FFT of the incoming IF samples, and start demodulating audio at
the rx frequency set.
It's working...but hard to use. Right now everything but the tuned
frequency is set at start via the command line:
usage: scanner.py [options]
options:
-h, --help show this help message and exit
--center-freq=FREQ set FFT center frequency to FREQ
--rx-freq=Hz set receive frequency to FREQ
--rx-board=SIDE select USRP Rx side A or B
--if-rate=RATE set IF sample rate to RATE, complex
--middle-rate=RATE set first downconversion sample rate (complex)
--output-rate=RATE set output sample rate to RATE, float
--channel-pass=Hz set channel bandpass frequency to Hz
--channel-stop=Hz set channel stopband frequency to Hz
--channel-rate=RATE set channel sample rate to RATE, complex
--deviation=Hz set FM deviation to Hz
--audio-rate=RATE set final audio sampling rate, float
--audio-pass=Hz set audio filter passband Hz
--audio-stop=Hz set audio filter stopband Hz
--modulation=TYPE set modulation type (AM,FM)
--squelch=dB set RF squelch to dB
--gain=dB set RF gain
None of the options have defaults right now, and none of them are
checked for validity.
Moving the mouse around will display the frequency in a tooltip along
the bottom. (You might have to leave the window with the mouse and
return to it.)
Clicking on a trace will set the demodulation frequency to that trace.
It's pretty cool.
Also attached are some helper scripts:
./2m will set up a 144-148 MHz waterfall, click to decode FM.
./440m will set up a 440-445 MHz waterfall, click to decode FM.
./460m will set up a 460-465 (UHF LO business band) waterfall
./aviation will set up a 119-125 MHz waterfall, click to decode AM
./fm center_freq rx_freq will do center_freq +-3MHz and decode FM
./wfm center_freq rx_freq will do the same for WFM
./kfox will play good music in the San Francisco Bay Area
There is much, much more work to do--I plan to add a bunch of controls
under the waterfall to control everything currently specified on the
command line as well as a keyboard shortcuts to "fly around" the spectrum.
A note on parameters: Some sound cards (like mine) only accept 48000
sample rate. The flow graph demodulates the selected signal down to
'audio_rate' samples per second, then resamples this to the
'output_rate'. So you may need to edit the helper scripts to change the
'output_rate' parameter to something your sound card likes. It needs to
be a rational fraction of the audio_rate parameter.
if_rate, middle_rate, channel_rate, audio_rate all need to be decreasing
integral divisors. (e.g., 6400000, 128000, 32000, 8000)
Testing only done under current CVS gnuradio/usrp and Ubuntu 6.06 Linux.
Comments, tweaks, bugs reports, etc., are welcome.
Enjoy.
-Johnathan, AE6HO
#!/usr/bin/env python
import wx
from math import pi
import wx.lib.evtmgr as em
from gnuradio import gr, gru, usrp, optfir, audio, eng_notation, blks
from gnuradio.eng_option import eng_option
from gnuradio.wxgui import waterfallsink
from optparse import OptionParser
class fm_demod(gr.hier_block):
def __init__(self,
fg, # Flow graph
channel_rate, # Channel sample rate
deviation): # Maximum deviation
k = channel_rate/(2*pi*deviation) # Max deviation maps to -1.0
<--> 1.0
QUAD = gr.quadrature_demod_cf(k)
DEEMPH = blks.fm_deemph(fg, channel_rate, 75e-6) # FIXME: uses magic
numbers
fg.connect(QUAD, DEEMPH)
gr.hier_block.__init__(self, fg, QUAD, DEEMPH)
class am_demod(gr.hier_block):
def __init__(self, fg):
MAG = gr.complex_to_mag()
DCR = gr.add_const_ff(-1.0)
VOL = gr.multiply_const_ff(0.95)
fg.connect(MAG, DCR, VOL)
gr.hier_block.__init__(self, fg, MAG, VOL)
class two_stage_ddc(gr.hier_block):
def __init__(self,
fg, # Flow graph
input_rate, # Input sample rate
middle_rate, # First decimator output rate
output_rate, # Output sample rate
passband, # Output filter passband
stopband, # Output filter stopband
ripple=0.1, # db passband ripple
atten=60): # db stopband attenuation
ddc1_taps = optfir.low_pass(1.0, input_rate,
passband, middle_rate - passband,
ripple, atten)
self.DDC1 = gr.freq_xlating_fir_filter_ccf(input_rate // middle_rate,
ddc1_taps, 0.0, input_rate)
print "DDC1 filter length is", len(ddc1_taps), "taps."
ddc2_taps = optfir.low_pass(1.0, middle_rate,
passband, stopband,
ripple, atten)
DDC2 = gr.freq_xlating_fir_filter_ccf(middle_rate // output_rate,
ddc2_taps, 0.0, middle_rate)
print "DDC2 filter length is", len(ddc2_taps), "taps."
fg.connect(self.DDC1, DDC2)
gr.hier_block.__init__(self, fg, self.DDC1, DDC2)
def set_offset(self, offset):
self.DDC1.set_center_freq(offset)
class receive_strip(gr.hier_block):
def __init__(self,
fg, # Flow graph
if_rate, # IF input sample rate (complex)
middle_rate,
channel_rate, # Final channel sample rate (complex)
channel_pass, # Occupied spectrum for narrowband channel
channel_stop, # Total channel + guard band
demodulator, # Block to convert channel samples to audio
samples (no decimation or filtering)
audio_pass, # Demodulated audio passband frequency
audio_stop, # Demodulated audio stopband frequency
audio_rate, # Final demodulated audio sample rate (float)
output_rate): # Audio output sample rate (float)
# Downconvert from IF to baseband channel
self.DDC = two_stage_ddc(fg, if_rate, middle_rate, channel_rate,
channel_pass, channel_stop)
# Implement RF squelch block
self.RFSQL = gr.simple_squelch_cc(-50.0)
self.RFSQL.set_alpha(0.01)
# Implement AGC block
self.AGC = gr.agc_cc()
# Demodulation block
DEMOD = demodulator
# Filter audio signal to fit audio mask
audio_taps = optfir.low_pass(1.0, channel_rate,
audio_pass, audio_stop,
0.1, 60)
self.AUDIO = gr.fir_filter_fff(channel_rate / audio_rate, audio_taps)
print "AUDIO filter length is", len(audio_taps), "taps."
# Skip AF squelch for now
# Create resampler to match desired output rate
out_lcm = gru.lcm(audio_rate, output_rate)
out_interp = int(out_lcm // audio_rate)
out_decim = int(out_lcm // output_rate)
print "Output interpolation rate is", out_interp
print "Output decimation rate is", out_decim
RESAMP = blks.rational_resampler_fff(fg, out_interp, out_decim)
# Tie receive chain together
fg.connect(self.DDC, self.RFSQL, self.AGC, DEMOD, self.AUDIO, RESAMP)
gr.hier_block.__init__(self, fg, self.DDC, RESAMP)
def set_squelch(self, threshold):
self.RFSQL.set_threshold(threshold)
def set_offset(self, offset):
self.DDC.set_offset(offset)
class scanner_flow_graph(gr.flow_graph):
def __init__(self, options, args, fftpanel):
gr.flow_graph.__init__(self)
self.options = options
self.args = args
if options.modulation == 'AM':
demodulator = am_demod(self)
elif options.modulation == 'FM':
demodulator = fm_demod(self, options.channel_rate,
options.deviation)
# Use USRP as data source
SRC = usrp.source_c()
if options.rx_board is None:
options.rx_board = usrp.pick_rx_subdevice(SRC)
subdev = usrp.selected_subdev(SRC, options.rx_board)
SRC.set_mux(usrp.determine_rx_mux_value(SRC, options.rx_board))
usrp_decim = SRC.adc_rate() // options.if_rate
SRC.set_decim_rate(usrp_decim)
result = usrp.tune(SRC, 0, subdev, options.center_freq)
print "Using %s daughterboard" % (subdev.side_and_name(),)
print "Center frequency is", options.center_freq
print "Rx frequency is", options.rx_freq
print "USRP decimation rate is", usrp_decim
if result:
print "Residual frequency after tuning is " + `result.residual_freq`
subdev.set_gain(options.gain)
self.RECV = receive_strip(self, options.if_rate, options.middle_rate,
options.channel_rate,
options.channel_pass, options.channel_stop,
demodulator,
options.audio_pass, options.audio_stop,
options.audio_rate,
options.output_rate)
self.RECV.set_squelch(options.squelch)
offset = options.center_freq - options.rx_freq
# self.set_freq(options.rx_freq)
# Use audio sink
OUT = audio.sink(options.output_rate, "")
MLT = gr.multiply_const_cc(100) # FIXME: parameterize
self.FFT = waterfallsink.waterfall_sink_c(self,
parent=fftpanel,
sample_rate=options.if_rate,
fft_size=1024,
fft_rate=10,
size=(1024,350))
# Connect pipeline
self.connect(SRC, self.RECV, OUT)
self.connect(SRC, MLT, self.FFT)
def set_freq(self, frequency):
offset = self.options.center_freq - frequency
self.RECV.set_offset(offset)
class scanner_frame(wx.Frame):
def __init__(self, options, args):
wx.Frame.__init__(self, None, -1, "USRP Scanner")
self.options = options
self.args = args
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
self.make_menu()
self.make_controls()
self.fg = scanner_flow_graph(options, args, self.fftpanel)
self.fg.start()
em.eventManager.Register(self.evt_mouse_motion, wx.EVT_MOTION,
self.fg.FFT.win)
wx.ToolTip.Enable(True)
wx.ToolTip.SetDelay(0)
em.eventManager.Register(self.evt_mouse_down, wx.EVT_LEFT_DOWN,
self.fg.FFT.win)
def make_menu(self):
bar = wx.MenuBar()
menu = wx.Menu()
item = menu.Append(wx.ID_EXIT, 'E&xit', 'Exit')
self.Bind (wx.EVT_MENU, self.OnCloseWindow, item)
bar.Append(menu, "&File")
self.SetMenuBar(bar)
def make_controls(self):
# Main window is a vertical box sizer
sizer = wx.BoxSizer(wx.VERTICAL)
# First is fft output
self.fftpanel = wx.Panel(self, -1, size=(1024,350)) #TEMP
# self.fftpanel.SetBackgroundColour(wx.BLACK)
sizer.Add(self.fftpanel, 1, wx.EXPAND)
self.SetSizer(sizer)
self.SetAutoLayout(True)
sizer.Fit(self)
def evt_mouse_motion(self, event):
f =
(event.GetX()/1024.0-0.5)*self.options.if_rate+self.options.center_freq
str = eng_notation.num_to_str(f)
self.fftpanel.SetToolTip(wx.ToolTip(str))
def evt_mouse_down(self, event):
f =
(event.GetX()/1024.0-0.5)*self.options.if_rate+self.options.center_freq
str = eng_notation.num_to_str(f)
print str
self.fg.set_freq(f)
def OnCloseWindow(self, event):
self.fg.stop()
self.Destroy()
class scanner_app(wx.App):
def __init__(self, options, args):
self.options = options
self.args = args
wx.App.__init__(self, redirect=True)
def OnInit(self):
frame = scanner_frame(self.options, self.args)
frame.Show(True)
self.SetTopWindow(frame)
return True
def main():
parser = OptionParser(option_class=eng_option)
parser.add_option("", "--center-freq", type="eng_float", help="set FFT
center frequency to FREQ", metavar="FREQ")
parser.add_option("", "--rx-freq", type="eng_float", help="set
receive frequency to FREQ", metavar="Hz")
parser.add_option("", "--rx-board", type="subdev", help="select USRP
Rx side A or B (default=first daughterboard found)", metavar="SIDE")
parser.add_option("" , "--if-rate", type="int", help="set IF sample
rate to RATE, complex (default=%default)", metavar="RATE")
parser.add_option("" , "--middle-rate", type="int", help="set first IF
downconversion sample rate to RATE, complex", metavar="RATE")
parser.add_option("" , "--output-rate", type="int", help="set output
sample rate to RATE, float", metavar="RATE")
parser.add_option("" , "--channel-pass", type="int", help="set channel
bandpass frequency to Hz", metavar="Hz")
parser.add_option("" , "--channel-stop", type="int", help="set channel
stopband frequency to Hz", metavar="Hz")
parser.add_option("" , "--channel-rate", type="int", help="set channel
sample rate to RATE, complex", metavar="RATE")
parser.add_option("" , "--deviation", type="int", help="set FM
deviation to Hz", metavar="Hz")
parser.add_option("" , "--audio-rate", type="int", help="set final audio
sampling rate, float", metavar="RATE")
parser.add_option("" , "--audio-pass", type="int", help="set audio
filter passband Hz", metavar="Hz")
parser.add_option("" , "--audio-stop", type="int", help="set audio
filter stopband Hz", metavar="Hz")
parser.add_option("", "--modulation", type="choice",
choices=('AM','FM'), help="set modulation type (AM,FM)", metavar="TYPE")
parser.add_option("", "--squelch", type="int", help="set RF squelch
to dB (default -50 dB)", metavar="dB")
parser.add_option("", "--gain", type="int", help="set RF gain",
metavar="dB")
(options, args) = parser.parse_args()
# FIXME: parameter sanity checks
if options.center_freq < 1e6:
options.center_freq *= 1e6
if options.rx_freq < 1e6:
options.rx_freq *= 1e6
app = scanner_app(options, args)
app.MainLoop()
if __name__ == "__main__":
main()
#!/bin/sh
./scanner.py \
--center-freq 146.000 \
--rx-freq 146.527 \
--if-rate 4000000 \
--middle-rate 100000 \
--channel-pass 8000 \
--channel-stop 10000 \
--channel-rate 20000 \
--audio-pass 3000 \
--audio-stop 5000 \
--audio-rate 10000 \
--output-rate 48000 \
--modulation FM \
--deviation 5000 \
--squelch 0 \
--gain 70
#!/bin/sh
./scanner.py \
--center-freq 442.500M \
--rx-freq 443.200M \
--if-rate 6400000 \
--middle-rate 128000 \
--channel-pass 8000 \
--channel-stop 10000 \
--channel-rate 32000 \
--audio-pass 3000 \
--audio-stop 5000 \
--audio-rate 16000 \
--output-rate 48000 \
--modulation FM \
--deviation 5000 \
--squelch -10 \
--gain 70
#!/bin/sh
./scanner.py \
--center-freq 462.500M \
--rx-freq 460.480M \
--if-rate 6400000 \
--middle-rate 128000 \
--channel-pass 8000 \
--channel-stop 10000 \
--channel-rate 32000 \
--audio-pass 3000 \
--audio-stop 5000 \
--audio-rate 16000 \
--output-rate 48000 \
--modulation FM \
--deviation 5000 \
--squelch -10 \
--gain 70
#!/bin/sh
./scanner.py \
--center-freq 122M \
--rx-freq 124M \
--if-rate 6400000 \
--middle-rate 128000 \
--channel-pass 5000 \
--channel-stop 8000 \
--channel-rate 16000 \
--audio-pass 3000 \
--audio-stop 4000 \
--audio-rate 8000 \
--output-rate 48000 \
--modulation AM \
--squelch -10 \
--gain 70
#!/bin/sh
./scanner.py \
--center-freq $1 \
--rx-freq $2 \
--if-rate 6400000 \
--middle-rate 128000 \
--channel-pass 8000 \
--channel-stop 10000 \
--channel-rate 32000 \
--audio-pass 3000 \
--audio-stop 5000 \
--audio-rate 16000 \
--output-rate 48000 \
--modulation FM \
--deviation 5000 \
--squelch 0 \
--gain 70
#!/bin/sh
./wfm 98.5 98.5
#!/bin/sh
./scanner.py \
--center-freq $1 \
--rx-freq $2 \
--if-rate 6400000 \
--middle-rate 1280000 \
--channel-pass 90000 \
--channel-stop 125000 \
--channel-rate 256000 \
--audio-pass 15000 \
--audio-stop 19000 \
--audio-rate 64000 \
--output-rate 48000 \
--modulation FM \
--deviation 75000 \
--squelch -50 \
--gain 80
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [Discuss-gnuradio] "Click to tune" prototype USRP Scanner (files attached),
Johnathan Corgan <=