I have my ‘900 MHz AM Receiver with AFC’ running satisfactorily, but I’m
not
happy with some of the approaches I ended up using to accomplish the
AFC. I
spliced together most of fftsink2.py and usrp_am_rcvr.py and modified
both
as follows:
A. fftsink2.py
Added code to input_watcher class run method (thread):
1)find highest peak in FFT data arriving via msgq
2)calculate the frequency difference between nominal tuning freq and the
peak, i.e., tuning error or drift.
3)calculate new tune freq and retune the usrp
B. usrp_am_rcvr.py:
Adjusted filter parameters to my requirements.
Added staticmethod access to its topblock class ‘set_freq’ method.
Added global variables to contain information about the single instance
of
this class that is invoked by main.
What I don’t like about the above is using staticmethod to get access to
the
set_freq method of the top block. However, I have not figured out how to
address the single instance of the topblock that exists. Considering the
code snippets below, how can I address the instance of wm_rx_block
(which I
referred to as ‘top block’ earlier) that gets passed to stdgui2?
A little more description of the app:
3 stdgui2 FFTs are in view: FFT of USRP data, FFT of channel filter
output,
FFT of audio
Channel filter output is supposed to have a single peak centered on 0
frequency. Any difference can be used to adjust usrp tuning freq. In
practice Thread-2 handles channel filter data and knows the freq offset.
As
usual, the usrp object is bound to a topblock, an instance of class
wam_rx_block. What I don’t understand is how to address this instance.
You
might be able to tell me by simply looking at the main code below.
I’m sure that you longtime Python and gnuradio folks will consider this
trivial, but I have a new bald spot. Any hints will be appreciated.
Paul M.
Here’s main
if name == ‘main’:
app = stdgui2.stdapp (wam_rx_block, “USRP 915MHz AM ISM RX”)
app.MainLoop ()
And here are the most important bits of wam_rx_block:
class wam_rx_block (stdgui2.std_top_block):
my_usrp = None
my_usrp_subdev = None
def __init__(self,frame,panel,vbox,argv):
global target_freq, tune_freq, my_usrp, my_usrp_subdev
< option parsing and other standard setup stuff snipped >
tune_freq = self.freq
target_freq = tune_freq
my_IF_freq = self.IF_freq # allow class method access
################# Build main flowgraph ##################
#TODO: add an AGC after the channel filter and before the
AM_demod
self.u = usrp.source_c() # usrp is data
source
my_usrp = self.u # allow global
access
< more standard setup stuff snipped >
self.u.set_mux(usrp.determine_rx_mux_value(self.u,
options.rx_subdev_spec))
self.subdev = usrp.selected_subdev(self.u,
options.rx_subdev_spec)
my_usrp_subdev = self.subdev
< wx GUI setup stuff snipped >
def set_freq(self, usrp_freq):
"""
Set the center frequency we're interested in.
@param target_freq: frequency in Hz
@rypte: bool
Tuning is a two step process. First we ask the front-end to
tune as close to the desired frequency as it can. Then we use
the result of that operation and our target_frequency to
determine the value for the digital down converter.
"""
r = usrp.tune(self.u, 0, self.subdev, usrp_freq + self.IF_freq)
#TODO: check if db is inverting the spectrum or not to decide
# if we should do + self.IF_freq or - self.IF_freq
return r
def set_freq_static(usrp_freq):
global target_freq, tune_freq, my_usrp, my_IF_freq,
my_usrp_subdev
r = usrp.tune(my_usrp, 0, my_usrp_subdev, usrp_freq +
my_IF_freq)
set_freq_static = staticmethod(set_freq_static)
And, here’s the modified run method for input_watcher in fftsink2, with
the
call to set_freq_static near the end:
def run (self):
global target_freq, tune_freq
while (self.keep_running):
msg = self.msgq.delete_head() # blocking read of message
queue
# FFT results are prefixed by vector size and number of
frames.
itemsize = int(msg.arg1())
nitems = int(msg.arg2())
s = msg.to_string() # get the body of the msg as
a
string
# There may be more than one FFT frame in the message.
# If so, we take only the last one
if nitems > 1:
start = itemsize * (nitems - 1)
s = s[start:start + itemsize]
# String actually contains f.p. numbers, so convert.
complex_data = fromstring (s, float32)
# find location and amplitude of highest peak
self.maxval = max(complex_data)
self.pk_bin = argmax(complex_data)
self.avgval = mean(complex_data)
self.cycles_per_bin = self.sample_rate / self.fft_size
# Show size of packet, max value, index of max, and mean
value.
# decide if peak is significant
# FIXME: make threshold a parameter and/or dynamic
if ((self.maxval - self.avgval) >= 8):
# 1st vlen/2 bins get peak when center_freq is low,
# with offset maximum = 1/2 of BW at bin vlen/2
# 2nd vlen/2 bins get peak when center_freq is high,
# with offset maximum = 1/2 of BW at bin vlen/2
# Both halves repeat for freq diffs > FFT width / 2.
# For usrp_rate = 64 MHz / 256 = 250 kHz, do bin
(vlen/2)
# represents f = -125 kHz, +125 kHz,…
# If we restrict the range of center_freq to within BW/2
# of carrier, there can be no ambiguity.
# Given a peak located at bin N, solve for frequency:
# cycles_per_bin = usrp_rate/vlen,
# This can be refined by calculating a mean pk_freq
value
# based on several bins,
# starting with the values on either side of the peak:
if self.pk_bin > 0:
left_bin = self.pk_bin - 1
else:
left_bin = (self.fft_size - 1)
if self.pk_bin < (self.fft_size - 2):
right_bin = self.pk_bin + 1
else:
right_bin = 0
left_val = complex_data[left_bin]
right_val = complex_data[right_bin]
# Calculate a weighted average bin location starting
with
# the fraction of a bin to offset the center.
wtd_sum = (right_val - left_val) / (left_val +
right_val)
self.pk_bin += wtd_sum
# Adjust for bottom wraparound
if (self.pk_bin < 0): self.pk_bin += (nitems - 1)
# Adjust for top wraparound
if (self.pk_bin > (self.fft_size - 1)):
self.pk_bin += (1 - self.fft_size)
# Calculate equivalent freq of bin, accounting for
spectral
# folding around center.
if self.pk_bin < self.fft_size / 2:
self.pk_freq = int(self.center_freq +
(self.pk_bin *
self.cycles_per_bin))
else:
self.pk_freq = int(self.center_freq +
((self.pk_bin - (self.fft_size -
1))
*
self.cycles_per_bin))
################### RETUNING OCCURS HERE:
####################
# NOTE: For the passband data, pk_freq represents freq
drift.
if self.afc:
if abs(self.pk_freq) > 200:
target_freq = tune_freq + self.pk_freq/3 #
correct for drift
print “retune:”, self.pk_freq, “to”, target_freq
wam_rx_block.set_freq_static(target_freq)
################################################################
# package the data as a wx event object
de = DataEvent (complex_data)
# package this packet as a data event within wx and
# push this single fft plot data packet into fftsink msgq
wx.PostEvent (self.event_receiver, de)
del de # no further need for this copy of data