discuss-gnuradio
[Top][All Lists]
Advanced

[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
    

reply via email to

[Prev in Thread] Current Thread [Next in Thread]