Discussion:
[Linuxptp-devel] [PATCH 0/2] Servo improvements
Miroslav Lichvar
2013-06-28 15:09:12 UTC
Permalink
This set should be applied on top of
[PATCH RFC 1/3] Let the clock servo know the expected sync interval.
[PATCH RFC 2/3] Use a dynamic frequency estimation interval.

Miroslav Lichvar (2):
phc2sys: Set servo sync interval.
Adjust constants of PI servo according to sync interval.

clock.c | 2 +-
config.c | 36 +++++++++++++++++++++++++++++++++++
config.h | 6 ++++++
default.cfg | 6 ++++++
gPTP.cfg | 6 ++++++
phc2sys.c | 6 +++++-
pi.c | 58 ++++++++++++++++++++++++++++++++++++++++++++-------------
pi.h | 44 +++++++++++++++++++++++++++++++++++++++++++
ptp4l.8 | 46 +++++++++++++++++++++++++++++++++++++++++----
ptp4l.c | 6 ++++++
servo.c | 4 ++--
servo.h | 4 ++--
servo_private.h | 2 +-
13 files changed, 202 insertions(+), 24 deletions(-)
--
1.8.1.4
Miroslav Lichvar
2013-06-28 15:09:13 UTC
Permalink
The function needs to be changed to accept the interval as double,
because phc2sys allows arbitrary update intervals.

Signed-off-by: Miroslav Lichvar <***@redhat.com>
---
clock.c | 2 +-
phc2sys.c | 6 +++++-
pi.c | 2 +-
servo.c | 4 ++--
servo.h | 4 ++--
servo_private.h | 2 +-
6 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/clock.c b/clock.c
index 76406e9..f81ca70 100644
--- a/clock.c
+++ b/clock.c
@@ -1095,7 +1095,7 @@ void clock_sync_interval(struct clock *c, int n)
}
c->stats.max_count = (1 << shift);

- servo_sync_interval(c->servo, n);
+ servo_sync_interval(c->servo, n > 0 ? 1 << n : 1.0 / (1 << -n));
}

struct timePropertiesDS *clock_time_properties(struct clock *c)
diff --git a/phc2sys.c b/phc2sys.c
index 8f9e18b..366587f 100644
--- a/phc2sys.c
+++ b/phc2sys.c
@@ -782,8 +782,12 @@ int main(int argc, char *argv[])

dst_clock.servo = servo_create(CLOCK_SERVO_PI, -ppb, max_ppb, 0);

- if (pps_fd >= 0)
+ if (pps_fd >= 0) {
+ servo_sync_interval(dst_clock.servo, 1.0);
return do_pps_loop(&dst_clock, pps_fd, src, phc_readings);
+ }
+
+ servo_sync_interval(dst_clock.servo, phc_interval);

phc_interval_tp.tv_sec = phc_interval;
phc_interval_tp.tv_nsec = (phc_interval - phc_interval_tp.tv_sec) * 1e9;
diff --git a/pi.c b/pi.c
index d5e6359..5f4930e 100644
--- a/pi.c
+++ b/pi.c
@@ -146,7 +146,7 @@ static double pi_sample(struct servo *servo,
return ppb;
}

-static void pi_sync_interval(struct servo *servo, int n)
+static void pi_sync_interval(struct servo *servo, double interval)
{
}

diff --git a/servo.c b/servo.c
index a6fd299..45c5832 100644
--- a/servo.c
+++ b/servo.c
@@ -42,7 +42,7 @@ double servo_sample(struct servo *servo,
return servo->sample(servo, offset, local_ts, state);
}

