Phc2pwm is an utility program to synchronize a pwm with ptp clock to generate PPS. This is mainly intended for the below hardware configuration: - A PTP supporting IP that is not capable of generating PPS signal - A PWM signal that is connected to the above PTP hardware - On every rising edge of PWM, PTP hardware should be able to send the current timestamp to userspace. - This PWM signal can be used as a PPS signal that is synchronized to PTP clock.
Signed-off-by: Lokesh Vutla <lokeshvu...@ti.com> --- ToDO: - I did not really use kp and ki for calculating errors. Even without that I could see the error within ~60ns. May be in future these constants might be needed - I could not fit pwm servo within existing servo implementations. Let me know if you have any idea to do it. makefile | 6 +- phc2pwm.c | 233 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 phc2pwm.c diff --git a/makefile b/makefile index a965bd4..4d807bd 100644 --- a/makefile +++ b/makefile @@ -22,7 +22,7 @@ CC = $(CROSS_COMPILE)gcc VER = -DVER=$(version) CFLAGS = -Wall $(VER) $(incdefs) $(DEBUG) $(EXTRA_CFLAGS) LDLIBS = -lm -lrt $(EXTRA_LDFLAGS) -PRG = ptp4l hwstamp_ctl nsm phc2sys phc_ctl pmc timemaster +PRG = ptp4l hwstamp_ctl nsm phc2sys phc_ctl pmc timemaster phc2pwm FILTERS = filter.o mave.o mmedian.o SERVOS = linreg.o ntpshm.o nullf.o pi.o servo.o TRANSP = raw.o transport.o udp.o udp6.o uds.o @@ -33,7 +33,7 @@ OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o designated_fsm.o \ unicast_fsm.o unicast_service.o util.o version.o pwm.o OBJECTS = $(OBJ) hwstamp_ctl.o nsm.o phc2sys.o phc_ctl.o pmc.o pmc_common.o \ - sysoff.o timemaster.o + sysoff.o timemaster.o phc2pwm.o SRC = $(OBJECTS:.o=.c) DEPEND = $(OBJECTS:.o=.d) srcdir := $(dir $(lastword $(MAKEFILE_LIST))) @@ -60,6 +60,8 @@ phc2sys: clockadj.o clockcheck.o config.o hash.o interface.o msg.o \ phc.o phc2sys.o pmc_common.o print.o $(SERVOS) sk.o stats.o \ sysoff.o tlv.o $(TRANSP) util.o version.o +phc2pwm: util.o pwm.o phc2pwm.o phc.o print.o sk.o + hwstamp_ctl: hwstamp_ctl.o version.o phc_ctl: phc_ctl.o phc.o sk.o util.o clockadj.o sysoff.o print.o version.o diff --git a/phc2pwm.c b/phc2pwm.c new file mode 100644 index 0000000..a2d9a13 --- /dev/null +++ b/phc2pwm.c @@ -0,0 +1,233 @@ +/** + * @file phc2pwm.c + * @brief Utility program to synchronize a pwm with ptp clock to generate PPS. + * This is mainly intended for the below hardware configuration: + * - A PTP supporting IP that is not capable of generating PPS signal + * - A PWM signal that is connected to the above PTP hardware + * - On every rising edge of PWM, PTP hardware should be able to send + * the current timestamp to userspace. + * - This PWM signal can be used as a PPS signal that is synchronized to + * PTP clock. + * + * @assumptions: + * - PWM period and duty_cycle can be updated on the fly. + * - Update to PWM period and duty_cycle gets reflected in next cycle. + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" +#include "pwm.h" +#include "phc.h" +#include "missing.h" +#include "print.h" + +#define NS_PER_SEC 1000000000LL +#define PWM_PERIOD 1000000000 +#define PWM_DUTY_CYCLE 300000000 + +static void usage(char *progname) +{ + fprintf(stderr, + "\n" + "usage: %s [options]\n\n" + "\n" + " -p [dev] Clock device to use\n" + " -e [id] PTP index for event/trigger\n" + " -w [id] PWM chip device id\n" + " -c [id] PWM channel id from PWM chip\n" + " -l [num] set the logging level to 'num'\n" + " -h prints this message and exits\n" + "\n", + progname); +} + +#define PWM_SERVO_BUF 32 +#define PWM_DRIFT_COUNT 16 + +struct pwm_servo { + uint64_t period[PWM_SERVO_BUF]; + uint64_t ts[PWM_SERVO_BUF]; + int state; + int count; +}; + +static uint64_t pwm_servo_sample(struct pwm_servo *ps, uint64_t ts) +{ + double avg_period, ts_diff; + int offset, i, drift_cnt; + uint64_t period, base; + + offset = ts % NS_PER_SEC; + if (offset > 500000000) + offset = offset - NS_PER_SEC; + + switch (ps->state) { + /* + * Since update to PWM period gets reflected in next cycle, + * Servo state 0 and 1 are used to bring down the error + * below 1ms. + */ + case 0: + period = ps->period[1] = PWM_PERIOD; + ps->state = 1; + break; + case 1: + if (abs(offset) < 1000000) { + period = ps->period[ps->count + 2] = PWM_PERIOD; + ps->ts[ps->count] = ts; + ps->state = 2; + ps->count++; + } else { + period = PWM_PERIOD - offset; + ps->state = 0; + } + break; + case 2: + /* + * Offset is calculated in 2 different methods: + * - samples collected are < PWM_DRIFT_COUNT: Calculate offset + * based on the last 2 timestamps + * - samples collected are > PWM_DRIFT_COUNT: Calculate offset + * based on last PWM_DRIFT_COUNT timestamps. + */ + ps->ts[ps->count % PWM_SERVO_BUF] = ts; + drift_cnt = (ps->count > PWM_DRIFT_COUNT) ? PWM_DRIFT_COUNT : 1; + avg_period = 0; + for (i = 0; i < drift_cnt; i++) + avg_period += ps->period[(ps->count - i) % PWM_SERVO_BUF]; + ts_diff = ps->ts[ps->count % PWM_SERVO_BUF] - + ps->ts[(ps->count - drift_cnt) % PWM_SERVO_BUF]; + + /* + * Calculate the base period based on programmed period and + * observed timestamps. + */ + base = avg_period / (ts_diff*1e-9) + 0.5; + + /* + * Account for the drift in current period which is programmed + * in last cycle. + * */ + offset += ps->period[(ps->count + 1) % PWM_SERVO_BUF] - base; + + period = base - offset; + ps->period[(ps->count + 2) % PWM_SERVO_BUF] = period; + ps->count++; + + /* If error is out of bounds, reset the servo */ + if (abs(offset) > 1000000) { + period = PWM_PERIOD; + ps->count = 0; + ps->state = 0; + } + break; + } + + return period; +} + +int main(int argc, char *argv[]) +{ + unsigned int pwm_chip, pwm_chan, event_index; + int c, err, level = LOG_INFO; + char *progname, *ptp_dev; + struct pwm_chan *chan; + struct pwm_servo ps; + clockid_t clkid; + uint64_t ts; + + handle_term_signals(); + + /* Process the command line arguments. */ + progname = strrchr(argv[0], '/'); + progname = progname ? 1+progname : argv[0]; + + while (EOF != (c = getopt(argc, argv, "p:e:w:c:l:h"))) { + switch (c) { + case 'p': + ptp_dev = optarg; + break; + case 'e': + event_index = atoi(optarg); + break; + case 'w': + pwm_chip = atoi(optarg); + break; + case 'c': + pwm_chan = atoi(optarg); + break; + case 'l': + level = atoi(optarg); + break; + case 'h': + usage(progname); + return 0; + case '?': + default: + usage(progname); + return -1; + } + } + handle_term_signals(); + print_set_progname(progname); + print_set_level(level); + + clkid = phc_open(ptp_dev); + if (clkid == CLOCK_INVALID) + return -1; + + chan = pwm_chan_create(pwm_chip, pwm_chan); + if (!chan) { + err = -1; + goto phc_clean; + } + + err = phc_enable_extts(clkid, event_index); + if (err) + goto pwm_chan_clean; + + pwm_chan_set_period(chan, PWM_PERIOD); + pwm_chan_set_duty_cycle(chan, PWM_DUTY_CYCLE); + ps.period[0] = PWM_PERIOD; + ps.count = ps.state = 0; + + err = pwm_chan_enable(chan); + if (err) + goto extts_clean; + + while(is_running()) { + if (phc_read_extts(clkid, &ts)) + continue; + + fprintf(stdout, "Timestamp = %lld.%09lld\n", ts / NS_PER_SEC, + ts % NS_PER_SEC); + pwm_chan_set_period(chan, pwm_servo_sample(&ps, ts)); + } + +extts_clean: + phc_disable_extts(clkid, event_index); +pwm_chan_clean: + pwm_chan_destroy(chan); +phc_clean: + phc_close(clkid); + + return err; +} -- 2.23.0 _______________________________________________ Linuxptp-devel mailing list Linuxptp-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/linuxptp-devel