Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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() {

}
4 changes: 4 additions & 0 deletions src/QNEthernet.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "lwip/prot/ethernet.h"
#include "qnethernet/QNEthernetClient.h"
#include "qnethernet/QNEthernetFrame.h"
#include "qnethernet/QNEthernetIEEE1588.h"
#include "qnethernet/QNEthernetServer.h"
#include "qnethernet/QNEthernetUDP.h"
#include "qnethernet/QNMDNS.h"
Expand Down Expand Up @@ -507,6 +508,9 @@ class EthernetClass final {
// Instance for interacting with the library.
STATIC_INIT_DECL(EthernetClass, Ethernet);

// Instance for using IEEE 1588 functions.
extern EthernetIEEE1588Class &EthernetIEEE1588;

#if QNETHERNET_CUSTOM_WRITE
// stdout output.
extern Print *stdoutPrint;
Expand Down
9 changes: 7 additions & 2 deletions src/lwipopts.h
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,13 @@ void qnethernet_hal_check_core_locking(const char *file, int line,
// #define PBUF_LINK_ENCAPSULATION_HLEN 0
// #define PBUF_POOL_BUFSIZE 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
// #define LWIP_PBUF_CUSTOM_DATA_INIT(p)
#define LWIP_PBUF_CUSTOM_DATA \
u8_t timestampValid; \
struct timespec timestamp;
#define LWIP_PBUF_CUSTOM_DATA_INIT(p) \
(p)->timestampValid = 0; \
(p)->timestamp.tv_sec = 0; \
(p)->timestamp.tv_nsec = 0;

// Network Interfaces options
#define LWIP_SINGLE_NETIF 1 /* 0 */
Expand Down
18 changes: 18 additions & 0 deletions src/qnethernet/QNEthernetFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ err_t EthernetFrameClass::recvFunc(struct pbuf *const p,
}
frame.receivedTimestamp = timestamp;

frame.hasTimestamp = p->timestampValid;
if (frame.hasTimestamp) {
frame.timestamp = p->timestamp;
}

// Increment the size
if (EthernetFrame.inBufSize_ != 0 &&
EthernetFrame.inBufTail_ == EthernetFrame.inBufHead_) {
Expand Down Expand Up @@ -125,6 +130,10 @@ int EthernetFrameClass::parseFrame() {

// Pop (from the tail)
frame_ = inBuf_[inBufTail_];
inBuf_[inBufTail_].data.clear();
inBuf_[inBufTail_].hasTimestamp = false;
inBuf_[inBufTail_].timestamp.tv_sec = 0;
inBuf_[inBufTail_].timestamp.tv_nsec = 0;
inBuf_[inBufTail_].clear();
inBufTail_ = (inBufTail_ + 1) % inBuf_.size();
inBufSize_--;
Expand Down Expand Up @@ -235,6 +244,15 @@ void EthernetFrameClass::setReceiveQueueCapacity(const size_t capacity) {
inBuf_.shrink_to_fit();
}

bool EthernetFrameClass::timestamp(timespec &timestamp) const {
// NOTE: This is not "concurrent safe"
if (frame_.hasTimestamp) {
timestamp = frame_.timestamp;
return true;
}
return false;
}

// --------------------------------------------------------------------------
// Transmission
// --------------------------------------------------------------------------
Expand Down
Loading