commit c4d00cfd9a611c9157f57a97babde11255f4b4e4 Author: Miroslav Lichvar Date: Mon Jun 3 16:43:50 2024 +0200 ts2phc: Provide source type. Save the PPS source type in the instance and add a function to retrieve it. This will be needed for NMEA-specific rounding of timestamps. Signed-off-by: Miroslav Lichvar Reviewed-by: Jacob Keller diff --git a/ts2phc_pps_source.c b/ts2phc_pps_source.c index c333f65..ae2ad46 100644 --- a/ts2phc_pps_source.c +++ b/ts2phc_pps_source.c @@ -26,6 +26,8 @@ struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv, src = ts2phc_phc_pps_source_create(priv, dev); break; } + if (src) + src->type = type; return src; } @@ -46,3 +48,8 @@ struct ts2phc_clock *ts2phc_pps_source_get_clock(struct ts2phc_pps_source *src) return NULL; } + +enum ts2phc_pps_source_type ts2phc_pps_source_get_type(struct ts2phc_pps_source *src) +{ + return src->type; +} diff --git a/ts2phc_pps_source.h b/ts2phc_pps_source.h index 293c693..c87e3af 100644 --- a/ts2phc_pps_source.h +++ b/ts2phc_pps_source.h @@ -53,4 +53,11 @@ int ts2phc_pps_source_getppstime(struct ts2phc_pps_source *src, struct timespec struct ts2phc_clock *ts2phc_pps_source_get_clock(struct ts2phc_pps_source *src); +/** + * Returns the type of the PPS source + * @param src Pointer to a source obtained via @ref ts2phc_pps_source_create(). + * @return The type of the clock. + */ +enum ts2phc_pps_source_type ts2phc_pps_source_get_type(struct ts2phc_pps_source *src); + #endif diff --git a/ts2phc_pps_source_private.h b/ts2phc_pps_source_private.h index 99e6a78..ea6a8ad 100644 --- a/ts2phc_pps_source_private.h +++ b/ts2phc_pps_source_private.h @@ -13,6 +13,7 @@ #include "ts2phc_pps_source.h" struct ts2phc_pps_source { + enum ts2phc_pps_source_type type; void (*destroy)(struct ts2phc_pps_source *src); int (*getppstime)(struct ts2phc_pps_source *src, struct timespec *ts); struct ts2phc_clock *(*get_clock)(struct ts2phc_pps_source *src); commit e9f8d8fa119109f15e2377aef0ce4c62e261cba4 Author: Miroslav Lichvar Date: Mon Jun 3 16:43:51 2024 +0200 ts2phc: Fix edge rejection for pulse widths over 0.5s. If the configured pulse width is longer than 0.5 seconds, the calculated range of ignored offsets is too small to cover the wrong edge. Fix the calculation of the limits to use the minimum of pulsewidth and (1.0s - pulsewidth). A pulsewidth of 0.5s should give the shortest interval. Signed-off-by: Miroslav Lichvar Reviewed-by: Jacob Keller diff --git a/ts2phc_pps_sink.c b/ts2phc_pps_sink.c index e3e9caf..33df968 100644 --- a/ts2phc_pps_sink.c +++ b/ts2phc_pps_sink.c @@ -175,6 +175,8 @@ static struct ts2phc_pps_sink *ts2phc_pps_sink_create(struct ts2phc_private *pri sink->correction = nanoseconds_to_tmv(correction); pulsewidth = config_get_int(cfg, device, "ts2phc.pulsewidth"); + if (pulsewidth > 500000000) + pulsewidth = 1000000000 - pulsewidth; pulsewidth /= 2; sink->ignore_upper = 1000000000 - pulsewidth; sink->ignore_lower = pulsewidth; commit 81c447a24c75d56d414bf318ea5b5a6fc35d5129 Author: Miroslav Lichvar Date: Mon Jun 3 16:43:52 2024 +0200 ts2phc: Move upper/lower rejection limit calculation. The ignore_upper and ignore_lower fields of the ts2phc_pps_sink struct are calculated when the PPS source is not known yet. Replace the fields with the configured pulsewidth and calculate the limits locally later when needed in ts2phc_pps_sink_ignore(). This will allow an NMEA-specific calculation of the limits. Signed-off-by: Miroslav Lichvar Reviewed-by: Jacob Keller diff --git a/ts2phc_pps_sink.c b/ts2phc_pps_sink.c index 33df968..be3bd9a 100644 --- a/ts2phc_pps_sink.c +++ b/ts2phc_pps_sink.c @@ -30,8 +30,7 @@ struct ts2phc_pps_sink { struct ptp_pin_desc pin_desc; unsigned int polarity; tmv_t correction; - uint32_t ignore_lower; - uint32_t ignore_upper; + uint32_t pulsewidth; struct ts2phc_clock *clock; }; @@ -153,8 +152,8 @@ static struct ts2phc_pps_sink *ts2phc_pps_sink_create(struct ts2phc_private *pri struct config *cfg = priv->cfg; struct ptp_extts_request extts; struct ts2phc_pps_sink *sink; - int err, pulsewidth; int32_t correction; + int err; sink = calloc(1, sizeof(*sink)); if (!sink) { @@ -174,12 +173,9 @@ static struct ts2phc_pps_sink *ts2phc_pps_sink_create(struct ts2phc_private *pri correction = config_get_int(cfg, device, "ts2phc.extts_correction"); sink->correction = nanoseconds_to_tmv(correction); - pulsewidth = config_get_int(cfg, device, "ts2phc.pulsewidth"); - if (pulsewidth > 500000000) - pulsewidth = 1000000000 - pulsewidth; - pulsewidth /= 2; - sink->ignore_upper = 1000000000 - pulsewidth; - sink->ignore_lower = pulsewidth; + sink->pulsewidth = config_get_int(cfg, device, "ts2phc.pulsewidth"); + if (sink->pulsewidth > 500000000) + sink->pulsewidth = 1000000000 - sink->pulsewidth; sink->clock = ts2phc_clock_add(priv, device); if (!sink->clock) { @@ -248,12 +244,16 @@ static bool ts2phc_pps_sink_ignore(struct ts2phc_private *priv, struct timespec source_ts) { tmv_t source_tmv = timespec_to_tmv(source_ts); + uint32_t ignore_lower, ignore_upper; source_tmv = tmv_sub(source_tmv, priv->perout_phase); source_ts = tmv_to_timespec(source_tmv); - return source_ts.tv_nsec > sink->ignore_lower && - source_ts.tv_nsec < sink->ignore_upper; + ignore_upper = 1000000000 - sink->pulsewidth / 2; + ignore_lower = sink->pulsewidth / 2; + + return source_ts.tv_nsec > ignore_lower && + source_ts.tv_nsec < ignore_upper; } static enum extts_result ts2phc_pps_sink_event(struct ts2phc_private *priv, commit 683f66aa46b7f9a5bd434e65a2d5703145c43b09 Author: Miroslav Lichvar Date: Mon Jun 3 16:43:53 2024 +0200 ts2phc: Allow longer NMEA delays. Timestamps from PPS sources are rounded to the nearest pulse, i.e. for correct synchronization the offset between the source timestamp and external PPS event timestamp needs to be between -0.5 and +0.5 seconds. For the pulse edge rejection to work correctly it needs to be even smaller, between -pulsewidth/2 and +pulsewidth/2. With the NMEA PPS source the offset is the delay in the reception of the RMC message. This message is not expected to come exactly on time with the corresponding pulse. A positive delay is expected, typically at least few tens of milliseconds, but some receivers have delays longer than 0.5 seconds. Add NMEA-specific rounding of the offset to not expect negative delays and allow positive delays of up to 1 second, or whole pulsewidth if the edge rejection is enabled. Signed-off-by: Miroslav Lichvar Reviewed-by: Jacob Keller diff --git a/ts2phc.c b/ts2phc.c index 9fca34d..39d31b6 100644 --- a/ts2phc.c +++ b/ts2phc.c @@ -420,9 +420,16 @@ static int ts2phc_pps_source_implicit_tstamp(struct ts2phc_private *priv, * deduce the timestamp (actually only seconds part, nanoseconds are by * construction zero) of this edge at the emitter based on the * emitter's current time. + * + * With an NMEA source assume its messages always follow the pulse, i.e. + * assign the timestamp to the previous pulse instead of nearest pulse. */ - if (source_ts.tv_nsec > NS_PER_SEC / 2) + if (ts2phc_pps_source_get_type(priv->src) == TS2PHC_PPS_SOURCE_NMEA) { source_ts.tv_sec++; + } else { + if (source_ts.tv_nsec > NS_PER_SEC / 2) + source_ts.tv_sec++; + } source_ts.tv_nsec = 0; tmv = timespec_to_tmv(source_ts); diff --git a/ts2phc_pps_sink.c b/ts2phc_pps_sink.c index be3bd9a..f285c2b 100644 --- a/ts2phc_pps_sink.c +++ b/ts2phc_pps_sink.c @@ -249,8 +249,13 @@ static bool ts2phc_pps_sink_ignore(struct ts2phc_private *priv, source_tmv = tmv_sub(source_tmv, priv->perout_phase); source_ts = tmv_to_timespec(source_tmv); - ignore_upper = 1000000000 - sink->pulsewidth / 2; - ignore_lower = sink->pulsewidth / 2; + if (ts2phc_pps_source_get_type(priv->src) == TS2PHC_PPS_SOURCE_NMEA) { + ignore_upper = sink->pulsewidth; + ignore_lower = 0; + } else { + ignore_upper = 1000000000 - sink->pulsewidth / 2; + ignore_lower = sink->pulsewidth / 2; + } return source_ts.tv_nsec > ignore_lower && source_ts.tv_nsec < ignore_upper; commit 50f5a22df7a9a3f361e42bd29f84e26af04ac78b Author: Miroslav Lichvar Date: Mon Jun 3 16:43:54 2024 +0200 ts2phc: Add option to correct for NMEA delay. Add an option to specify the minimum expected delay of NMEA RMC messages to correct timestamps returned by the NMEA PPS time source. This enables operation with receivers that have delays outside of the expected 0-1.0s interval, or 0-pulsewidth if the PPS edge rejection is enabled. [ RPC: Preserve alphabetical ordering in the ts2phc man page. ] Signed-off-by: Miroslav Lichvar Signed-off-by: Richard Cochran Reviewed-by: Jacob Keller diff --git a/config.c b/config.c index 5dcf7ca..d0bc32c 100644 --- a/config.c +++ b/config.c @@ -373,6 +373,7 @@ struct config_item config_tab[] = { PORT_ITEM_INT("ts2phc.holdover", 0, 0, INT_MAX), PORT_ITEM_INT("ts2phc.master", 0, 0, 1), PORT_ITEM_INT("ts2phc.nmea_baudrate", 9600, 300, INT_MAX), + PORT_ITEM_INT("ts2phc.nmea_delay", 0, INT_MIN, INT_MAX), GLOB_ITEM_STR("ts2phc.nmea_remote_host", ""), GLOB_ITEM_STR("ts2phc.nmea_remote_port", ""), GLOB_ITEM_STR("ts2phc.nmea_serialport", "/dev/ttyS0"), diff --git a/ts2phc.8 b/ts2phc.8 index 41f91d9..177586d 100644 --- a/ts2phc.8 +++ b/ts2phc.8 @@ -239,16 +239,6 @@ The kp_scale 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 ts2phc.holdover -The holdover interval, specified in seconds. When the ToD information stops -working (e.g. GNSS receiver lost its fix), ts2phc is allowed for the specified -interval to continue synchronizing the target clock as long as the servo is in -the SERVO_LOCKED_STABLE state. The servo state needs be enabled by the -\fBservo_offset_threshold\fP option. The holdover is not supported with the -\fB-a\fP option and when \fBts2phc.extts_polarity\fP is set to \fIboth\fP. -The default is 0 (disabled). - .TP .B sa_file Specifies the location of the file containing Security Associations used @@ -288,6 +278,26 @@ for all messages in accordance to the corresponding security association sourced via the \fBsa_file\fR directive. Not compatible with one step ports. Must be in the range of -1 to 255, inclusive. The default is -1 (disabled). +.TP +.B ts2phc.holdover +The holdover interval, specified in seconds. When the ToD information stops +working (e.g. GNSS receiver lost its fix), ts2phc is allowed for the specified +interval to continue synchronizing the target clock as long as the servo is in +the SERVO_LOCKED_STABLE state. The servo state needs be enabled by the +\fBservo_num_offset_values\fP option. The holdover is not supported with the +\fB-a\fP option and when \fBts2phc.extts_polarity\fP is set to \fIboth\fP. +The default is 0 (disabled). + +.TP +.B ts2phc.nmea_delay +Specifies the minimum expected delay of NMEA RMC messages in nanoseconds. +If the maximum delay is longer than 1 second, or 'ts2phc.pulsewidth' +if 'ts2phc.extts_polarity' is set to "both", this option needs to be set +accordingly to allow the timestamps from NMEA messages to be correctly +assigned to pulses from the PPS signal and wrong PPS edges to be rejected if +the edge rejection is enabled. +The default is 0 nanoseconds. + .TP .B ts2phc.nmea_remote_host, ts2phc.nmea_remote_port Specifies the remote host providing ToD information when using the diff --git a/ts2phc_nmea_pps_source.c b/ts2phc_nmea_pps_source.c index 7a28433..3385e39 100644 --- a/ts2phc_nmea_pps_source.c +++ b/ts2phc_nmea_pps_source.c @@ -33,6 +33,7 @@ struct ts2phc_nmea_pps_source { pthread_t worker; /* Protects anonymous struct fields, below, from concurrent access. */ pthread_mutex_t mutex; + tmv_t delay_correction; struct { struct timespec local_monotime; struct timespec local_utctime; @@ -187,6 +188,7 @@ static int ts2phc_nmea_pps_source_getppstime(struct ts2phc_pps_source *src, return -1; } rmc = tmv_add(rmc, duration_since_rmc); + rmc = tmv_add(rmc, m->delay_correction); utc_time = tmv_to_nanoseconds(rmc); utc_time /= (int64_t) 1000000000; *ts = tmv_to_timespec(rmc); @@ -232,6 +234,8 @@ struct ts2phc_pps_source *ts2phc_nmea_pps_source_create(struct ts2phc_private *p s->pps_source.destroy = ts2phc_nmea_pps_source_destroy; s->pps_source.getppstime = ts2phc_nmea_pps_source_getppstime; s->config = priv->cfg; + s->delay_correction = nanoseconds_to_tmv( + config_get_int(priv->cfg, NULL, "ts2phc.nmea_delay")); pthread_mutex_init(&s->mutex, NULL); err = pthread_create(&s->worker, NULL, monitor_nmea_status, s); if (err) {