mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
drivers/periph: Added PTP clock API
This commit is contained in:
parent
d59607ddb7
commit
7d1edd51f4
@ -102,6 +102,10 @@ ifneq (,$(filter nrfmin,$(USEMODULE)))
|
||||
USEMODULE += netif
|
||||
endif
|
||||
|
||||
ifneq (,$(filter periph_ptp_timer periph_ptp_speed_adjustment,$(FEATURES_USED)))
|
||||
FEATURES_REQUIRED += periph_ptp
|
||||
endif
|
||||
|
||||
ifneq (,$(filter qmc5883l_%,$(USEMODULE)))
|
||||
USEMODULE += qmc5883l
|
||||
endif
|
||||
|
408
drivers/include/periph/ptp.h
Normal file
408
drivers/include/periph/ptp.h
Normal file
@ -0,0 +1,408 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup drivers_periph_ptp PTP-Clock
|
||||
* @ingroup drivers_periph
|
||||
* @brief Low-level PTP clock peripheral driver
|
||||
*
|
||||
* # Introduction
|
||||
*
|
||||
* The [Precision Time Protocol (PTP)](https://standards.ieee.org/content/ieee-standards/en/standard/1588-2019.html)
|
||||
* can be used to synchronize clocks with hardware support in an Ethernet PHY,
|
||||
* that allows to precisely estimate (and, thus, compensate) network delay
|
||||
* between time server and client. This allows PTP synchronization to be highly
|
||||
* accurate (<< 1 µs offset between server and client), but requires hardware
|
||||
* support on each synchronized node and the PTP server, all nodes to be
|
||||
* connected to the same* Ethernet network, and (for best results) hardware
|
||||
* support in all intermediate switches.
|
||||
*
|
||||
* # (No) Synchronization Using This API
|
||||
*
|
||||
* This API is intended to provide basic access to a PTP hardware clock. This
|
||||
* does not cover any synchronization. Some PTP hardware clocks (e.g. on the
|
||||
* STM32) can be used without synchronization by manually setting the time.
|
||||
* Thus, the PTP clock can be useful even without synchronization implemented.
|
||||
*
|
||||
* It is intended that the actual synchronization is implemented externally
|
||||
* later on. The functions @ref ptp_clock_adjust and @ref ptp_clock_adjust_speed
|
||||
* are specified with the needs of a synchronization solution in mind.
|
||||
*
|
||||
* # Clock vs Timer
|
||||
*
|
||||
* This API provides both `ptp_clock_*()` and `ptp_timer_*()` functions, to
|
||||
* distinguish between the feature set being used. A PTP peripheral might only
|
||||
* support the feature `periph_ptp`, but not `periph_ptp_timer`.
|
||||
*
|
||||
* # (Lack of) Power Considerations
|
||||
*
|
||||
* It is assumed that a board connected to a wired network is also powered
|
||||
* from mains. Additionally, a high-resolution high-accuracy clock that is
|
||||
* periodically synced over network is just not going to play well with
|
||||
* low-power application scenarios.
|
||||
*
|
||||
* @{
|
||||
* @file
|
||||
* @brief Low-level PTP clock peripheral driver interface definitions
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
*/
|
||||
|
||||
#ifndef PERIPH_PTP_H
|
||||
#define PERIPH_PTP_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "periph_cpu.h"
|
||||
#include "timex.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* verify settings from periph_cpu.h */
|
||||
#if !defined(HAVE_PTP_CLOCK_READ) && !defined(HAVE_PTP_CLOCK_READ_U64)
|
||||
#error "Neither ptp_clock_read() nor ptp_clock_read_u64() implemented"
|
||||
#endif
|
||||
|
||||
#if !defined(HAVE_PTP_CLOCK_SET) && !defined(HAVE_PTP_CLOCK_SET_U64)
|
||||
#error "Neither ptp_clock_set() nor ptp_clock_set_u64() implemented"
|
||||
#endif
|
||||
|
||||
#if \
|
||||
!defined(HAVE_PTP_TIMER_SET_ABSOLUTE) && \
|
||||
!defined(HAVE_PTP_TIMER_SET_ABSOLUTE_U64) && \
|
||||
IS_USED(MODULE_PERIPH_PTP_TIMER)
|
||||
#error "Neither ptp_timer_set_absolute() nor ptp_timer_set_absolute_u64() implemented"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Unsigned integer type to store seconds since epoch for use in PTP
|
||||
*
|
||||
* The PTP protocol defines the seconds part of PTP timestamps as an 48 bit
|
||||
* unsigned integer. We go for 32 bit for now (works until year 2106) and will
|
||||
* later extend this type to 64 bit. Users are highly encouraged to use this
|
||||
* type instead of `uint32_t`, if they intent that their software still works
|
||||
* in a couple of decades.
|
||||
*/
|
||||
typedef uint32_t ptp_seconds_t;
|
||||
|
||||
/**
|
||||
* @brief A PTP timestamp in seconds + nanoseconds since UNIX epoch
|
||||
*
|
||||
* According to IEEE 1588-2019 specification in section "5.3.3 Timestamp",
|
||||
* timestamps are represented as seconds and nanoseconds since epoch. For the
|
||||
* seconds parts an 48 bit unsigned integer is used in the protocol and a 32 bit
|
||||
* unsigned integer for the nanoseconds.
|
||||
*/
|
||||
typedef struct {
|
||||
ptp_seconds_t seconds; /**< Seconds since UNIX epoch */
|
||||
uint32_t nanoseconds; /**< Nanoseconds part */
|
||||
} ptp_timestamp_t;
|
||||
|
||||
/**
|
||||
* @brief Compare two PTP timestamps
|
||||
*
|
||||
* @param a First timestamp
|
||||
* @param b Second timestamp
|
||||
*
|
||||
* @retval -1 @p a < @p b
|
||||
* @retval 0 @p a == @p b
|
||||
* @retval 1 @p a > @p b
|
||||
*/
|
||||
static inline int ptp_cmp(const ptp_timestamp_t *a, const ptp_timestamp_t *b)
|
||||
{
|
||||
if (a->seconds < b->seconds) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a->seconds > b->seconds) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a->nanoseconds < b->nanoseconds) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a->nanoseconds > b->nanoseconds) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add a given offset onto the given timestamp
|
||||
*
|
||||
* @param[in,out] t Timestamp to add offset to
|
||||
* @param[in] offset Offset to apply
|
||||
*/
|
||||
static inline void ptp_add(ptp_timestamp_t *t, int64_t offset)
|
||||
{
|
||||
/* Modulo for negative numbers should be avoided */
|
||||
if (offset >= 0) {
|
||||
uint64_t abs_offset = offset;
|
||||
t->seconds += abs_offset / NS_PER_SEC;
|
||||
t->nanoseconds += abs_offset % NS_PER_SEC;
|
||||
/* correct overflow of nanosecond part */
|
||||
if (t->nanoseconds >= NS_PER_SEC) {
|
||||
t->nanoseconds -= NS_PER_SEC;
|
||||
t->seconds++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
uint64_t abs_offset = -offset;
|
||||
t->seconds -= abs_offset / NS_PER_SEC;
|
||||
t->nanoseconds -= abs_offset % NS_PER_SEC;
|
||||
/* correct underflow of nanosecond part */
|
||||
if (t->nanoseconds > NS_PER_SEC) {
|
||||
t->nanoseconds += NS_PER_SEC;
|
||||
t->seconds--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert time from nanoseconds since epoch to ptp_timestamp_t format
|
||||
*
|
||||
* @param[out] dest The timestamp will be written here
|
||||
* @param[in] ns_since_epoch Time in nanoseconds since epoch to convert
|
||||
*/
|
||||
static inline void ptp_ns2ts(ptp_timestamp_t *dest, uint64_t ns_since_epoch)
|
||||
{
|
||||
dest->seconds = ns_since_epoch / NS_PER_SEC;
|
||||
dest->nanoseconds = ns_since_epoch % NS_PER_SEC;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert time from nanoseconds since epoch to ptp_timestamp_t format
|
||||
*
|
||||
* @param[in] t Time to convert to nanoseconds since epoch
|
||||
*
|
||||
* @return The time specified by @p t in nanoseconds since epoch
|
||||
*/
|
||||
static inline uint64_t ptp_ts2ns(const ptp_timestamp_t *t)
|
||||
{
|
||||
return t->seconds * NS_PER_SEC + t->nanoseconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the given PTP peripheral
|
||||
*
|
||||
* @note Implementations of this functions have to use `assert()` to make
|
||||
* the configuration is valid.
|
||||
*
|
||||
* @pre This function must be called at most once
|
||||
*
|
||||
* After calling this, the PTP clock (and the PTP timer, if the feature
|
||||
* `periph_ptp_timer` is used in addition to `periph_ptp_clock`) can be used.
|
||||
*/
|
||||
void ptp_init(void);
|
||||
|
||||
/**
|
||||
* @brief Adjust the PTP clock speed as given
|
||||
*
|
||||
* @param correction Specification of the clock speed adjustment
|
||||
*
|
||||
* @note This implies feature `periph_ptp_speed_adjustment`
|
||||
*
|
||||
* The clock speed is adjusted in regard to its nominal clock speed. This means
|
||||
* that calls with the same value in @p correction are idempotent.
|
||||
*
|
||||
* 1. A call with @p correction set to `0` restores the nominal clock speed.
|
||||
* 2. A call with a positive value for @p correction speeds the clock up
|
||||
* by `correction / (1 << 16)` (so up to ~50% for `INT16_MAX`).
|
||||
* 3. A call with a negative value for @p correction slows the clock down by
|
||||
* `-correction / (1 << 16)` (so up to 50% for `INT16_MIN`).
|
||||
*
|
||||
* This allows the clock speed to be adjusted in steps ± 0.00153% in relation
|
||||
* to its nominal clock speed, thus, allowing to counter systematic clock drift.
|
||||
* In addition, this is intended to "smoothly" synchronize the clock over time
|
||||
* to avoid jumps in the system time. (Especially calling @ref ptp_clock_adjust
|
||||
* with negative values might be something to avoid, when applications are not
|
||||
* prepared with clocks going backwards.)
|
||||
*/
|
||||
void ptp_clock_adjust_speed(int16_t correction);
|
||||
|
||||
/**
|
||||
* @brief Adjust the PTP clock as given
|
||||
*
|
||||
* @param offset Offset to add onto current system time
|
||||
*
|
||||
* Same as `ptp_clock_set_u64(ptp_clock_read_u64() + offset)`, but implemented
|
||||
* to introduce as little error as possible while setting the offset. This
|
||||
* is intended to be used by clock synchronization implementations.
|
||||
*/
|
||||
void ptp_clock_adjust(int64_t offset);
|
||||
|
||||
#if defined(HAVE_PTP_CLOCK_READ) || defined(DOXYGEN)
|
||||
/**
|
||||
* @brief Get the current system time as PTP timestamp
|
||||
*
|
||||
* @param[out] timestamp The timestamp will be written here
|
||||
* @pre The PTP clock is initialized
|
||||
*/
|
||||
void ptp_clock_read(ptp_timestamp_t *timestamp);
|
||||
#endif /* HAVE_PTP_CLOCK_READ */
|
||||
|
||||
#if defined(HAVE_PTP_CLOCK_READ_U64) || defined(DOXYGEN)
|
||||
/**
|
||||
* @brief Get the current system time in nanosecond since UNIX epoch
|
||||
*
|
||||
* @return Nanoseconds since 1. 1. 1970 0:00:00 UTC
|
||||
*
|
||||
* @pre The PTP clock is initialized
|
||||
*
|
||||
* @note An `uint64_t` allows nanosecond timestamps within 1970-01-01 and
|
||||
* 2554-07-21 to be represented.
|
||||
*/
|
||||
uint64_t ptp_clock_read_u64(void);
|
||||
#endif /* HAVE_PTP_CLOCK_READ_U64 */
|
||||
|
||||
#if defined(HAVE_PTP_CLOCK_SET) || defined(DOXYGEN)
|
||||
/**
|
||||
* @brief Set the current system time
|
||||
*
|
||||
* @param time The new system time
|
||||
*
|
||||
* @pre The PTP clock is initialized
|
||||
*/
|
||||
void ptp_clock_set(const ptp_timestamp_t *time);
|
||||
#endif /* HAVE_PTP_CLOCK_SET */
|
||||
|
||||
#if defined(HAVE_PTP_CLOCK_SET_U64) || defined(DOXYGEN)
|
||||
/**
|
||||
* @brief Set the current system time in nanosecond since UNIX epoch
|
||||
*
|
||||
* @param ns_since_epoch New time to set
|
||||
*
|
||||
* @pre The PTP clock is initialized
|
||||
*/
|
||||
void ptp_clock_set_u64(uint64_t ns_since_epoch);
|
||||
#endif /* HAVE_PTP_CLOCK_SET_U64 */
|
||||
|
||||
/**
|
||||
* @brief External function to call when the PTP clock timer fired
|
||||
*
|
||||
* @note This function needs to be implemented by the *user* of this API
|
||||
* @note This function implies feature `periph_ptp_timer`
|
||||
*
|
||||
* Since at most one PTP clock is expected on each board, we can avoid the
|
||||
* overhead of indirect function calls here and let users of this API just
|
||||
* implement this function.
|
||||
*/
|
||||
void ptp_timer_cb(void);
|
||||
|
||||
#if defined(HAVE_PTP_TIMER_SET_ABSOLUTE) || defined(DOXYGEN)
|
||||
/**
|
||||
* @brief Set an absolute timeout value, possibly overwriting an existing
|
||||
* timeout
|
||||
*
|
||||
* @param target Timestamp when the timeout should fire
|
||||
*
|
||||
* @note Only a single timeout is supported by the PTP timer
|
||||
* @note This function implies feature `periph_ptp_timer`
|
||||
*
|
||||
* @details If the target time is in the past or equal to the current time, the
|
||||
* IRQ should trigger right away.
|
||||
*/
|
||||
void ptp_timer_set_absolute(const ptp_timestamp_t *target);
|
||||
#endif /* HAVE_PTP_TIMER_SET_ABSOLUTE */
|
||||
|
||||
#if defined(HAVE_PTP_TIMER_SET_ABSOLUTE_U64) || defined(DOXYGEN)
|
||||
/**
|
||||
* @brief Set an absolute timeout value, possibly overwriting an existing
|
||||
* timeout
|
||||
*
|
||||
* @param target Timestamp when the timeout should fire (ns since epoch)
|
||||
*
|
||||
* @note Only a single timeout is supported by the PTP timer
|
||||
* @note This function implies feature `periph_ptp_timer`
|
||||
*
|
||||
* @details If the target time is in the past or equal to the current time, the
|
||||
* IRQ should trigger right away.
|
||||
*/
|
||||
void ptp_timer_set_absolute_u64(uint64_t target);
|
||||
#endif /* HAVE_PTP_TIMER_SET_ABSOLUTE_U64 */
|
||||
|
||||
/**
|
||||
* @brief Set an relative timeout value, possibly overwriting an existing
|
||||
* timeout
|
||||
*
|
||||
* @param target Number of nanoseconds after which the timeout should fire
|
||||
*
|
||||
* @note Only a single timeout is supported by the PTP timer
|
||||
* @note This function implies feature `periph_ptp_timer`
|
||||
*/
|
||||
void ptp_timer_set_u64(uint64_t target);
|
||||
|
||||
/**
|
||||
* @brief Clears any pending timeout on the PTP timer
|
||||
*
|
||||
* @note This function implies feature `periph_ptp_timer`
|
||||
*/
|
||||
void ptp_timer_clear(void);
|
||||
|
||||
/* Fallback implementations (the driver can implement either the
|
||||
* functions using `ptp_timestamp_t` or `uint64_t`, the other flavor will
|
||||
* be provided on top here): */
|
||||
|
||||
#ifndef HAVE_PTP_CLOCK_READ
|
||||
static inline void ptp_clock_read(struct ptp_timestamp_t *timestamp)
|
||||
{
|
||||
ptp_ns2ts(timestamp, ptp_clock_read_u64());
|
||||
}
|
||||
#endif /* !HAVE_PTP_CLOCK_READ */
|
||||
|
||||
#ifndef HAVE_PTP_CLOCK_READ_U64
|
||||
static inline uint64_t ptp_clock_read_u64(void)
|
||||
{
|
||||
ptp_timestamp_t ts;
|
||||
ptp_clock_read(&ts);
|
||||
return ptp_ts2ns(&ts);
|
||||
}
|
||||
#endif /* !HAVE_PTP_CLOCK_READ_U64 */
|
||||
|
||||
#ifndef HAVE_PTP_CLOCK_SET
|
||||
static inline void ptp_clock_set(const ptp_timestamp_t *time)
|
||||
{
|
||||
ptp_clock_set_u64(ptp_ts2ns(time));
|
||||
}
|
||||
#endif /* !HAVE_PTP_CLOCK_SET */
|
||||
|
||||
#ifndef HAVE_PTP_CLOCK_SET_U64
|
||||
static inline void ptp_clock_set_u64(uint64_t ns_since_epoch)
|
||||
{
|
||||
ptp_timestamp_t time;
|
||||
ptp_ns2ts(&time, ns_since_epoch);
|
||||
ptp_clock_set(&time);
|
||||
}
|
||||
#endif /* !HAVE_PTP_CLOCK_SET_U64 */
|
||||
|
||||
#ifndef HAVE_PTP_TIMER_SET_ABSOLUTE
|
||||
static inline void ptp_timer_set_absolute(const ptp_timestamp_t *target)
|
||||
{
|
||||
ptp_timer_set_absolute_u64(ptp_ts2ns(target));
|
||||
}
|
||||
#endif /* !HAVE_PTP_TIMER_SET_ABSOLUTE */
|
||||
|
||||
#ifndef HAVE_PTP_TIMER_SET_ABSOLUTE_U64
|
||||
static inline void ptp_timer_set_absolute_u64(uint64_t target)
|
||||
{
|
||||
ptp_timestamp_t ts;
|
||||
ptp_ns2ts(&ts, target);
|
||||
ptp_timer_set_absolute(&ts);
|
||||
}
|
||||
#endif /* !HAVE_PTP_TIMER_SET_ABSOLUTE_U64 */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PERIPH_PTP_H */
|
||||
/** @} */
|
@ -46,6 +46,9 @@
|
||||
#ifdef MODULE_PERIPH_INIT_WDT
|
||||
#include "periph/wdt.h"
|
||||
#endif
|
||||
#ifdef MODULE_PERIPH_INIT_PTP
|
||||
#include "periph/ptp.h"
|
||||
#endif
|
||||
#endif /* MODULE_PERIPH_INIT */
|
||||
|
||||
void periph_init(void)
|
||||
@ -92,5 +95,9 @@ void periph_init(void)
|
||||
wdt_init();
|
||||
#endif
|
||||
|
||||
#if defined(MODULE_PERIPH_INIT_PTP)
|
||||
ptp_init();
|
||||
#endif
|
||||
|
||||
#endif /* MODULE_PERIPH_INIT */
|
||||
}
|
||||
|
50
drivers/periph_common/ptp.c
Normal file
50
drivers/periph_common/ptp.c
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser General
|
||||
* Public License v2.1. See the file LICENSE in the top level directory for more
|
||||
* details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup drivers_periph_ptp
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Common code for PTP clocks and timers
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
#include "kernel_defines.h"
|
||||
#if IS_USED(MODULE_PERIPH_PTP_TIMER)
|
||||
#include "irq.h"
|
||||
#include "periph/ptp.h"
|
||||
|
||||
#if !defined(HAVE_PTP_TIMER_SET_U64)
|
||||
void ptp_timer_set_u64(uint64_t target)
|
||||
{
|
||||
unsigned irq_state = irq_disable();
|
||||
if (IS_ACTIVE(HAVE_PTP_TIMER_SET_ABSOLUTE) && IS_ACTIVE(HAVE_PTP_CLOCK_READ)) {
|
||||
/* This is slightly more efficient when the PTP clock implementation
|
||||
* uses ptp_timestamp_t natively */
|
||||
ptp_timestamp_t now;
|
||||
ptp_clock_read(&now);
|
||||
now.seconds += target / NS_PER_SEC;
|
||||
now.nanoseconds += target % NS_PER_SEC;
|
||||
while (now.nanoseconds >= NS_PER_SEC) {
|
||||
now.seconds++;
|
||||
now.nanoseconds -= NS_PER_SEC;
|
||||
}
|
||||
ptp_timer_set_absolute(&now);
|
||||
}
|
||||
else {
|
||||
ptp_timer_set_absolute_u64(ptp_clock_read_u64() + target);
|
||||
}
|
||||
irq_restore(irq_state);
|
||||
}
|
||||
#endif /* !defined(HAVE_PTP_TIMER_SET_U64) */
|
||||
#else
|
||||
typedef int dont_be_pedantic;
|
||||
#endif
|
@ -205,6 +205,21 @@ config HAS_PERIPH_PM
|
||||
help
|
||||
Indicates that a Power Management (PM) peripheral is present.
|
||||
|
||||
config HAS_PERIPH_PTP
|
||||
bool
|
||||
help
|
||||
Indicates that a PTP clock is present.
|
||||
|
||||
config HAS_PERIPH_PTP_SPEED_ADJUSTMENT
|
||||
bool
|
||||
help
|
||||
Indicates that the PTP clock speed can be adjust. This can be used for clock drift correction and synchronization.
|
||||
|
||||
config HAS_PERIPH_PTP_TIMER
|
||||
bool
|
||||
help
|
||||
Indicates that the PTP clock can be used as timer.
|
||||
|
||||
config HAS_PERIPH_PWM
|
||||
bool
|
||||
help
|
||||
|
Loading…
Reference in New Issue
Block a user