From b3fee05fd0fe96bc343710389f063a6348124bac Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 31 Mar 2015 16:47:21 -0400 Subject: updates to c4fm pre-emphasis and de-emphasis filtering --- op25/gr-op25_repeater/apps/p25_demodulator.py | 16 ++-- op25/gr-op25_repeater/apps/scope.py | 7 +- op25/gr-op25_repeater/apps/tx/op25_c4fm_mod.py | 101 ++++++++++++++++--------- 3 files changed, 76 insertions(+), 48 deletions(-) diff --git a/op25/gr-op25_repeater/apps/p25_demodulator.py b/op25/gr-op25_repeater/apps/p25_demodulator.py index 4936348..cca35ab 100644 --- a/op25/gr-op25_repeater/apps/p25_demodulator.py +++ b/op25/gr-op25_repeater/apps/p25_demodulator.py @@ -2,7 +2,7 @@ # Copyright 2005,2006,2007 Free Software Foundation, Inc. # # OP25 Demodulator Block -# Copyright 2009, 2010, 2011, 2012, 2013, 2014 Max H. Parke KA1RBI +# Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015 Max H. Parke KA1RBI # # This file is part of GNU Radio and part of OP25 # @@ -26,6 +26,7 @@ P25 C4FM/CQPSK demodulation block. """ +import sys from gnuradio import gr, gru, eng_notation from gnuradio import filter, analog, digital, blocks from gnuradio.eng_option import eng_option @@ -33,9 +34,11 @@ import op25 import op25_repeater from math import pi +sys.path.append('tx') +import op25_c4fm_mod + # default values (used in __init__ and add_options) _def_output_sample_rate = 48000 -_def_excess_bw = 0.1 _def_if_rate = 24000 _def_gain_mu = 0.025 _def_costas_alpha = 0.04 @@ -62,11 +65,8 @@ class p25_demod_base(gr.hier_block2): self.bb_sink = None self.baseband_amp = blocks.multiply_const_ff(_def_bb_gain) - sps = int(self.if_rate // self.symbol_rate) - symbol_decim = 1 - ntaps = 11 * sps - rrc_coeffs = (1.0/sps,)*sps - self.symbol_filter = filter.fir_filter_fff(symbol_decim, rrc_coeffs) + coeffs = op25_c4fm_mod.c4fm_taps(sample_rate=self.if_rate, span=9, generator=op25_c4fm_mod.transfer_function_rx).generate() + 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) @@ -168,7 +168,7 @@ class p25_demod_cb(p25_demod_base): # 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, 15000, 1500, filter.firdes.WIN_HANN) + 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) diff --git a/op25/gr-op25_repeater/apps/scope.py b/op25/gr-op25_repeater/apps/scope.py index 4a56425..3a38441 100755 --- a/op25/gr-op25_repeater/apps/scope.py +++ b/op25/gr-op25_repeater/apps/scope.py @@ -2,7 +2,7 @@ # Copyright 2008-2011 Steve Glass # -# Copyright 2011, 2012, 2013, 2014 Max H. Parke KA1RBI +# Copyright 2011, 2012, 2013, 2014, 2015 Max H. Parke KA1RBI # # Copyright 2003,2004,2005,2006 Free Software Foundation, Inc. # (from radiorausch) @@ -418,7 +418,8 @@ class p25_rx_block (stdgui2.std_top_block): sel = self.notebook.GetSelection() self.lock() self.disconnect_data_scope() - self.disconnect_constellation_scope() + if not self.baseband_input: + self.disconnect_constellation_scope() if sel == 0: # spectrum if not self.baseband_input: self.set_connection(fft=1) @@ -871,7 +872,7 @@ class p25_rx_block (stdgui2.std_top_block): self.state = new_state if "STOPPED" == self.state: # menu items - can_capture = self.usrp is not None + can_capture = False # self.usrp is not None self.file_new.Enable(can_capture) self.file_open.Enable(True) self.file_properties.Enable(False) diff --git a/op25/gr-op25_repeater/apps/tx/op25_c4fm_mod.py b/op25/gr-op25_repeater/apps/tx/op25_c4fm_mod.py index d93a8b5..4ee4c78 100755 --- a/op25/gr-op25_repeater/apps/tx/op25_c4fm_mod.py +++ b/op25/gr-op25_repeater/apps/tx/op25_c4fm_mod.py @@ -2,10 +2,8 @@ # Copyright 2005,2006,2007 Free Software Foundation, Inc. # # OP25 4-Level Modulator Block -# Copyright 2009, 2014 Max H. Parke KA1RBI +# Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015 Max H. Parke KA1RBI # -# coeffs for shaping and cosine filters from Eric Ramsey thesis -# # This file is part of GNU Radio and part of OP25 # # This is free software; you can redistribute it and/or modify @@ -34,13 +32,69 @@ from gnuradio import filter, digital, blocks from gnuradio.eng_option import eng_option from optparse import OptionParser import math +from math import sin, cos, pi +import numpy as np # default values (used in __init__ and add_options) _def_output_sample_rate = 48000 -_def_excess_bw = 0.2 +_def_symbol_rate = 4800 _def_reverse = False _def_verbose = False _def_log = False +_def_span = 13 #desired number of impulse response coeffs, in units of symbols + +def transfer_function_rx(): + # p25 c4fm de-emphasis filter + # Specs undefined above 2,880 Hz. It would be nice to have a sharper + # rolloff, but this filter is cheap enough.... + xfer = [] # frequency domain transfer function + for f in xrange(0,4800): + # D(f) + t = pi * f / 4800 + if t < 1e-6: + df = 1.0 + else: + df = sin (t) / t + xfer.append(df) + return xfer + +def transfer_function_tx(): + xfer = [] # frequency domain transfer function + for f in xrange(0, 2881): # specs cover 0 - 2,880 Hz + # H(f) + if f < 1920: + hf = 1.0 + else: + hf = 0.5 + 0.5 * cos (2 * pi * float(f) / 1920.0) + # P(f) + t = pi * f / 4800.0 + if t < 1e-6: + pf = 1 + else: + pf = t / sin (t) + # time domain convolution == frequency domain multiplication + xfer.append(pf * hf) + return xfer + +class c4fm_taps(object): + """Generate filter coefficients as per P25 C4FM spec""" + def __init__(self, filter_gain = 1.0, sample_rate=_def_output_sample_rate, symbol_rate=_def_symbol_rate, span=_def_span, generator=transfer_function_tx): + self.sample_rate = sample_rate + self.symbol_rate = symbol_rate + self.filter_gain = filter_gain + self.sps = int(sample_rate / symbol_rate) + self.ntaps = (self.sps * span) | 1 + self.generator = generator + + def generate(self): + impulse_response = np.fft.fftshift(np.fft.irfft(self.generator(), self.sample_rate)) + start = np.argmax(impulse_response) - (self.ntaps-1) / 2 + coeffs = impulse_response[start: start+self.ntaps] + gain = self.filter_gain / sum(coeffs) + return coeffs * gain + + def generate_code(self, varname='taps'): + return '%s = [\n\t%s]' % (varname, ',\n\t'.join(['%10.6e' % f for f in self.generate()])) # ///////////////////////////////////////////////////////////////////////////// # modulator @@ -50,7 +104,6 @@ class p25_mod_bf(gr.hier_block2): def __init__(self, output_sample_rate=_def_output_sample_rate, - excess_bw=_def_excess_bw, reverse=_def_reverse, verbose=_def_verbose, log=_def_log): @@ -66,8 +119,6 @@ class p25_mod_bf(gr.hier_block2): @param output_sample_rate: output sample rate @type output_sample_rate: integer - @param excess_bw: Root-raised cosine filter excess bandwidth - @type excess_bw: float @param reverse: reverse polarity flag @type reverse: bool @param verbose: Print information about modulator? @@ -84,7 +135,6 @@ class p25_mod_bf(gr.hier_block2): lcm = gru.lcm(input_sample_rate, output_sample_rate) self._interp_factor = int(lcm // input_sample_rate) self._decimation = int(lcm // output_sample_rate) - self._excess_bw = excess_bw mod_map = [1.0/3.0, 1.0, -(1.0/3.0), -1.0] self.C2S = digital.chunks_to_symbols_bf(mod_map) @@ -93,27 +143,7 @@ class p25_mod_bf(gr.hier_block2): else: self.polarity = blocks.multiply_const_ff( 1) - ntaps = 11 * self._interp_factor - rrc_taps = filter.firdes.root_raised_cosine( - self._interp_factor, # gain (since we're interpolating by sps) - lcm, # sampling rate - input_sample_rate, # symbol rate - self._excess_bw, # excess bandwidth (roll-off factor) - ntaps) - - # rrc_coeffs work slightly differently: each input sample - # (from mod_map above) at 4800 rate, then 9 zeros are inserted - # to bring to 48000 rate, then this filter is applied: - # rrc_filter = gr.fir_filter_fff(1, rrc_coeffs) - # FIXME: how to insert the 9 zero samples using gr ? - # rrc_coeffs = [0, -0.003, -0.006, -0.009, -0.012, -0.014, -0.014, -0.013, -0.01, -0.006, 0, 0.007, 0.014, 0.02, 0.026, 0.029, 0.029, 0.027, 0.021, 0.012, 0, -0.013, -0.027, -0.039, -0.049, -0.054, -0.055, -0.049, -0.038, -0.021, 0, 0.024, 0.048, 0.071, 0.088, 0.098, 0.099, 0.09, 0.07, 0.039, 0, -0.045, -0.091, -0.134, -0.17, -0.193, -0.199, -0.184, -0.147, -0.085, 0, 0.105, 0.227, 0.36, 0.496, 0.629, 0.751, 0.854, 0.933, 0.983, 1, 0.983, 0.933, 0.854, 0.751, 0.629, 0.496, 0.36, 0.227, 0.105, 0, -0.085, -0.147, -0.184, -0.199, -0.193, -0.17, -0.134, -0.091, -0.045, 0, 0.039, 0.07, 0.09, 0.099, 0.098, 0.088, 0.071, 0.048, 0.024, 0, -0.021, -0.038, -0.049, -0.055, -0.054, -0.049, -0.039, -0.027, -0.013, 0, 0.012, 0.021, 0.027, 0.029, 0.029, 0.026, 0.02, 0.014, 0.007, 0, -0.006, -0.01, -0.013, -0.014, -0.014, -0.012, -0.009, -0.006, -0.003, 0] - - self.rrc_filter = filter.interp_fir_filter_fff(self._interp_factor, rrc_taps) - - - # FM pre-emphasis filter - shaping_coeffs = [-0.018, 0.0347, 0.0164, -0.0064, -0.0344, -0.0522, -0.0398, 0.0099, 0.0798, 0.1311, 0.121, 0.0322, -0.113, -0.2499, -0.3007, -0.2137, -0.0043, 0.2825, 0.514, 0.604, 0.514, 0.2825, -0.0043, -0.2137, -0.3007, -0.2499, -0.113, 0.0322, 0.121, 0.1311, 0.0798, 0.0099, -0.0398, -0.0522, -0.0344, -0.0064, 0.0164, 0.0347, -0.018] - self.shaping_filter = filter.fir_filter_fff(1, shaping_coeffs) + self.filter = filter.interp_fir_filter_fff(self._interp_factor, c4fm_taps(sample_rate=output_sample_rate).generate()) if verbose: self._print_verbage() @@ -121,16 +151,15 @@ class p25_mod_bf(gr.hier_block2): if log: self._setup_logging() - self.connect(self, self.C2S, self.polarity, self.rrc_filter, self.shaping_filter) + self.connect(self, self.C2S, self.polarity, self.filter) if (self._decimation > 1): self.decimator = filter.rational_resampler_fff(1, self._decimation) - self.connect(self.shaping_filter, self.decimator, self) + self.connect(self.filter, self.decimator, self) else: - self.connect(self.shaping_filter, self) + self.connect(self.filter, self) def _print_verbage(self): print "\nModulator:" - print "RRS roll-off factor: %f" % self._excess_bw print "interpolation: %d decimation: %d" %(self._interp_factor, self._decimation) def _setup_logging(self): @@ -139,8 +168,8 @@ class p25_mod_bf(gr.hier_block2): gr.file_sink(gr.sizeof_float, "tx_chunks2symbols.dat")) self.connect(self.polarity, gr.file_sink(gr.sizeof_float, "tx_polarity.dat")) - self.connect(self.rrc_filter, - gr.file_sink(gr.sizeof_float, "tx_rrc_filter.dat")) + self.connect(self.filter, + gr.file_sink(gr.sizeof_float, "tx_filter.dat")) self.connect(self.shaping_filter, gr.file_sink(gr.sizeof_float, "tx_shaping_filter.dat")) if (self._decimation > 1): @@ -151,8 +180,6 @@ class p25_mod_bf(gr.hier_block2): """ Adds QPSK modulation-specific options to the standard parser """ - parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, - help="set RRC excess bandwith factor [default=%default] (PSK)") add_options=staticmethod(add_options) -- cgit v1.2.3