updates to c4fm pre-emphasis and de-emphasis filtering
This commit is contained in:
parent
af3e07d769
commit
b3fee05fd0
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue