Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 51723c82 authored by Mike Lockwood's avatar Mike Lockwood Committed by Android (Google) Code Review
Browse files

Merge "New clock sync control loop."

parents e13ac73a 11bc45fc
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,8 @@ LOCAL_SRC_FILES := \
    common_clock.cpp \
    common_clock.cpp \
    main.cpp
    main.cpp


# Uncomment to enable vesbose logging and debug service.
#TIME_SERVICE_DEBUG=true
ifeq ($(TIME_SERVICE_DEBUG), true)
ifeq ($(TIME_SERVICE_DEBUG), true)
LOCAL_SRC_FILES += diag_thread.cpp
LOCAL_SRC_FILES += diag_thread.cpp
LOCAL_CFLAGS += -DTIME_SERVICE_DEBUG
LOCAL_CFLAGS += -DTIME_SERVICE_DEBUG
+96 −156
Original line number Original line Diff line number Diff line
@@ -33,6 +33,14 @@
#include "diag_thread.h"
#include "diag_thread.h"
#endif
#endif


// Define log macro so we can make LOGV into LOGE when we are exclusively
// debugging this code.
#ifdef TIME_SERVICE_DEBUG
#define LOG_TS ALOGE
#else
#define LOG_TS ALOGV
#endif

namespace android {
namespace android {


ClockRecoveryLoop::ClockRecoveryLoop(LocalClock* local_clock,
ClockRecoveryLoop::ClockRecoveryLoop(LocalClock* local_clock,
@@ -46,7 +54,6 @@ ClockRecoveryLoop::ClockRecoveryLoop(LocalClock* local_clock,
    local_clock_can_slew_ = local_clock_->initCheck() &&
    local_clock_can_slew_ = local_clock_->initCheck() &&
                           (local_clock_->setLocalSlew(0) == OK);
                           (local_clock_->setLocalSlew(0) == OK);


    computePIDParams();
    reset(true, true);
    reset(true, true);


#ifdef TIME_SERVICE_DEBUG
#ifdef TIME_SERVICE_DEBUG
@@ -66,6 +73,19 @@ ClockRecoveryLoop::~ClockRecoveryLoop() {
#endif
#endif
}
}


// Constants.
const float ClockRecoveryLoop::dT = 1.0;
const float ClockRecoveryLoop::Kc = 1.0f;
const float ClockRecoveryLoop::Ti = 15.0f;
const float ClockRecoveryLoop::Tf = 0.05;
const float ClockRecoveryLoop::bias_Fc = 0.01;
const float ClockRecoveryLoop::bias_RC = (dT / (2 * 3.14159f * bias_Fc));
const float ClockRecoveryLoop::bias_Alpha = (dT / (bias_RC + dT));
const int64_t ClockRecoveryLoop::panic_thresh_ = 50000;
const int64_t ClockRecoveryLoop::control_thresh_ = 10000;
const float ClockRecoveryLoop::COmin = -100.0f;
const float ClockRecoveryLoop::COmax = 100.0f;

void ClockRecoveryLoop::reset(bool position, bool frequency) {
void ClockRecoveryLoop::reset(bool position, bool frequency) {
    Mutex::Autolock lock(&lock_);
    Mutex::Autolock lock(&lock_);
    reset_l(position, frequency);
    reset_l(position, frequency);
@@ -86,6 +106,16 @@ bool ClockRecoveryLoop::pushDisciplineEvent(int64_t local_time,
                                            int64_t rtt) {
                                            int64_t rtt) {
    Mutex::Autolock lock(&lock_);
    Mutex::Autolock lock(&lock_);


    int64_t local_common_time = 0;
    common_clock_->localToCommon(local_time, &local_common_time);
    int64_t raw_delta = nominal_common_time - local_common_time;

#ifdef TIME_SERVICE_DEBUG
    ALOGE("local=%lld, common=%lld, delta=%lld, rtt=%lld\n",
         local_common_time, nominal_common_time,
         raw_delta, rtt);
#endif

    // If we have not defined a basis for common time, then we need to use these
    // If we have not defined a basis for common time, then we need to use these
    // initial points to do so.  In order to avoid significant initial error
    // initial points to do so.  In order to avoid significant initial error
    // from a particularly bad startup data point, we collect the first N data
    // from a particularly bad startup data point, we collect the first N data
@@ -113,11 +143,8 @@ bool ClockRecoveryLoop::pushDisciplineEvent(int64_t local_time,


    int64_t observed_common;
    int64_t observed_common;
    int64_t delta;
    int64_t delta;
    int32_t delta32;
    float delta_f, dCO;
    int32_t correction_cur;
    int32_t correction_cur;
    int32_t correction_cur_P = 0;
    int32_t correction_cur_I = 0;
    int32_t correction_cur_D = 0;


    if (OK != common_clock_->localToCommon(local_time, &observed_common)) {
    if (OK != common_clock_->localToCommon(local_time, &observed_common)) {
        // Since we just checked to make certain that this conversion was valid,
        // Since we just checked to make certain that this conversion was valid,
@@ -165,72 +192,69 @@ bool ClockRecoveryLoop::pushDisciplineEvent(int64_t local_time,
    filter_data_[filter_wr_].nominal_common_time  = nominal_common_time;
    filter_data_[filter_wr_].nominal_common_time  = nominal_common_time;
    filter_data_[filter_wr_].rtt                  = rtt;
    filter_data_[filter_wr_].rtt                  = rtt;
    filter_data_[filter_wr_].point_used           = false;
    filter_data_[filter_wr_].point_used           = false;
    uint32_t current_point = filter_wr_;
    filter_wr_ = (filter_wr_ + 1) % kFilterSize;
    filter_wr_ = (filter_wr_ + 1) % kFilterSize;
    if (!filter_wr_)
    if (!filter_wr_)
        filter_full_ = true;
        filter_full_ = true;


    // Scan the accumulated data for the point with the minimum RTT.  If that
    // point has never been used before, go ahead and use it now, otherwise just
    // do nothing.
    uint32_t scan_end = filter_full_ ? kFilterSize : filter_wr_;
    uint32_t scan_end = filter_full_ ? kFilterSize : filter_wr_;
    uint32_t min_rtt = findMinRTTNdx(filter_data_, scan_end);
    uint32_t min_rtt = findMinRTTNdx(filter_data_, scan_end);
    if (filter_data_[min_rtt].point_used)
    // We only use packets with low RTTs for control. If the packet RTT
        return true;
    // is less than the panic threshold, we can probably eat the jitter with the

    // control loop. Otherwise, take the packet only if it better than all
    local_time          = filter_data_[min_rtt].local_time;
    // of the packets we have in the history. That way we try to track
    observed_common     = filter_data_[min_rtt].observed_common_time;
    // something, even if it is noisy.
    nominal_common_time = filter_data_[min_rtt].nominal_common_time;
    if (current_point == min_rtt || rtt < control_thresh_) {
    filter_data_[min_rtt].point_used = true;
        delta_f = delta = nominal_common_time - observed_common;


    // Compute the error then clamp to the panic threshold.  If we ever exceed
        // Compute the error then clamp to the panic threshold.  If we ever
    // this amt of error, its time to panic and reset the system.  Given that
        // exceed this amt of error, its time to panic and reset the system.
    // the error in the measurement of the error could be as high as the RTT of
        // Given that the error in the measurement of the error could be as
    // the data point, we don't actually panic until the implied error (delta)
        // high as the RTT of the data point, we don't actually panic until
    // is greater than the absolute panic threashold plus the RTT.  IOW - we
        // the implied error (delta) is greater than the absolute panic
    // don't panic until we are absoluely sure that our best case sync is worse
        // threashold plus the RTT.  IOW - we don't panic until we are
    // than the absolute panic threshold.
        // absoluely sure that our best case sync is worse than the absolute
    int64_t effective_panic_thresh = panic_thresh_ + filter_data_[min_rtt].rtt;
        // panic threshold.
    delta = nominal_common_time - observed_common;
        int64_t effective_panic_thresh = panic_thresh_ + rtt;
    if ((delta > effective_panic_thresh) || (delta < -effective_panic_thresh)) {
        if ((delta > effective_panic_thresh) ||
            (delta < -effective_panic_thresh)) {
            // PANIC!!!
            // PANIC!!!
        //
        // TODO(johngro) : need to report this to the upper levels of
        // code.
            reset_l(false, true);
            reset_l(false, true);
            return false;
            return false;
    } else
        }
        delta32 = delta;


    } else {
    // Accumulate error into the integrated error, then clamp.
        // We do not have a good packet to look at, but we also do not want to
    integrated_error_ += delta32;
        // free-run the clock at some crazy slew rate. So we guess the
    if (integrated_error_ > pid_params_.integrated_delta_max)
        // trajectory of the clock based on the last controller output and the
        integrated_error_ = pid_params_.integrated_delta_max;
        // estimated bias of our clock against the master.
    else if (integrated_error_ < pid_params_.integrated_delta_min)
        // The net effect of this is that CO == CObias after some extended
        integrated_error_ = pid_params_.integrated_delta_min;
        // period of no feedback.

        delta_f = last_delta_f_ - dT*(CO - CObias);
    // Compute the difference in error between last time and this time, then
        delta = delta_f;
    // update last_delta_
    }
    int32_t input_D = last_delta_valid_ ? delta32 - last_delta_ : 0;

    last_delta_valid_ = true;
    // Velocity form PI control equation.
    last_delta_ = delta32;
    dCO = Kc * (1.0f + dT/Ti) * delta_f - Kc * last_delta_f_;

    CO += dCO * Tf; // Filter CO by applying gain <1 here.
    // Compute the various components of the correction value.

    correction_cur_P = doGainScale(pid_params_.gain_P, delta32);
    // Save error terms for later.
    correction_cur_I = doGainScale(pid_params_.gain_I, integrated_error_);
    last_delta_f_ = delta_f;

    last_delta_ = delta;
    // TODO(johngro) : the differential portion of this code used to rely

    // upon a completely homogeneous discipline frequency.  Now that the
    // Clamp CO to +/- 100ppm.
    // discipline frequency may not be homogeneous, its probably important
    if (CO < COmin)
    // to divide by the amt of time between discipline events during the
        CO = COmin;
    // gain calculation.
    else if (CO > COmax)
    correction_cur_D = doGainScale(pid_params_.gain_D, input_D);
        CO = COmax;


    // Compute the final correction value and clamp.
    // Update the controller bias.
    correction_cur = correction_cur_P + correction_cur_I + correction_cur_D;
    CObias = bias_Alpha * CO + (1.0f - bias_Alpha) * lastCObias;
    if (correction_cur < pid_params_.correction_min)
    lastCObias = CObias;
        correction_cur = pid_params_.correction_min;

    else if (correction_cur > pid_params_.correction_max)
    // Convert PPM to 16-bit int range. Add some guard band (-0.01) so we
        correction_cur = pid_params_.correction_max;
    // don't get fp weirdness.
    correction_cur = CO * 327.66;


    // If there was a change in the amt of correction to use, update the
    // If there was a change in the amt of correction to use, update the
    // system.
    // system.
@@ -239,17 +263,7 @@ bool ClockRecoveryLoop::pushDisciplineEvent(int64_t local_time,
        applySlew();
        applySlew();
    }
    }


    ALOGV("rtt %lld observed %lld nominal %lld delta = %5lld "
    LOG_TS("clock_loop %lld %f %f %f %d\n", raw_delta, delta_f, CO, CObias, correction_cur);
          "int = %7d correction %5d (P %5d, I %5d, D %5d)\n",
          filter_data_[min_rtt].rtt,
          observed_common,
          nominal_common_time,
          nominal_common_time - observed_common,
          integrated_error_,
          correction_cur,
          correction_cur_P,
          correction_cur_I,
          correction_cur_D);


#ifdef TIME_SERVICE_DEBUG
#ifdef TIME_SERVICE_DEBUG
    diag_thread_->pushDisciplineEvent(
    diag_thread_->pushDisciplineEvent(
@@ -257,9 +271,7 @@ bool ClockRecoveryLoop::pushDisciplineEvent(int64_t local_time,
            observed_common,
            observed_common,
            nominal_common_time,
            nominal_common_time,
            correction_cur,
            correction_cur,
            correction_cur_P,
            rtt);
            correction_cur_I,
            correction_cur_D);
#endif
#endif


    return true;
    return true;
@@ -274,46 +286,6 @@ int32_t ClockRecoveryLoop::getLastErrorEstimate() {
        return ICommonClock::kErrorEstimateUnknown;
        return ICommonClock::kErrorEstimateUnknown;
}
}


void ClockRecoveryLoop::computePIDParams() {
    // TODO(johngro) : add the ability to fetch parameters from the driver/board
    // level in case they have a HW clock discipline solution with parameters
    // tuned specifically for it.

    // Correction factor is limited to MIN/MAX_INT_16
    pid_params_.correction_min = -0x8000;
    pid_params_.correction_max =  0x7FFF;

    // Default proportional gain to 2^15:1000.  (max proportional drive at 1mSec
    // of instantaneous error)
    memset(&pid_params_.gain_P, 0, sizeof(pid_params_.gain_P));
    pid_params_.gain_P.a_to_b_numer = 0x8000;
    pid_params_.gain_P.a_to_b_denom = 1000;

    // Set the integral gain to 2^15:5000
    memset(&pid_params_.gain_I, 0, sizeof(pid_params_.gain_I));
    pid_params_.gain_I.a_to_b_numer = 0x8000;
    pid_params_.gain_I.a_to_b_denom = 5000;

    // Default controller is just a PI controller.  Right now, the network based
    // measurements of the error are way to noisy to feed into the differential
    // component of a PID controller.  Someday we might come back and add some
    // filtering of the error channel, but until then leave the controller as a
    // simple PI controller.
    memset(&pid_params_.gain_D, 0, sizeof(pid_params_.gain_D));

    // Don't let the integral component of the controller wind up to
    // the point where it would want to drive the correction factor
    // past saturation.
    int64_t tmp;
    pid_params_.gain_I.doReverseTransform(pid_params_.correction_min, &tmp);
    pid_params_.integrated_delta_min = static_cast<int32_t>(tmp);
    pid_params_.gain_I.doReverseTransform(pid_params_.correction_max, &tmp);
    pid_params_.integrated_delta_max = static_cast<int32_t>(tmp);

    // By default, panic when are certain that the sync error is > 20mSec;
    panic_thresh_ = 20000;
}

void ClockRecoveryLoop::reset_l(bool position, bool frequency) {
void ClockRecoveryLoop::reset_l(bool position, bool frequency) {
    assert(NULL != common_clock_);
    assert(NULL != common_clock_);


@@ -325,8 +297,10 @@ void ClockRecoveryLoop::reset_l(bool position, bool frequency) {
    if (frequency) {
    if (frequency) {
        last_delta_valid_ = false;
        last_delta_valid_ = false;
        last_delta_ = 0;
        last_delta_ = 0;
        integrated_error_ = 0;
        last_delta_f_ = 0.0;
        correction_cur_ = 0;
        correction_cur_ = 0x0;
        CO = 0.0f;
        lastCObias = CObias = 0.0f;
        applySlew();
        applySlew();
    }
    }


@@ -334,47 +308,13 @@ void ClockRecoveryLoop::reset_l(bool position, bool frequency) {
    filter_full_ = false;
    filter_full_ = false;
}
}


int32_t ClockRecoveryLoop::doGainScale(const LinearTransform& gain,
                                       int32_t val) {
    if (!gain.a_to_b_numer || !gain.a_to_b_denom || !val)
        return 0;

    int64_t tmp;
    int64_t val64 = static_cast<int64_t>(val);
    if (!gain.doForwardTransform(val64, &tmp)) {
        ALOGW("Overflow/Underflow while scaling %d in %s",
             val, __PRETTY_FUNCTION__);
        return (val < 0) ? INT32_MIN : INT32_MAX;
    }

    if (tmp > INT32_MAX) {
        ALOGW("Overflow while scaling %d in %s", val, __PRETTY_FUNCTION__);
        return INT32_MAX;
    }

    if (tmp < INT32_MIN) {
        ALOGW("Underflow while scaling %d in %s", val, __PRETTY_FUNCTION__);
        return INT32_MIN;
    }

    return static_cast<int32_t>(tmp);
}

void ClockRecoveryLoop::applySlew() {
void ClockRecoveryLoop::applySlew() {
    if (local_clock_can_slew_) {
    if (local_clock_can_slew_) {
        local_clock_->setLocalSlew(correction_cur_);
        local_clock_->setLocalSlew(correction_cur_);
    } else {
    } else {
        // The SW clock recovery implemented by the common clock class expects
        // The SW clock recovery implemented by the common clock class expects
        // values expressed in PPM.  Map the MIN/MAX_INT_16 drive range to +/-
        // values expressed in PPM. CO is in ppm.
        // 100ppm.
        common_clock_->setSlew(local_clock_->getLocalTime(), CO);
        int sw_correction;
        sw_correction  = correction_cur_ - pid_params_.correction_min;
        sw_correction *= 200;
        sw_correction /= (pid_params_.correction_max -
                          pid_params_.correction_min);
        sw_correction -= 100;

        common_clock_->setSlew(local_clock_->getLocalTime(), sw_correction);
    }
    }
}
}


+46 −31
Original line number Original line Diff line number Diff line
@@ -43,27 +43,38 @@ class ClockRecoveryLoop {
    int32_t getLastErrorEstimate();
    int32_t getLastErrorEstimate();


  private:
  private:
    typedef struct {

        // Limits for the correction factor supplied to set_counter_slew_rate.
    // Tuned using the "Good Gain" method.
        // The controller will always clamp its output to the range expressed by
    // See:
        // correction_(min|max)
    // http://techteach.no/publications/books/dynamics_and_control/tuning_pid_controller.pdf
        int32_t correction_min;

        int32_t correction_max;
    // Controller period (1Hz for now).

    static const float dT;
        // Limits for the internal integration accumulator in the PID

        // controller.  The value of the accumulator is scaled by gain_I to
    // Controller gain, positive and unitless. Larger values converge faster,
        // produce the integral component of the PID controller output.
    // but can cause instability.
        // Platforms can use these limits to prevent windup in the system
    static const float Kc;
        // if/when the correction factor needs to be driven to saturation for

        // extended periods of time.
    // Integral reset time. Smaller values cause loop to track faster, but can
        int32_t integrated_delta_min;
    // also cause instability.
        int32_t integrated_delta_max;
    static const float Ti;


        // Gain for the P, I and D components of the controller.
    // Controller output filter time constant. Range (0-1). Smaller values make
        LinearTransform gain_P;
    // output smoother, but slow convergence.
        LinearTransform gain_I;
    static const float Tf;
        LinearTransform gain_D;

    } PIDParams;
    // Low-pass filter for bias tracker.
    static const float bias_Fc; // HZ
    static const float bias_RC; // Computed in constructor.
    static const float bias_Alpha; // Computed inconstructor.

    // The maximum allowed error (as indicated by a  pushDisciplineEvent) before
    // we panic.
    static const int64_t panic_thresh_;

    // The maximum allowed error rtt time for packets to be used for control
    // feedback, unless the packet is the best in recent memory.
    static const int64_t control_thresh_;


    typedef struct {
    typedef struct {
        int64_t local_time;
        int64_t local_time;
@@ -75,9 +86,7 @@ class ClockRecoveryLoop {


    static uint32_t findMinRTTNdx(DisciplineDataPoint* data, uint32_t count);
    static uint32_t findMinRTTNdx(DisciplineDataPoint* data, uint32_t count);


    void computePIDParams();
    void reset_l(bool position, bool frequency);
    void reset_l(bool position, bool frequency);
    static int32_t doGainScale(const LinearTransform& gain, int32_t val);
    void applySlew();
    void applySlew();


    // The local clock HW abstraction we use as the basis for common time.
    // The local clock HW abstraction we use as the basis for common time.
@@ -89,22 +98,28 @@ class ClockRecoveryLoop {
    CommonClock* common_clock_;
    CommonClock* common_clock_;
    Mutex lock_;
    Mutex lock_;


    // The parameters computed to be used for the PID Controller.
    PIDParams pid_params_;

    // The maximum allowed error (as indicated by a  pushDisciplineEvent) before
    // we panic.
    int32_t panic_thresh_;

    // parameters maintained while running and reset during a reset
    // parameters maintained while running and reset during a reset
    // of the frequency correction.
    // of the frequency correction.
    bool    last_delta_valid_;
    bool    last_delta_valid_;
    int32_t last_delta_;
    int32_t last_delta_;
    float last_delta_f_;
    int32_t integrated_error_;
    int32_t integrated_error_;
    int32_t correction_cur_;
    int32_t correction_cur_;


    // Contoller Output.
    float CO;

    // Bias tracking for trajectory estimation.
    float CObias;
    float lastCObias;

    // Controller output bounds. The controller will not try to
    // slew faster that +/-100ppm offset from center per interation.
    static const float COmin;
    static const float COmax;

    // State kept for filtering the discipline data.
    // State kept for filtering the discipline data.
    static const uint32_t kFilterSize = 6;
    static const uint32_t kFilterSize = 16;
    DisciplineDataPoint filter_data_[kFilterSize];
    DisciplineDataPoint filter_data_[kFilterSize];
    uint32_t filter_wr_;
    uint32_t filter_wr_;
    bool filter_full_;
    bool filter_full_;
+1 −1
Original line number Original line Diff line number Diff line
@@ -870,7 +870,7 @@ bool CommonTimeServer::handleSyncResponse(
            if (shouldPanicNotGettingGoodData())
            if (shouldPanicNotGettingGoodData())
                return becomeInitial("RX panic, no good data");
                return becomeInitial("RX panic, no good data");
        } else {
        } else {
            result = mClockRecovery.pushDisciplineEvent(avgLocal, avgCommon, rtt);
            result = mClockRecovery.pushDisciplineEvent(avgLocal, avgCommon, rttCommon);
            mClient_LastGoodSyncRX = clientRxLocalTime;
            mClient_LastGoodSyncRX = clientRxLocalTime;


            if (result) {
            if (result) {
+0 −1
Original line number Original line Diff line number Diff line
@@ -328,4 +328,3 @@ class CommonTimeServer : public Thread {
}  // namespace android
}  // namespace android


#endif  // ANDROID_COMMON_TIME_SERVER_H
#endif  // ANDROID_COMMON_TIME_SERVER_H
Loading