1
0
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:
Marian Buschsieweke 2020-07-15 17:18:07 +02:00
parent d59607ddb7
commit 7d1edd51f4
No known key found for this signature in database
GPG Key ID: 61F64C6599B1539F
5 changed files with 484 additions and 0 deletions

View File

@ -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

View 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 */
/** @} */

View File

@ -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 */
}

View 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

View File

@ -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