updates to c4fm pre-emphasis and de-emphasis filtering

This commit is contained in:
Max 2015-03-31 16:47:21 -04:00
parent af3e07d769
commit b3fee05fd0
3 changed files with 76 additions and 48 deletions

View File

@ -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)

View File

@ -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,6 +418,7 @@ class p25_rx_block (stdgui2.std_top_block):
sel = self.notebook.GetSelection()
self.lock()
self.disconnect_data_scope()
if not self.baseband_input:
self.disconnect_constellation_scope()
if sel == 0: # spectrum
if not self.baseband_input:
@ -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)

View File

@ -2,9 +2,7 @@
# Copyright 2005,2006,2007 Free Software Foundation, Inc.
#
# OP25 4-Level Modulator Block
# Copyright 2009, 2014 Max H. Parke KA1RBI
#
# coeffs for shaping and cosine filters from Eric Ramsey thesis
# Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015 Max H. Parke KA1RBI
#
# This file is part of GNU Radio and part of OP25
#
@ -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)