From cd26a90d4983843a2845e9553f2c6b80e855b9be Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Thu, 13 Jan 2022 20:29:52 -0800 Subject: [PATCH 01/24] Add low-level IEEE 1588 functions --- src/lwip_t41.c | 223 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lwip_t41.h | 82 ++++++++++++++++++ 2 files changed, 305 insertions(+) diff --git a/src/lwip_t41.c b/src/lwip_t41.c index 158f9c2a2..641ef5720 100644 --- a/src/lwip_t41.c +++ b/src/lwip_t41.c @@ -15,6 +15,7 @@ #include #include +#include #include "lwip/err.h" #include "lwip/etharp.h" @@ -154,6 +155,9 @@ static bool linkIsFullDuplex = false; // Is Ethernet initialized? static bool isInitialized = false; +// Tracks seconds since the IEEE 1588 timer was started. +static volatile uint32_t ieee1588Seconds = 0; + void enet_isr(); // -------------------------------------------------------------------------- @@ -505,6 +509,11 @@ static inline volatile enetbufferdesc_t *rxbd_next() { } void enet_isr() { + if ((ENET_EIR & ENET_EIR_TS_TIMER) != 0) { + ENET_EIR = ENET_EIR_TS_TIMER; + ieee1588Seconds++; + } + if (ENET_EIR & ENET_EIR_RXF) { ENET_EIR = ENET_EIR_RXF; rx_ready = 1; @@ -624,6 +633,8 @@ void enet_init(const uint8_t macaddr[ETH_HWADDR_LEN], void enet_deinit() { isInitialized = false; + enet_ieee1588_deinit(); + if (isNetifAdded) { netif_remove(&t41_netif); netif_remove_ext_callback(&netif_callback); @@ -870,4 +881,216 @@ void enet_leave_group(const ip4_addr_t *group) { } #endif // !QNETHERNET_PROMISCUOUS_MODE +// -------------------------------------------------------------------------- +// IEEE 1588 functions +// -------------------------------------------------------------------------- + +#define ENET_ATCR_SLAVE ((uint32_t)(1U << 13)) +#define ENET_ATCR_CAPTURE ((uint32_t)(1U << 11)) +#define ENET_ATCR_RESTART ((uint32_t)(1U << 9)) +#define ENET_ATCR_PINPER ((uint32_t)(1U << 7)) +#define ENET_ATCR_Reserved ((uint32_t)(1U << 5)) // Spec says always write a 1 +#define ENET_ATCR_PEREN ((uint32_t)(1U << 4)) +#define ENET_ATCR_OFFRST ((uint32_t)(1U << 3)) +#define ENET_ATCR_OFFEN ((uint32_t)(1U << 2)) +#define ENET_ATCR_EN ((uint32_t)(1U << 0)) + +#define ENET_ATCOR_COR_MASK (0x7fffffffU) +#define ENET_ATINC_INC_MASK (0x00007f00U) +#define ENET_ATINC_INC_CORR(n) ((uint32_t)(((n) & 0x7f) << 8)) +#define ENET_ATINC_INC(n) ((uint32_t)(((n) & 0x7f) << 0)) + +#define NANOSECONDS_PER_SECOND (1000 * 1000 * 1000) +#define F_ENET_TS_CLK (25 * 1000 * 1000) + +#define ENET_TCSR_TMODE_MASK (0x0000003cU) +#define ENET_TCSR_TMODE(n) ((uint32_t)(((n) & 0x0f) << 2)) +#define ENET_TCSR_TPWC(n) ((uint32_t)(((n) & 0x1f) << 11)) +#define ENET_TCSR_TF ((uint32_t)(1U << 7)) + +void enet_ieee1588_init() { + ENET_ATCR = ENET_ATCR_RESTART | ENET_ATCR_Reserved; // Reset timer + ENET_ATPER = NANOSECONDS_PER_SECOND; // Wrap at 10^9 + ENET_ATINC = ENET_ATINC_INC(NANOSECONDS_PER_SECOND / F_ENET_TS_CLK); + ENET_ATCOR = 0; // Start with no corr. + while ((ENET_ATCR & ENET_ATCR_RESTART) != 0) { + // Wait for bit to clear before being able to write to ATCR + } + + // Reset the seconds counter to zero + ieee1588Seconds = 0; + + // Enable the timer and periodic event + ENET_ATCR = ENET_ATCR_PINPER | ENET_ATCR_Reserved | ENET_ATCR_PEREN | + ENET_ATCR_EN; + + ENET_EIMR |= ENET_EIMR_TS_TIMER; +} + +void enet_ieee1588_deinit() { + ENET_EIMR &= ~(ENET_EIMR_TS_AVAIL | ENET_EIMR_TS_TIMER); + ENET_ATCR = ENET_ATCR_Reserved; +} + +bool enet_ieee1588_is_enabled() { + return ((ENET_ATCR & ENET_ATCR_EN) != 0); +} + +bool enet_ieee1588_read_timer(struct IEEE1588Time *t) { + if (t == NULL) { + return false; + } + + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + t->sec = ieee1588Seconds; + + ENET_ATCR |= ENET_ATCR_CAPTURE; + while ((ENET_ATCR & ENET_ATCR_CAPTURE) != 0) { + // Wait for bit to clear + } + t->nsec = ENET_ATVR; + + // The timer could have wrapped while we were doing stuff + // Leave the interrupt set so that our internal timer will catch it + if ((ENET_EIR & ENET_EIR_TS_TIMER) != 0) { + t->sec++; + } + } + + return true; +} + +bool enet_ieee1588_write_timer(struct IEEE1588Time *t) { + if (t == NULL) { + return false; + } + + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + ieee1588Seconds = t->sec; + ENET_ATVR = t->nsec; + } + + return true; +} + +void enet_ieee1588_adjust_timer(uint32_t corrInc, uint32_t corrPeriod) { + CLRSET(ENET_ATINC, ENET_ATINC_INC_MASK, ENET_ATINC_INC(corrInc)); + ENET_ATCOR = corrPeriod | ENET_ATCOR_COR_MASK; +} + +void enet_ieee1588_adjust_freq(int nsps) { + if (nsps == 0) { + ENET_ATCOR = 0; + return; + } + + uint32_t inc = NANOSECONDS_PER_SECOND / F_ENET_TS_CLK; + + if (nsps < 0) { + // Slow down + inc--; + nsps = -nsps; + } else { + // Speed up + inc++; + } + enet_ieee1588_adjust_timer(inc, F_ENET_TS_CLK / nsps); +} + +// Channels + +static volatile uint32_t *tcsrReg(int channel) { + switch (channel) { + case 0: return &ENET_TCSR0; + case 1: return &ENET_TCSR1; + case 2: return &ENET_TCSR2; + case 3: return &ENET_TCSR3; + default: + return NULL; + } +} + +static volatile uint32_t *tccrReg(int channel) { + switch (channel) { + case 0: return &ENET_TCCR0; + case 1: return &ENET_TCCR1; + case 2: return &ENET_TCCR2; + case 3: return &ENET_TCCR3; + default: + return NULL; + } + +} + +bool enet_ieee1588_set_channel_mode(int channel, enum TimerChannelModes mode) { + switch (mode) { + case kTimerChannelPulseLowOnCompare: + case kTimerChannelPulseHighOnCompare: + return false; + default: + break; + } + + volatile uint32_t *tcsr = tcsrReg(channel); + if (tcsr == NULL) { + return false; + } + + *tcsr = 0; + while ((*tcsr & ENET_TCSR_TMODE_MASK) != 0) { + // Check until the channel is disabled + } + *tcsr = ENET_TCSR_TMODE(mode); + + return true; +} + +bool enet_ieee1588_set_channel_output_pulse_width(int channel, + enum TimerChannelModes mode, + int pulseWidth) { + switch (mode) { + case kTimerChannelPulseLowOnCompare: + case kTimerChannelPulseHighOnCompare: + break; + default: + return true; + } + + volatile uint32_t *tcsr = tcsrReg(channel); + if (tcsr == NULL) { + return false; + } + + *tcsr = 0; + while ((*tcsr & ENET_TCSR_TMODE_MASK) != 0) { + // Check until the channel is disabled + } + *tcsr = ENET_TCSR_TMODE(mode) | ENET_TCSR_TPWC(pulseWidth); + + return true; +} + +bool enet_ieee1588_set_channel_compare_value(int channel, uint32_t value) { + volatile uint32_t *tccr = tccrReg(channel); + if (tccr == NULL) { + return false; + } + *tccr = value; + return true; +} + +bool enet_ieee1588_get_and_clear_channel_status(int channel) { + volatile uint32_t *tcsr = tcsrReg(channel); + if (tcsr == NULL) { + return false; + } + if ((*tcsr & ENET_TCSR_TF) != 0) { + *tcsr |= ENET_TCSR_TF; + ENET_TGSR = (1 << channel); + return true; + } else { + return false; + } +} + #endif // ARDUINO_TEENSY41 diff --git a/src/lwip_t41.h b/src/lwip_t41.h index af24d875a..cf757d013 100644 --- a/src/lwip_t41.h +++ b/src/lwip_t41.h @@ -20,6 +20,31 @@ #include "lwip/netif.h" #include "lwip/prot/ethernet.h" +#ifdef __cplusplus +namespace qindesign { +namespace network { +#endif // __cplusplus + +struct IEEE1588Time { + uint32_t sec; // Seconds + uint32_t nsec; // Nanoseconds, 0-999,999,999 +}; + +enum TimerChannelModes { + kTimerChannelDisable = 0, + kTimerChannelCaptureOnRising = 1, + kTimerChannelCaptureOnFalling = 2, + kTimerChannelCaptureOnBoth = 3, + kTimerChannelSoftwareCompare = 4, + kTimerChannelToggleOnCompare = 5, + kTimerChannelClearOnCompare = 6, + kTimerChannelSetOnCompare = 7, + kTimerChannelClearOnCompareSetOnOverflow = 10, + kTimerChannelSetOnCompareClearOnOverflow = 11, + kTimerChannelPulseLowOnCompare = 14, + kTimerChannelPulseHighOnCompare = 15, +}; + #ifdef __cplusplus extern "C" { #endif @@ -88,10 +113,67 @@ void enet_leave_group(const ip4_addr_t *group); void enet_set_mac_address_allowed(const uint8_t *mac, bool allow); #endif // !QNETHERNET_PROMISCUOUS_MODE +// -------------------------------------------------------------------------- +// IEEE 1588 functions +// -------------------------------------------------------------------------- + +// Initializes and enables the IEEE 1588 timer and functionality. The internal +// time is reset to zero. +void enet_ieee1588_init(); + +// Deinitializes and stops the IEEE 1588 timer. +void enet_ieee1588_deinit(); + +// Tests if the IEEE 1588 timer is enabled. +bool enet_ieee1588_is_enabled(); + +// Reads the IEEE 1588 timer. This returns whether successful. +bool enet_ieee1588_read_timer(struct IEEE1588Time *t); + +// Writes the IEEE 1588 timer. This returns whether successful. +bool enet_ieee1588_write_timer(struct IEEE1588Time *t); + +// Directly adjust the correction increase and correction period. To adjust the +// timer in "nanoseconds per second", see `enet_ieee1588_adjust_freq`. +void enet_ieee1588_adjust_timer(uint32_t corrInc, uint32_t corrPeriod); + +// Adjust the correction in nanoseconds per second. This uses +// `enet_ieee1588_adjust_timer()` under the hood. +void enet_ieee1588_adjust_freq(int nsps); + +// Sets the channel mode for a given channel. This returns whether successful. +// +// This will return false for an unknown channel or if the mode is one of the +// output compare pulse modes. +bool enet_ieee1588_set_channel_mode(int channel, enum TimerChannelModes mode); + +// Sets the output compare pulse mode and pulse width for a given channel. This +// returns whether successful. +// +// This will return false for an unknown channel or if the mode is not one of +// the output compare pulse modes. +bool enet_ieee1588_set_channel_output_pulse_width(int channel, + enum TimerChannelModes mode, + int pulseWidth); + +// Sets the channel compare value. This returns whether successful. +// +// This will return false for an unknown channel. +bool enet_ieee1588_set_channel_compare_value(int channel, uint32_t value); + +// Retrieves and then clears the status for the given channel. This will return +// false for an unknown channel. +bool enet_ieee1588_get_and_clear_channel_status(int channel); + #ifdef __cplusplus } // extern "C" #endif +#ifdef __cplusplus +} // namespace network +} // namespace qindesign +#endif // __cplusplus + #endif // ARDUINO_TEENSY41 #endif // QNE_LWIP_T41_H_ From cd11c5c03a5964dbe99d099c389a4d8f9b03a1a1 Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Thu, 13 Jan 2022 20:52:13 -0800 Subject: [PATCH 02/24] Change enet_ieee1588_write_timer() and check pulse width The function now takes a const and the pulse width is now checked. --- src/lwip_t41.c | 8 ++++++-- src/lwip_t41.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lwip_t41.c b/src/lwip_t41.c index 641ef5720..65bca5ca5 100644 --- a/src/lwip_t41.c +++ b/src/lwip_t41.c @@ -960,7 +960,7 @@ bool enet_ieee1588_read_timer(struct IEEE1588Time *t) { return true; } -bool enet_ieee1588_write_timer(struct IEEE1588Time *t) { +bool enet_ieee1588_write_timer(const struct IEEE1588Time *t) { if (t == NULL) { return false; } @@ -1056,6 +1056,10 @@ bool enet_ieee1588_set_channel_output_pulse_width(int channel, return true; } + if (pulseWidth < 1 || 32 < pulseWidth) { + return false; + } + volatile uint32_t *tcsr = tcsrReg(channel); if (tcsr == NULL) { return false; @@ -1065,7 +1069,7 @@ bool enet_ieee1588_set_channel_output_pulse_width(int channel, while ((*tcsr & ENET_TCSR_TMODE_MASK) != 0) { // Check until the channel is disabled } - *tcsr = ENET_TCSR_TMODE(mode) | ENET_TCSR_TPWC(pulseWidth); + *tcsr = ENET_TCSR_TMODE(mode) | ENET_TCSR_TPWC(pulseWidth - 1); return true; } diff --git a/src/lwip_t41.h b/src/lwip_t41.h index cf757d013..2f1af2a18 100644 --- a/src/lwip_t41.h +++ b/src/lwip_t41.h @@ -131,7 +131,7 @@ bool enet_ieee1588_is_enabled(); bool enet_ieee1588_read_timer(struct IEEE1588Time *t); // Writes the IEEE 1588 timer. This returns whether successful. -bool enet_ieee1588_write_timer(struct IEEE1588Time *t); +bool enet_ieee1588_write_timer(const struct IEEE1588Time *t); // Directly adjust the correction increase and correction period. To adjust the // timer in "nanoseconds per second", see `enet_ieee1588_adjust_freq`. From 1e50b66ac2b51ed041091a34e96008126dc70d81 Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Thu, 13 Jan 2022 21:03:44 -0800 Subject: [PATCH 03/24] Add argument checks to enet_ieee1588_adjust_timer() Also updated the docs for that and enet_ieee1588_set_channel_output_pulse_width(). --- src/lwip_t41.c | 6 +++++- src/lwip_t41.h | 15 +++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/lwip_t41.c b/src/lwip_t41.c index 65bca5ca5..0c694521f 100644 --- a/src/lwip_t41.c +++ b/src/lwip_t41.c @@ -973,9 +973,13 @@ bool enet_ieee1588_write_timer(const struct IEEE1588Time *t) { return true; } -void enet_ieee1588_adjust_timer(uint32_t corrInc, uint32_t corrPeriod) { +bool enet_ieee1588_adjust_timer(uint32_t corrInc, uint32_t corrPeriod) { + if (corrInc >= 128 || corrPeriod >= (1U << 31)) { + return false; + } CLRSET(ENET_ATINC, ENET_ATINC_INC_MASK, ENET_ATINC_INC(corrInc)); ENET_ATCOR = corrPeriod | ENET_ATCOR_COR_MASK; + return true; } void enet_ieee1588_adjust_freq(int nsps) { diff --git a/src/lwip_t41.h b/src/lwip_t41.h index 2f1af2a18..6c678f05d 100644 --- a/src/lwip_t41.h +++ b/src/lwip_t41.h @@ -134,8 +134,13 @@ bool enet_ieee1588_read_timer(struct IEEE1588Time *t); bool enet_ieee1588_write_timer(const struct IEEE1588Time *t); // Directly adjust the correction increase and correction period. To adjust the -// timer in "nanoseconds per second", see `enet_ieee1588_adjust_freq`. -void enet_ieee1588_adjust_timer(uint32_t corrInc, uint32_t corrPeriod); +// timer in "nanoseconds per second", see `enet_ieee1588_adjust_freq`. This +// returns whether successful. +// +// This will return false if: +// 1. The correction increment is not in the range 0-127, or +// 2. The correction period is not in the range 0-(2^31-1). +bool enet_ieee1588_adjust_timer(uint32_t corrInc, uint32_t corrPeriod); // Adjust the correction in nanoseconds per second. This uses // `enet_ieee1588_adjust_timer()` under the hood. @@ -150,8 +155,10 @@ bool enet_ieee1588_set_channel_mode(int channel, enum TimerChannelModes mode); // Sets the output compare pulse mode and pulse width for a given channel. This // returns whether successful. // -// This will return false for an unknown channel or if the mode is not one of -// the output compare pulse modes. +// This will return false if: +// 1. The channel is unknown, +// 2. The mode is not one of the output compare pulse modes, or +// 3. The pulse width is not in the range 1-32. bool enet_ieee1588_set_channel_output_pulse_width(int channel, enum TimerChannelModes mode, int pulseWidth); From 8154650b34ad2aaf98b8b783b2ae1c52b9929314 Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Thu, 13 Jan 2022 21:09:18 -0800 Subject: [PATCH 04/24] Update the docs for read/write timer to explain when it returns false --- src/lwip_t41.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lwip_t41.h b/src/lwip_t41.h index 6c678f05d..1c79c195a 100644 --- a/src/lwip_t41.h +++ b/src/lwip_t41.h @@ -128,9 +128,13 @@ void enet_ieee1588_deinit(); bool enet_ieee1588_is_enabled(); // Reads the IEEE 1588 timer. This returns whether successful. +// +// This will return false if the argument is NULL. bool enet_ieee1588_read_timer(struct IEEE1588Time *t); // Writes the IEEE 1588 timer. This returns whether successful. +// +// This will return false if the argument is NULL. bool enet_ieee1588_write_timer(const struct IEEE1588Time *t); // Directly adjust the correction increase and correction period. To adjust the From 10f119880c5a2aa7a3742089ea2792e91fc75f4a Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Thu, 13 Jan 2022 21:12:34 -0800 Subject: [PATCH 05/24] Change enet_ieee1588_adjust_freq() to return a bool It now returns the result from enet_ieee1588_adjust_timer(). --- src/lwip_t41.c | 6 +++--- src/lwip_t41.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lwip_t41.c b/src/lwip_t41.c index 0c694521f..30e87cf14 100644 --- a/src/lwip_t41.c +++ b/src/lwip_t41.c @@ -982,10 +982,10 @@ bool enet_ieee1588_adjust_timer(uint32_t corrInc, uint32_t corrPeriod) { return true; } -void enet_ieee1588_adjust_freq(int nsps) { +bool enet_ieee1588_adjust_freq(int nsps) { if (nsps == 0) { ENET_ATCOR = 0; - return; + return true; } uint32_t inc = NANOSECONDS_PER_SECOND / F_ENET_TS_CLK; @@ -998,7 +998,7 @@ void enet_ieee1588_adjust_freq(int nsps) { // Speed up inc++; } - enet_ieee1588_adjust_timer(inc, F_ENET_TS_CLK / nsps); + return enet_ieee1588_adjust_timer(inc, F_ENET_TS_CLK / nsps); } // Channels diff --git a/src/lwip_t41.h b/src/lwip_t41.h index 1c79c195a..a99b1f9e8 100644 --- a/src/lwip_t41.h +++ b/src/lwip_t41.h @@ -148,7 +148,7 @@ bool enet_ieee1588_adjust_timer(uint32_t corrInc, uint32_t corrPeriod); // Adjust the correction in nanoseconds per second. This uses // `enet_ieee1588_adjust_timer()` under the hood. -void enet_ieee1588_adjust_freq(int nsps); +bool enet_ieee1588_adjust_freq(int nsps); // Sets the channel mode for a given channel. This returns whether successful. // From 575fe7a3b7ed812ec4c1dd09ac807072667f63c0 Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Thu, 13 Jan 2022 21:15:21 -0800 Subject: [PATCH 06/24] Make small doc updates for IEEE 1588 functions --- src/lwip_t41.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lwip_t41.h b/src/lwip_t41.h index a99b1f9e8..b5bee2b22 100644 --- a/src/lwip_t41.h +++ b/src/lwip_t41.h @@ -150,14 +150,15 @@ bool enet_ieee1588_adjust_timer(uint32_t corrInc, uint32_t corrPeriod); // `enet_ieee1588_adjust_timer()` under the hood. bool enet_ieee1588_adjust_freq(int nsps); -// Sets the channel mode for a given channel. This returns whether successful. +// Sets the channel mode for the given channel. This does not set the output +// compare pulse modes. This returns whether successful. // // This will return false for an unknown channel or if the mode is one of the // output compare pulse modes. bool enet_ieee1588_set_channel_mode(int channel, enum TimerChannelModes mode); -// Sets the output compare pulse mode and pulse width for a given channel. This -// returns whether successful. +// Sets the output compare pulse mode and pulse width for the given channel. +// This returns whether successful. // // This will return false if: // 1. The channel is unknown, From 4c5069d52ad3c34b7b11e28f6384c39f29a38c9e Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Thu, 13 Jan 2022 21:23:21 -0800 Subject: [PATCH 07/24] Add a high-level interface to the IEEE 1588 functions New object, in the Arduino style: EthernetIEEE1588. --- src/QNEthernet.h | 4 ++ src/QNEthernetIEEE1588.cpp | 69 ++++++++++++++++++++++++++++++ src/QNEthernetIEEE1588.h | 87 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 src/QNEthernetIEEE1588.cpp create mode 100644 src/QNEthernetIEEE1588.h diff --git a/src/QNEthernet.h b/src/QNEthernet.h index fe48e1846..d7d1bb671 100644 --- a/src/QNEthernet.h +++ b/src/QNEthernet.h @@ -18,6 +18,7 @@ #include "QNEthernetClient.h" #include "QNEthernetFrame.h" +#include "QNEthernetIEEE1588.h" #include "QNEthernetServer.h" #include "QNEthernetUDP.h" #include "QNMDNS.h" @@ -289,6 +290,9 @@ extern MDNSClass &MDNS; extern EthernetFrameClass &EthernetFrame; #endif // !QNETHERNET_DISABLE_RAW_FRAME_SUPPORT +// Instance for using IEEE 1588 functions. +extern EthernetIEEE1588Class &EthernetIEEE1588; + // Lets user code use stdout and stderr. extern Print *stdPrint; diff --git a/src/QNEthernetIEEE1588.cpp b/src/QNEthernetIEEE1588.cpp new file mode 100644 index 000000000..17abd77be --- /dev/null +++ b/src/QNEthernetIEEE1588.cpp @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: (c) 2022 Shawn Silverman +// SPDX-License-Identifier: MIT + +// QNEthernetIEEE1588.cpp contains the EthernetIEEE1588 implementation. +// This file is part of the QNEthernet library. + +#include "QNEthernetIEEE1588.h" + +namespace qindesign { +namespace network { + +// Define the singleton instance. +EthernetIEEE1588Class EthernetIEEE1588Class::instance_; + +// A reference to the singleton. +EthernetIEEE1588Class &EthernetIEEE1588 = EthernetIEEE1588Class::instance(); + +void EthernetIEEE1588Class::begin() const { + enet_ieee1588_init(); +} + +void EthernetIEEE1588Class::end() const { + enet_ieee1588_deinit(); +} + +bool EthernetIEEE1588Class::readTimer(IEEE1588Time &t) const { + return enet_ieee1588_read_timer(&t); +} + +bool EthernetIEEE1588Class::writeTimer(const IEEE1588Time &t) const { + return enet_ieee1588_write_timer(&t); +} + +bool EthernetIEEE1588Class::adjustTimer(uint32_t corrInc, + uint32_t corrPeriod) const { + return enet_ieee1588_adjust_timer(corrInc, corrPeriod); +} + +bool EthernetIEEE1588Class::adjustFreq(int nsps) const { + return enet_ieee1588_adjust_freq(nsps); +} + +bool EthernetIEEE1588Class::setChannelMode(int channel, + TimerChannelModes mode) const { + return enet_ieee1588_set_channel_mode(channel, mode); +} + +bool EthernetIEEE1588Class::setChannelOutputPulseWidth(int channel, + TimerChannelModes mode, + int pulseWidth) const { + return enet_ieee1588_set_channel_output_pulse_width(channel, mode, + pulseWidth); +} + +bool EthernetIEEE1588Class::setChannelCompareValue(int channel, + uint32_t value) const { + return enet_ieee1588_set_channel_compare_value(channel, value); +} + +bool EthernetIEEE1588Class::getAndClearChannelStatus(int channel) const { + return enet_ieee1588_get_and_clear_channel_status(channel); +} + +EthernetIEEE1588Class::operator bool() const { + return enet_ieee1588_is_enabled(); +} + +} // namespace network +} // namespace qindesign diff --git a/src/QNEthernetIEEE1588.h b/src/QNEthernetIEEE1588.h new file mode 100644 index 000000000..1868d8d13 --- /dev/null +++ b/src/QNEthernetIEEE1588.h @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: (c) 2022 Shawn Silverman +// SPDX-License-Identifier: MIT + +// QNEthernetIEEE1588.h defines an interface to IEEE 1588 functions. +// This file is part of the QNEthernet library. + +#ifndef QNE_ETHERNETIEEE1588_H_ +#define QNE_ETHERNETIEEE1588_H_ + +// C++ includes +#include + +#include "lwip_t41.h" + +namespace qindesign { +namespace network { + +// Provides an API for raw Ethernet frames, similar to the UDP API. +class EthernetIEEE1588Class final { + public: + // Accesses the singleton instance. + static EthernetIEEE1588Class &instance() { + return instance_; + } + + // EthernetIEEE1588Class is neither copyable nor movable. + EthernetIEEE1588Class(const EthernetIEEE1588Class &) = delete; + EthernetIEEE1588Class &operator=(const EthernetIEEE1588Class &) = delete; + + // Starts the IEEE 1588 timer. + void begin() const; + + // Stops the IEEE 1588 timer. + void end() const; + + // Reads the current IEEE 1588 timer value. This returns whether successful. + bool readTimer(IEEE1588Time &t) const; + + // Writes the current IEEE 1588 timer value. This returns whether successful. + bool writeTimer(const IEEE1588Time &t) const; + + // Adjusts the raw correction settings. The increment must be in the range + // 0-127 and the period must be in the range 0-(2^31-1), zero meaning + // no correction. This will return whether successful. + bool adjustTimer(uint32_t corrInc, uint32_t corrPeriod) const; + + // Adjusts the correction frequency in nanoseconds per second. To slow down + // the timer, specify a negative value. To speed it up, specify a positive + // value. This returns whether successful. + bool adjustFreq(int nsps) const; + + // Sets the channel mode for the given channel. This does not set the output + // compare pulse modes. This returns whether successful. + // + // This will return false for an unknown channel or if the mode is one of the + // output compare pulse modes. + bool setChannelMode(int channel, TimerChannelModes mode) const; + + // Sets the output compare pulse mode and pulse width for the given channel. + // The pulse width must be in the range 1-32. This only sets the output + // compare pulse modes. This returns whether successful. + bool setChannelOutputPulseWidth(int channel, + TimerChannelModes mode, + int pulseWidth) const; + + // Sets the channel compare value. This returns whether successful. + bool setChannelCompareValue(int channel, uint32_t value) const; + + // Retrieves and then clears the status for the given channel. This will + // return false for an unknown channel. + bool getAndClearChannelStatus(int channel) const; + + // Tests if the IEEE 1588 timer has been started. + operator bool() const; + + private: + EthernetIEEE1588Class() = default; + ~EthernetIEEE1588Class() = default; + + // The singleton instance. + static EthernetIEEE1588Class instance_; +}; + +} // namespace network +} // namespace qindesign + +#endif // #ifndef QNE_ETHERNETIEEE1588_H_ From 7f08d391c1c4cdbb29bf34b7d7b819a1ce2ef3fe Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Thu, 25 Nov 2021 09:25:32 -0800 Subject: [PATCH 08/24] lwip: Add a way to initialize the custom pbuf data --- src/lwip/opt.h | 8 ++++++++ src/lwip/pbuf.c | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/lwip/opt.h b/src/lwip/opt.h index d8c82d156..699bca5fe 100644 --- a/src/lwip/opt.h +++ b/src/lwip/opt.h @@ -1563,6 +1563,14 @@ #if !defined LWIP_PBUF_CUSTOM_DATA || defined __DOXYGEN__ #define LWIP_PBUF_CUSTOM_DATA #endif + +/** + * LWIP_PBUF_CUSTOM_DATA_INIT: Initialize the private pbuf data. The parameter + * 'p' is a 'struct pbuf *'. + */ +#if !defined LWIP_PBUF_CUSTOM_DATA_INIT || defined __DOXYGEN__ +#define LWIP_PBUF_CUSTOM_DATA_INIT(p) +#endif /** * @} */ diff --git a/src/lwip/pbuf.c b/src/lwip/pbuf.c index 7638dfda8..16eeb1f17 100644 --- a/src/lwip/pbuf.c +++ b/src/lwip/pbuf.c @@ -186,6 +186,8 @@ pbuf_init_alloced_pbuf(struct pbuf *p, void *payload, u16_t tot_len, u16_t len, p->flags = flags; p->ref = 1; p->if_idx = NETIF_NO_INDEX; + + LWIP_PBUF_CUSTOM_DATA_INIT(p) } /** From 9dfbc08f28169884b34144df9f32585773730eca Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Thu, 13 Jan 2022 21:37:17 -0800 Subject: [PATCH 09/24] Add custom pbuf timestamp data --- src/lwipopts.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lwipopts.h b/src/lwipopts.h index cb60eb9f6..64e19a11d 100644 --- a/src/lwipopts.h +++ b/src/lwipopts.h @@ -197,6 +197,12 @@ extern void *ram_heap; LWIP_MEM_ALIGN_SIZE(TCP_MSS + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN + \ PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN) #define LWIP_PBUF_REF_T u8_t +#define LWIP_PBUF_CUSTOM_DATA \ + u8_t timestampValid; \ + u32_t timestamp; +#define LWIP_PBUF_CUSTOM_DATA_INIT(p) \ + (p)->timestampValid = 0; \ + (p)->timestamp = 0; // Network Interfaces options #define LWIP_SINGLE_NETIF 1 From ac3419f4a4d8874b05e5ab8099f4532b60a20ad0 Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Thu, 13 Jan 2022 22:06:33 -0800 Subject: [PATCH 10/24] Rename IEEE1588Time to IEEE1588Timestamp --- src/QNEthernetIEEE1588.cpp | 4 ++-- src/QNEthernetIEEE1588.h | 4 ++-- src/lwip_t41.c | 4 ++-- src/lwip_t41.h | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/QNEthernetIEEE1588.cpp b/src/QNEthernetIEEE1588.cpp index 17abd77be..43c0d69e6 100644 --- a/src/QNEthernetIEEE1588.cpp +++ b/src/QNEthernetIEEE1588.cpp @@ -23,11 +23,11 @@ void EthernetIEEE1588Class::end() const { enet_ieee1588_deinit(); } -bool EthernetIEEE1588Class::readTimer(IEEE1588Time &t) const { +bool EthernetIEEE1588Class::readTimer(IEEE1588Timestamp &t) const { return enet_ieee1588_read_timer(&t); } -bool EthernetIEEE1588Class::writeTimer(const IEEE1588Time &t) const { +bool EthernetIEEE1588Class::writeTimer(const IEEE1588Timestamp &t) const { return enet_ieee1588_write_timer(&t); } diff --git a/src/QNEthernetIEEE1588.h b/src/QNEthernetIEEE1588.h index 1868d8d13..06ef9e774 100644 --- a/src/QNEthernetIEEE1588.h +++ b/src/QNEthernetIEEE1588.h @@ -34,10 +34,10 @@ class EthernetIEEE1588Class final { void end() const; // Reads the current IEEE 1588 timer value. This returns whether successful. - bool readTimer(IEEE1588Time &t) const; + bool readTimer(IEEE1588Timestamp &t) const; // Writes the current IEEE 1588 timer value. This returns whether successful. - bool writeTimer(const IEEE1588Time &t) const; + bool writeTimer(const IEEE1588Timestamp &t) const; // Adjusts the raw correction settings. The increment must be in the range // 0-127 and the period must be in the range 0-(2^31-1), zero meaning diff --git a/src/lwip_t41.c b/src/lwip_t41.c index 30e87cf14..da7173439 100644 --- a/src/lwip_t41.c +++ b/src/lwip_t41.c @@ -936,7 +936,7 @@ bool enet_ieee1588_is_enabled() { return ((ENET_ATCR & ENET_ATCR_EN) != 0); } -bool enet_ieee1588_read_timer(struct IEEE1588Time *t) { +bool enet_ieee1588_read_timer(struct IEEE1588Timestamp *t) { if (t == NULL) { return false; } @@ -960,7 +960,7 @@ bool enet_ieee1588_read_timer(struct IEEE1588Time *t) { return true; } -bool enet_ieee1588_write_timer(const struct IEEE1588Time *t) { +bool enet_ieee1588_write_timer(const struct IEEE1588Timestamp *t) { if (t == NULL) { return false; } diff --git a/src/lwip_t41.h b/src/lwip_t41.h index b5bee2b22..c74bb1aa8 100644 --- a/src/lwip_t41.h +++ b/src/lwip_t41.h @@ -25,7 +25,7 @@ namespace qindesign { namespace network { #endif // __cplusplus -struct IEEE1588Time { +struct IEEE1588Timestamp { uint32_t sec; // Seconds uint32_t nsec; // Nanoseconds, 0-999,999,999 }; @@ -130,12 +130,12 @@ bool enet_ieee1588_is_enabled(); // Reads the IEEE 1588 timer. This returns whether successful. // // This will return false if the argument is NULL. -bool enet_ieee1588_read_timer(struct IEEE1588Time *t); +bool enet_ieee1588_read_timer(struct IEEE1588Timestamp *t); // Writes the IEEE 1588 timer. This returns whether successful. // // This will return false if the argument is NULL. -bool enet_ieee1588_write_timer(const struct IEEE1588Time *t); +bool enet_ieee1588_write_timer(const struct IEEE1588Timestamp *t); // Directly adjust the correction increase and correction period. To adjust the // timer in "nanoseconds per second", see `enet_ieee1588_adjust_freq`. This From f9e48620a1ad2307c7b69a5fba1b4f913e6be51b Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Fri, 14 Jan 2022 12:47:26 -0800 Subject: [PATCH 11/24] Add RX and TX timestamps for UDP and raw frames --- src/QNEthernetFrame.cpp | 34 ++++++++++++++++++++++++-- src/QNEthernetFrame.h | 16 ++++++++++++ src/QNEthernetIEEE1588.cpp | 4 +++ src/QNEthernetIEEE1588.h | 6 +++++ src/QNEthernetUDP.cpp | 47 +++++++++++++++++++++++++++++++++-- src/QNEthernetUDP.h | 30 ++++++++++++++++++++++- src/lwip_t41.c | 50 ++++++++++++++++++++++++++++++++------ src/lwip_t41.h | 15 ++++++++++-- 8 files changed, 187 insertions(+), 15 deletions(-) diff --git a/src/QNEthernetFrame.cpp b/src/QNEthernetFrame.cpp index 5ff3c5cfd..9eab74a91 100644 --- a/src/QNEthernetFrame.cpp +++ b/src/QNEthernetFrame.cpp @@ -52,6 +52,9 @@ err_t EthernetFrameClass::recvFunc(struct pbuf *p, struct netif *netif) { p = p->next; } + frame.hasTimestamp = pHead->timestampValid; + frame.timestamp = pHead->timestamp; + // Increment the size if (EthernetFrame.inBufSize_ != 0 && EthernetFrame.inBufTail_ == EthernetFrame.inBufHead_) { @@ -87,6 +90,8 @@ int EthernetFrameClass::parseFrame() { // Pop (from the tail) frame_ = inBuf_[inBufTail_]; inBuf_[inBufTail_].data.clear(); + inBuf_[inBufTail_].hasTimestamp = false; + inBuf_[inBufTail_].timestamp = 0; inBufTail_ = (inBufTail_ + 1) % inBuf_.size(); inBufSize_--; @@ -179,6 +184,17 @@ void EthernetFrameClass::setReceiveQueueSize(size_t size) { } } +bool EthernetFrameClass::timestamp(uint32_t *timestamp) const { + // NOTE: This is not "concurrent safe" + if (frame_.hasTimestamp) { + if (timestamp != nullptr) { + *timestamp = frame_.timestamp; + } + return true; + } + return false; +} + // -------------------------------------------------------------------------- // Transmission // -------------------------------------------------------------------------- @@ -214,18 +230,32 @@ void EthernetFrameClass::beginVLANFrame(const uint8_t dstAddr[6], } bool EthernetFrameClass::endFrame() { + return endFrame(false); +} + +bool EthernetFrameClass::endFrameWithTimestamp() { + return endFrame(true); +} + +bool EthernetFrameClass::endFrame(bool doTimestamp) { if (!hasOutFrame_) { return false; } hasOutFrame_ = false; - bool retval = enet_output_frame(outFrame_.data.data(), outFrame_.data.size()); + bool retval = enet_output_frame(outFrame_.data.data(), outFrame_.data.size(), + doTimestamp); outFrame_.data.clear(); return retval; } bool EthernetFrameClass::send(const uint8_t *frame, size_t len) const { - return enet_output_frame(frame, len); + return enet_output_frame(frame, len, false); +} + +bool EthernetFrameClass::sendWithTimestamp(const uint8_t *frame, + size_t len) const { + return enet_output_frame(frame, len, true); } size_t EthernetFrameClass::write(uint8_t b) { diff --git a/src/QNEthernetFrame.h b/src/QNEthernetFrame.h index 90c9317f4..536bec076 100644 --- a/src/QNEthernetFrame.h +++ b/src/QNEthernetFrame.h @@ -80,6 +80,9 @@ class EthernetFrameClass final : public Stream { // 3. The length is not in the range 60-(maxFrameLen()-4) (excludes the FCS). bool endFrame(); + // Same as `endFrame()` but also timestamps the frame. + bool endFrameWithTimestamp(); + // Sends a frame and returns whether the send was successful. This causes less // overhead than beginFrame()/write()/endFrame(). // @@ -92,6 +95,9 @@ class EthernetFrameClass final : public Stream { // 3. The length is not in the range 60-(maxFrameLen()-4) (excludes the FCS). bool send(const uint8_t *frame, size_t len) const; + // Same as `send(frame, len)`, but adds a timestamp to the frame. + bool sendWithTimestamp(const uint8_t *frame, size_t len) const; + // Bring Print::write functions into scope using Print::write; @@ -127,9 +133,16 @@ class EthernetFrameClass final : public Stream { // with the receive function if called from an ISR. void setReceiveQueueSize(size_t size); + // Gets the IEEE 1588 timestamp for the received frame and assigns it to the + // `timestamp` parameter, if available. This returns whether the received + // frame has a timestamp. + bool timestamp(uint32_t *timestamp) const; + private: struct Frame { std::vector data; + volatile bool hasTimestamp = false; + volatile uint32_t timestamp = 0; }; EthernetFrameClass(); @@ -137,6 +150,9 @@ class EthernetFrameClass final : public Stream { static err_t recvFunc(struct pbuf *p, struct netif *netif); + // Ends the frame and optionally adds a timestamp. + bool endFrame(bool doTimestamp); + // Checks if there's data still available in the packet. bool isAvailable() const; diff --git a/src/QNEthernetIEEE1588.cpp b/src/QNEthernetIEEE1588.cpp index 43c0d69e6..6e4a53bde 100644 --- a/src/QNEthernetIEEE1588.cpp +++ b/src/QNEthernetIEEE1588.cpp @@ -31,6 +31,10 @@ bool EthernetIEEE1588Class::writeTimer(const IEEE1588Timestamp &t) const { return enet_ieee1588_write_timer(&t); } +bool EthernetIEEE1588Class::readAndClearTxTimestamp(uint32_t *timestamp) const { + return enet_ieee1588_read_and_clear_tx_timestamp(timestamp); +} + bool EthernetIEEE1588Class::adjustTimer(uint32_t corrInc, uint32_t corrPeriod) const { return enet_ieee1588_adjust_timer(corrInc, corrPeriod); diff --git a/src/QNEthernetIEEE1588.h b/src/QNEthernetIEEE1588.h index 06ef9e774..e48be4596 100644 --- a/src/QNEthernetIEEE1588.h +++ b/src/QNEthernetIEEE1588.h @@ -39,6 +39,12 @@ class EthernetIEEE1588Class final { // Writes the current IEEE 1588 timer value. This returns whether successful. bool writeTimer(const IEEE1588Timestamp &t) const; + // Attempts to retrieve the timestamp of the last transmitted frame and + // returns whether one is available. If available and the parameter is not + // NULL then the timestamp is assigned to `*timestamp`. This clears the + // timestamp state so that a subsequent call will return false. + bool readAndClearTxTimestamp(uint32_t *timestamp) const; + // Adjusts the raw correction settings. The increment must be in the range // 0-127 and the period must be in the range 0-(2^31-1), zero meaning // no correction. This will return whether successful. diff --git a/src/QNEthernetUDP.cpp b/src/QNEthernetUDP.cpp index 0cc601b64..f2e648cd6 100644 --- a/src/QNEthernetUDP.cpp +++ b/src/QNEthernetUDP.cpp @@ -53,6 +53,8 @@ void EthernetUDP::recvFunc(void *arg, struct udp_pcb *pcb, struct pbuf *p, p = p->next; } } + packet.hasTimestamp = pHead->timestampValid; + packet.timestamp = pHead->timestamp; packet.addr = *addr; packet.port = port; @@ -145,6 +147,8 @@ void EthernetUDP::stop() { packet_.addr = *IP_ANY_TYPE; packet_.port = 0; + packet_.hasTimestamp = false; + packet_.timestamp = 0; } EthernetUDP::operator bool() const { @@ -168,6 +172,8 @@ int EthernetUDP::parsePacket() { // Pop (from the tail) packet_ = inBuf_[inBufTail_]; inBuf_[inBufTail_].data.clear(); + inBuf_[inBufTail_].hasTimestamp = false; + inBuf_[inBufTail_].timestamp = 0; inBufTail_ = (inBufTail_ + 1) % inBuf_.size(); inBufSize_--; @@ -241,6 +247,17 @@ uint16_t EthernetUDP::remotePort() { return packet_.port; } +bool EthernetUDP::timestamp(uint32_t *timestamp) const { + // NOTE: This is not "concurrent safe" + if (packet_.hasTimestamp) { + if (timestamp != nullptr) { + *timestamp = packet_.timestamp; + } + return true; + } + return false; +} + // -------------------------------------------------------------------------- // Transmission // -------------------------------------------------------------------------- @@ -277,6 +294,14 @@ bool EthernetUDP::beginPacket(const ip_addr_t *ipaddr, uint16_t port) { } int EthernetUDP::endPacket() { + return endPacket(false); +} + +bool EthernetUDP::endPacketWithTimestamp() { + return endPacket(true); +} + +bool EthernetUDP::endPacket(bool doTimestamp) { if (!hasOutPacket_) { return false; } @@ -294,6 +319,7 @@ int EthernetUDP::endPacket() { } pbuf_take(p, out_.data.data(), out_.data.size()); out_.data.clear(); + p->timestampValid = doTimestamp; bool retval = (udp_sendto(pcb_, p, &out_.addr, out_.port) == ERR_OK); pbuf_free(p); return retval; @@ -302,7 +328,13 @@ int EthernetUDP::endPacket() { bool EthernetUDP::send(const IPAddress &ip, uint16_t port, const uint8_t *data, size_t len) { ip_addr_t ipaddr IPADDR4_INIT(get_uint32(ip)); - return send(&ipaddr, port, data, len); + return send(&ipaddr, port, data, len, false); +} + +bool EthernetUDP::sendWithTimestamp(const IPAddress &ip, uint16_t port, + const uint8_t *data, size_t len) { + ip_addr_t ipaddr IPADDR4_INIT(get_uint32(ip)); + return send(&ipaddr, port, data, len, true); } bool EthernetUDP::send(const char *host, uint16_t port, @@ -314,8 +346,18 @@ bool EthernetUDP::send(const char *host, uint16_t port, return send(ip, port, data, len); } +bool EthernetUDP::sendWithTimestamp(const char *host, uint16_t port, + const uint8_t *data, size_t len) { + IPAddress ip; + if (!DNSClient::getHostByName(host, ip, kDNSLookupTimeout)) { + return false; + } + return sendWithTimestamp(ip, port, data, len); +} + bool EthernetUDP::send(const ip_addr_t *ipaddr, uint16_t port, - const uint8_t *data, size_t len) { + const uint8_t *data, size_t len, + bool doTimestamp) { if (len > UINT16_MAX) { return false; } @@ -332,6 +374,7 @@ bool EthernetUDP::send(const ip_addr_t *ipaddr, uint16_t port, return false; } pbuf_take(p, data, len); + p->timestampValid = doTimestamp; bool retval = (udp_sendto(pcb_, p, ipaddr, port) == ERR_OK); pbuf_free(p); return retval; diff --git a/src/QNEthernetUDP.h b/src/QNEthernetUDP.h index e8587ba22..93a32dd5f 100644 --- a/src/QNEthernetUDP.h +++ b/src/QNEthernetUDP.h @@ -59,6 +59,11 @@ class EthernetUDP : public UDP { int beginPacket(const char *host, uint16_t port) final; int endPacket() final; + // Same as `endPacket()` but adds a timestamp. + // + // Timestamping must have been enabled first with EthernetIEEE1588.begin(). + bool endPacketWithTimestamp(); + // Sends a UDP packet and returns whether the attempt was successful. This // combines the functions of beginPacket(), write(), and endPacket(), and // causes less overhead. @@ -68,6 +73,18 @@ class EthernetUDP : public UDP { // Calls the other send() function after performing a DNS lookup. bool send(const char *host, uint16_t port, const uint8_t *data, size_t len); + // Same as `send(ip, port, data, len)` but adds a timestamp to the packet. + // + // Timestamping must have been enabled first with EthernetIEEE1588.begin(). + bool sendWithTimestamp(const IPAddress &ip, uint16_t port, + const uint8_t *data, size_t len); + + // Same as `send(host, port, data, len)` but adds a timestamp to the packet. + // + // Timestamping must have been enabled first with EthernetIEEE1588.begin(). + bool sendWithTimestamp(const char *host, uint16_t port, + const uint8_t *data, size_t len); + // Bring Print::write functions into scope using Print::write; @@ -105,11 +122,18 @@ class EthernetUDP : public UDP { // Returns whether the socket is listening. explicit operator bool() const; + // Gets the IEEE 1588 timestamp for the received packet and assigns it to the + // `timestamp` parameter, if available. This returns whether the received + // packet has a timestamp. + bool timestamp(uint32_t *timestamp) const; + private: struct Packet { std::vector data; ip_addr_t addr = *IP_ANY_TYPE; volatile uint16_t port = 0; + volatile bool hasTimestamp = false; + volatile uint32_t timestamp = 0; }; static void recvFunc(void *arg, struct udp_pcb *pcb, struct pbuf *p, @@ -118,7 +142,11 @@ class EthernetUDP : public UDP { // ip_addr_t versions of transmission functions bool beginPacket(const ip_addr_t *ipaddr, uint16_t port); bool send(const ip_addr_t *ipaddr, uint16_t port, - const uint8_t *data, size_t len); + const uint8_t *data, size_t len, + bool doTimestamp); + + // Timestamping send functions + bool endPacket(bool doTimestamp); // Checks if there's data still available in the packet. bool isAvailable() const; diff --git a/src/lwip_t41.c b/src/lwip_t41.c index da7173439..6a982b600 100644 --- a/src/lwip_t41.c +++ b/src/lwip_t41.c @@ -155,8 +155,10 @@ static bool linkIsFullDuplex = false; // Is Ethernet initialized? static bool isInitialized = false; -// Tracks seconds since the IEEE 1588 timer was started. -static volatile uint32_t ieee1588Seconds = 0; +// IEEE 1588 +static volatile uint32_t ieee1588Seconds = 0; // Since the timer was started +static volatile bool hasTxTimestamp = false; +static volatile uint32_t txTimestamp = 0; void enet_isr(); @@ -355,6 +357,10 @@ static void t41_low_level_init() { attachInterruptVector(IRQ_ENET, enet_isr); NVIC_ENABLE_IRQ(IRQ_ENET); + // Set the IEEE 1588 timestamp to zero, in case it's used but not enabled + ENET_ATVR = 0; + ieee1588Seconds = 0; + // Last, enable the Ethernet MAC ENET_ECR = 0x70000000 | ENET_ECR_DBSWP | ENET_ECR_EN1588 | ENET_ECR_ETHEREN; @@ -405,6 +411,8 @@ static struct pbuf *t41_low_level_input(volatile enetbufferdesc_t *bdPtr) { arm_dcache_delete(bdPtr->buffer, MULTIPLE_OF_32(p->tot_len)); #endif // !QNETHERNET_BUFFERS_IN_RAM1 pbuf_take(p, bdPtr->buffer, p->tot_len); + p->timestampValid = ((bdPtr->status & kEnetRxBdLast) != 0); + p->timestamp = bdPtr->timestamp; LINK_STATS_INC(link.recv); } else { LINK_STATS_INC(link.drop); @@ -432,13 +440,21 @@ static inline volatile enetbufferdesc_t *get_bufdesc() { // Update a buffer descriptor. Meant to be used with get_bufdesc(). static inline void update_bufdesc(volatile enetbufferdesc_t *bdPtr, - uint16_t len) { + uint16_t len, + bool doTimestamp) { bdPtr->length = len; bdPtr->status = (bdPtr->status & kEnetTxBdWrap) | kEnetTxBdTransmitCrc | kEnetTxBdLast | kEnetTxBdReady; + hasTxTimestamp = false; // The timestamp isn't yet available + if (doTimestamp) { + bdPtr->extend1 |= kEnetTxBdTimestamp; + } else { + bdPtr->extend1 &= ~kEnetTxBdTimestamp; + } + ENET_TDAR = ENET_TDAR_TDAR; if (bdPtr->status & kEnetTxBdWrap) { @@ -459,7 +475,7 @@ static err_t t41_low_level_output(struct netif *netif, struct pbuf *p) { #ifndef QNETHERNET_BUFFERS_IN_RAM1 arm_dcache_flush_delete(bdPtr->buffer, MULTIPLE_OF_32(copied)); #endif // !QNETHERNET_BUFFERS_IN_RAM1 - update_bufdesc(bdPtr, copied); + update_bufdesc(bdPtr, copied, p->timestampValid); return ERR_OK; } @@ -514,6 +530,12 @@ void enet_isr() { ieee1588Seconds++; } + if ((ENET_EIR & ENET_EIR_TS_AVAIL) != 0) { + ENET_EIR = ENET_EIR_TS_AVAIL; + hasTxTimestamp = true; + txTimestamp = ENET_ATSTMP; + } + if (ENET_EIR & ENET_EIR_RXF) { ENET_EIR = ENET_EIR_RXF; rx_ready = 1; @@ -706,7 +728,7 @@ bool enet_link_is_full_duplex() { return linkIsFullDuplex; } -bool enet_output_frame(const uint8_t *frame, size_t len) { +bool enet_output_frame(const uint8_t *frame, size_t len, bool doTimestamp) { if (!isInitialized) { return false; } @@ -719,13 +741,13 @@ bool enet_output_frame(const uint8_t *frame, size_t len) { #ifndef QNETHERNET_BUFFERS_IN_RAM1 arm_dcache_flush_delete(bdPtr->buffer, MULTIPLE_OF_32(len + ETH_PAD_SIZE)); #endif // !QNETHERNET_BUFFERS_IN_RAM1 - update_bufdesc(bdPtr, len + ETH_PAD_SIZE); + update_bufdesc(bdPtr, len + ETH_PAD_SIZE, doTimestamp); #else memcpy(bdPtr->buffer, frame, len); #ifndef QNETHERNET_BUFFERS_IN_RAM1 arm_dcache_flush_delete(bdPtr->buffer, MULTIPLE_OF_32(len)); #endif // !QNETHERNET_BUFFERS_IN_RAM1 - update_bufdesc(bdPtr, len); + update_bufdesc(bdPtr, len, doTimestamp); #endif // ETH_PAD_SIZE return true; } @@ -924,7 +946,7 @@ void enet_ieee1588_init() { ENET_ATCR = ENET_ATCR_PINPER | ENET_ATCR_Reserved | ENET_ATCR_PEREN | ENET_ATCR_EN; - ENET_EIMR |= ENET_EIMR_TS_TIMER; + ENET_EIMR |= ENET_EIMR_TS_AVAIL | ENET_EIMR_TS_TIMER; } void enet_ieee1588_deinit() { @@ -973,6 +995,18 @@ bool enet_ieee1588_write_timer(const struct IEEE1588Timestamp *t) { return true; } +bool enet_ieee1588_read_and_clear_tx_timestamp(uint32_t *timestamp) { + // NOTE: This is not "concurrent safe" + if (hasTxTimestamp) { + hasTxTimestamp = false; + if (timestamp != NULL) { + *timestamp = txTimestamp; + } + return true; + } + return false; +} + bool enet_ieee1588_adjust_timer(uint32_t corrInc, uint32_t corrPeriod) { if (corrInc >= 128 || corrPeriod >= (1U << 31)) { return false; diff --git a/src/lwip_t41.h b/src/lwip_t41.h index c74bb1aa8..2438aa946 100644 --- a/src/lwip_t41.h +++ b/src/lwip_t41.h @@ -90,10 +90,11 @@ bool enet_link_is_full_duplex(); // Outputs a raw ethernet frame. This returns false if frame is NULL or if the // length is not in the range 60-(MAX_FRAME_LEN-4) (excludes the FCS (frame -// check sequence)). This also returns false if Ethernet is not initialized. +// check sequence)). This also returns false if Ethernet is not initialized. The +// `doTimestamp` parameter specifies whether to timestamp the packet. // // This adds any extra padding bytes given by ETH_PAD_SIZE. -bool enet_output_frame(const uint8_t *frame, size_t len); +bool enet_output_frame(const uint8_t *frame, size_t len, bool doTimestamp); #ifndef QNETHERNET_PROMISCUOUS_MODE // For joining and leaving multicast groups; these call @@ -137,6 +138,16 @@ bool enet_ieee1588_read_timer(struct IEEE1588Timestamp *t); // This will return false if the argument is NULL. bool enet_ieee1588_write_timer(const struct IEEE1588Timestamp *t); +// Returns whether an IEEE 1588 transmit timestamp is available. If available +// and the parameter is not NULL then it is assigned to `*timestamp`. This +// clears the timestamp state so that a subsequent call will return false. +// +// This function is used after sending a packet having its transmit timestamp +// sent. Note that this only returns the latest value, so if a second +// timestamped packet is sent before retrieving the timestamp for the first +// then this will return the second timestamp (if already available). +bool enet_ieee1588_read_and_clear_tx_timestamp(uint32_t *timestamp); + // Directly adjust the correction increase and correction period. To adjust the // timer in "nanoseconds per second", see `enet_ieee1588_adjust_freq`. This // returns whether successful. From e6a4e128f6c8d59d21ffc4bf4532343ac40ee947 Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Fri, 14 Jan 2022 16:06:45 -0800 Subject: [PATCH 12/24] Change TX timestamp API to require calling a function before sending EthernetIEEE1588.timestampNextFrame(). --- src/QNEthernetFrame.cpp | 18 ++---------------- src/QNEthernetFrame.h | 9 --------- src/QNEthernetIEEE1588.cpp | 4 ++++ src/QNEthernetIEEE1588.h | 8 ++++++++ src/QNEthernetUDP.cpp | 29 ++--------------------------- src/QNEthernetUDP.h | 23 +---------------------- src/lwip_t41.c | 19 ++++++++++++------- src/lwip_t41.h | 7 +++++-- 8 files changed, 34 insertions(+), 83 deletions(-) diff --git a/src/QNEthernetFrame.cpp b/src/QNEthernetFrame.cpp index 9eab74a91..8a25206cf 100644 --- a/src/QNEthernetFrame.cpp +++ b/src/QNEthernetFrame.cpp @@ -230,32 +230,18 @@ void EthernetFrameClass::beginVLANFrame(const uint8_t dstAddr[6], } bool EthernetFrameClass::endFrame() { - return endFrame(false); -} - -bool EthernetFrameClass::endFrameWithTimestamp() { - return endFrame(true); -} - -bool EthernetFrameClass::endFrame(bool doTimestamp) { if (!hasOutFrame_) { return false; } hasOutFrame_ = false; - bool retval = enet_output_frame(outFrame_.data.data(), outFrame_.data.size(), - doTimestamp); + bool retval = enet_output_frame(outFrame_.data.data(), outFrame_.data.size()); outFrame_.data.clear(); return retval; } bool EthernetFrameClass::send(const uint8_t *frame, size_t len) const { - return enet_output_frame(frame, len, false); -} - -bool EthernetFrameClass::sendWithTimestamp(const uint8_t *frame, - size_t len) const { - return enet_output_frame(frame, len, true); + return enet_output_frame(frame, len); } size_t EthernetFrameClass::write(uint8_t b) { diff --git a/src/QNEthernetFrame.h b/src/QNEthernetFrame.h index 536bec076..f43e7205b 100644 --- a/src/QNEthernetFrame.h +++ b/src/QNEthernetFrame.h @@ -80,9 +80,6 @@ class EthernetFrameClass final : public Stream { // 3. The length is not in the range 60-(maxFrameLen()-4) (excludes the FCS). bool endFrame(); - // Same as `endFrame()` but also timestamps the frame. - bool endFrameWithTimestamp(); - // Sends a frame and returns whether the send was successful. This causes less // overhead than beginFrame()/write()/endFrame(). // @@ -95,9 +92,6 @@ class EthernetFrameClass final : public Stream { // 3. The length is not in the range 60-(maxFrameLen()-4) (excludes the FCS). bool send(const uint8_t *frame, size_t len) const; - // Same as `send(frame, len)`, but adds a timestamp to the frame. - bool sendWithTimestamp(const uint8_t *frame, size_t len) const; - // Bring Print::write functions into scope using Print::write; @@ -150,9 +144,6 @@ class EthernetFrameClass final : public Stream { static err_t recvFunc(struct pbuf *p, struct netif *netif); - // Ends the frame and optionally adds a timestamp. - bool endFrame(bool doTimestamp); - // Checks if there's data still available in the packet. bool isAvailable() const; diff --git a/src/QNEthernetIEEE1588.cpp b/src/QNEthernetIEEE1588.cpp index 6e4a53bde..1067c0642 100644 --- a/src/QNEthernetIEEE1588.cpp +++ b/src/QNEthernetIEEE1588.cpp @@ -31,6 +31,10 @@ bool EthernetIEEE1588Class::writeTimer(const IEEE1588Timestamp &t) const { return enet_ieee1588_write_timer(&t); } +void EthernetIEEE1588Class::timestampNextFrame() const { + enet_ieee1588_timestamp_next_frame(); +} + bool EthernetIEEE1588Class::readAndClearTxTimestamp(uint32_t *timestamp) const { return enet_ieee1588_read_and_clear_tx_timestamp(timestamp); } diff --git a/src/QNEthernetIEEE1588.h b/src/QNEthernetIEEE1588.h index e48be4596..02846bce4 100644 --- a/src/QNEthernetIEEE1588.h +++ b/src/QNEthernetIEEE1588.h @@ -39,10 +39,18 @@ class EthernetIEEE1588Class final { // Writes the current IEEE 1588 timer value. This returns whether successful. bool writeTimer(const IEEE1588Timestamp &t) const; + // Tells the driver to timestamp the next transmitted frame. This should be + // called before functions like `EthernetUDP::endPacket()`, + // `EthernetUDP::send()`, and any of the `EthernetFrame` send functions. + void timestampNextFrame() const; + // Attempts to retrieve the timestamp of the last transmitted frame and // returns whether one is available. If available and the parameter is not // NULL then the timestamp is assigned to `*timestamp`. This clears the // timestamp state so that a subsequent call will return false. + // + // This will always returns false if `EthernetIEEE1588.timestampNextFrame()` + // was not called before this. bool readAndClearTxTimestamp(uint32_t *timestamp) const; // Adjusts the raw correction settings. The increment must be in the range diff --git a/src/QNEthernetUDP.cpp b/src/QNEthernetUDP.cpp index f2e648cd6..0ec3b3d4d 100644 --- a/src/QNEthernetUDP.cpp +++ b/src/QNEthernetUDP.cpp @@ -294,14 +294,6 @@ bool EthernetUDP::beginPacket(const ip_addr_t *ipaddr, uint16_t port) { } int EthernetUDP::endPacket() { - return endPacket(false); -} - -bool EthernetUDP::endPacketWithTimestamp() { - return endPacket(true); -} - -bool EthernetUDP::endPacket(bool doTimestamp) { if (!hasOutPacket_) { return false; } @@ -319,7 +311,6 @@ bool EthernetUDP::endPacket(bool doTimestamp) { } pbuf_take(p, out_.data.data(), out_.data.size()); out_.data.clear(); - p->timestampValid = doTimestamp; bool retval = (udp_sendto(pcb_, p, &out_.addr, out_.port) == ERR_OK); pbuf_free(p); return retval; @@ -328,13 +319,7 @@ bool EthernetUDP::endPacket(bool doTimestamp) { bool EthernetUDP::send(const IPAddress &ip, uint16_t port, const uint8_t *data, size_t len) { ip_addr_t ipaddr IPADDR4_INIT(get_uint32(ip)); - return send(&ipaddr, port, data, len, false); -} - -bool EthernetUDP::sendWithTimestamp(const IPAddress &ip, uint16_t port, - const uint8_t *data, size_t len) { - ip_addr_t ipaddr IPADDR4_INIT(get_uint32(ip)); - return send(&ipaddr, port, data, len, true); + return send(&ipaddr, port, data, len); } bool EthernetUDP::send(const char *host, uint16_t port, @@ -346,18 +331,9 @@ bool EthernetUDP::send(const char *host, uint16_t port, return send(ip, port, data, len); } -bool EthernetUDP::sendWithTimestamp(const char *host, uint16_t port, - const uint8_t *data, size_t len) { - IPAddress ip; - if (!DNSClient::getHostByName(host, ip, kDNSLookupTimeout)) { - return false; - } - return sendWithTimestamp(ip, port, data, len); -} bool EthernetUDP::send(const ip_addr_t *ipaddr, uint16_t port, - const uint8_t *data, size_t len, - bool doTimestamp) { + const uint8_t *data, size_t len) { if (len > UINT16_MAX) { return false; } @@ -374,7 +350,6 @@ bool EthernetUDP::send(const ip_addr_t *ipaddr, uint16_t port, return false; } pbuf_take(p, data, len); - p->timestampValid = doTimestamp; bool retval = (udp_sendto(pcb_, p, ipaddr, port) == ERR_OK); pbuf_free(p); return retval; diff --git a/src/QNEthernetUDP.h b/src/QNEthernetUDP.h index 93a32dd5f..8b84daeda 100644 --- a/src/QNEthernetUDP.h +++ b/src/QNEthernetUDP.h @@ -59,11 +59,6 @@ class EthernetUDP : public UDP { int beginPacket(const char *host, uint16_t port) final; int endPacket() final; - // Same as `endPacket()` but adds a timestamp. - // - // Timestamping must have been enabled first with EthernetIEEE1588.begin(). - bool endPacketWithTimestamp(); - // Sends a UDP packet and returns whether the attempt was successful. This // combines the functions of beginPacket(), write(), and endPacket(), and // causes less overhead. @@ -73,18 +68,6 @@ class EthernetUDP : public UDP { // Calls the other send() function after performing a DNS lookup. bool send(const char *host, uint16_t port, const uint8_t *data, size_t len); - // Same as `send(ip, port, data, len)` but adds a timestamp to the packet. - // - // Timestamping must have been enabled first with EthernetIEEE1588.begin(). - bool sendWithTimestamp(const IPAddress &ip, uint16_t port, - const uint8_t *data, size_t len); - - // Same as `send(host, port, data, len)` but adds a timestamp to the packet. - // - // Timestamping must have been enabled first with EthernetIEEE1588.begin(). - bool sendWithTimestamp(const char *host, uint16_t port, - const uint8_t *data, size_t len); - // Bring Print::write functions into scope using Print::write; @@ -142,11 +125,7 @@ class EthernetUDP : public UDP { // ip_addr_t versions of transmission functions bool beginPacket(const ip_addr_t *ipaddr, uint16_t port); bool send(const ip_addr_t *ipaddr, uint16_t port, - const uint8_t *data, size_t len, - bool doTimestamp); - - // Timestamping send functions - bool endPacket(bool doTimestamp); + const uint8_t *data, size_t len); // Checks if there's data still available in the packet. bool isAvailable() const; diff --git a/src/lwip_t41.c b/src/lwip_t41.c index 6a982b600..678dbaa25 100644 --- a/src/lwip_t41.c +++ b/src/lwip_t41.c @@ -157,6 +157,7 @@ static bool isInitialized = false; // IEEE 1588 static volatile uint32_t ieee1588Seconds = 0; // Since the timer was started +static volatile bool doTimestampNext = false; static volatile bool hasTxTimestamp = false; static volatile uint32_t txTimestamp = 0; @@ -440,8 +441,7 @@ static inline volatile enetbufferdesc_t *get_bufdesc() { // Update a buffer descriptor. Meant to be used with get_bufdesc(). static inline void update_bufdesc(volatile enetbufferdesc_t *bdPtr, - uint16_t len, - bool doTimestamp) { + uint16_t len) { bdPtr->length = len; bdPtr->status = (bdPtr->status & kEnetTxBdWrap) | kEnetTxBdTransmitCrc | @@ -449,7 +449,8 @@ static inline void update_bufdesc(volatile enetbufferdesc_t *bdPtr, kEnetTxBdReady; hasTxTimestamp = false; // The timestamp isn't yet available - if (doTimestamp) { + if (doTimestampNext) { + doTimestampNext = false; bdPtr->extend1 |= kEnetTxBdTimestamp; } else { bdPtr->extend1 &= ~kEnetTxBdTimestamp; @@ -475,7 +476,7 @@ static err_t t41_low_level_output(struct netif *netif, struct pbuf *p) { #ifndef QNETHERNET_BUFFERS_IN_RAM1 arm_dcache_flush_delete(bdPtr->buffer, MULTIPLE_OF_32(copied)); #endif // !QNETHERNET_BUFFERS_IN_RAM1 - update_bufdesc(bdPtr, copied, p->timestampValid); + update_bufdesc(bdPtr, copied); return ERR_OK; } @@ -728,7 +729,7 @@ bool enet_link_is_full_duplex() { return linkIsFullDuplex; } -bool enet_output_frame(const uint8_t *frame, size_t len, bool doTimestamp) { +bool enet_output_frame(const uint8_t *frame, size_t len) { if (!isInitialized) { return false; } @@ -741,13 +742,13 @@ bool enet_output_frame(const uint8_t *frame, size_t len, bool doTimestamp) { #ifndef QNETHERNET_BUFFERS_IN_RAM1 arm_dcache_flush_delete(bdPtr->buffer, MULTIPLE_OF_32(len + ETH_PAD_SIZE)); #endif // !QNETHERNET_BUFFERS_IN_RAM1 - update_bufdesc(bdPtr, len + ETH_PAD_SIZE, doTimestamp); + update_bufdesc(bdPtr, len + ETH_PAD_SIZE); #else memcpy(bdPtr->buffer, frame, len); #ifndef QNETHERNET_BUFFERS_IN_RAM1 arm_dcache_flush_delete(bdPtr->buffer, MULTIPLE_OF_32(len)); #endif // !QNETHERNET_BUFFERS_IN_RAM1 - update_bufdesc(bdPtr, len, doTimestamp); + update_bufdesc(bdPtr, len); #endif // ETH_PAD_SIZE return true; } @@ -995,6 +996,10 @@ bool enet_ieee1588_write_timer(const struct IEEE1588Timestamp *t) { return true; } +void enet_ieee1588_timestamp_next_frame() { + doTimestampNext = true; +} + bool enet_ieee1588_read_and_clear_tx_timestamp(uint32_t *timestamp) { // NOTE: This is not "concurrent safe" if (hasTxTimestamp) { diff --git a/src/lwip_t41.h b/src/lwip_t41.h index 2438aa946..283a6d85b 100644 --- a/src/lwip_t41.h +++ b/src/lwip_t41.h @@ -91,10 +91,10 @@ bool enet_link_is_full_duplex(); // Outputs a raw ethernet frame. This returns false if frame is NULL or if the // length is not in the range 60-(MAX_FRAME_LEN-4) (excludes the FCS (frame // check sequence)). This also returns false if Ethernet is not initialized. The -// `doTimestamp` parameter specifies whether to timestamp the packet. +// frame is timestamped if `enet_timestamp_next_frame()` was called first. // // This adds any extra padding bytes given by ETH_PAD_SIZE. -bool enet_output_frame(const uint8_t *frame, size_t len, bool doTimestamp); +bool enet_output_frame(const uint8_t *frame, size_t len); #ifndef QNETHERNET_PROMISCUOUS_MODE // For joining and leaving multicast groups; these call @@ -138,6 +138,9 @@ bool enet_ieee1588_read_timer(struct IEEE1588Timestamp *t); // This will return false if the argument is NULL. bool enet_ieee1588_write_timer(const struct IEEE1588Timestamp *t); +// Tells the driver to timestamp the next transmitted frame. +void enet_ieee1588_timestamp_next_frame(); + // Returns whether an IEEE 1588 transmit timestamp is available. If available // and the parameter is not NULL then it is assigned to `*timestamp`. This // clears the timestamp state so that a subsequent call will return false. From 03e500d99c03530c187f43aaa352e22edb1b54d4 Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Fri, 14 Jan 2022 16:54:47 -0800 Subject: [PATCH 13/24] Change timestamp retrieval to use IEEE1588Timestamp --- src/QNEthernetFrame.cpp | 19 +++++++++++++------ src/QNEthernetFrame.h | 4 ++-- src/QNEthernetIEEE1588.cpp | 14 ++++++++++++-- src/QNEthernetIEEE1588.h | 5 ++++- src/QNEthernetUDP.cpp | 22 +++++++++++++++------- src/QNEthernetUDP.h | 5 +++-- 6 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/QNEthernetFrame.cpp b/src/QNEthernetFrame.cpp index 8a25206cf..c27951745 100644 --- a/src/QNEthernetFrame.cpp +++ b/src/QNEthernetFrame.cpp @@ -53,7 +53,15 @@ err_t EthernetFrameClass::recvFunc(struct pbuf *p, struct netif *netif) { } frame.hasTimestamp = pHead->timestampValid; - frame.timestamp = pHead->timestamp; + if (frame.hasTimestamp) { + uint32_t ts = pHead->timestamp; + EthernetIEEE1588.readTimer(frame.timestamp); + if (frame.timestamp.nsec < ts) { + // The timer has wrapped around + frame.timestamp.sec--; + } + frame.timestamp.nsec = ts; + } // Increment the size if (EthernetFrame.inBufSize_ != 0 && @@ -91,7 +99,8 @@ int EthernetFrameClass::parseFrame() { frame_ = inBuf_[inBufTail_]; inBuf_[inBufTail_].data.clear(); inBuf_[inBufTail_].hasTimestamp = false; - inBuf_[inBufTail_].timestamp = 0; + inBuf_[inBufTail_].timestamp.sec = 0; + inBuf_[inBufTail_].timestamp.nsec = 0; inBufTail_ = (inBufTail_ + 1) % inBuf_.size(); inBufSize_--; @@ -184,12 +193,10 @@ void EthernetFrameClass::setReceiveQueueSize(size_t size) { } } -bool EthernetFrameClass::timestamp(uint32_t *timestamp) const { +bool EthernetFrameClass::timestamp(IEEE1588Timestamp ×tamp) const { // NOTE: This is not "concurrent safe" if (frame_.hasTimestamp) { - if (timestamp != nullptr) { - *timestamp = frame_.timestamp; - } + timestamp = frame_.timestamp; return true; } return false; diff --git a/src/QNEthernetFrame.h b/src/QNEthernetFrame.h index f43e7205b..226f72576 100644 --- a/src/QNEthernetFrame.h +++ b/src/QNEthernetFrame.h @@ -130,13 +130,13 @@ class EthernetFrameClass final : public Stream { // Gets the IEEE 1588 timestamp for the received frame and assigns it to the // `timestamp` parameter, if available. This returns whether the received // frame has a timestamp. - bool timestamp(uint32_t *timestamp) const; + bool timestamp(IEEE1588Timestamp ×tamp) const; private: struct Frame { std::vector data; volatile bool hasTimestamp = false; - volatile uint32_t timestamp = 0; + IEEE1588Timestamp timestamp{0, 0}; }; EthernetFrameClass(); diff --git a/src/QNEthernetIEEE1588.cpp b/src/QNEthernetIEEE1588.cpp index 1067c0642..64a817f8c 100644 --- a/src/QNEthernetIEEE1588.cpp +++ b/src/QNEthernetIEEE1588.cpp @@ -35,8 +35,18 @@ void EthernetIEEE1588Class::timestampNextFrame() const { enet_ieee1588_timestamp_next_frame(); } -bool EthernetIEEE1588Class::readAndClearTxTimestamp(uint32_t *timestamp) const { - return enet_ieee1588_read_and_clear_tx_timestamp(timestamp); +bool EthernetIEEE1588Class::readAndClearTxTimestamp(IEEE1588Timestamp ×tamp) const { + uint32_t ts; + if (!enet_ieee1588_read_and_clear_tx_timestamp(&ts)) { + return false; + } + + readTimer(timestamp); + if (timestamp.nsec < ts) { + timestamp.sec--; + } + timestamp.nsec = ts; + return true; } bool EthernetIEEE1588Class::adjustTimer(uint32_t corrInc, diff --git a/src/QNEthernetIEEE1588.h b/src/QNEthernetIEEE1588.h index 02846bce4..9e2e2881f 100644 --- a/src/QNEthernetIEEE1588.h +++ b/src/QNEthernetIEEE1588.h @@ -51,7 +51,10 @@ class EthernetIEEE1588Class final { // // This will always returns false if `EthernetIEEE1588.timestampNextFrame()` // was not called before this. - bool readAndClearTxTimestamp(uint32_t *timestamp) const; + // + // In order for the "seconds" field to be accurate, this must be called as + // soon as possible after sending the frame. + bool readAndClearTxTimestamp(IEEE1588Timestamp ×tamp) const; // Adjusts the raw correction settings. The increment must be in the range // 0-127 and the period must be in the range 0-(2^31-1), zero meaning diff --git a/src/QNEthernetUDP.cpp b/src/QNEthernetUDP.cpp index 0ec3b3d4d..f0afeebba 100644 --- a/src/QNEthernetUDP.cpp +++ b/src/QNEthernetUDP.cpp @@ -54,7 +54,15 @@ void EthernetUDP::recvFunc(void *arg, struct udp_pcb *pcb, struct pbuf *p, } } packet.hasTimestamp = pHead->timestampValid; - packet.timestamp = pHead->timestamp; + if (packet.hasTimestamp) { + uint32_t ts = pHead->timestamp; + EthernetIEEE1588.readTimer(packet.timestamp); + if (packet.timestamp.nsec < ts) { + // The timer has wrapped around + packet.timestamp.sec--; + } + packet.timestamp.nsec = ts; + } packet.addr = *addr; packet.port = port; @@ -148,7 +156,8 @@ void EthernetUDP::stop() { packet_.addr = *IP_ANY_TYPE; packet_.port = 0; packet_.hasTimestamp = false; - packet_.timestamp = 0; + packet_.timestamp.sec = 0; + packet_.timestamp.nsec = 0; } EthernetUDP::operator bool() const { @@ -173,7 +182,8 @@ int EthernetUDP::parsePacket() { packet_ = inBuf_[inBufTail_]; inBuf_[inBufTail_].data.clear(); inBuf_[inBufTail_].hasTimestamp = false; - inBuf_[inBufTail_].timestamp = 0; + inBuf_[inBufTail_].timestamp.sec = 0; + inBuf_[inBufTail_].timestamp.nsec = 0; inBufTail_ = (inBufTail_ + 1) % inBuf_.size(); inBufSize_--; @@ -247,12 +257,10 @@ uint16_t EthernetUDP::remotePort() { return packet_.port; } -bool EthernetUDP::timestamp(uint32_t *timestamp) const { +bool EthernetUDP::timestamp(IEEE1588Timestamp ×tamp) const { // NOTE: This is not "concurrent safe" if (packet_.hasTimestamp) { - if (timestamp != nullptr) { - *timestamp = packet_.timestamp; - } + timestamp = packet_.timestamp; return true; } return false; diff --git a/src/QNEthernetUDP.h b/src/QNEthernetUDP.h index 8b84daeda..b44f5a588 100644 --- a/src/QNEthernetUDP.h +++ b/src/QNEthernetUDP.h @@ -17,6 +17,7 @@ #include "lwip/ip_addr.h" #include "lwip/opt.h" #include "lwip/udp.h" +#include "lwip_t41.h" namespace qindesign { namespace network { @@ -108,7 +109,7 @@ class EthernetUDP : public UDP { // Gets the IEEE 1588 timestamp for the received packet and assigns it to the // `timestamp` parameter, if available. This returns whether the received // packet has a timestamp. - bool timestamp(uint32_t *timestamp) const; + bool timestamp(IEEE1588Timestamp ×tamp) const; private: struct Packet { @@ -116,7 +117,7 @@ class EthernetUDP : public UDP { ip_addr_t addr = *IP_ANY_TYPE; volatile uint16_t port = 0; volatile bool hasTimestamp = false; - volatile uint32_t timestamp = 0; + IEEE1588Timestamp timestamp{0, 0}; }; static void recvFunc(void *arg, struct udp_pcb *pcb, struct pbuf *p, From 0ecf085e48f1753f0e814b2c6648e7dfe67720dc Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Fri, 14 Jan 2022 17:10:35 -0800 Subject: [PATCH 14/24] Change uses of IEEE1588Timestamp to timespec --- src/QNEthernetFrame.cpp | 12 ++++++------ src/QNEthernetFrame.h | 5 +++-- src/QNEthernetIEEE1588.cpp | 12 ++++++------ src/QNEthernetIEEE1588.h | 7 ++++--- src/QNEthernetUDP.cpp | 16 ++++++++-------- src/QNEthernetUDP.h | 6 +++--- src/lwip_t41.c | 14 +++++++------- src/lwip_t41.h | 10 +++------- 8 files changed, 40 insertions(+), 42 deletions(-) diff --git a/src/QNEthernetFrame.cpp b/src/QNEthernetFrame.cpp index c27951745..461fa0e50 100644 --- a/src/QNEthernetFrame.cpp +++ b/src/QNEthernetFrame.cpp @@ -56,11 +56,11 @@ err_t EthernetFrameClass::recvFunc(struct pbuf *p, struct netif *netif) { if (frame.hasTimestamp) { uint32_t ts = pHead->timestamp; EthernetIEEE1588.readTimer(frame.timestamp); - if (frame.timestamp.nsec < ts) { + if (static_cast(frame.timestamp.tv_nsec) < ts) { // The timer has wrapped around - frame.timestamp.sec--; + frame.timestamp.tv_sec--; } - frame.timestamp.nsec = ts; + frame.timestamp.tv_nsec = ts; } // Increment the size @@ -99,8 +99,8 @@ int EthernetFrameClass::parseFrame() { frame_ = inBuf_[inBufTail_]; inBuf_[inBufTail_].data.clear(); inBuf_[inBufTail_].hasTimestamp = false; - inBuf_[inBufTail_].timestamp.sec = 0; - inBuf_[inBufTail_].timestamp.nsec = 0; + inBuf_[inBufTail_].timestamp.tv_sec = 0; + inBuf_[inBufTail_].timestamp.tv_nsec = 0; inBufTail_ = (inBufTail_ + 1) % inBuf_.size(); inBufSize_--; @@ -193,7 +193,7 @@ void EthernetFrameClass::setReceiveQueueSize(size_t size) { } } -bool EthernetFrameClass::timestamp(IEEE1588Timestamp ×tamp) const { +bool EthernetFrameClass::timestamp(timespec ×tamp) const { // NOTE: This is not "concurrent safe" if (frame_.hasTimestamp) { timestamp = frame_.timestamp; diff --git a/src/QNEthernetFrame.h b/src/QNEthernetFrame.h index 226f72576..be7c6262c 100644 --- a/src/QNEthernetFrame.h +++ b/src/QNEthernetFrame.h @@ -11,6 +11,7 @@ // C++ includes #include +#include #include #include @@ -130,13 +131,13 @@ class EthernetFrameClass final : public Stream { // Gets the IEEE 1588 timestamp for the received frame and assigns it to the // `timestamp` parameter, if available. This returns whether the received // frame has a timestamp. - bool timestamp(IEEE1588Timestamp ×tamp) const; + bool timestamp(timespec ×tamp) const; private: struct Frame { std::vector data; volatile bool hasTimestamp = false; - IEEE1588Timestamp timestamp{0, 0}; + timespec timestamp{0, 0}; }; EthernetFrameClass(); diff --git a/src/QNEthernetIEEE1588.cpp b/src/QNEthernetIEEE1588.cpp index 64a817f8c..280d7f203 100644 --- a/src/QNEthernetIEEE1588.cpp +++ b/src/QNEthernetIEEE1588.cpp @@ -23,11 +23,11 @@ void EthernetIEEE1588Class::end() const { enet_ieee1588_deinit(); } -bool EthernetIEEE1588Class::readTimer(IEEE1588Timestamp &t) const { +bool EthernetIEEE1588Class::readTimer(timespec &t) const { return enet_ieee1588_read_timer(&t); } -bool EthernetIEEE1588Class::writeTimer(const IEEE1588Timestamp &t) const { +bool EthernetIEEE1588Class::writeTimer(const timespec &t) const { return enet_ieee1588_write_timer(&t); } @@ -35,17 +35,17 @@ void EthernetIEEE1588Class::timestampNextFrame() const { enet_ieee1588_timestamp_next_frame(); } -bool EthernetIEEE1588Class::readAndClearTxTimestamp(IEEE1588Timestamp ×tamp) const { +bool EthernetIEEE1588Class::readAndClearTxTimestamp(timespec ×tamp) const { uint32_t ts; if (!enet_ieee1588_read_and_clear_tx_timestamp(&ts)) { return false; } readTimer(timestamp); - if (timestamp.nsec < ts) { - timestamp.sec--; + if (static_cast(timestamp.tv_nsec) < ts) { + timestamp.tv_sec--; } - timestamp.nsec = ts; + timestamp.tv_nsec = ts; return true; } diff --git a/src/QNEthernetIEEE1588.h b/src/QNEthernetIEEE1588.h index 9e2e2881f..8d430135a 100644 --- a/src/QNEthernetIEEE1588.h +++ b/src/QNEthernetIEEE1588.h @@ -9,6 +9,7 @@ // C++ includes #include +#include #include "lwip_t41.h" @@ -34,10 +35,10 @@ class EthernetIEEE1588Class final { void end() const; // Reads the current IEEE 1588 timer value. This returns whether successful. - bool readTimer(IEEE1588Timestamp &t) const; + bool readTimer(timespec &t) const; // Writes the current IEEE 1588 timer value. This returns whether successful. - bool writeTimer(const IEEE1588Timestamp &t) const; + bool writeTimer(const timespec &t) const; // Tells the driver to timestamp the next transmitted frame. This should be // called before functions like `EthernetUDP::endPacket()`, @@ -54,7 +55,7 @@ class EthernetIEEE1588Class final { // // In order for the "seconds" field to be accurate, this must be called as // soon as possible after sending the frame. - bool readAndClearTxTimestamp(IEEE1588Timestamp ×tamp) const; + bool readAndClearTxTimestamp(timespec ×tamp) const; // Adjusts the raw correction settings. The increment must be in the range // 0-127 and the period must be in the range 0-(2^31-1), zero meaning diff --git a/src/QNEthernetUDP.cpp b/src/QNEthernetUDP.cpp index f0afeebba..2f7cad8c0 100644 --- a/src/QNEthernetUDP.cpp +++ b/src/QNEthernetUDP.cpp @@ -57,11 +57,11 @@ void EthernetUDP::recvFunc(void *arg, struct udp_pcb *pcb, struct pbuf *p, if (packet.hasTimestamp) { uint32_t ts = pHead->timestamp; EthernetIEEE1588.readTimer(packet.timestamp); - if (packet.timestamp.nsec < ts) { + if (static_cast(packet.timestamp.tv_nsec) < ts) { // The timer has wrapped around - packet.timestamp.sec--; + packet.timestamp.tv_sec--; } - packet.timestamp.nsec = ts; + packet.timestamp.tv_nsec = ts; } packet.addr = *addr; packet.port = port; @@ -156,8 +156,8 @@ void EthernetUDP::stop() { packet_.addr = *IP_ANY_TYPE; packet_.port = 0; packet_.hasTimestamp = false; - packet_.timestamp.sec = 0; - packet_.timestamp.nsec = 0; + packet_.timestamp.tv_sec = 0; + packet_.timestamp.tv_nsec = 0; } EthernetUDP::operator bool() const { @@ -182,8 +182,8 @@ int EthernetUDP::parsePacket() { packet_ = inBuf_[inBufTail_]; inBuf_[inBufTail_].data.clear(); inBuf_[inBufTail_].hasTimestamp = false; - inBuf_[inBufTail_].timestamp.sec = 0; - inBuf_[inBufTail_].timestamp.nsec = 0; + inBuf_[inBufTail_].timestamp.tv_sec = 0; + inBuf_[inBufTail_].timestamp.tv_nsec = 0; inBufTail_ = (inBufTail_ + 1) % inBuf_.size(); inBufSize_--; @@ -257,7 +257,7 @@ uint16_t EthernetUDP::remotePort() { return packet_.port; } -bool EthernetUDP::timestamp(IEEE1588Timestamp ×tamp) const { +bool EthernetUDP::timestamp(timespec ×tamp) const { // NOTE: This is not "concurrent safe" if (packet_.hasTimestamp) { timestamp = packet_.timestamp; diff --git a/src/QNEthernetUDP.h b/src/QNEthernetUDP.h index b44f5a588..087c3d60a 100644 --- a/src/QNEthernetUDP.h +++ b/src/QNEthernetUDP.h @@ -9,6 +9,7 @@ // C++ includes #include +#include #include #include @@ -17,7 +18,6 @@ #include "lwip/ip_addr.h" #include "lwip/opt.h" #include "lwip/udp.h" -#include "lwip_t41.h" namespace qindesign { namespace network { @@ -109,7 +109,7 @@ class EthernetUDP : public UDP { // Gets the IEEE 1588 timestamp for the received packet and assigns it to the // `timestamp` parameter, if available. This returns whether the received // packet has a timestamp. - bool timestamp(IEEE1588Timestamp ×tamp) const; + bool timestamp(timespec ×tamp) const; private: struct Packet { @@ -117,7 +117,7 @@ class EthernetUDP : public UDP { ip_addr_t addr = *IP_ANY_TYPE; volatile uint16_t port = 0; volatile bool hasTimestamp = false; - IEEE1588Timestamp timestamp{0, 0}; + timespec timestamp{0, 0}; }; static void recvFunc(void *arg, struct udp_pcb *pcb, struct pbuf *p, diff --git a/src/lwip_t41.c b/src/lwip_t41.c index 678dbaa25..84cc44a8d 100644 --- a/src/lwip_t41.c +++ b/src/lwip_t41.c @@ -959,38 +959,38 @@ bool enet_ieee1588_is_enabled() { return ((ENET_ATCR & ENET_ATCR_EN) != 0); } -bool enet_ieee1588_read_timer(struct IEEE1588Timestamp *t) { +bool enet_ieee1588_read_timer(struct timespec *t) { if (t == NULL) { return false; } ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - t->sec = ieee1588Seconds; + t->tv_sec = ieee1588Seconds; ENET_ATCR |= ENET_ATCR_CAPTURE; while ((ENET_ATCR & ENET_ATCR_CAPTURE) != 0) { // Wait for bit to clear } - t->nsec = ENET_ATVR; + t->tv_nsec = ENET_ATVR; // The timer could have wrapped while we were doing stuff // Leave the interrupt set so that our internal timer will catch it if ((ENET_EIR & ENET_EIR_TS_TIMER) != 0) { - t->sec++; + t->tv_sec++; } } return true; } -bool enet_ieee1588_write_timer(const struct IEEE1588Timestamp *t) { +bool enet_ieee1588_write_timer(const struct timespec *t) { if (t == NULL) { return false; } ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - ieee1588Seconds = t->sec; - ENET_ATVR = t->nsec; + ieee1588Seconds = t->tv_sec; + ENET_ATVR = t->tv_nsec; } return true; diff --git a/src/lwip_t41.h b/src/lwip_t41.h index 283a6d85b..70b271d32 100644 --- a/src/lwip_t41.h +++ b/src/lwip_t41.h @@ -15,6 +15,7 @@ #include #include #include +#include #include "lwip/ip_addr.h" #include "lwip/netif.h" @@ -25,11 +26,6 @@ namespace qindesign { namespace network { #endif // __cplusplus -struct IEEE1588Timestamp { - uint32_t sec; // Seconds - uint32_t nsec; // Nanoseconds, 0-999,999,999 -}; - enum TimerChannelModes { kTimerChannelDisable = 0, kTimerChannelCaptureOnRising = 1, @@ -131,12 +127,12 @@ bool enet_ieee1588_is_enabled(); // Reads the IEEE 1588 timer. This returns whether successful. // // This will return false if the argument is NULL. -bool enet_ieee1588_read_timer(struct IEEE1588Timestamp *t); +bool enet_ieee1588_read_timer(struct timespec *t); // Writes the IEEE 1588 timer. This returns whether successful. // // This will return false if the argument is NULL. -bool enet_ieee1588_write_timer(const struct IEEE1588Timestamp *t); +bool enet_ieee1588_write_timer(const struct timespec *t); // Tells the driver to timestamp the next transmitted frame. void enet_ieee1588_timestamp_next_frame(); From cc3b3e83af4ab086321c36a5f7a12897fffcb915 Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Sat, 22 Jan 2022 07:13:42 -0800 Subject: [PATCH 15/24] Simplify TimerChannelModes enum use --- src/QNEthernetIEEE1588.cpp | 7 +++++-- src/QNEthernetIEEE1588.h | 17 +++++++++++++++-- src/lwip_t41.c | 17 +++++++++++------ src/lwip_t41.h | 35 ++++++----------------------------- 4 files changed, 37 insertions(+), 39 deletions(-) diff --git a/src/QNEthernetIEEE1588.cpp b/src/QNEthernetIEEE1588.cpp index 280d7f203..553a8437d 100644 --- a/src/QNEthernetIEEE1588.cpp +++ b/src/QNEthernetIEEE1588.cpp @@ -6,6 +6,8 @@ #include "QNEthernetIEEE1588.h" +#include "lwip_t41.h" + namespace qindesign { namespace network { @@ -60,13 +62,14 @@ bool EthernetIEEE1588Class::adjustFreq(int nsps) const { bool EthernetIEEE1588Class::setChannelMode(int channel, TimerChannelModes mode) const { - return enet_ieee1588_set_channel_mode(channel, mode); + return enet_ieee1588_set_channel_mode(channel, static_cast(mode)); } bool EthernetIEEE1588Class::setChannelOutputPulseWidth(int channel, TimerChannelModes mode, int pulseWidth) const { - return enet_ieee1588_set_channel_output_pulse_width(channel, mode, + return enet_ieee1588_set_channel_output_pulse_width(channel, + static_cast(mode), pulseWidth); } diff --git a/src/QNEthernetIEEE1588.h b/src/QNEthernetIEEE1588.h index 8d430135a..fb046d6eb 100644 --- a/src/QNEthernetIEEE1588.h +++ b/src/QNEthernetIEEE1588.h @@ -11,14 +11,27 @@ #include #include -#include "lwip_t41.h" - namespace qindesign { namespace network { // Provides an API for raw Ethernet frames, similar to the UDP API. class EthernetIEEE1588Class final { public: + enum class TimerChannelModes { + kDisable = 0, + kCaptureOnRising = 1, + kCaptureOnFalling = 2, + kCaptureOnBoth = 3, + kSoftwareCompare = 4, + kToggleOnCompare = 5, + kClearOnCompare = 6, + kSetOnCompare = 7, + kClearOnCompareSetOnOverflow = 10, + kSetOnCompareClearOnOverflow = 11, + kPulseLowOnCompare = 14, + kPulseHighOnCompare = 15, + }; + // Accesses the singleton instance. static EthernetIEEE1588Class &instance() { return instance_; diff --git a/src/lwip_t41.c b/src/lwip_t41.c index 84cc44a8d..3b961e4b3 100644 --- a/src/lwip_t41.c +++ b/src/lwip_t41.c @@ -1065,12 +1065,17 @@ static volatile uint32_t *tccrReg(int channel) { } -bool enet_ieee1588_set_channel_mode(int channel, enum TimerChannelModes mode) { +bool enet_ieee1588_set_channel_mode(int channel, int mode) { switch (mode) { - case kTimerChannelPulseLowOnCompare: - case kTimerChannelPulseHighOnCompare: + case 14: // kTimerChannelPulseLowOnCompare + case 15: // kTimerChannelPulseHighOnCompare + case 12: // Reserved + case 13: // Reserved return false; default: + if (mode < 0 || 0x0f < mode) { + return false; + } break; } @@ -1089,11 +1094,11 @@ bool enet_ieee1588_set_channel_mode(int channel, enum TimerChannelModes mode) { } bool enet_ieee1588_set_channel_output_pulse_width(int channel, - enum TimerChannelModes mode, + int mode, int pulseWidth) { switch (mode) { - case kTimerChannelPulseLowOnCompare: - case kTimerChannelPulseHighOnCompare: + case 14: // kTimerChannelPulseLowOnCompare + case 15: // kTimerChannelPulseHighOnCompare break; default: return true; diff --git a/src/lwip_t41.h b/src/lwip_t41.h index 70b271d32..0f50883cb 100644 --- a/src/lwip_t41.h +++ b/src/lwip_t41.h @@ -21,26 +21,6 @@ #include "lwip/netif.h" #include "lwip/prot/ethernet.h" -#ifdef __cplusplus -namespace qindesign { -namespace network { -#endif // __cplusplus - -enum TimerChannelModes { - kTimerChannelDisable = 0, - kTimerChannelCaptureOnRising = 1, - kTimerChannelCaptureOnFalling = 2, - kTimerChannelCaptureOnBoth = 3, - kTimerChannelSoftwareCompare = 4, - kTimerChannelToggleOnCompare = 5, - kTimerChannelClearOnCompare = 6, - kTimerChannelSetOnCompare = 7, - kTimerChannelClearOnCompareSetOnOverflow = 10, - kTimerChannelSetOnCompareClearOnOverflow = 11, - kTimerChannelPulseLowOnCompare = 14, - kTimerChannelPulseHighOnCompare = 15, -}; - #ifdef __cplusplus extern "C" { #endif @@ -163,9 +143,11 @@ bool enet_ieee1588_adjust_freq(int nsps); // Sets the channel mode for the given channel. This does not set the output // compare pulse modes. This returns whether successful. // -// This will return false for an unknown channel or if the mode is one of the -// output compare pulse modes. -bool enet_ieee1588_set_channel_mode(int channel, enum TimerChannelModes mode); +// This will return false if: +// 1. The channel is unknown, +// 2. The mode is one of the output compare pulse modes, or +// 3. The mode is a reserved value or unknown. +bool enet_ieee1588_set_channel_mode(int channel, int mode); // Sets the output compare pulse mode and pulse width for the given channel. // This returns whether successful. @@ -175,7 +157,7 @@ bool enet_ieee1588_set_channel_mode(int channel, enum TimerChannelModes mode); // 2. The mode is not one of the output compare pulse modes, or // 3. The pulse width is not in the range 1-32. bool enet_ieee1588_set_channel_output_pulse_width(int channel, - enum TimerChannelModes mode, + int mode, int pulseWidth); // Sets the channel compare value. This returns whether successful. @@ -191,11 +173,6 @@ bool enet_ieee1588_get_and_clear_channel_status(int channel); } // extern "C" #endif -#ifdef __cplusplus -} // namespace network -} // namespace qindesign -#endif // __cplusplus - #endif // ARDUINO_TEENSY41 #endif // QNE_LWIP_T41_H_ From bb4c0b1ece2e03cb7f0ff5ee9949c41c1e9656ea Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Wed, 26 Jan 2022 11:28:50 -0800 Subject: [PATCH 16/24] Change internal timestamps to use struct timespec The seconds are captured at the same time as the nanoseconds. This removes the need to adjust the seconds in calling code. --- src/QNEthernetFrame.cpp | 8 +------- src/QNEthernetIEEE1588.cpp | 15 +++------------ src/QNEthernetIEEE1588.h | 3 --- src/QNEthernetUDP.cpp | 8 +------- src/lwip_t41.c | 30 ++++++++++++++++++++---------- src/lwip_t41.h | 2 +- src/lwipopts.h | 5 +++-- 7 files changed, 29 insertions(+), 42 deletions(-) diff --git a/src/QNEthernetFrame.cpp b/src/QNEthernetFrame.cpp index 461fa0e50..7dbeec43d 100644 --- a/src/QNEthernetFrame.cpp +++ b/src/QNEthernetFrame.cpp @@ -54,13 +54,7 @@ err_t EthernetFrameClass::recvFunc(struct pbuf *p, struct netif *netif) { frame.hasTimestamp = pHead->timestampValid; if (frame.hasTimestamp) { - uint32_t ts = pHead->timestamp; - EthernetIEEE1588.readTimer(frame.timestamp); - if (static_cast(frame.timestamp.tv_nsec) < ts) { - // The timer has wrapped around - frame.timestamp.tv_sec--; - } - frame.timestamp.tv_nsec = ts; + frame.timestamp = pHead->timestamp; } // Increment the size diff --git a/src/QNEthernetIEEE1588.cpp b/src/QNEthernetIEEE1588.cpp index 553a8437d..94af43c4e 100644 --- a/src/QNEthernetIEEE1588.cpp +++ b/src/QNEthernetIEEE1588.cpp @@ -37,18 +37,9 @@ void EthernetIEEE1588Class::timestampNextFrame() const { enet_ieee1588_timestamp_next_frame(); } -bool EthernetIEEE1588Class::readAndClearTxTimestamp(timespec ×tamp) const { - uint32_t ts; - if (!enet_ieee1588_read_and_clear_tx_timestamp(&ts)) { - return false; - } - - readTimer(timestamp); - if (static_cast(timestamp.tv_nsec) < ts) { - timestamp.tv_sec--; - } - timestamp.tv_nsec = ts; - return true; +bool EthernetIEEE1588Class::readAndClearTxTimestamp( + struct timespec ×tamp) const { + return enet_ieee1588_read_and_clear_tx_timestamp(×tamp); } bool EthernetIEEE1588Class::adjustTimer(uint32_t corrInc, diff --git a/src/QNEthernetIEEE1588.h b/src/QNEthernetIEEE1588.h index fb046d6eb..a02b7d3b6 100644 --- a/src/QNEthernetIEEE1588.h +++ b/src/QNEthernetIEEE1588.h @@ -65,9 +65,6 @@ class EthernetIEEE1588Class final { // // This will always returns false if `EthernetIEEE1588.timestampNextFrame()` // was not called before this. - // - // In order for the "seconds" field to be accurate, this must be called as - // soon as possible after sending the frame. bool readAndClearTxTimestamp(timespec ×tamp) const; // Adjusts the raw correction settings. The increment must be in the range diff --git a/src/QNEthernetUDP.cpp b/src/QNEthernetUDP.cpp index 2f7cad8c0..cad8b19bf 100644 --- a/src/QNEthernetUDP.cpp +++ b/src/QNEthernetUDP.cpp @@ -55,13 +55,7 @@ void EthernetUDP::recvFunc(void *arg, struct udp_pcb *pcb, struct pbuf *p, } packet.hasTimestamp = pHead->timestampValid; if (packet.hasTimestamp) { - uint32_t ts = pHead->timestamp; - EthernetIEEE1588.readTimer(packet.timestamp); - if (static_cast(packet.timestamp.tv_nsec) < ts) { - // The timer has wrapped around - packet.timestamp.tv_sec--; - } - packet.timestamp.tv_nsec = ts; + packet.timestamp = pHead->timestamp; } packet.addr = *addr; packet.port = port; diff --git a/src/lwip_t41.c b/src/lwip_t41.c index 3b961e4b3..76a6a252e 100644 --- a/src/lwip_t41.c +++ b/src/lwip_t41.c @@ -159,7 +159,7 @@ static bool isInitialized = false; static volatile uint32_t ieee1588Seconds = 0; // Since the timer was started static volatile bool doTimestampNext = false; static volatile bool hasTxTimestamp = false; -static volatile uint32_t txTimestamp = 0; +static volatile struct timespec txTimestamp = {0, 0}; void enet_isr(); @@ -413,7 +413,14 @@ static struct pbuf *t41_low_level_input(volatile enetbufferdesc_t *bdPtr) { #endif // !QNETHERNET_BUFFERS_IN_RAM1 pbuf_take(p, bdPtr->buffer, p->tot_len); p->timestampValid = ((bdPtr->status & kEnetRxBdLast) != 0); - p->timestamp = bdPtr->timestamp; + if (p->timestampValid) { + enet_ieee1588_read_timer(&p->timestamp); + if ((unsigned long)p->timestamp.tv_nsec < bdPtr->timestamp) { + // The timer has wrapped around + p->timestamp.tv_sec--; + } + p->timestamp.tv_nsec = bdPtr->timestamp; + } LINK_STATS_INC(link.recv); } else { LINK_STATS_INC(link.drop); @@ -534,7 +541,8 @@ void enet_isr() { if ((ENET_EIR & ENET_EIR_TS_AVAIL) != 0) { ENET_EIR = ENET_EIR_TS_AVAIL; hasTxTimestamp = true; - txTimestamp = ENET_ATSTMP; + txTimestamp.tv_sec = ieee1588Seconds; + txTimestamp.tv_nsec = ENET_ATSTMP; } if (ENET_EIR & ENET_EIR_RXF) { @@ -1000,14 +1008,16 @@ void enet_ieee1588_timestamp_next_frame() { doTimestampNext = true; } -bool enet_ieee1588_read_and_clear_tx_timestamp(uint32_t *timestamp) { - // NOTE: This is not "concurrent safe" - if (hasTxTimestamp) { - hasTxTimestamp = false; - if (timestamp != NULL) { - *timestamp = txTimestamp; +bool enet_ieee1588_read_and_clear_tx_timestamp(struct timespec *timestamp) { + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + if (hasTxTimestamp) { + hasTxTimestamp = false; + if (timestamp != NULL) { + timestamp->tv_sec = txTimestamp.tv_sec; + timestamp->tv_nsec = txTimestamp.tv_nsec; + } + return true; } - return true; } return false; } diff --git a/src/lwip_t41.h b/src/lwip_t41.h index 0f50883cb..d2d132f76 100644 --- a/src/lwip_t41.h +++ b/src/lwip_t41.h @@ -125,7 +125,7 @@ void enet_ieee1588_timestamp_next_frame(); // sent. Note that this only returns the latest value, so if a second // timestamped packet is sent before retrieving the timestamp for the first // then this will return the second timestamp (if already available). -bool enet_ieee1588_read_and_clear_tx_timestamp(uint32_t *timestamp); +bool enet_ieee1588_read_and_clear_tx_timestamp(struct timespec *timestamp); // Directly adjust the correction increase and correction period. To adjust the // timer in "nanoseconds per second", see `enet_ieee1588_adjust_freq`. This diff --git a/src/lwipopts.h b/src/lwipopts.h index 64e19a11d..bc9f2b479 100644 --- a/src/lwipopts.h +++ b/src/lwipopts.h @@ -199,10 +199,11 @@ extern void *ram_heap; #define LWIP_PBUF_REF_T u8_t #define LWIP_PBUF_CUSTOM_DATA \ u8_t timestampValid; \ - u32_t timestamp; + struct timespec timestamp; #define LWIP_PBUF_CUSTOM_DATA_INIT(p) \ (p)->timestampValid = 0; \ - (p)->timestamp = 0; + (p)->timestamp.tv_sec = 0; \ + (p)->timestamp.tv_nsec = 0; // Network Interfaces options #define LWIP_SINGLE_NETIF 1 From 42708f473969cf06e8290e0a1fc6faebfa7cfbae Mon Sep 17 00:00:00 2001 From: Shawn Silverman Date: Thu, 10 Feb 2022 00:34:01 -0800 Subject: [PATCH 17/24] Add SNTPClientWithTimestamps demo --- .../SNTPClientWithTimestamps.ino | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 examples/SNTPClientWithTimestamps/SNTPClientWithTimestamps.ino diff --git a/examples/SNTPClientWithTimestamps/SNTPClientWithTimestamps.ino b/examples/SNTPClientWithTimestamps/SNTPClientWithTimestamps.ino new file mode 100644 index 000000000..f698ad55c --- /dev/null +++ b/examples/SNTPClientWithTimestamps/SNTPClientWithTimestamps.ino @@ -0,0 +1,168 @@ +// SPDX-FileCopyrightText: (c) 2021-2022 Shawn Silverman +// SPDX-License-Identifier: MIT + +// SNTPClientWithTimestamps demonstrates, using a simple SNTP client, +// how to send and receive timestamped packets. Think of this example +// as a vehicle to show these concepts and not as an SNTP client. +// +// This file is part of the QNEthernet library. + +// C++ includes +#include + +#include +#include + +using namespace qindesign::network; + +// -------------------------------------------------------------------------- +// Configuration +// -------------------------------------------------------------------------- + +constexpr uint32_t kDHCPTimeout = 10000; // 10 seconds +constexpr uint16_t kNTPPort = 123; + +// 01-Jan-1900 00:00:00 -> 01-Jan-1970 00:00:00 +constexpr uint32_t kEpochDiff = 2'208'988'800; + +// Epoch -> 07-Feb-2036 06:28:16 +constexpr uint32_t kBreakTime = 2'085'978'496; + +// -------------------------------------------------------------------------- +// Program state +// -------------------------------------------------------------------------- + +// UDP port. +EthernetUDP udp; + +// Buffer. +uint8_t buf[48]; + +// -------------------------------------------------------------------------- +// Main program +// -------------------------------------------------------------------------- + +// Program setup. +void setup() { + Serial.begin(115200); + while (!Serial && millis() < 4000) { + // Wait for Serial to initialize + } + stdPrint = &Serial; // Make printf work (a QNEthernet feature) + printf("Starting...\n"); + + uint8_t mac[6]; + Ethernet.macAddress(mac); + printf("MAC = %02x:%02x:%02x:%02x:%02x:%02x\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + printf("Starting Ethernet with DHCP...\n"); + if (!Ethernet.begin()) { + printf("Failed to start Ethernet\n"); + return; + } + if (!Ethernet.waitForLocalIP(kDHCPTimeout)) { + printf("Failed to get IP address from DHCP\n"); + return; + } + + IPAddress ip = Ethernet.localIP(); + printf(" Local IP = %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]); + ip = Ethernet.subnetMask(); + printf(" Subnet mask = %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]); + ip = Ethernet.gatewayIP(); + printf(" Gateway = %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]); + ip = Ethernet.dnsServerIP(); + printf(" DNS = %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]); + + EthernetIEEE1588.begin(); + + // Start UDP listening on the NTP port + udp.begin(kNTPPort); + + // Send an SNTP request + + memset(buf, 0, 48); + buf[0] = 0b00'100'011; // LI=0, VN=4, Mode=3 (Client) + + // Set the Transmit Timestamp + uint32_t t = Teensy3Clock.get(); + if (t >= kBreakTime) { + t -= kBreakTime; + } else { + t += kEpochDiff; + } + buf[40] = t >> 24; + buf[41] = t >> 16; + buf[42] = t >> 8; + buf[43] = t; + + // Send the packet with a timestamp + printf("Sending SNTP request to the gateway..."); + EthernetIEEE1588.timestampNextFrame(); + if (!udp.send(Ethernet.gatewayIP(), kNTPPort, buf, 48)) { + printf("ERROR."); + } + printf("\n"); + + // Get the timestamp of the transmitted packet + timespec ts; + while (!EthernetIEEE1588.readAndClearTxTimestamp(ts)) { + // Wait for the timestamp + } + printf("TX timestamp: %ld.%09ld s\n", ts.tv_sec, ts.tv_nsec); +} + +// Main program loop. +void loop() { + int size = udp.parsePacket(); + if (size != 48 && size != 68) { + return; + } + + // Get the timestamp of the received packet + timespec ts; + if (udp.timestamp(ts)) { + printf("RX timestamp: %ld.%09ld s\n", ts.tv_sec, ts.tv_nsec); + } + + if (udp.read(buf, 48) != 48) { + printf("Not enough bytes\n"); + return; + } + + // See: Section 5, "SNTP Client Operations" + int mode = buf[0] & 0x07; + if (((buf[0] & 0xc0) == 0xc0) || // LI == 3 (Alarm condition) + (buf[1] == 0) || // Stratum == 0 (Kiss-o'-Death) + !(mode == 4 || mode == 5)) { // Must be Server or Broadcast mode + printf("Discarding reply\n"); + return; + } + + uint32_t t = (uint32_t{buf[40]} << 24) | + (uint32_t{buf[41]} << 16) | + (uint32_t{buf[42]} << 8) | + uint32_t{buf[43]}; + if (t == 0) { + printf("Discarding reply\n"); + return; // Also discard when the Transmit Timestamp is zero + } + if ((t & 0x80000000U) == 0) { + // See: Section 3, "NTP Timestamp Format" + t += kBreakTime; + } else { + t -= kEpochDiff; + } + + // Set the RTC and time + Teensy3Clock.set(t); + setTime(t); + + // Print the time + tmElements_t tm; + breakTime(t, tm); + printf("SNTP reply: %04u-%02u-%02u %02u:%02u:%02u\n", + tm.Year + 1970, tm.Month, tm.Day, + tm.Hour, tm.Minute, tm.Second); +} From a8a0531f8cb83d476434c442535c3e25e7790c14 Mon Sep 17 00:00:00 2001 From: Jens Schleusner Date: Wed, 8 Mar 2023 16:13:33 +0100 Subject: [PATCH 18/24] Change enet_ieee1588_adjust_timer to correctly mask the timer registers --- examples/TimerAdjustment/TimerAdjustment.ino | 46 ++++++++++++++++++++ src/lwip_t41.c | 4 +- 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 examples/TimerAdjustment/TimerAdjustment.ino diff --git a/examples/TimerAdjustment/TimerAdjustment.ino b/examples/TimerAdjustment/TimerAdjustment.ino new file mode 100644 index 000000000..4a189f721 --- /dev/null +++ b/examples/TimerAdjustment/TimerAdjustment.ino @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: (c) 2023 Jens Schleusner +// SPDX-License-Identifier: MIT +// +// This file is part of the QNEthernet library. + +#include +using namespace qindesign::network; + +IntervalTimer myTimer; + +int adjustment = 0; +timespec tmlast; + +void setup() { + + Serial.begin(2000000); + Serial.println("Setup EthernetIEEE1588"); + + qindesign::network::Ethernet.begin(); + qindesign::network::EthernetIEEE1588.begin(); + + myTimer.begin(timerInterrupt, 1000000); + EthernetIEEE1588.readTimer(tmlast); + +} + +void timerInterrupt() { + timespec tm; + EthernetIEEE1588.readTimer(tm);//read current timer value + int lastadjustment=adjustment; + adjustment += 100; //increase timer spped by 100ns per second + EthernetIEEE1588.adjustFreq(adjustment); + + int diff = (static_cast(tm.tv_sec) - static_cast(tmlast.tv_sec)) * (1000 * 1000 * 1000) + (static_cast(tm.tv_nsec) - static_cast(tmlast.tv_nsec)); + diff -= (1000 * 1000 * 1000); + + Serial.printf("Adjustment:%d nsps ATINC %08" PRIX32 "h ATCOR %d Diff %d\n",lastadjustment,ENET_ATINC,ENET_ATCOR,diff); + //Serial.printf("%d %d\n", lastadjustment, diff); + + tmlast = tm; + +} + +void loop() { + +} diff --git a/src/lwip_t41.c b/src/lwip_t41.c index 76a6a252e..0bf3d587b 100644 --- a/src/lwip_t41.c +++ b/src/lwip_t41.c @@ -1026,8 +1026,8 @@ bool enet_ieee1588_adjust_timer(uint32_t corrInc, uint32_t corrPeriod) { if (corrInc >= 128 || corrPeriod >= (1U << 31)) { return false; } - CLRSET(ENET_ATINC, ENET_ATINC_INC_MASK, ENET_ATINC_INC(corrInc)); - ENET_ATCOR = corrPeriod | ENET_ATCOR_COR_MASK; + CLRSET(ENET_ATINC, ENET_ATINC_INC_MASK, ENET_ATINC_INC_CORR(corrInc)); + ENET_ATCOR = corrPeriod & ENET_ATCOR_COR_MASK; return true; } From eb960c68af49473f95a1f331d4c4b456248cd6d6 Mon Sep 17 00:00:00 2001 From: Jens Schleusner Date: Wed, 8 Mar 2023 16:35:25 +0100 Subject: [PATCH 19/24] Change ieee1588 timer mode setup to remove side effects while changing registers. Add PPSOut example --- examples/PPSOut/PPSOut.ino | 41 +++++++++++++++++++++++++++++ src/QNEthernetIEEE1588.cpp | 2 -- src/QNEthernetIEEE1588.h | 6 ++--- src/lwip_t41.c | 53 ++++++++++++++++++++------------------ src/lwip_t41.h | 4 +-- 5 files changed, 72 insertions(+), 34 deletions(-) create mode 100644 examples/PPSOut/PPSOut.ino diff --git a/examples/PPSOut/PPSOut.ino b/examples/PPSOut/PPSOut.ino new file mode 100644 index 000000000..d114201ac --- /dev/null +++ b/examples/PPSOut/PPSOut.ino @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: (c) 2023 Jens Schleusner +// SPDX-License-Identifier: MIT + +// PPSOut generates a Pulse per Second using the IEEE1588-Timer +// Connect Oscilloscope to Pins 14,24,34 +// Connect LED to Pin 14 +// +// This file is part of the QNEthernet library. + +#include +using namespace qindesign::network; + +void setup() { + + Serial.begin(2000000); + Serial.println("Setup EthernetIEEE1588"); + + qindesign::network::Ethernet.begin(); + qindesign::network::EthernetIEEE1588.begin(); + + IOMUXC_SW_MUX_CTL_PAD_GPIO_B1_13 = 3; //ENET_1588_EVENT0_OUT, IOMUX: ALT3, Teensy Pin: 34 + EthernetIEEE1588.setChannelCompareValue(0, 0); //Compare at counter value 0 + EthernetIEEE1588.setChannelMode(0, qindesign::network::EthernetIEEE1588.TimerChannelModes::kPulseHighOnCompare); //enable Channel0 positive pulse + EthernetIEEE1588.setChannelOutputPulseWidth(0, 25); //Generate a Pulse width of 25 25MHz clock cycles (1us) + + IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B0_12 = 6; //ENET_1588_EVENT1_OUT, IOMUX: ALT6, Teensy Pin: 24 + EthernetIEEE1588.setChannelCompareValue(1, 1000); //Compare at counter value 1000 + EthernetIEEE1588.setChannelOutputPulseWidth(1, 10); //Generate a Pulse width of 10 25MHz clock cycles (400ns) + EthernetIEEE1588.setChannelMode(1, qindesign::network::EthernetIEEE1588.TimerChannelModes::kPulseLowOnCompare); //enable Channel1 negative pulse + + IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_02 = 4; //ENET_1588_EVENT2_OUT, IOMUX: ALT4, Teensy Pin: 14 + EthernetIEEE1588.setChannelCompareValue(2, 500 * 1000 * 1000); //Compare at counter 500ms + EthernetIEEE1588.setChannelMode(2, qindesign::network::EthernetIEEE1588.TimerChannelModes::kClearOnCompareSetOnOverflow); //enable Channel2 for 50/50 On-Off Signal + + Serial.printf("TCSR Register state: ENET_TCSR0 %08" PRIX32 "h ENET_TCSR1 %08" PRIX32 "h ENET_TCSR2 %08" PRIX32 "h\n", ENET_TCSR0, ENET_TCSR1, ENET_TCSR2); // (pg 2247, Rev. 3) +} + +void loop() { + // put your main code here, to run repeatedly: + +} diff --git a/src/QNEthernetIEEE1588.cpp b/src/QNEthernetIEEE1588.cpp index 94af43c4e..0b5489386 100644 --- a/src/QNEthernetIEEE1588.cpp +++ b/src/QNEthernetIEEE1588.cpp @@ -57,10 +57,8 @@ bool EthernetIEEE1588Class::setChannelMode(int channel, } bool EthernetIEEE1588Class::setChannelOutputPulseWidth(int channel, - TimerChannelModes mode, int pulseWidth) const { return enet_ieee1588_set_channel_output_pulse_width(channel, - static_cast(mode), pulseWidth); } diff --git a/src/QNEthernetIEEE1588.h b/src/QNEthernetIEEE1588.h index a02b7d3b6..eac73814b 100644 --- a/src/QNEthernetIEEE1588.h +++ b/src/QNEthernetIEEE1588.h @@ -84,11 +84,9 @@ class EthernetIEEE1588Class final { // output compare pulse modes. bool setChannelMode(int channel, TimerChannelModes mode) const; - // Sets the output compare pulse mode and pulse width for the given channel. - // The pulse width must be in the range 1-32. This only sets the output - // compare pulse modes. This returns whether successful. + // Sets the output compare pulse width for the given channel. + // The pulse width must be in the range 1-32. This returns whether successful. bool setChannelOutputPulseWidth(int channel, - TimerChannelModes mode, int pulseWidth) const; // Sets the channel compare value. This returns whether successful. diff --git a/src/lwip_t41.c b/src/lwip_t41.c index 0bf3d587b..998068adc 100644 --- a/src/lwip_t41.c +++ b/src/lwip_t41.c @@ -936,7 +936,10 @@ void enet_leave_group(const ip4_addr_t *group) { #define ENET_TCSR_TMODE_MASK (0x0000003cU) #define ENET_TCSR_TMODE(n) ((uint32_t)(((n) & 0x0f) << 2)) + +#define ENET_TCSR_TPWC_MASK ((uint32_t)(0x1f << 11)) #define ENET_TCSR_TPWC(n) ((uint32_t)(((n) & 0x1f) << 11)) + #define ENET_TCSR_TF ((uint32_t)(1U << 7)) void enet_ieee1588_init() { @@ -1076,44 +1079,38 @@ static volatile uint32_t *tccrReg(int channel) { } bool enet_ieee1588_set_channel_mode(int channel, int mode) { - switch (mode) { - case 14: // kTimerChannelPulseLowOnCompare - case 15: // kTimerChannelPulseHighOnCompare - case 12: // Reserved - case 13: // Reserved - return false; - default: - if (mode < 0 || 0x0f < mode) { - return false; - } - break; + if (channel < 0 || channel > 3){ + return false; + } + + if (mode < 0 || mode > 15 || mode == 8 || mode == 12 || mode == 13) {//Check for reserverd modes + return false; } - volatile uint32_t *tcsr = tcsrReg(channel); if (tcsr == NULL) { return false; } + uint32_t state = *tcsr; // Backup current state *tcsr = 0; while ((*tcsr & ENET_TCSR_TMODE_MASK) != 0) { // Check until the channel is disabled } - *tcsr = ENET_TCSR_TMODE(mode); + CLRSET(state,ENET_TCSR_TMODE_MASK,ENET_TCSR_TMODE(mode)); + *tcsr = state; + while (*tcsr != state) { + // Check until the channel is enabled + } return true; } bool enet_ieee1588_set_channel_output_pulse_width(int channel, - int mode, - int pulseWidth) { - switch (mode) { - case 14: // kTimerChannelPulseLowOnCompare - case 15: // kTimerChannelPulseHighOnCompare - break; - default: - return true; + int pulseWidth) { + if (channel < 0 || channel > 3){ + return false; } - + if (pulseWidth < 1 || 32 < pulseWidth) { return false; } @@ -1122,17 +1119,23 @@ bool enet_ieee1588_set_channel_output_pulse_width(int channel, if (tcsr == NULL) { return false; } - + uint32_t state = *tcsr; // Backup current state *tcsr = 0; while ((*tcsr & ENET_TCSR_TMODE_MASK) != 0) { // Check until the channel is disabled } - *tcsr = ENET_TCSR_TMODE(mode) | ENET_TCSR_TPWC(pulseWidth - 1); - + CLRSET(state,ENET_TCSR_TPWC_MASK,ENET_TCSR_TPWC(pulseWidth - 1)); + *tcsr = state; + while (*tcsr != state) { + // Check until the channel is enabled + } return true; } bool enet_ieee1588_set_channel_compare_value(int channel, uint32_t value) { + if (channel < 0 || channel > 3) { + return false; + } volatile uint32_t *tccr = tccrReg(channel); if (tccr == NULL) { return false; diff --git a/src/lwip_t41.h b/src/lwip_t41.h index d2d132f76..0b80ebc98 100644 --- a/src/lwip_t41.h +++ b/src/lwip_t41.h @@ -154,10 +154,8 @@ bool enet_ieee1588_set_channel_mode(int channel, int mode); // // This will return false if: // 1. The channel is unknown, -// 2. The mode is not one of the output compare pulse modes, or -// 3. The pulse width is not in the range 1-32. +// 2. The pulse width is not in the range 1-32. bool enet_ieee1588_set_channel_output_pulse_width(int channel, - int mode, int pulseWidth); // Sets the channel compare value. This returns whether successful. From 9384254ba854ddd1db8380ec59fbb926ccb2385c Mon Sep 17 00:00:00 2001 From: Jens Schleusner Date: Wed, 8 Mar 2023 16:43:13 +0100 Subject: [PATCH 20/24] Add IEEE1588 functions to handle external timer event interrupts. Add PPSIn example. --- examples/PPSIn/PPSIn.ino | 64 ++++++++++++++++++++++++++++++++++++++ src/QNEthernetIEEE1588.cpp | 9 ++++++ src/QNEthernetIEEE1588.h | 7 +++++ src/lwip_t41.c | 30 ++++++++++++++++++ src/lwip_t41.h | 13 ++++++-- 5 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 examples/PPSIn/PPSIn.ino diff --git a/examples/PPSIn/PPSIn.ino b/examples/PPSIn/PPSIn.ino new file mode 100644 index 000000000..fcab65763 --- /dev/null +++ b/examples/PPSIn/PPSIn.ino @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: (c) 2023 Jens Schleusner +// SPDX-License-Identifier: MIT + +// PPSOut generates a Pulse per Second using the IEEE1588-Timer on Pin 24 +// We read the pulse back on channels 0 and 2 to measure the timer delay +// Connect Oscilloscope to Pin 24 +// Connect Pins 24(out), 15(in), 35(in) +// +// This file is part of the QNEthernet library. + +#include +using namespace qindesign::network; + +void setup() { + + Serial.begin(2000000); + Serial.println("Setup EthernetIEEE1588"); + + qindesign::network::Ethernet.begin(); + qindesign::network::EthernetIEEE1588.begin(); + + //PPS-Out on Channel 1 + IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B0_12 = 6; //ENET_1588_EVENT1_OUT, IOMUX: ALT6, Teensy Pin: 24 + EthernetIEEE1588.setChannelCompareValue(1, 0); //Compare at counter value 0 + EthernetIEEE1588.setChannelMode(1, qindesign::network::EthernetIEEE1588.TimerChannelModes::kPulseHighOnCompare); //enable Channel0 positive pulse + EthernetIEEE1588.setChannelOutputPulseWidth(1, 25); //Generate a Pulse width of 25 25MHz clock cycles (1us) + + //PPS-IN + attachInterruptVector(IRQ_ENET_TIMER, interrupt_1588_timer); //Configure Interrupt Handler + NVIC_ENABLE_IRQ(IRQ_ENET_TIMER); //Enable Interrupt Handling + + //PPS-In on Channel 2 + IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_03 = 4; //ENET_1588_EVENT2_IN, IOMUX: ALT4, Teensy Pin: 15 + EthernetIEEE1588.setChannelMode(2, qindesign::network::EthernetIEEE1588.TimerChannelModes::kCaptureOnRising); //enable Channel2 rising edge trigger + EthernetIEEE1588.setChannelInterruptEnable(2, true); //Configure Interrupt generation + + //PPS-In on Channel 0 + IOMUXC_SW_MUX_CTL_PAD_GPIO_B1_12 = 3; //ENET_1588_EVENT0_IN, IOMUX: ALT3, Teensy Pin: 35 + IOMUXC_ENET0_TIMER_SELECT_INPUT = 0b10; // route B1_12 to 1588 timer (pg 796, Rev. 3) + EthernetIEEE1588.setChannelMode(0, qindesign::network::EthernetIEEE1588.TimerChannelModes::kCaptureOnFalling); + EthernetIEEE1588.setChannelInterruptEnable(0, true); + + + Serial.printf("TCSR Register state: ENET_TCSR0 %08" PRIX32 "h ENET_TCSR1 %08" PRIX32 "h ENET_TCSR2 %08" PRIX32 "h\n", ENET_TCSR0, ENET_TCSR1, ENET_TCSR2); // (pg 2247, Rev. 3) +} + +void loop() { + // put your main code here, to run repeatedly: + +} + +static void interrupt_1588_timer() { + uint32_t t; + if(EthernetIEEE1588.getAndClearChannelStatus(0)){ + EthernetIEEE1588.getChannelCompareValue(0,t); + Serial.printf("Timer0 Falling Edge: %d\n\n", t); + } + if (EthernetIEEE1588.getAndClearChannelStatus(2)) { + EthernetIEEE1588.getChannelCompareValue(2,t); + Serial.printf("Timer2 Rising Edge: %d\n", t); + } + asm("dsb"); // allow write to complete so the interrupt doesn't fire twice +} + diff --git a/src/QNEthernetIEEE1588.cpp b/src/QNEthernetIEEE1588.cpp index 0b5489386..e9a9d7754 100644 --- a/src/QNEthernetIEEE1588.cpp +++ b/src/QNEthernetIEEE1588.cpp @@ -67,10 +67,19 @@ bool EthernetIEEE1588Class::setChannelCompareValue(int channel, return enet_ieee1588_set_channel_compare_value(channel, value); } +bool EthernetIEEE1588Class::getChannelCompareValue(int channel, + uint32_t &value) const { + return enet_ieee1588_get_channel_compare_value(channel, &value); +} + bool EthernetIEEE1588Class::getAndClearChannelStatus(int channel) const { return enet_ieee1588_get_and_clear_channel_status(channel); } +bool EthernetIEEE1588Class::setChannelInterruptEnable(int channel, bool enable) const { + return enet_ieee1588_set_channel_interrupt_enable(channel, enable); +} + EthernetIEEE1588Class::operator bool() const { return enet_ieee1588_is_enabled(); } diff --git a/src/QNEthernetIEEE1588.h b/src/QNEthernetIEEE1588.h index eac73814b..3138d2f9e 100644 --- a/src/QNEthernetIEEE1588.h +++ b/src/QNEthernetIEEE1588.h @@ -92,10 +92,17 @@ class EthernetIEEE1588Class final { // Sets the channel compare value. This returns whether successful. bool setChannelCompareValue(int channel, uint32_t value) const; + // Gets the channel compare value. This returns whether successful. + bool getChannelCompareValue(int channel, uint32_t &value) const; + // Retrieves and then clears the status for the given channel. This will // return false for an unknown channel. bool getAndClearChannelStatus(int channel) const; + // Enables or disables timer interrupt generation for a channel. This will + // return false for an unknown channel. + bool setChannelInterruptEnable(int channel, bool enable) const; + // Tests if the IEEE 1588 timer has been started. operator bool() const; diff --git a/src/lwip_t41.c b/src/lwip_t41.c index 998068adc..240633805 100644 --- a/src/lwip_t41.c +++ b/src/lwip_t41.c @@ -942,6 +942,9 @@ void enet_leave_group(const ip4_addr_t *group) { #define ENET_TCSR_TF ((uint32_t)(1U << 7)) +#define ENET_TCSR_TIE_MASK ((uint32_t)(1U << 6)) +#define ENET_TCSR_TIE(n) ((uint32_t)(((n) & 0x01) << 6)) + void enet_ieee1588_init() { ENET_ATCR = ENET_ATCR_RESTART | ENET_ATCR_Reserved; // Reset timer ENET_ATPER = NANOSECONDS_PER_SECOND; // Wrap at 10^9 @@ -1144,7 +1147,22 @@ bool enet_ieee1588_set_channel_compare_value(int channel, uint32_t value) { return true; } +bool enet_ieee1588_get_channel_compare_value(int channel, uint32_t *value) { + if (channel < 0 || channel > 3) { + return false; + } + volatile uint32_t *tccr = tccrReg(channel); + if (tccr == NULL) { + return false; + } + *value = *tccr; + return true; +} + bool enet_ieee1588_get_and_clear_channel_status(int channel) { + if (channel < 0 || channel > 3) { + return false; + } volatile uint32_t *tcsr = tcsrReg(channel); if (tcsr == NULL) { return false; @@ -1158,4 +1176,16 @@ bool enet_ieee1588_get_and_clear_channel_status(int channel) { } } +bool enet_ieee1588_set_channel_interrupt_enable(int channel, bool enable){ + if (channel < 0 || channel > 3) { + return false; + } + + volatile uint32_t *tcsr = tcsrReg(channel); + if (tcsr == NULL) { + return false; + } + CLRSET(*tcsr,ENET_TCSR_TIE_MASK,ENET_TCSR_TIE(enable)); +} + #endif // ARDUINO_TEENSY41 diff --git a/src/lwip_t41.h b/src/lwip_t41.h index 0b80ebc98..295f949bc 100644 --- a/src/lwip_t41.h +++ b/src/lwip_t41.h @@ -163,10 +163,19 @@ bool enet_ieee1588_set_channel_output_pulse_width(int channel, // This will return false for an unknown channel. bool enet_ieee1588_set_channel_compare_value(int channel, uint32_t value); -// Retrieves and then clears the status for the given channel. This will return -// false for an unknown channel. +// Gets the channel compare value. This returns whether successful. +// +// This will return false for an unknown channel. +bool enet_ieee1588_get_channel_compare_value(int channel, uint32_t *value); + +// Retrieves and then clears the status for the given channel. This will +// return false for an unknown channel. bool enet_ieee1588_get_and_clear_channel_status(int channel); +// Enables or disables timer interrupt generation for a channel. This will +// return false for an unknown channel. +bool enet_ieee1588_set_channel_interrupt_enable(int channel, bool enable); + #ifdef __cplusplus } // extern "C" #endif From 2ecdf665dd708718516e1ee945c8e471f5ad8f8c Mon Sep 17 00:00:00 2001 From: Jens Schleusner Date: Tue, 11 Jul 2023 12:19:19 +0200 Subject: [PATCH 21/24] Add IEEE1588 coarse offset adjust method --- src/QNEthernetIEEE1588.cpp | 4 ++++ src/QNEthernetIEEE1588.h | 3 +++ src/lwip_t41.c | 12 ++++++++++++ src/lwip_t41.h | 4 ++++ 4 files changed, 23 insertions(+) diff --git a/src/QNEthernetIEEE1588.cpp b/src/QNEthernetIEEE1588.cpp index e9a9d7754..e76b6b914 100644 --- a/src/QNEthernetIEEE1588.cpp +++ b/src/QNEthernetIEEE1588.cpp @@ -33,6 +33,10 @@ bool EthernetIEEE1588Class::writeTimer(const timespec &t) const { return enet_ieee1588_write_timer(&t); } +bool EthernetIEEE1588Class::offsetTimer(int64_t ns) const{ + return enet_ieee1588_offset_timer(ns); +} + void EthernetIEEE1588Class::timestampNextFrame() const { enet_ieee1588_timestamp_next_frame(); } diff --git a/src/QNEthernetIEEE1588.h b/src/QNEthernetIEEE1588.h index 3138d2f9e..b92fd4ec6 100644 --- a/src/QNEthernetIEEE1588.h +++ b/src/QNEthernetIEEE1588.h @@ -53,6 +53,9 @@ class EthernetIEEE1588Class final { // Writes the current IEEE 1588 timer value. This returns whether successful. bool writeTimer(const timespec &t) const; + // Adds an offset to the current IEEE 1588 timer value. This returns whether successful. + bool offsetTimer(int64_t ns) const; + // Tells the driver to timestamp the next transmitted frame. This should be // called before functions like `EthernetUDP::endPacket()`, // `EthernetUDP::send()`, and any of the `EthernetFrame` send functions. diff --git a/src/lwip_t41.c b/src/lwip_t41.c index 240633805..c3986c1f0 100644 --- a/src/lwip_t41.c +++ b/src/lwip_t41.c @@ -1010,6 +1010,18 @@ bool enet_ieee1588_write_timer(const struct timespec *t) { return true; } +bool enet_ieee1588_offset_timer(int64_t ns){ + struct timespec tm; + if(!enet_ieee1588_read_timer(&tm)){ + return false; + } + int64_t t = (((int64_t)tm.tv_sec) * NANOSECONDS_PER_SECOND) + ((int64_t)tm.tv_nsec); + t += ns; + tm.tv_nsec = t % NANOSECONDS_PER_SECOND; + tm.tv_sec = t / NANOSECONDS_PER_SECOND; + return enet_ieee1588_write_timer(&tm); +} + void enet_ieee1588_timestamp_next_frame() { doTimestampNext = true; } diff --git a/src/lwip_t41.h b/src/lwip_t41.h index 295f949bc..168cbc31d 100644 --- a/src/lwip_t41.h +++ b/src/lwip_t41.h @@ -114,6 +114,10 @@ bool enet_ieee1588_read_timer(struct timespec *t); // This will return false if the argument is NULL. bool enet_ieee1588_write_timer(const struct timespec *t); +// Adds an offset to the current timer value. Uses the read and write +// functions under the hood. This returns whether successful. +bool enet_ieee1588_offset_timer(int64_t ns); + // Tells the driver to timestamp the next transmitted frame. void enet_ieee1588_timestamp_next_frame(); From 50846660ef01509b75fba7ee07485982395497a1 Mon Sep 17 00:00:00 2001 From: Jens Schleusner Date: Tue, 11 Jul 2023 12:21:14 +0200 Subject: [PATCH 22/24] Modify frequency adjustment for IEEE1588 fine adjustment double datatype --- src/QNEthernetIEEE1588.cpp | 2 +- src/QNEthernetIEEE1588.h | 2 +- src/lwip_t41.c | 4 ++-- src/lwip_t41.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/QNEthernetIEEE1588.cpp b/src/QNEthernetIEEE1588.cpp index e76b6b914..2d7fb708c 100644 --- a/src/QNEthernetIEEE1588.cpp +++ b/src/QNEthernetIEEE1588.cpp @@ -51,7 +51,7 @@ bool EthernetIEEE1588Class::adjustTimer(uint32_t corrInc, return enet_ieee1588_adjust_timer(corrInc, corrPeriod); } -bool EthernetIEEE1588Class::adjustFreq(int nsps) const { +bool EthernetIEEE1588Class::adjustFreq(double nsps) const { return enet_ieee1588_adjust_freq(nsps); } diff --git a/src/QNEthernetIEEE1588.h b/src/QNEthernetIEEE1588.h index b92fd4ec6..f7bd66fed 100644 --- a/src/QNEthernetIEEE1588.h +++ b/src/QNEthernetIEEE1588.h @@ -78,7 +78,7 @@ class EthernetIEEE1588Class final { // Adjusts the correction frequency in nanoseconds per second. To slow down // the timer, specify a negative value. To speed it up, specify a positive // value. This returns whether successful. - bool adjustFreq(int nsps) const; + bool adjustFreq(double nsps) const; // Sets the channel mode for the given channel. This does not set the output // compare pulse modes. This returns whether successful. diff --git a/src/lwip_t41.c b/src/lwip_t41.c index c3986c1f0..947906b16 100644 --- a/src/lwip_t41.c +++ b/src/lwip_t41.c @@ -1049,7 +1049,7 @@ bool enet_ieee1588_adjust_timer(uint32_t corrInc, uint32_t corrPeriod) { return true; } -bool enet_ieee1588_adjust_freq(int nsps) { +bool enet_ieee1588_adjust_freq(double nsps) { if (nsps == 0) { ENET_ATCOR = 0; return true; @@ -1065,7 +1065,7 @@ bool enet_ieee1588_adjust_freq(int nsps) { // Speed up inc++; } - return enet_ieee1588_adjust_timer(inc, F_ENET_TS_CLK / nsps); + return enet_ieee1588_adjust_timer(inc, round(F_ENET_TS_CLK / nsps)); } // Channels diff --git a/src/lwip_t41.h b/src/lwip_t41.h index 168cbc31d..bb9f503e1 100644 --- a/src/lwip_t41.h +++ b/src/lwip_t41.h @@ -142,7 +142,7 @@ bool enet_ieee1588_adjust_timer(uint32_t corrInc, uint32_t corrPeriod); // Adjust the correction in nanoseconds per second. This uses // `enet_ieee1588_adjust_timer()` under the hood. -bool enet_ieee1588_adjust_freq(int nsps); +bool enet_ieee1588_adjust_freq(double nsps); // Sets the channel mode for the given channel. This does not set the output // compare pulse modes. This returns whether successful. From d00fbc634fa7e655d813c165f4ca07296d764cc1 Mon Sep 17 00:00:00 2001 From: Jens Schleusner Date: Wed, 9 Aug 2023 16:09:22 +0200 Subject: [PATCH 23/24] Add missing return value in interrupt handler function --- src/lwip_t41.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lwip_t41.c b/src/lwip_t41.c index 947906b16..026142f88 100644 --- a/src/lwip_t41.c +++ b/src/lwip_t41.c @@ -1183,9 +1183,8 @@ bool enet_ieee1588_get_and_clear_channel_status(int channel) { *tcsr |= ENET_TCSR_TF; ENET_TGSR = (1 << channel); return true; - } else { - return false; } + return false; } bool enet_ieee1588_set_channel_interrupt_enable(int channel, bool enable){ @@ -1198,6 +1197,7 @@ bool enet_ieee1588_set_channel_interrupt_enable(int channel, bool enable){ return false; } CLRSET(*tcsr,ENET_TCSR_TIE_MASK,ENET_TCSR_TIE(enable)); + return true; } #endif // ARDUINO_TEENSY41 From 6edbded44ee610e6e08c061dff594065cde321d2 Mon Sep 17 00:00:00 2001 From: Jens Schleusner Date: Fri, 8 Mar 2024 16:47:20 +0100 Subject: [PATCH 24/24] Revert "Add missing operator==() and operator!=() for const IPAddress" This reverts commit 9b37509521348ee9f92f3827cc20a5d125acd99e. --- README.md | 10 ---------- src/QNEthernet.h | 1 - src/util/ip_tools.cpp | 8 -------- src/util/ip_tools.h | 4 ---- 4 files changed, 23 deletions(-) diff --git a/README.md b/README.md index d4abcb66f..9bb791ed6 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ files provided with the lwIP release. 6. [`MDNS`](#mdns) 7. [`DNSClient`](#dnsclient) 8. [Print utilities](#print-utilities) - 9. [`IPAddress` operators](#ipaddress-operators) 3. [How to run](#how-to-run) 4. [How to write data to connections](#how-to-write-data-to-connections) 1. [Write immediacy](#write-immediacy) @@ -394,15 +393,6 @@ interface so that it is easy to print `Printable` objects to `stdout` or `stderr` without having to worry about buffering and the need to flush any output before printing a `Printable` directly to, say, `Serial`. -### `IPAddress` operators - -The core library version of `IPAddress` is missing `==` and `!=` operators that -can compare `const IPAddress` values. Provided in this library are these two -operators. They are declared as follows in the usual namespace: - -1. `bool operator==(const IPAddress &a, const IPAddress &b);` -2. `bool operator!=(const IPAddress &a, const IPAddress &b);` - ## How to run This library works with both PlatformIO and Arduino. To use it with Arduino, diff --git a/src/QNEthernet.h b/src/QNEthernet.h index d7d1bb671..bb9ddc03c 100644 --- a/src/QNEthernet.h +++ b/src/QNEthernet.h @@ -28,7 +28,6 @@ #include "lwip/opt.h" #include "lwip_t41.h" #include "util/PrintUtils.h" -#include "util/ip_tools.h" namespace qindesign { namespace network { diff --git a/src/util/ip_tools.cpp b/src/util/ip_tools.cpp index 921bb0d5a..6fd9a2c16 100644 --- a/src/util/ip_tools.cpp +++ b/src/util/ip_tools.cpp @@ -24,14 +24,6 @@ uint32_t ip_addr_get_ip4_uint32(const ip_addr_t *ip) { return IPADDR_ANY; } -bool operator==(const IPAddress &a, const IPAddress &b) { - return (const_cast(a) == b); -} - -bool operator!=(const IPAddress &a, const IPAddress &b) { - return !(const_cast(a) == b); -} - uint32_t get_uint32(const IPAddress &ip) { // The uint32_t operator doesn't work with const IPAddress, hence // the const_cast diff --git a/src/util/ip_tools.h b/src/util/ip_tools.h index c54e4f2e9..bcdb5bc41 100644 --- a/src/util/ip_tools.h +++ b/src/util/ip_tools.h @@ -22,10 +22,6 @@ namespace network { // non-IPv4-mapped addresses. uint32_t ip_addr_get_ip4_uint32(const ip_addr_t *ip); -// Missing IPAddress operators -bool operator==(const IPAddress &a, const IPAddress &b); -bool operator!=(const IPAddress &a, const IPAddress &b); - // Gets the 32-bit address from the given const IPAddress. uint32_t get_uint32(const IPAddress &ip);