-void servo_sync_interval(struct servo *servo, int n)
+void servo_sync_interval(struct servo *servo, double interval)
{
- servo->sync_interval(servo, n);
+ servo->sync_interval(servo, interval);
}
diff --git a/servo.h b/servo.h
index 4efa50e..052740e 100644
--- a/servo.h
+++ b/servo.h
@@ -89,8 +89,8 @@ double servo_sample(struct servo *servo,
/**
* Inform a clock servo about the master's sync interval.
* @param servo Pointer to a servo obtained via @ref servo_create().
- * @param n The logarithm base two of the sync interval.
+ * @param interval The sync interval in seconds.
*/
-void servo_sync_interval(struct servo *servo, int n);
+void servo_sync_interval(struct servo *servo, double interval);

#endif
diff --git a/servo_private.h b/servo_private.h
index 2df49d2..98c7db2 100644
--- a/servo_private.h
+++ b/servo_private.h
@@ -29,7 +29,7 @@ struct servo {
int64_t offset, uint64_t local_ts,
enum servo_state *state);

- void (*sync_interval)(struct servo *servo, int n);
+ void (*sync_interval)(struct servo *servo, double interval);
};

#endif
--
1.8.1.4
Miroslav Lichvar
2013-06-28 15:09:14 UTC
Permalink
Instead of using fixed constants, set them by the following formula from
the current sync to allow good performance of the servo even when the
sync interval changes in runtime and to avoid instability.

kp = min(kp_scale * sync^kp_exponent, kp_norm_max / sync)
ki = min(ki_scale * sync^ki_exponent, ki_norm_max / sync)

The scale, exponent and norm_max constants are configurable. The
defaults are chosen so there is no change to the previous default
constants of the servo with one second sync interval. The automatic
adjustment can be disabled by setting the pi_proportional_const and
pi_integral_const options to a non-zero value, but stability of the
servo is always enforced.

Signed-off-by: Miroslav Lichvar <***@redhat.com>
---
config.c | 36 ++++++++++++++++++++++++++++++++++++
config.h | 6 ++++++
default.cfg | 6 ++++++
gPTP.cfg | 6 ++++++
pi.c | 56 ++++++++++++++++++++++++++++++++++++++++++++------------
pi.h | 44 ++++++++++++++++++++++++++++++++++++++++++++
ptp4l.8 | 46 ++++++++++++++++++++++++++++++++++++++++++----
ptp4l.c | 6 ++++++
8 files changed, 190 insertions(+), 16 deletions(-)

