summaryrefslogtreecommitdiff
path: root/gr-digital/lib/clock_tracking_loop.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gr-digital/lib/clock_tracking_loop.cc')
-rw-r--r--gr-digital/lib/clock_tracking_loop.cc307
1 files changed, 307 insertions, 0 deletions
diff --git a/gr-digital/lib/clock_tracking_loop.cc b/gr-digital/lib/clock_tracking_loop.cc
new file mode 100644
index 0000000000..d74e4b2120
--- /dev/null
+++ b/gr-digital/lib/clock_tracking_loop.cc
@@ -0,0 +1,307 @@
+/* -*- c++ -*- */
+/*
+ * Copyright (C) 2011,2013,2016-2017 Free Software Foundation, Inc.
+ *
+ * This file is part of GNU Radio
+ *
+ * GNU Radio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3, or (at your option)
+ * any later version.
+ *
+ * GNU Radio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Radio; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "clock_tracking_loop.h"
+#include <gnuradio/math.h>
+#include <stdexcept>
+
+namespace gr {
+ namespace digital {
+
+ clock_tracking_loop::clock_tracking_loop(float loop_bw,
+ float max_period, float min_period,
+ float nominal_period,
+ float damping)
+ : d_avg_period(nominal_period),
+ d_max_avg_period(max_period),
+ d_min_avg_period(min_period),
+ d_nom_avg_period(nominal_period),
+ d_inst_period(nominal_period),
+ d_phase(0.0f),
+ d_zeta(damping),
+ d_omega_n_norm(loop_bw),
+ d_alpha(0.0f),
+ d_beta(0.0f),
+ d_prev_avg_period(nominal_period),
+ d_prev_inst_period(nominal_period),
+ d_prev_phase(0.0f)
+ {
+ set_max_avg_period(max_period);
+ set_min_avg_period(min_period);
+ set_nom_avg_period(nominal_period);
+
+ set_avg_period(d_nom_avg_period);
+ set_inst_period(d_nom_avg_period);
+
+ if (d_zeta < 0.0f)
+ throw std::out_of_range (
+ "clock_tracking_loop: damping factor must be > 0.0");
+
+ if (d_omega_n_norm < 0.0f)
+ throw std::out_of_range (
+ "clock_tracking_loop: loop bandwidth must be greater than 0.0");
+
+ update_gains();
+ }
+
+ clock_tracking_loop::~clock_tracking_loop()
+ {
+ }
+
+ void
+ clock_tracking_loop::update_gains()
+ {
+ float omega_n_T, omega_d_T, zeta_omega_n_T, k1, cosx_omega_d_T;
+ float alpha, beta;
+
+ omega_n_T = d_omega_n_norm;
+ zeta_omega_n_T = d_zeta * omega_n_T;
+ k1 = 2.0f * expf(-zeta_omega_n_T);
+
+ if (d_zeta > 1.0f) { // Over-damped (or critically-damped too)
+
+ omega_d_T = omega_n_T * sqrtf(d_zeta * d_zeta - 1.0f);
+ cosx_omega_d_T = coshf(omega_d_T);
+ // cosh ---------^^^^
+
+ } else if (d_zeta == 1.0f) { // Critically-damped
+
+ omega_d_T = 0.0f;
+ cosx_omega_d_T = 1.0f;
+ // cosh(omega_d_T) & cos(omega_d_T) are both 1 for omega_d_T == 0
+
+ } else { // Under-damped (or critically-damped too)
+
+ omega_d_T = omega_n_T * sqrtf(1.0 - d_zeta * d_zeta);
+ cosx_omega_d_T = cosf(omega_d_T);
+ // cos ----------^^^
+ }
+
+ alpha = k1 * sinhf(zeta_omega_n_T);
+ beta = 2.0f - (alpha + k1 * cosx_omega_d_T);
+
+ set_alpha(alpha);
+ set_beta(beta);
+ }
+
+ void
+ clock_tracking_loop::advance_loop(float error)
+ {
+ // So the loop can be reverted one step, if needed.
+ d_prev_avg_period = d_avg_period;
+ d_prev_inst_period = d_inst_period;
+ d_prev_phase = d_phase;
+
+ // Integral arm of PI filter
+ d_avg_period = d_avg_period + d_beta * error;
+ // Proportional arm of PI filter and final sum of PI filter arms
+ d_inst_period = d_avg_period + d_alpha * error;
+ // Compute the new, unwrapped clock phase
+ d_phase = d_phase + d_inst_period;
+ }
+
+ void
+ clock_tracking_loop::revert_loop()
+ {
+ d_avg_period = d_prev_avg_period;
+ d_inst_period = d_prev_inst_period;
+ d_phase = d_prev_phase;
+ }
+
+ void
+ clock_tracking_loop::phase_wrap()
+ {
+ float period = d_avg_period; // One could argue d_inst_period instead
+ float limit = period/2.0f;
+
+ while (d_phase > limit)
+ d_phase -= period;
+
+ while (d_phase <= -limit)
+ d_phase += period;
+ }
+
+ void
+ clock_tracking_loop::period_limit()
+ {
+ if (d_avg_period > d_max_avg_period)
+ d_avg_period = d_max_avg_period;
+ else if (d_avg_period < d_min_avg_period)
+ d_avg_period = d_min_avg_period;
+ }
+
+ /*******************************************************************
+ * SET FUNCTIONS
+ *******************************************************************/
+
+ void
+ clock_tracking_loop::set_loop_bandwidth(float bw)
+ {
+ if (bw < 0.0f)
+ throw std::out_of_range (
+ "clock_tracking_loop: loop bandwidth must be greater than 0.0");
+
+ d_omega_n_norm = bw;
+ update_gains();
+ }
+
+ void
+ clock_tracking_loop::set_damping_factor(float df)
+ {
+ if (df < 0.0f)
+ throw std::out_of_range (
+ "clock_tracking_loop: damping factor must be > 0.0");
+
+ d_zeta = df;
+ update_gains();
+ }
+
+ void
+ clock_tracking_loop::set_alpha(float alpha)
+ {
+ d_alpha = alpha;
+ }
+
+ void
+ clock_tracking_loop::set_beta(float beta)
+ {
+ d_beta = beta;
+ }
+
+ void
+ clock_tracking_loop::set_avg_period(float period)
+ {
+ d_avg_period = period;
+ d_prev_avg_period = period;
+ }
+
+ void
+ clock_tracking_loop::set_inst_period(float period)
+ {
+ d_inst_period = period;
+ d_prev_inst_period = period;
+ }
+
+ void
+ clock_tracking_loop::set_phase(float phase)
+ {
+ // This previous phase is likely inconsistent with the tracking,
+ // but if the caller is setting the phase, the odds of
+ // revert_loop() being called are slim.
+ d_prev_phase = phase;
+
+ d_phase = phase;
+ }
+
+ void
+ clock_tracking_loop::set_max_avg_period(float period)
+ {
+ d_max_avg_period = period;
+ }
+
+ void
+ clock_tracking_loop::set_min_avg_period(float period)
+ {
+ d_min_avg_period = period;
+ }
+
+ void
+ clock_tracking_loop::set_nom_avg_period(float period)
+ {
+ if (period < d_min_avg_period or
+ period > d_max_avg_period ) {
+ d_nom_avg_period = (d_max_avg_period + d_min_avg_period)/2.0f;
+ } else {
+ d_nom_avg_period = period;
+ }
+ }
+
+ /*******************************************************************
+ * GET FUNCTIONS
+ *******************************************************************/
+
+ float
+ clock_tracking_loop::get_loop_bandwidth() const
+ {
+ return d_omega_n_norm;
+ }
+
+ float
+ clock_tracking_loop::get_damping_factor() const
+ {
+ return d_zeta;
+ }
+
+ float
+ clock_tracking_loop::get_alpha() const
+ {
+ return d_alpha;
+ }
+
+ float
+ clock_tracking_loop::get_beta() const
+ {
+ return d_beta;
+ }
+
+ float
+ clock_tracking_loop::get_avg_period() const
+ {
+ return d_avg_period;
+ }
+
+ float
+ clock_tracking_loop::get_inst_period() const
+ {
+ return d_inst_period;
+ }
+
+ float
+ clock_tracking_loop::get_phase() const
+ {
+ return d_phase;
+ }
+
+ float
+ clock_tracking_loop::get_max_avg_period() const
+ {
+ return d_max_avg_period;
+ }
+
+ float
+ clock_tracking_loop::get_min_avg_period() const
+ {
+ return d_min_avg_period;
+ }
+
+ float
+ clock_tracking_loop::get_nom_avg_period() const
+ {
+ return d_nom_avg_period;
+ }
+
+ } /* namespace digital */
+} /* namespace gr */