Created
January 14, 2017 07:17
-
-
Save boxlor/02deb422096e98a63d284499c727f53c to your computer and use it in GitHub Desktop.
SDR-related utility to compute channels for PFB channelizers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Computes channelization scheme for a set of input frequencies. | |
Example output: | |
$ python channelize.py | |
Input frequency spacing: | |
freq[00] 854037500 | |
freq[01] 854212500 dfreq=175000.000000 | |
freq[02] 854312500 dfreq=100000.000000 | |
freq[03] 854362500 dfreq=50000.000000 | |
freq[04] 854512500 dfreq=150000.000000 | |
freq[05] 854962500 dfreq=450000.000000 | |
freq[06] 855487500 dfreq=525000.000000 | |
freq[07] 855737500 dfreq=250000.000000 | |
freq[08] 856212500 dfreq=475000.000000 | |
freq[09] 856237500 dfreq=25000.000000 | |
freq[10] 856587500 dfreq=350000.000000 | |
freq[11] 856687500 dfreq=100000.000000 | |
freq[12] 856962500 dfreq=275000.000000 | |
freq[13] 857212500 dfreq=250000.000000 | |
freq[14] 857237500 dfreq=25000.000000 | |
freq[15] 858212500 dfreq=975000.000000 | |
freq[16] 858237500 dfreq=25000.000000 | |
freq[17] 858687500 dfreq=450000.000000 | |
freq[18] 859212500 dfreq=525000.000000 | |
freq[19] 859237500 dfreq=25000.000000 | |
*** Forcing number of channels to be odd by adding a channel. | |
*** NOTE: Center channel (ch[19], pfb_ch[00]) may have issues if there is I/Q DC offset leakage. | |
group[0]: low: 854025000.000000, high: 855000000.000000, center: 854512500.000000, samp_rate: 975000.000000, num_chans: 39, chan_width: 25000 | |
ch[00], pfb_ch[20]: 854037500.000000 | |
ch[07], pfb_ch[27]: 854212500.000000 | |
ch[11], pfb_ch[31]: 854312500.000000 | |
ch[13], pfb_ch[33]: 854362500.000000 | |
ch[19], pfb_ch[00]: 854512500.000000 | |
ch[37], pfb_ch[18]: 854962500.000000 | |
group[1]: low: 855475000.000000, high: 857250000.000000, center: 856362500.000000, samp_rate: 1775000.000000, num_chans: 71, chan_width: 25000 | |
ch[00], pfb_ch[36]: 855487500.000000 | |
ch[10], pfb_ch[46]: 855737500.000000 | |
ch[29], pfb_ch[65]: 856212500.000000 | |
ch[30], pfb_ch[66]: 856237500.000000 | |
ch[44], pfb_ch[09]: 856587500.000000 | |
ch[48], pfb_ch[13]: 856687500.000000 | |
ch[59], pfb_ch[24]: 856962500.000000 | |
ch[69], pfb_ch[34]: 857212500.000000 | |
ch[70], pfb_ch[35]: 857237500.000000 | |
*** Forcing number of channels to be odd by adding a channel. | |
group[2]: low: 858200000.000000, high: 859275000.000000, center: 858737500.000000, samp_rate: 1075000.000000, num_chans: 43, chan_width: 25000 | |
ch[00], pfb_ch[22]: 858212500.000000 | |
ch[01], pfb_ch[23]: 858237500.000000 | |
ch[19], pfb_ch[41]: 858687500.000000 | |
ch[40], pfb_ch[19]: 859212500.000000 | |
ch[41], pfb_ch[20]: 859237500.000000 | |
""" | |
M = 1.0e6 | |
# Example P25 system: | |
# http://www.radioreference.com/apps/db/?sid=8285 | |
# Not included: control channels. | |
INPUT_FREQS = [854.0375 * M, | |
854.2125 * M, | |
854.3125 * M, | |
854.3625 * M, | |
854.5125 * M, | |
854.9625 * M, | |
855.4875 * M, | |
855.7375 * M, | |
856.2125 * M, | |
856.2375 * M, | |
856.5875 * M, | |
856.6875 * M, | |
856.9625 * M, | |
857.2125 * M, | |
857.2375 * M, | |
858.2125 * M, | |
858.2375 * M, | |
858.6875 * M, | |
859.2125 * M, | |
859.2375 * M] | |
# See note below about defining groups by hand. I did this because I liked my grouping | |
# better than what define_groups produced. | |
GROUPS = [[], [], []] | |
# group0: 37 channels w/25kHz spacing | |
GROUPS[0] = [854.0375 * M, | |
854.2125 * M, | |
854.3125 * M, | |
854.3625 * M, | |
854.5125 * M, | |
854.9625 * M] | |
# group1: 71 channels w/25kHz spacing | |
GROUPS[1] = [855.4875 * M, | |
855.7375 * M, | |
856.2125 * M, | |
856.2375 * M, | |
856.5875 * M, | |
856.6875 * M, | |
856.9625 * M, | |
857.2125 * M, | |
857.2375 * M] | |
# group2: 42 channels w/25kHz spacing | |
GROUPS[2] = [858.2125 * M, | |
858.2375 * M, | |
858.6875 * M, | |
859.2125 * M, | |
859.2375 * M] | |
MAX_BW_HZ = 2.54 * M # RTL max reliable sample rate = 2.56 MS/s | |
def test_chan_width(freqs, chan_width): | |
""" | |
Given a sorted list of frequencies and possible channel width, returns | |
True if some multiple of chan_width lands exactly on each frequency in the | |
list. | |
""" | |
freq_idx = 0 | |
current_freq = freqs[0] | |
while(True): | |
if(freqs[freq_idx] == current_freq): | |
freq_idx += 1 | |
else: | |
# Try next channel | |
current_freq += chan_width | |
# Check for done | |
# Matched all freqs | |
if(freq_idx == len(freqs)): | |
return True | |
# Stop searching after maximum frequency | |
if(current_freq > freqs[freq_idx]): | |
return False | |
def compute_chan_width(freqs): | |
""" | |
Given a list of frequencies, returns the largest channel size that would | |
result in all frequencies being at the center of a channel. | |
""" | |
freqs.sort() | |
last_freq = None | |
dfreqs = [] | |
for freq in freqs: | |
if(last_freq != None): | |
dfreq = (freq - last_freq) | |
dfreqs.append(dfreq) | |
# print freq, dfreq | |
# else: | |
# print freq, 0 | |
last_freq = freq | |
# Largest possible channel width that could work is the minimum spacing | |
# between frequencies. This is the starting point. | |
chan_width = min(dfreqs) | |
# Iteratively search for a channel spacing that works. Might end up at 1Hz! | |
while((test_chan_width(freqs, chan_width) is False) and (chan_width > 0)): | |
chan_width -= 1 | |
num_chans = int((freqs[-1] - freqs[0]) / chan_width) + 1 | |
# print "chan_width: %f, num_chans: %d" % (chan_width, num_chans) | |
return chan_width, num_chans | |
def define_groups(freqs, max_bw_hz): | |
""" | |
Greedily divide list of input frequencies into groups of at most max_bw_hz. | |
""" | |
freqs.sort() | |
groups = [] | |
low_freq = freqs[0] | |
group = [] | |
for freq in freqs: | |
# for i, freq in enumerate(freqs): | |
if((freq - low_freq) <= max_bw_hz): | |
group.append(freq) | |
else: | |
# Append previous group | |
groups.append(group) | |
group = [freq] | |
low_freq = freq | |
if(len(group) != 0): | |
groups.append(group) | |
msg = "Partitioned %d input frequencies into %d groups " | |
msg += "based on your requirement for maximum group b/w of %f Hz:" | |
msg %= (len(freqs), len(groups), MAX_BW_HZ) | |
print msg | |
for i, group in enumerate(groups): | |
msg = "group[%d]: %d frequencies spanning %f Hz: %s" | |
msg %= (i, len(group), group[-1] - group[0], repr(group)) | |
print msg | |
return groups | |
def show_spacing(freqs): | |
""" | |
Prints freqs list and delta frequency between each item in the list. | |
Assumes list is sorted. | |
""" | |
print "Input frequency spacing:" | |
last_freq = None | |
for i, freq in enumerate(freqs): | |
if(last_freq != None): | |
dfreq = freq - last_freq | |
msg = "\tfreq[%02d] %d\tdfreq=%f" | |
msg %= (i, freq, dfreq) | |
print msg | |
else: | |
msg = "\tfreq[%02d] %d" | |
msg %= (i, freq) | |
print msg | |
last_freq = freq | |
def main(): | |
show_spacing(INPUT_FREQS) | |
# Originally the program divided the frequency list into groups based on | |
# the max b/w of each RTL dongle, but it's not as good as doing it | |
# manually. The define_groups routine should be re-written such that it | |
# divides the groups at the largest gaps, to reduce overall system b/w. | |
# But for now they are manually allocated at the top of the file. | |
# groups = define_groups(INPUT_FREQS, MAX_BW_HZ) | |
groups = GROUPS | |
for i, group in enumerate(groups): | |
chan_width, num_chans = compute_chan_width(group) | |
# Get channel numbers - depending on whether the center frequency is | |
# used these may change. Also these numbers are zero-based but the | |
# PFB channelizer in gnuradio starts with zero in the middle of the | |
# tuned band, so they always need to be remapped. | |
# Preliminary channel list | |
base = group[0] | |
chans = [] | |
for freq in group: | |
chan = int((freq - base)/chan_width) | |
chans.append(chan) | |
if(num_chans & 1 == 0): | |
# Even number of channels: make odd by adding another channel so | |
# that center of channels lines up with desired signal in PFB | |
# output. | |
num_chans += 1 | |
print "*** Forcing number of channels to be odd by adding a channel." | |
# Compute shift for channel remapping for PFB | |
shift = (num_chans+1)/2 | |
pfb_chan_map = range(num_chans)[shift:] + range(num_chans)[:shift] | |
center_chan = (num_chans - 1) / 2 | |
if(center_chan in chans): | |
# FIXME: Perhaps do something to avoid using the enter channel | |
# because of the I/Q DC leakage artifact? | |
msg = "*** NOTE: Center channel (ch[%02d], pfb_ch[%02d]) " | |
msg += " may have issues if there is I/Q DC offset leakage." | |
msg %= (center_chan, pfb_chan_map[center_chan]) | |
print msg | |
tune_low = group[0] - (chan_width / 2.0) | |
tune_high = tune_low + (num_chans * chan_width) | |
tune_freq = (tune_high + tune_low) / 2.0 | |
samp_rate = tune_high - tune_low | |
msg = 'group[%d]: low: %f, high: %f, center: %f, samp_rate: %f, num_chans: %d, chan_width: %d' | |
msg %= (i, tune_low, tune_high, tune_freq, samp_rate, num_chans, chan_width) | |
print msg | |
# remapped_chans = [] | |
# for chan in chans: | |
for freq in group: | |
chan = int((freq - base)/chan_width) | |
# chans.append(chan) | |
# remapped_chans.append(pfb_chan_map[chan]) | |
print '\tch[%02d], pfb_ch[%02d]: %f' % (chan, pfb_chan_map[chan], freq) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment