summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMax <ikj1234i@yahoo.com>2015-03-31 16:47:21 -0400
committerMax <ikj1234i@yahoo.com>2015-03-31 16:47:21 -0400
commitb3fee05fd0fe96bc343710389f063a6348124bac (patch)
treee50ce828c6e1dac11830c31c7d0714182b91f0a8
parentaf3e07d769434a18715bd47d9dbd2c1e702a78bb (diff)
updates to c4fm pre-emphasis and de-emphasis filtering
-rw-r--r--op25/gr-op25_repeater/apps/p25_demodulator.py16
-rwxr-xr-xop25/gr-op25_repeater/apps/scope.py7
-rwxr-xr-xop25/gr-op25_repeater/apps/tx/op25_c4fm_mod.py101
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)