|
|
|
@ -45,14 +45,35 @@ _def_costas_alpha = 0.04 |
|
|
|
|
_def_symbol_rate = 4800 |
|
|
|
|
_def_symbol_deviation = 600.0 |
|
|
|
|
_def_bb_gain = 1.0 |
|
|
|
|
_def_excess_bw = 0.2 |
|
|
|
|
|
|
|
|
|
# ///////////////////////////////////////////////////////////////////////////// |
|
|
|
|
# demodulator |
|
|
|
|
# ///////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
|
|
|
|
def get_decim(speed): |
|
|
|
|
s = int(speed) |
|
|
|
|
if_freqs = [24000, 25000, 32000] |
|
|
|
|
for i_f in if_freqs: |
|
|
|
|
if s % i_f != 0: |
|
|
|
|
continue |
|
|
|
|
q = s / i_f |
|
|
|
|
if q & 1: |
|
|
|
|
continue |
|
|
|
|
if q >= 40 and q & 3 == 0: |
|
|
|
|
decim = q/4 |
|
|
|
|
decim2 = 4 |
|
|
|
|
else: |
|
|
|
|
decim = q/2 |
|
|
|
|
decim2 = 2 |
|
|
|
|
return decim, decim2 |
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
class p25_demod_base(gr.hier_block2): |
|
|
|
|
def __init__(self, |
|
|
|
|
if_rate = None, |
|
|
|
|
filter_type = None, |
|
|
|
|
excess_bw = _def_excess_bw, |
|
|
|
|
symbol_rate = _def_symbol_rate): |
|
|
|
|
""" |
|
|
|
|
Hierarchical block for P25 demodulation base class |
|
|
|
@ -66,6 +87,12 @@ class p25_demod_base(gr.hier_block2): |
|
|
|
|
|
|
|
|
|
self.baseband_amp = blocks.multiply_const_ff(_def_bb_gain) |
|
|
|
|
coeffs = op25_c4fm_mod.c4fm_taps(sample_rate=self.if_rate, span=9, generator=op25_c4fm_mod.transfer_function_rx).generate() |
|
|
|
|
if filter_type == 'rrc': |
|
|
|
|
sps = self.if_rate / 4800 |
|
|
|
|
ntaps = 7 * sps |
|
|
|
|
if ntaps & 1 == 0: |
|
|
|
|
ntaps += 1 |
|
|
|
|
coeffs = filter.firdes.root_raised_cosine(1.0, if_rate, symbol_rate, excess_bw, ntaps) |
|
|
|
|
self.symbol_filter = filter.fir_filter_fff(1, coeffs) |
|
|
|
|
autotuneq = gr.msg_queue(2) |
|
|
|
|
self.fsk4_demod = op25.fsk4_demod_ff(autotuneq, self.if_rate, self.symbol_rate) |
|
|
|
@ -73,6 +100,9 @@ class p25_demod_base(gr.hier_block2): |
|
|
|
|
levels = [ -2.0, 0.0, 2.0, 4.0 ] |
|
|
|
|
self.slicer = op25_repeater.fsk4_slicer_fb(levels) |
|
|
|
|
|
|
|
|
|
def set_symbol_rate(self, rate): |
|
|
|
|
self.symbol_rate = rate |
|
|
|
|
|
|
|
|
|
def set_baseband_gain(self, k): |
|
|
|
|
self.baseband_amp.set_k(k) |
|
|
|
|
|
|
|
|
@ -97,6 +127,8 @@ class p25_demod_fb(p25_demod_base): |
|
|
|
|
|
|
|
|
|
def __init__(self, |
|
|
|
|
input_rate = None, |
|
|
|
|
filter_type = None, |
|
|
|
|
excess_bw = _def_excess_bw, |
|
|
|
|
symbol_rate = _def_symbol_rate): |
|
|
|
|
""" |
|
|
|
|
Hierarchical block for P25 demodulation. |
|
|
|
@ -110,7 +142,7 @@ class p25_demod_fb(p25_demod_base): |
|
|
|
|
gr.io_signature(1, 1, gr.sizeof_float), # Input signature |
|
|
|
|
gr.io_signature(1, 1, gr.sizeof_char)) # Output signature |
|
|
|
|
|
|
|
|
|
p25_demod_base.__init__(self, if_rate=input_rate, symbol_rate=symbol_rate) |
|
|
|
|
p25_demod_base.__init__(self, if_rate=input_rate, symbol_rate=symbol_rate, filter_type=filter_type) |
|
|
|
|
|
|
|
|
|
self.input_rate = input_rate |
|
|
|
|
self.float_sink = None |
|
|
|
@ -135,6 +167,8 @@ class p25_demod_cb(p25_demod_base): |
|
|
|
|
def __init__(self, |
|
|
|
|
input_rate = None, |
|
|
|
|
demod_type = 'cqpsk', |
|
|
|
|
filter_type = None, |
|
|
|
|
excess_bw = _def_excess_bw, |
|
|
|
|
relative_freq = 0, |
|
|
|
|
offset = 0, |
|
|
|
|
if_rate = _def_if_rate, |
|
|
|
@ -153,7 +187,7 @@ class p25_demod_cb(p25_demod_base): |
|
|
|
|
gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature |
|
|
|
|
gr.io_signature(1, 1, gr.sizeof_char)) # Output signature |
|
|
|
|
# gr.io_signature(0, 0, 0)) # Output signature |
|
|
|
|
p25_demod_base.__init__(self, if_rate=if_rate, symbol_rate=symbol_rate) |
|
|
|
|
p25_demod_base.__init__(self, if_rate=if_rate, symbol_rate=symbol_rate, filter_type=filter_type) |
|
|
|
|
|
|
|
|
|
self.input_rate = input_rate |
|
|
|
|
self.if_rate = if_rate |
|
|
|
@ -164,22 +198,52 @@ class p25_demod_cb(p25_demod_base): |
|
|
|
|
self.lo_freq = 0 |
|
|
|
|
self.float_sink = None |
|
|
|
|
self.complex_sink = None |
|
|
|
|
self.if1 = None |
|
|
|
|
self.if2 = None |
|
|
|
|
self.t_cache = {} |
|
|
|
|
if filter_type == 'rrc': |
|
|
|
|
self.set_baseband_gain(0.61) |
|
|
|
|
|
|
|
|
|
# local osc |
|
|
|
|
self.lo = analog.sig_source_c (input_rate, analog.GR_SIN_WAVE, 0, 1.0, 0) |
|
|
|
|
self.mixer = blocks.multiply_cc() |
|
|
|
|
lpf_coeffs = filter.firdes.low_pass(1.0, input_rate, 7250, 725, filter.firdes.WIN_HANN) |
|
|
|
|
decimation = int(input_rate / if_rate) |
|
|
|
|
self.lpf = filter.fir_filter_ccf(decimation, lpf_coeffs) |
|
|
|
|
|
|
|
|
|
resampled_rate = float(input_rate) / float(decimation) # rate at output of self.lpf |
|
|
|
|
|
|
|
|
|
self.arb_resampler = filter.pfb.arb_resampler_ccf( |
|
|
|
|
float(self.if_rate) / resampled_rate) |
|
|
|
|
decimator_values = get_decim(input_rate) |
|
|
|
|
if decimator_values: |
|
|
|
|
self.decim, self.decim2 = decimator_values |
|
|
|
|
self.if1 = input_rate / self.decim |
|
|
|
|
self.if2 = self.if1 / self.decim2 |
|
|
|
|
sys.stderr.write( 'Using two-stage decimator for speed=%d, decim=%d/%d if1=%d if2=%d\n' % (input_rate, self.decim, self.decim2, self.if1, self.if2)) |
|
|
|
|
bpf_coeffs = filter.firdes.complex_band_pass(1.0, input_rate, -self.if1/2, self.if1/2, self.if1/2, filter.firdes.WIN_HAMMING) |
|
|
|
|
self.t_cache[0] = bpf_coeffs |
|
|
|
|
fa = 6250 |
|
|
|
|
fb = self.if2 / 2 |
|
|
|
|
lpf_coeffs = filter.firdes.low_pass(1.0, self.if1, (fb+fa)/2, fb-fa, filter.firdes.WIN_HAMMING) |
|
|
|
|
self.bpf = filter.fir_filter_ccc(self.decim, bpf_coeffs) |
|
|
|
|
self.lpf = filter.fir_filter_ccf(self.decim2, lpf_coeffs) |
|
|
|
|
resampled_rate = self.if2 |
|
|
|
|
self.bfo = analog.sig_source_c (self.if1, analog.GR_SIN_WAVE, 0, 1.0, 0) |
|
|
|
|
self.connect(self, self.bpf, (self.mixer, 0)) |
|
|
|
|
self.connect(self.bfo, (self.mixer, 1)) |
|
|
|
|
else: |
|
|
|
|
sys.stderr.write( 'Unable to use two-stage decimator for speed=%d\n' % (input_rate)) |
|
|
|
|
# local osc |
|
|
|
|
self.lo = analog.sig_source_c (input_rate, analog.GR_SIN_WAVE, 0, 1.0, 0) |
|
|
|
|
lpf_coeffs = filter.firdes.low_pass(1.0, input_rate, 7250, 725, filter.firdes.WIN_HANN) |
|
|
|
|
decimation = int(input_rate / if_rate) |
|
|
|
|
self.lpf = filter.fir_filter_ccf(decimation, lpf_coeffs) |
|
|
|
|
resampled_rate = float(input_rate) / float(decimation) # rate at output of self.lpf |
|
|
|
|
self.connect(self, (self.mixer, 0)) |
|
|
|
|
self.connect(self.lo, (self.mixer, 1)) |
|
|
|
|
self.connect(self.mixer, self.lpf) |
|
|
|
|
|
|
|
|
|
if self.if_rate != resampled_rate: |
|
|
|
|
self.if_out = filter.pfb.arb_resampler_ccf(float(self.if_rate) / resampled_rate) |
|
|
|
|
self.connect(self.lpf, self.if_out) |
|
|
|
|
else: |
|
|
|
|
self.if_out = self.lpf |
|
|
|
|
|
|
|
|
|
self.connect(self, (self.mixer, 0)) |
|
|
|
|
self.connect(self.lo, (self.mixer, 1)) |
|
|
|
|
self.connect(self.mixer, self.lpf, self.arb_resampler) |
|
|
|
|
fa = 6250 |
|
|
|
|
fb = fa + 625 |
|
|
|
|
cutoff_coeffs = filter.firdes.low_pass(1.0, self.if_rate, (fb+fa)/2, fb-fa, filter.firdes.WIN_HANN) |
|
|
|
|
self.cutoff = filter.fir_filter_ccf(1, cutoff_coeffs) |
|
|
|
|
|
|
|
|
|
levels = [ -2.0, 0.0, 2.0, 4.0 ] |
|
|
|
|
self.slicer = op25_repeater.fsk4_slicer_fb(levels) |
|
|
|
@ -214,6 +278,9 @@ class p25_demod_cb(p25_demod_base): |
|
|
|
|
|
|
|
|
|
self.set_relative_frequency(relative_freq) |
|
|
|
|
|
|
|
|
|
def get_freq_error(self): # get error in Hz (approx). |
|
|
|
|
return int(self.clock.get_freq_error() * self.symbol_rate) |
|
|
|
|
|
|
|
|
|
def set_omega(self, omega): |
|
|
|
|
sps = self.if_rate / float(omega) |
|
|
|
|
if sps == self.sps: |
|
|
|
@ -228,17 +295,28 @@ class p25_demod_cb(p25_demod_base): |
|
|
|
|
return False |
|
|
|
|
if freq == self.lo_freq: |
|
|
|
|
return True |
|
|
|
|
#print 'set_relative_frequency', freq |
|
|
|
|
self.lo_freq = freq |
|
|
|
|
self.lo.set_frequency(self.lo_freq) |
|
|
|
|
if self.if1: |
|
|
|
|
if freq not in self.t_cache.keys(): |
|
|
|
|
self.t_cache[freq] = filter.firdes.complex_band_pass(1.0, self.input_rate, -freq - self.if1/2, -freq + self.if1/2, self.if1/2, filter.firdes.WIN_HAMMING) |
|
|
|
|
self.bpf.set_taps(self.t_cache[freq]) |
|
|
|
|
bfo_f = self.decim * -freq / float(self.input_rate) |
|
|
|
|
bfo_f -= int(bfo_f) |
|
|
|
|
if bfo_f < -0.5: |
|
|
|
|
bfo_f += 1.0 |
|
|
|
|
if bfo_f > 0.5: |
|
|
|
|
bfo_f -= 1.0 |
|
|
|
|
self.bfo.set_frequency(-bfo_f * self.if1) |
|
|
|
|
else: |
|
|
|
|
self.lo.set_frequency(self.lo_freq) |
|
|
|
|
return True |
|
|
|
|
|
|
|
|
|
# assumes lock held or init |
|
|
|
|
def disconnect_chain(self): |
|
|
|
|
if self.connect_state == 'cqpsk': |
|
|
|
|
self.disconnect(self.arb_resampler, self.agc, self.clock, self.diffdec, self.to_float, self.rescale, self.slicer) |
|
|
|
|
self.disconnect(self.if_out, self.cutoff, self.agc, self.clock, self.diffdec, self.to_float, self.rescale, self.slicer) |
|
|
|
|
elif self.connect_state == 'fsk4': |
|
|
|
|
self.disconnect(self.arb_resampler, self.fm_demod, self.baseband_amp, self.symbol_filter, self.fsk4_demod, self.slicer) |
|
|
|
|
self.disconnect(self.if_out, self.cutoff, self.fm_demod, self.baseband_amp, self.symbol_filter, self.fsk4_demod, self.slicer) |
|
|
|
|
self.connect_state = None |
|
|
|
|
|
|
|
|
|
# assumes lock held or init |
|
|
|
@ -248,9 +326,9 @@ class p25_demod_cb(p25_demod_base): |
|
|
|
|
self.disconnect_chain() |
|
|
|
|
self.connect_state = demod_type |
|
|
|
|
if demod_type == 'fsk4': |
|
|
|
|
self.connect(self.arb_resampler, self.fm_demod, self.baseband_amp, self.symbol_filter, self.fsk4_demod, self.slicer) |
|
|
|
|
self.connect(self.if_out, self.cutoff, self.fm_demod, self.baseband_amp, self.symbol_filter, self.fsk4_demod, self.slicer) |
|
|
|
|
elif demod_type == 'cqpsk': |
|
|
|
|
self.connect(self.arb_resampler, self.agc, self.clock, self.diffdec, self.to_float, self.rescale, self.slicer) |
|
|
|
|
self.connect(self.if_out, self.cutoff, self.agc, self.clock, self.diffdec, self.to_float, self.rescale, self.slicer) |
|
|
|
|
else: |
|
|
|
|
print 'connect_chain failed, type: %s' % demod_type |
|
|
|
|
assert 0 == 1 |
|
|
|
@ -299,3 +377,12 @@ class p25_demod_cb(p25_demod_base): |
|
|
|
|
elif src == 'src': |
|
|
|
|
self.connect(self, sink) |
|
|
|
|
self.complex_sink = [self, sink] |
|
|
|
|
elif src == 'bpf': |
|
|
|
|
self.connect(self.bpf, sink) |
|
|
|
|
self.complex_sink = [self.bpf, sink] |
|
|
|
|
elif src == 'if_out': |
|
|
|
|
self.connect(self.if_out, sink) |
|
|
|
|
self.complex_sink = [self.if_out, sink] |
|
|
|
|
elif src == 'agc': |
|
|
|
|
self.connect(self.agc, sink) |
|
|
|
|
self.complex_sink = [self.agc, sink] |
|
|
|
|