diff options
-rw-r--r-- | gr-sounder/README | 80 | ||||
-rw-r--r-- | gr-sounder/src/fpga/tb/sounder_tb.sav | 8 | ||||
-rw-r--r-- | gr-sounder/src/fpga/tb/sounder_tb.v | 4 | ||||
-rw-r--r-- | gr-sounder/src/python/Makefile.am | 15 | ||||
-rw-r--r-- | gr-sounder/src/python/sounder.py | 233 | ||||
-rwxr-xr-x | gr-sounder/src/python/sounder_loopback.sh | 4 | ||||
-rwxr-xr-x | gr-sounder/src/python/test_lp.sh | 4 | ||||
-rwxr-xr-x | gr-sounder/src/python/test_rx.sh | 4 | ||||
-rwxr-xr-x | gr-sounder/src/python/test_tx.sh | 3 | ||||
-rwxr-xr-x | gr-sounder/src/python/usrp_sounder.py | 198 |
10 files changed, 367 insertions, 186 deletions
diff --git a/gr-sounder/README b/gr-sounder/README index ef495f1e8..3c5df21d4 100644 --- a/gr-sounder/README +++ b/gr-sounder/README @@ -1,10 +1,9 @@ This is a work-in-progress implementation of a m-sequence based channel sounder for GNU Radio and the USRP. -At present, only the transmitter is implemented. When completed, you -will be able to place the sounder transmitter at one location, the -sounder receiver at another location, then determine in real-time the -channel impulse and frequency response between them. +In typical use, the user would run the sounder as a transmitter on one +USRP, and a receiver on another at a different location. The receiver +will determine the impulse response of the RF channel in between. The sounder uses a custom FPGA bitstream that is able to generate and receive a sounder waveform across a full 32 MHz wide swath of RF spectrum; @@ -17,45 +16,76 @@ out this response during calibration. The sounder is based on sending a maximal-length PN code modulated as BPSK with the supplied center frequency, with a chip-rate of 32 MHz. The -receiver (partially implemented at this time but not working yet) correlates -the received signal across all phases of the PN code and outputs an impulse -response vector. As auto-correlation of an m-sequence is near zero for -any relative phase shift, the actual measured energy at a particular -phase shift is related to the impulse response for that time delay. This -is the same principle used in spread-spectrum RAKE receivers such as are +receiver correlates the received signal across all phases of the PN code +and outputs an impulse response vector. As auto-correlation of an m-sequence +is near zero for any relative phase shift, the actual measured energy at a +particular phase shift is related to the impulse response for that time delay. +This is the same principle used in spread-spectrum RAKE receivers such as are used with GPS and CDMA. -The transmitter and receiver are designed to work only with the board in -side A. This may be a standalone LFTX/LFRX or an RFX daughterboard. +The transmitter is designed to work only with the board in side A. The +receiver may be in side A or side B. The boards may be standalone LFTX/LFRXs +or RFX daughterboards. To use, the following script is installed into $prefix/bin: -usage: usrp_sounder.py [options] +Usage: usrp_sounder.py [options] -options: +Options: -h, --help show this help message and exit + -R RX_SUBDEV_SPEC, --rx-subdev-spec=RX_SUBDEV_SPEC + select USRP Rx side A or B -f FREQ, --frequency=FREQ set frequency to FREQ in Hz, default is 0.0 + -d DEGREE, --degree=DEGREE + set sounding sequence degree (2-12), default is 12, -t, --transmit enable sounding transmitter -r, --receive enable sounding receiver - -d DEGREE, --degree=DEGREE - set souding sequence degree (len=2^degree-1), default - is 16 - -n SAMPLES, --samples=SAMPLES - number of samples to capture on receive, default is - infinite -l, --loopback enable digital loopback, default is disabled + -v, --verbose enable verbose output, default is disabled + -D, --debug enable debugging output, default is disabled + -F FILENAME, --filename=FILENAME + log received impulse responses to file To use with an LFTX board, set the center frequency to 16M: $ usrp_sounder.py -f 16M -t -You can vary the m-sequence degree between 1 and 16, which will create -sequence lengths between 1 (DC) and 65535 (1.023 us). This will affect -how frequently the receiver can calculate impulse response vectors (to be -documented as the receiver is completed.) +The sounder receiver command line is: + +$ usrp_sounder.py -f 16M -r -F output.dat + +You can vary the m-sequence degree between 2 and 12, which will create +sequence lengths between 3 and 4095 (128 us). This will affect +how frequently the receiver can calculate impulse response vectors. + +The correlator uses an O(N^2) algorithm, by using an entire PN period +of the received signal to correlate at each lag value. Thus, using a +degree 12 PN code of length 4095, it takes 4095*4095/32e6 seconds to +calculate a single impulse response vector, about a half a second. One +can reduce this time by a factor of 4 for each decrement in PN code +degree, but this also reduces the inherent processing gain by 6 dB as +well. + +The impulse response vectors are written to a file in complex float +format, and consist of the actual impulse response with a noise floor +dependent on the PN code degree in use. + +There is a loopback test mode that causes the sounding waveform to be +routed back to the receiver inside the USRP: + +$ usrp_sounder.py -r -t -l -F output.dat + +The resulting impulse response will be a spike followed by a near zero +value for the rest of the period. + +Synchronization at the receiver is not yet implemented, so the actual +impulse response may be time shifted an arbitrary value within the the +impulse response vector. If one assumes the first to arrive signal is +the strongest, then one can circularly rotate the vector until the peak +is at time zero. Johnathan Corgan Corgan Enterprises LLC jcorgan@corganenterprises.com -4/25/07 +5/28/07 diff --git a/gr-sounder/src/fpga/tb/sounder_tb.sav b/gr-sounder/src/fpga/tb/sounder_tb.sav index 74019f5ee..3a73a9445 100644 --- a/gr-sounder/src/fpga/tb/sounder_tb.sav +++ b/gr-sounder/src/fpga/tb/sounder_tb.sav @@ -1,4 +1,4 @@ -*-26.117517 250000000 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 +*-24.753519 93900000 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 @28 sounder_tb.uut.clk_i sounder_tb.rst @@ -23,7 +23,9 @@ sounder_tb.tx_dac_i[13:0] - @22 sounder_tb.fifo_strobe +@8421 sounder_tb.fifo_i[15:0] +@22 sounder_tb.fifo_q[15:0] @200 - @@ -32,3 +34,7 @@ sounder_tb.uut.transmitter.pn_code.pn_o sounder_tb.uut.receiver.pn_ref @200 - +@28 +sounder_tb.tx_strobe +@8420 +sounder_tb.uut.receiver.sum_i[31:0] diff --git a/gr-sounder/src/fpga/tb/sounder_tb.v b/gr-sounder/src/fpga/tb/sounder_tb.v index 045791e43..6a9eff0ea 100644 --- a/gr-sounder/src/fpga/tb/sounder_tb.v +++ b/gr-sounder/src/fpga/tb/sounder_tb.v @@ -197,14 +197,14 @@ module sounder_tb; #20 enable_rx(1); #20 enable_lp(1); #20 set_reset(0); - #((uut.len+1)*uut.len*20); + #((uut.len+1)*uut.len*20*2); end endtask // test_lp // Execute tests initial begin - #20 test_tx(12); + // #20 test_tx(12); #20 test_lp(12); #100 $finish; end diff --git a/gr-sounder/src/python/Makefile.am b/gr-sounder/src/python/Makefile.am index 7ce181271..16f7ff035 100644 --- a/gr-sounder/src/python/Makefile.am +++ b/gr-sounder/src/python/Makefile.am @@ -21,12 +21,23 @@ include $(top_srcdir)/Makefile.common +# Install this stuff so that it ends up as the gnuradio.sounder module +# This usually ends up at: +# ${prefix}/lib/python${python_version}/site-packages/gnuradio + +ourpythondir = $(grpythondir) +ourlibdir = $(grpyexecdir) + EXTRA_DIST = \ - usrp_sounder.py \ + usrp_sounder.py \ + sounder_loopback.sh \ qa_nothing.py \ run_tests.in bin_SCRIPTS = \ usrp_sounder.py -MOSTLYCLEANFILES = *~ *.pyc *.pyo output.* +ourpython_PYTHON = \ + sounder.py + +MOSTLYCLEANFILES = *~ *.pyc *.pyo
\ No newline at end of file diff --git a/gr-sounder/src/python/sounder.py b/gr-sounder/src/python/sounder.py new file mode 100644 index 000000000..b3c5423c4 --- /dev/null +++ b/gr-sounder/src/python/sounder.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python +# +# Copyright 2007 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 2, 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, usrp +from gnuradio import eng_notation + +n2s = eng_notation.num_to_str + +FR_MODE = usrp.FR_USER_0 +bmFR_MODE_RESET = 1 << 0 # bit 0: active high reset +bmFR_MODE_TX = 1 << 1 # bit 1: enable transmitter +bmFR_MODE_RX = 1 << 2 # bit 2: enable receiver +bmFR_MODE_LP = 1 << 3 # bit 3: enable digital loopback + +FR_DEGREE = usrp.FR_USER_1 + +def pick_subdevice(u): + """ + The user didn't specify a subdevice on the command line. + If there's a daughterboard on A, select A. + If there's a daughterboard on B, select B. + Otherwise, select A. + """ + if u.db[0][0].dbid() >= 0: # dbid is < 0 if there's no d'board or a problem + return (0, 0) + if u.db[1][0].dbid() >= 0: + return (1, 0) + return (0, 0) + +class sounder_tx: + def __init__(self, loopback=False,verbose=False): + self._loopback=loopback + self._verbose = verbose + self._u = usrp.sink_s(fpga_filename='usrp_sounder.rbf') + if not self._loopback: + self._subdev_spec = usrp.pick_tx_subdevice(self._u) + self._subdev = usrp.selected_subdev(self._u, self._subdev_spec) + if self._verbose: + print "Using", self._subdev.name(), "for sounder transmitter." + self._u.start() + + def tune(self, frequency): + if self._verbose: + print "Setting transmitter frequency to", n2s(frequency) + result = self._u.tune(0, self._subdev, frequency) + if result == False: + raise RuntimeError("Failed to set transmitter frequency.") + +class sounder_rx: + def __init__(self,subdev_spec=None,length=1,msgq=None,loopback=False,verbose=False,debug=False): + self._subdev_spec = subdev_spec + self._length = length + self._msgq = msgq + self._loopback = loopback + self._verbose = verbose + self._debug = debug + + self._fg = gr.flow_graph() + self._u = usrp.source_c(fpga_filename='usrp_sounder.rbf') + if not self._loopback: + if self._subdev_spec == None: + self._subdev_spec = pick_subdevice(self._u) + self._u.set_mux(usrp.determine_rx_mux_value(self._u, self._subdev_spec)) + self._subdev = usrp.selected_subdev(self._u, self._subdev_spec) + if self._verbose: + print "Using", self._subdev.name(), "for sounder receiver." + + self._vblen = gr.sizeof_gr_complex*self._length + if self._debug: + print "Generating impulse vectors of length", self._length, "byte length", self._vblen + + self._s2v = gr.stream_to_vector(gr.sizeof_gr_complex, self._length) + self._sink = gr.message_sink(self._vblen, self._msgq, True) + self._fg.connect(self._u, self._s2v, self._sink) + + def tune(self, frequency): + if self._verbose: + print "Setting receiver frequency to", n2s(frequency) + result = self._u.tune(0, self._subdev, frequency) + if result == False: + raise RuntimeError("Failed to set receiver frequency.") + + def start(self): + if self._debug: + print "Starting receiver flow graph." + self._fg.start() + + def wait(self): + if self._debug: + print "Waiting for threads..." + self._fg.wait() + + def stop(self): + if self._debug: + print "Stopping receiver flow graph." + self._fg.stop() + self.wait() + if self._debug: + print "Receiver flow graph stopped." + + +class sounder: + def __init__(self,transmit=False,receive=False,loopback=False,rx_subdev_spec=None, + frequency=0.0,degree=10,length=1,msgq=None,verbose=False,debug=False): + self._transmit = transmit + self._receive = receive + self._loopback = loopback + self._rx_subdev_spec = rx_subdev_spec + self._frequency = frequency + self._degree = degree + self._length = length + self._msgq = msgq + self._verbose = verbose + self._debug = debug + self._mode = 0 + self._u = None + self._trans = None + self._rcvr = None + self._transmitting = False + self._receiving = False + + if self._transmit: + self._trans = sounder_tx(loopback=self._loopback,verbose=self._verbose) + self._u = self._trans._u + + if self._receive: + self._rcvr = sounder_rx(subdev_spec=self._rx_subdev_spec,length=self._length, + msgq=self._msgq,loopback=self._loopback,verbose=self._verbose, + debug=self._debug) + self._u = self._rcvr._u # either receiver or transmitter object will do + + self.set_reset(True) + if self._loopback == False: + self.tune(self._frequency) + self.set_degree(self._degree) + self.set_loopback(self._loopback) + self.set_reset(False) + + def tune(self, frequency): + self._frequency = frequency + if self._rcvr: + self._rcvr.tune(frequency) + if self._trans: + self._trans.tune(frequency) + + def set_degree(self, degree): + if self._verbose: + print "Setting PN code degree to", degree + self._u._write_fpga_reg(FR_DEGREE, degree); + + def _write_mode(self): + if self._debug: + print "Writing mode register with:", hex(self._mode) + self._u._write_fpga_reg(FR_MODE, self._mode) + + def enable_tx(self, value): + if value: + if self._verbose: + print "Enabling transmitter." + self._mode |= bmFR_MODE_TX + self._transmitting = True + else: + if self._verbose: + print "Disabling transmitter." + self._mode &= ~bmFR_MODE_TX + self._write_mode() + + def enable_rx(self, value): + if value: + self._mode |= bmFR_MODE_RX + self._write_mode() + self._rcvr.start() + self._receiving = True + else: + self._rcvr.stop() + self._mode &= ~bmFR_MODE_RX + self._write_mode() + self._receiving = False + + def set_loopback(self, value): + if value: + if self._verbose: + print "Enabling digital loopback." + self._mode |= bmFR_MODE_LP + else: + if self._verbose: + print "Disabling digital loopback." + self._mode &= ~bmFR_MODE_LP + self._write_mode() + + def set_reset(self, value): + if value: + if self._debug: + print "Asserting reset." + self._mode |= bmFR_MODE_RESET + else: + if self._debug: + print "De-asserting reset." + self._mode &= ~bmFR_MODE_RESET + self._write_mode() + + def start(self): + if self._transmit: + self.enable_tx(True) + if self._receive: + self.enable_rx(True) + + def __del__(self): + if self._transmitting: + self.enable_tx(False) + + if self._receiving: + self.enable_rx(False) + diff --git a/gr-sounder/src/python/sounder_loopback.sh b/gr-sounder/src/python/sounder_loopback.sh new file mode 100755 index 000000000..a97a8fdcf --- /dev/null +++ b/gr-sounder/src/python/sounder_loopback.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +# Note this runs the installed script, not the one in the tree +usrp_sounder.py -r -l -t -d12 -v -F loopback.dat -D diff --git a/gr-sounder/src/python/test_lp.sh b/gr-sounder/src/python/test_lp.sh deleted file mode 100755 index fbcc3f533..000000000 --- a/gr-sounder/src/python/test_lp.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -usrp_sounder.py -r -l -t -n5000 -d12 -hexdump output.dat >output.hex diff --git a/gr-sounder/src/python/test_rx.sh b/gr-sounder/src/python/test_rx.sh deleted file mode 100755 index 21998b4de..000000000 --- a/gr-sounder/src/python/test_rx.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -usrp_sounder.py -r -n5000 -d12 -hexdump output.dat >output.hex diff --git a/gr-sounder/src/python/test_tx.sh b/gr-sounder/src/python/test_tx.sh deleted file mode 100755 index f29ba1f34..000000000 --- a/gr-sounder/src/python/test_tx.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -usrp_sounder.py -t -f 16M -d12 diff --git a/gr-sounder/src/python/usrp_sounder.py b/gr-sounder/src/python/usrp_sounder.py index 31e766ef5..4aada45d6 100755 --- a/gr-sounder/src/python/usrp_sounder.py +++ b/gr-sounder/src/python/usrp_sounder.py @@ -20,174 +20,82 @@ # Boston, MA 02110-1301, USA. # -from gnuradio import gr, usrp +from gnuradio import gr +from gnuradio.sounder import sounder from gnuradio import eng_notation from gnuradio.eng_option import eng_option from optparse import OptionParser -import sys, time +import sys n2s = eng_notation.num_to_str -FR_MODE = usrp.FR_USER_0 -bmFR_MODE_RESET = 1 << 0 # bit 0: active high reset -bmFR_MODE_TX = 1 << 1 # bit 1: enable transmitter -bmFR_MODE_RX = 1 << 2 # bit 2: enable receiver -bmFR_MODE_LP = 1 << 3 # bit 3: enable digital loopback - -FR_DEGREE = usrp.FR_USER_1 - -class sounder: - def __init__(self, options): - self._options = options - self._mode = 0 - - self._u = None - self._trans = None - self._rcvr = None - self._transmitting = False - self._receiving = False - - if options.transmit: - print "Creating sounder transmitter." - self._trans = usrp.sink_s(fpga_filename='usrp_sounder.rbf') - self._trans_subdev_spec = usrp.pick_tx_subdevice(self._trans) - self._trans_subdev = usrp.selected_subdev(self._trans, self._trans_subdev_spec) - self._trans.start() - self._u = self._trans - - if options.receive: - print "Creating sounder receiver." - self._fg = gr.flow_graph() - self._rcvr = usrp.source_s(fpga_filename='usrp_sounder.rbf', decim_rate=128) - self._rcvr_subdev_spec = usrp.pick_rx_subdevice(self._rcvr) - self._rcvr_subdev = usrp.selected_subdev(self._rcvr, self._rcvr_subdev_spec) - self._sink = gr.file_sink(gr.sizeof_short, "output.dat") - - if options.samples >= 0: - self._head = gr.head(gr.sizeof_short, options.samples*gr.sizeof_short) - self._fg.connect(self._rcvr, self._head, self._sink) - else: - self._fg.connect(self._rcvr, self._sink) - self._u = self._rcvr # either receiver or transmitter object will do - - self.set_reset(True) - self.set_freq(options.frequency) - self.set_degree(options.degree) - self.set_loopback(options.loopback) - self.set_reset(False) - - def set_freq(self, frequency): - print "Setting center frequency to", n2s(frequency) - if self._rcvr: - self._rcvr.tune(0, self._rcvr_subdev, frequency) - - if self._trans: - self._trans.tune(0, self._trans_subdev, frequency) - - def set_degree(self, degree): - print "Setting PN code degree to", degree - self._u._write_fpga_reg(FR_DEGREE, degree); - - def _write_mode(self): - print "Writing mode register with:", hex(self._mode) - self._u._write_fpga_reg(FR_MODE, self._mode) - - def enable_tx(self, value): - if value: - print "Enabling transmitter." - self._mode |= bmFR_MODE_TX - self._transmitting = True - else: - print "Disabling transmitter." - self._mode &= ~bmFR_MODE_TX - self._write_mode() - - def enable_rx(self, value): - if value: - print "Starting receiver flow graph." - self._mode |= bmFR_MODE_RX - self._write_mode() - self._fg.start() - self._receiving = True - if self._options.samples >= 0: - self._fg.wait() - else: - print "Stopping receiver flow graph." - if self._options.samples < 0: - self._fg.stop() - print "Waiting for threads..." - self._fg.wait() - print "Receiver flow graph stopped." - self._mode &= ~bmFR_MODE_RX - self._write_mode() - self._receiving = False - - def set_loopback(self, value): - if value: - print "Enabling digital loopback." - self._mode |= bmFR_MODE_LP - else: - print "Disabling digital loopback." - self._mode &= ~bmFR_MODE_LP - self._write_mode() - - def set_reset(self, value): - if value: - print "Asserting reset." - self._mode |= bmFR_MODE_RESET - else: - print "De-asserting reset." - self._mode &= ~bmFR_MODE_RESET - self._write_mode() - - def __del__(self): - if self._transmitting: - self.enable_tx(False) - - if self._receiving: - self.enable_rx(False) - -# ------------------------------------------------------------------------------ - def main(): parser = OptionParser(option_class=eng_option) + parser.add_option("-R", "--rx-subdev-spec", type="subdev", default=(0, 0), + help="select USRP Rx side A or B") parser.add_option("-f", "--frequency", type="eng_float", default=0.0, help="set frequency to FREQ in Hz, default is %default", metavar="FREQ") - - parser.add_option("-d", "--degree", type="int", default=16, - help="set souding sequence degree (len=2^degree-1), default is %default") - + parser.add_option("-d", "--degree", type="int", default=12, + help="set sounding sequence degree (2-12), default is %default,") parser.add_option("-t", "--transmit", action="store_true", default=False, help="enable sounding transmitter") - parser.add_option("-r", "--receive", action="store_true", default=False, help="enable sounding receiver") - - parser.add_option("-n", "--samples", type="int", default=-1, - help="number of samples to capture on receive, default is infinite") - parser.add_option("-l", "--loopback", action="store_true", default=False, help="enable digital loopback, default is disabled") - + parser.add_option("-v", "--verbose", action="store_true", default=False, + help="enable verbose output, default is disabled") + parser.add_option("-D", "--debug", action="store_true", default=False, + help="enable debugging output, default is disabled") + parser.add_option("-F", "--filename", default=None, + help="log received impulse responses to file") + (options, args) = parser.parse_args() if len(args) != 0 or not (options.transmit | options.receive): parser.print_help() sys.exit(1) - print "Using PN code degree of", options.degree, "length", 2**options.degree-1 - print "Sounding frequency range is", n2s(options.frequency-16e6), "to", n2s(options.frequency+16e6) - - s = sounder(options) - - if options.transmit: - s.enable_tx(True) + if options.receive and (options.filename == None): + print "Must supply filename when receiving." + sys.exit(1) + + if options.degree > 12 or options.degree < 2: + print "PN code degree must be between 2 and 12" + sys.exit(1) + + length = int(2**options.degree-1) + if options.verbose: + print "Using PN code degree of", options.degree, "length", length + if options.loopback == False: + print "Sounding frequency range is", n2s(options.frequency-16e6), "to", n2s(options.frequency+16e6) + if options.filename != None: + print "Logging impulse records to file: ", options.filename + + msgq = gr.msg_queue() + s = sounder(transmit=options.transmit,receive=options.receive,loopback=options.loopback, + rx_subdev_spec=options.rx_subdev_spec,frequency=options.frequency,degree=options.degree, + length=length,msgq=msgq,verbose=options.verbose,debug=options.debug) + s.start() if options.receive: - s.enable_rx(True) - - if options.samples < 0: - raw_input("Press enter to exit.") - + f = open(options.filename, "wb") + print "Enter CTRL-C to stop." + try: + while (1): + msg = msgq.delete_head() + if msg.type() == 1: + break + rec = msg.to_string()[:length*gr.sizeof_gr_complex] + if options.debug: + print "Received impulse vector of length", len(rec) + f.write(rec) + + except KeyboardInterrupt: + pass + else: + if options.transmit: + raw_input("Press return to exit.") + if __name__ == "__main__": main() |