Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cd26a90
Add low-level IEEE 1588 functions
ssilverman Jan 14, 2022
cd11c5c
Change enet_ieee1588_write_timer() and check pulse width
ssilverman Jan 14, 2022
1e50b66
Add argument checks to enet_ieee1588_adjust_timer()
ssilverman Jan 14, 2022
8154650
Update the docs for read/write timer to explain when it returns false
ssilverman Jan 14, 2022
10f1198
Change enet_ieee1588_adjust_freq() to return a bool
ssilverman Jan 14, 2022
575fe7a
Make small doc updates for IEEE 1588 functions
ssilverman Jan 14, 2022
4c5069d
Add a high-level interface to the IEEE 1588 functions
ssilverman Jan 14, 2022
7f08d39
lwip: Add a way to initialize the custom pbuf data
ssilverman Nov 25, 2021
9dfbc08
Add custom pbuf timestamp data
ssilverman Jan 14, 2022
ac3419f
Rename IEEE1588Time to IEEE1588Timestamp
ssilverman Jan 14, 2022
f9e4862
Add RX and TX timestamps for UDP and raw frames
ssilverman Jan 14, 2022
e6a4e12
Change TX timestamp API to require calling a function before sending
ssilverman Jan 15, 2022
03e500d
Change timestamp retrieval to use IEEE1588Timestamp
ssilverman Jan 15, 2022
0ecf085
Change uses of IEEE1588Timestamp to timespec
ssilverman Jan 15, 2022
cc3b3e8
Simplify TimerChannelModes enum use
ssilverman Jan 22, 2022
bb4c0b1
Change internal timestamps to use struct timespec
ssilverman Jan 26, 2022
42708f4
Add SNTPClientWithTimestamps demo
ssilverman Feb 10, 2022
a8a0531
Change enet_ieee1588_adjust_timer to correctly mask the timer registers
HedgeHawk Mar 8, 2023
eb960c6
Change ieee1588 timer mode setup to remove side effects while changin…
HedgeHawk Mar 8, 2023
9384254
Add IEEE1588 functions to handle external timer event interrupts. Add…
HedgeHawk Mar 8, 2023
2ecdf66
Add IEEE1588 coarse offset adjust method
HedgeHawk Jul 11, 2023
5084666
Modify frequency adjustment for IEEE1588 fine adjustment double datatype
HedgeHawk Jul 11, 2023
d00fbc6
Add missing return value in interrupt handler function
HedgeHawk Aug 9, 2023
6edbded
Revert "Add missing operator==() and operator!=() for const IPAddress"
HedgeHawk Mar 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
64 changes: 64 additions & 0 deletions examples/PPSIn/PPSIn.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: (c) 2023 Jens Schleusner <github@schleusner.dev>
// 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 <QNEthernet.h>
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
}

41 changes: 41 additions & 0 deletions examples/PPSOut/PPSOut.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: (c) 2023 Jens Schleusner <github@schleusner.dev>
// 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 <QNEthernet.h>
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:

}
168 changes: 168 additions & 0 deletions examples/SNTPClientWithTimestamps/SNTPClientWithTimestamps.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// SPDX-FileCopyrightText: (c) 2021-2022 Shawn Silverman <shawn@pobox.com>
// 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 <ctime>

#include <QNEthernet.h>
#include <TimeLib.h>

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);
}
46 changes: 46 additions & 0 deletions examples/TimerAdjustment/TimerAdjustment.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: (c) 2023 Jens Schleusner <github@schleusner.dev>
// SPDX-License-Identifier: MIT
//
// This file is part of the QNEthernet library.

#include <QNEthernet.h>
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<int64_t>(tm.tv_sec) - static_cast<int64_t>(tmlast.tv_sec)) * (1000 * 1000 * 1000) + (static_cast<int64_t>(tm.tv_nsec) - static_cast<int64_t>(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() {

}
5 changes: 4 additions & 1 deletion src/QNEthernet.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include "QNEthernetClient.h"
#include "QNEthernetFrame.h"
#include "QNEthernetIEEE1588.h"
#include "QNEthernetServer.h"
#include "QNEthernetUDP.h"
#include "QNMDNS.h"
Expand All @@ -27,7 +28,6 @@
#include "lwip/opt.h"
#include "lwip_t41.h"
#include "util/PrintUtils.h"
#include "util/ip_tools.h"

namespace qindesign {
namespace network {
Expand Down Expand Up @@ -289,6 +289,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;

Expand Down
Loading