diff --git a/config.c b/config.c
index ad63bba..de7e464 100644
--- a/config.c
+++ b/config.c
@@ -307,6 +307,42 @@ static enum parser_result parse_global_setting(const char *option,
return r;
*cfg->pi_integral_const = df;

+ } else if (!strcmp(option, "pi_proportional_scale")) {
+ r = get_ranged_double(value, &df, 0.0, DBL_MAX);
+ if (r != PARSED_OK)
+ return r;
+ *cfg->pi_proportional_scale = df;
+
+ } else if (!strcmp(option, "pi_proportional_exponent")) {
+ r = get_ranged_double(value, &df, -DBL_MAX, DBL_MAX);
+ if (r != PARSED_OK)
+ return r;
+ *cfg->pi_proportional_exponent = df;
+
+ } else if (!strcmp(option, "pi_proportional_norm_max")) {
+ r = get_ranged_double(value, &df, DBL_MIN, 1.0);
+ if (r != PARSED_OK)
+ return r;
+ *cfg->pi_proportional_norm_max = df;
+
+ } else if (!strcmp(option, "pi_integral_scale")) {
+ r = get_ranged_double(value, &df, 0.0, DBL_MAX);
+ if (r != PARSED_OK)
+ return r;
+ *cfg->pi_integral_scale = df;
+
+ } else if (!strcmp(option, "pi_integral_exponent")) {
+ r = get_ranged_double(value, &df, -DBL_MAX, DBL_MAX);
+ if (r != PARSED_OK)
+ return r;
+ *cfg->pi_integral_exponent = df;
+
+ } else if (!strcmp(option, "pi_integral_norm_max")) {
+ r = get_ranged_double(value, &df, DBL_MIN, 2.0);
+ if (r != PARSED_OK)
+ return r;
+ *cfg->pi_integral_norm_max = df;
+
} else if (!strcmp(option, "pi_offset_const")) {
r = get_ranged_double(value, &df, 0.0, DBL_MAX);
if (r != PARSED_OK)
diff --git a/config.h b/config.h
index c5de3ff..b014d7e 100644
--- a/config.h
+++ b/config.h
@@ -75,6 +75,12 @@ struct config {

double *pi_proportional_const;
double *pi_integral_const;
+ double *pi_proportional_scale;
+ double *pi_proportional_exponent;
+ double *pi_proportional_norm_max;
+ double *pi_integral_scale;
+ double *pi_integral_exponent;
+ double *pi_integral_norm_max;
double *pi_offset_const;
double *pi_f_offset_const;
int *pi_max_frequency;
diff --git a/default.cfg b/default.cfg
index 54b0aae..e4d8d7b 100644
--- a/default.cfg
+++ b/default.cfg
@@ -40,6 +40,12 @@ kernel_leap 1
#
pi_proportional_const 0.0
pi_integral_const 0.0
+pi_proportional_scale 0.0
+pi_proportional_exponent -0.3
+pi_proportional_norm_max 0.7
+pi_integral_scale 0.0
+pi_integral_exponent 0.4
+pi_integral_norm_max 0.3
pi_offset_const 0.0
pi_f_offset_const 0.0000001
pi_max_frequency 900000000
diff --git a/gPTP.cfg b/gPTP.cfg
index c4e0fab..00215b4 100644
--- a/gPTP.cfg
+++ b/gPTP.cfg
@@ -39,6 +39,12 @@ kernel_leap 1
#
pi_proportional_const 0.0
pi_integral_const 0.0
+pi_proportional_scale 0.0
+pi_proportional_exponent -0.3
+pi_proportional_norm_max 0.7
+pi_integral_scale 0.0
+pi_integral_exponent 0.4
+pi_integral_norm_max 0.3
pi_offset_const 0.0
pi_f_offset_const 0.0000001
pi_max_frequency 900000000
diff --git a/pi.c b/pi.c
index 5f4930e..3c8a635 100644
--- a/pi.c
+++ b/pi.c
@@ -21,13 +21,16 @@
#include <math.h>

#include "pi.h"
+#include "print.h"
#include "servo_private.h"

-#define HWTS_KP 0.7
-#define HWTS_KI 0.3
+#define HWTS_KP_SCALE 0.7
+#define HWTS_KI_SCALE 0.3
+#define SWTS_KP_SCALE 0.1
+#define SWTS_KI_SCALE 0.001

-#define SWTS_KP 0.1
-#define SWTS_KI 0.001
+#define MAX_KP_NORM_MAX 1.0
+#define MAX_KI_NORM_MAX 2.0

#define NSEC_PER_SEC 1000000000
#define FREQ_EST_MARGIN 0.001
@@ -35,6 +38,12 @@
/* These take their values from the configuration file. (see ptp4l.c) */
double configured_pi_kp = 0.0;
double configured_pi_ki = 0.0;
+double configured_pi_kp_scale = 0.0;
+double configured_pi_kp_exponent = -0.3;
+double configured_pi_kp_norm_max = 0.7;
+double configured_pi_ki_scale = 0.0;
+double configured_pi_ki_exponent = 0.4;
+double configured_pi_ki_norm_max = 0.3;
double configured_pi_offset = 0.0;
double configured_pi_f_offset = 0.0000001; /* 100 nanoseconds */
int configured_pi_max_freq = 900000000;
@@ -148,6 +157,18 @@ static double pi_sample(struct servo *servo,

static void pi_sync_interval(struct servo *servo, double interval)
{
+ struct pi_servo *s = container_of(servo, struct pi_servo, servo);
+
+ s->kp = configured_pi_kp_scale * pow(interval, configured_pi_kp_exponent);
+ if (s->kp > configured_pi_kp_norm_max / interval)
+ s->kp = configured_pi_kp_norm_max / interval;
+
+ s->ki = configured_pi_ki_scale * pow(interval, configured_pi_ki_exponent);
+ if (s->ki > configured_pi_ki_norm_max / interval)
+ s->ki = configured_pi_ki_norm_max / interval;
+
+ pr_debug("PI servo: sync interval %.3f kp %.3f ki %.6f",
+ interval, s->kp, s->ki);
}

struct servo *pi_servo_create(int fadj, int max_ppb, int sw_ts)
@@ -164,16 +185,27 @@ struct servo *pi_servo_create(int fadj, int max_ppb, int sw_ts)
s->drift = fadj;
s->maxppb = max_ppb;
s->first_update = 1;
+ s->kp = 0.0;
+ s->ki = 0.0;

if (configured_pi_kp && configured_pi_ki) {
- s->kp = configured_pi_kp;
- s->ki = configured_pi_ki;
- } else if (sw_ts) {
- s->kp = SWTS_KP;
- s->ki = SWTS_KI;
- } else {
- s->kp = HWTS_KP;
- s->ki = HWTS_KI;
+ /* Use the constants as configured by the user without
+ adjusting for sync interval unless they make the servo
+ unstable. */
+ configured_pi_kp_scale = configured_pi_kp;
+ configured_pi_ki_scale = configured_pi_ki;
+ configured_pi_kp_exponent = 0.0;
+ configured_pi_ki_exponent = 0.0;
+ configured_pi_kp_norm_max = MAX_KP_NORM_MAX;
+ configured_pi_ki_norm_max = MAX_KI_NORM_MAX;
+ } else if (!configured_pi_kp_scale || !configured_pi_ki_scale) {
+ if (sw_ts) {
+ configured_pi_kp_scale = SWTS_KP_SCALE;
+ configured_pi_ki_scale = SWTS_KI_SCALE;
+ } else {
+ configured_pi_kp_scale = HWTS_KP_SCALE;
+ configured_pi_ki_scale = HWTS_KI_SCALE;
+ }
}

if (configured_pi_offset > 0.0) {
diff --git a/pi.h b/pi.h
index 2f31bce..0d8994b 100644
--- a/pi.h
+++ b/pi.h
@@ -34,6 +34,50 @@ extern double configured_pi_kp;
extern double configured_pi_ki;

/**
+ * When set to a non-zero value, this variable determines the scale in the
+ * formula used to set the proportional constant of the PI controller from the
+ * sync interval.
+ * kp = min(kp_scale * sync^kp_exponent, kp_norm_max / sync)
+ */
+extern double configured_pi_kp_scale;
+
+/**
+ * This variable determines the exponent in the formula used to set the
+ * proportional constant of the PI controller from the sync interval.
+ * kp = min(kp_scale * sync^kp_exponent, kp_norm_max / sync)
+ */
+extern double configured_pi_kp_exponent;
+
+/**
+ * This variable determines the normalized maximum in the formula used to set
+ * the proportional constant of the PI controller from the sync interval.
+ * kp = min(kp_scale * sync^kp_exponent, kp_norm_max / sync)
+ */
+extern double configured_pi_kp_norm_max;
+
+/**
+ * When set to a non-zero value, this variable determines the scale in the
+ * formula used to set the integral constant of the PI controller from the
+ * sync interval.
+ * ki = min(ki_scale * sync^ki_exponent, ki_norm_max / sync)
+ */
+extern double configured_pi_ki_scale;
+
+/**
+ * This variable determines the exponent in the formula used to set the
+ * integral constant of the PI controller from the sync interval.
+ * ki = min(ki_scale * sync^ki_exponent, ki_norm_max / sync)
+ */
+extern double configured_pi_ki_exponent;
+
+/**
+ * This variable determines the normalized maximum in the formula used to set
+ * the integral constant of the PI controller from the sync interval.
+ * ki = min(ki_scale * sync^ki_exponent, ki_norm_max / sync)
+ */
+extern double configured_pi_ki_norm_max;
+
+/**
* When set to a non-zero value, this variable controls the maximum allowed
* offset before a clock jump occurs instead of the default clock-slewing
* mechanism.
diff --git a/ptp4l.8 b/ptp4l.8
index fb49b21..637fda3 100644
--- a/ptp4l.8
+++ b/ptp4l.8
@@ -254,17 +254,55 @@ servo is implemented, a PI controller.
The default is pi.
.TP
.B pi_proportional_const
-The proportional constant of the PI controller. When set to 0.0, the value will
-be selected from 0.7 and 0.1 for the hardware and software time stamping
-respectively.
+The proportional constant of the PI controller. When set to 0.0, the
+proportional constant will be set by the following formula from the current
+sync interval.
The default is 0.0.
+
+kp = min(kp_scale * sync^kp_exponent, kp_norm_max / sync))
.TP
.B pi_integral_const
-The integral constant of the PI controller. When set to 0.0, the value will be
+The integral constant of the PI controller. When set to 0.0, the
+integral constant will be set by the following formula from the current
+sync interval.
+The default is 0.0.
+
+ki = min(ki_scale * sync^ki_exponent, ki_norm_max / sync)
+.TP
+.B pi_proportional_scale
+The kp_scale constant in the formula used to set the proportional constant of
+the PI controller from the sync interval. When set to 0.0, the value will be
+selected from 0.7 and 0.1 for the hardware and software time stamping
+respectively.
+The default is 0.0.
+.TP
+.B pi_proportional_exponent
+The kp_exponent constant in the formula used to set the proportional constant of
+the PI controller from the sync interval.
+The default is -0.3.
+.TP
+.B pi_proportional_norm_max
+The kp_norm_max constant in the formula used to set the proportional constant of
+the PI controller from the sync interval.
+The default is 0.7
+.TP
+.B pi_integral_scale
+The ki_scale constant in the formula used to set the integral constant of
+the PI controller from the sync interval. When set to 0.0, the value will be
selected from 0.3 and 0.001 for the hardware and software time stamping
respectively.
The default is 0.0.
.TP
+.B pi_integral_exponent
+The ki_exponent constant in the formula used to set the integral constant of
+the PI controller from the sync interval.
+The default is 0.4.
+.TP
+.B pi_integral_norm_max
+The ki_norm_max constant in the formula used to set the integral constant of
+the PI controller from the sync interval.
+The default is 0.3.
+.TP
.B pi_offset_const
The maximum offset the PI controller will correct by changing the clock
frequency instead of stepping the clock. When set to 0.0, the controller will
diff --git a/ptp4l.c b/ptp4l.c
index 1e0304e..23dfa9e 100644
--- a/ptp4l.c
+++ b/ptp4l.c
@@ -95,6 +95,12 @@ static struct config cfg_settings = {

.pi_proportional_const = &configured_pi_kp,
.pi_integral_const = &configured_pi_ki,
+ .pi_proportional_scale = &configured_pi_kp_scale,
+ .pi_proportional_exponent = &configured_pi_kp_exponent,
+ .pi_proportional_norm_max = &configured_pi_kp_norm_max,
+ .pi_integral_scale = &configured_pi_ki_scale,
+ .pi_integral_exponent = &configured_pi_ki_exponent,
+ .pi_integral_norm_max = &configured_pi_ki_norm_max,
.pi_offset_const = &configured_pi_offset,
.pi_f_offset_const = &configured_pi_f_offset,
.pi_max_frequency = &configured_pi_max_freq,
--
1.8.1.4
Richard Cochran
2013-07-02 14:04:17 UTC
Permalink
Post by Miroslav Lichvar
This set should be applied on top of
[PATCH RFC 1/3] Let the clock servo know the expected sync interval.
[PATCH RFC 2/3] Use a dynamic frequency estimation interval.
phc2sys: Set servo sync interval.
Adjust constants of PI servo according to sync interval.
The results from my first set of tests with this new scheme look
pretty good. I'll post a bit more about this, soon.

Thanks,
Richard
Richard Cochran
2013-07-07 20:08:14 UTC
Permalink
Post by Richard Cochran
Post by Miroslav Lichvar
This set should be applied on top of
[PATCH RFC 1/3] Let the clock servo know the expected sync interval.
[PATCH RFC 2/3] Use a dynamic frequency estimation interval.
phc2sys: Set servo sync interval.
Adjust constants of PI servo according to sync interval.
The results from my first set of tests with this new scheme look
pretty good. I'll post a bit more about this, soon.
First of all, I really like that the defaults are just the same as
before. No one can complain that we messed the servo with this
change.

Secondly, the actual performance of these new constants looks good. I
ran a number of tests using the m5234bcc (with phyter) and the Intel
i210 PCIe card. Both of these devices have a PPS output and an event
trigger input. I tested the following configurations using the sync
interval set to 2^-4 seconds.

* Bcc Master Igb Slave (BMIS)
* Igb Master Bcc Slave (IMBS)
* Igb Twice (I2X) (that is, two i210s sitting in the same box)
* Igb Twice, roles reversed (X2I)

I ran each test with each of the following pairs of PI contants.

# method A at 2^-4
# pi_proportional_const 1.1
# pi_integral_const 0.037
# method B at 2^-4
# pi_proportional_const 3.2
# pi_integral_const 0.05
# method A' at 2^-4
# pi_proportional_const 1.6081776969958
# pi_integral_const 0.0989630933080
# method B' at 2^-4
# pi_proportional_const 2.8
# pi_integral_const 0.075

During each 600 second test, the slave recorded the master's PPS
events. Taking the steady state of the last 500 from each test, I
calculated the average offset and used that value to figure the RMS
error over the 500 second interval. The results can be see in the
following table.

| | A | B | A' | B' |
|------+---------+----------+---------+---------|
| BMIS | 7.73910 | 10.54620 | 8.20042 | 9.62360 |
| IMBS | 5.03458 | 5.32198 | 4.88282 | 4.63138 |
| I2X | 3.15417 | 2.56091 | 3.25391 | 2.85451 |
| X2I | 4.19654 | 3.29240 | 2.88466 | 2.80652 |

In addition, with the latest patch set from today, I tried changing
my OMTC 100 grand master's sync rate on the fly, like

2^0 ... 2^-5 ... 2^2 ... 2^-3

and the reported offset on the slave looks fine.

Thanks,
Richard
Miroslav Lichvar
2013-07-08 12:57:25 UTC
Permalink
Post by Richard Cochran
During each 600 second test, the slave recorded the master's PPS
events. Taking the steady state of the last 500 from each test, I
calculated the average offset and used that value to figure the RMS
error over the 500 second interval. The results can be see in the
following table.
| | A | B | A' | B' |
|------+---------+----------+---------+---------|
| BMIS | 7.73910 | 10.54620 | 8.20042 | 9.62360 |
| IMBS | 5.03458 | 5.32198 | 4.88282 | 4.63138 |
| I2X | 3.15417 | 2.56091 | 3.25391 | 2.85451 |
| X2I | 4.19654 | 3.29240 | 2.88466 | 2.80652 |
Interesting. Can we assume the actual accuracies for I2X and X2I are
identical and the differences between the I2X and X2I results are
showing the variance in the measured RMS error?
Post by Richard Cochran
In addition, with the latest patch set from today, I tried changing
my OMTC 100 grand master's sync rate on the fly, like
2^0 ... 2^-5 ... 2^2 ... 2^-3
and the reported offset on the slave looks fine.
Great. Thanks for trying it out.
--
Miroslav Lichvar
Richard Cochran
2013-07-08 16:20:04 UTC
Permalink
Post by Miroslav Lichvar
Post by Richard Cochran
| | A | B | A' | B' |
|------+---------+----------+---------+---------|
| BMIS | 7.73910 | 10.54620 | 8.20042 | 9.62360 |
| IMBS | 5.03458 | 5.32198 | 4.88282 | 4.63138 |
| I2X | 3.15417 | 2.56091 | 3.25391 | 2.85451 |
| X2I | 4.19654 | 3.29240 | 2.88466 | 2.80652 |
Interesting. Can we assume the actual accuracies for I2X and X2I are
identical and the differences between the I2X and X2I results are
showing the variance in the measured RMS error?
That is hard to say, but it could well be. I am not going to study
this any more right now, because it is close enough for me. At this
level (under ten nanoseconds), when working with the PPS outputs and
time stamping inputs, there are issues related to how the signals are
produced or conditioned that start to dwarf the PI servo performance.

For example, when you loop the phyter's PPS back to an input, you can
observe a random 8 nanosecond jitter (i.e. mostly the time stamp is 32
ns from the full second, occasionally 40). This is because the PPS
signal is produced by an internal 125 MHz clock, and the signal is
apparently right in between edges. That alone is the probable cause
of the higher error result in the BMIS test.

At the end of the day, all the numbers in a row are so close that
the exact weights probably don't matter too much.

Thanks,
Richard

Loading...