Step 3.2: TimeReceiver measurement (GM selection, Delay_Req/Resp, path delay)

- ptp_clock.c replaces the 3.1 logger: UDP/IPv4 multicast, E2E.
  Announce: IEEE 1588 dataset comparison picks the best GM, dropped after
  announceReceiptTimeout x its announce interval. Sync/Follow_Up (two-step
  and one-step) with HW t2 and correctionField. Delay_Req sent as a raw
  frame (DSCP ptp.dscp, TTL 1, clockIdentity = EUI-64 from MAC) with HW
  TX timestamp t3, randomised at the GM's Delay_Resp interval; Delay_Resp
  matched on requestingPortIdentity + seq.
- Path delay corrected for offset drift between t2 and t3 (rate from
  consecutive Syncs) so it is right before the clock is syntonised.
- ptp_hw: ptp_hw_send_event() builds Eth/IPv4/UDP 319 and returns the HW
  TX timestamp.
- Verified vs ptp4l (i210 GM, HP 2530 non-PTP switch, PC 1G / board 100M):
  GM selected, path delay settles at ~10.3 us and stays flat, rate
  -39.8 +-0.4 ppm, offset drifts at -40 us/s (no servo yet).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 23:50:46 +10:00
parent 880437948d
commit e2f4d81326
6 changed files with 409 additions and 113 deletions
+63
View File
@@ -22,6 +22,8 @@ typedef struct {
} rx_rec_t;
static esp_eth_handle_t s_eth;
static esp_netif_t *s_netif;
static uint16_t s_ip_id;
static rx_rec_t s_rx[RX_RING];
static int s_rx_next;
static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED;
@@ -75,9 +77,70 @@ esp_err_t ptp_hw_get_time(eth_mac_time_t *t)
return esp_eth_ioctl(s_eth, ETH_MAC_ESP_CMD_G_PTP_TIME, t);
}
static uint16_t ip_checksum(const uint8_t *h, size_t len)
{
uint32_t sum = 0;
for (size_t i = 0; i < len; i += 2) {
sum += (h[i] << 8) | h[i + 1];
}
while (sum >> 16) {
sum = (sum & 0xffff) + (sum >> 16);
}
return ~sum;
}
esp_err_t ptp_hw_send_event(const uint8_t dst_mac[6], uint32_t dst_ip, uint8_t dscp,
const uint8_t *msg, size_t len, eth_mac_time_t *tx_ts)
{
uint8_t f[14 + 20 + 8 + 64];
esp_netif_ip_info_t ip;
if (len > 64 || esp_netif_get_ip_info(s_netif, &ip) != ESP_OK || !ip.ip.addr) {
return ESP_ERR_INVALID_STATE;
}
// Ethernet
memcpy(f, dst_mac, 6);
esp_eth_ioctl(s_eth, ETH_CMD_G_MAC_ADDR, f + 6);
f[12] = 0x08;
f[13] = 0x00;
// IPv4: no options, DF, TTL 1 (PTP stays in the subnet), UDP
uint8_t *h = f + 14;
size_t ip_len = 20 + 8 + len;
uint16_t id = s_ip_id++;
const uint8_t hdr[20] = { 0x45, (uint8_t)(dscp << 2), ip_len >> 8, ip_len & 0xff, id >> 8, id & 0xff,
0x40, 0x00, 1, 17, 0, 0 };
memcpy(h, hdr, 12);
memcpy(h + 12, &ip.ip.addr, 4); // network byte order already
memcpy(h + 16, &dst_ip, 4);
uint16_t cs = ip_checksum(h, 20);
h[10] = cs >> 8;
h[11] = cs & 0xff;
// UDP 319 -> 319, checksum 0 (allowed for IPv4)
uint8_t *u = h + 20;
u[0] = PTP_EVENT_PORT >> 8;
u[1] = PTP_EVENT_PORT & 0xff;
u[2] = PTP_EVENT_PORT >> 8;
u[3] = PTP_EVENT_PORT & 0xff;
u[4] = (8 + len) >> 8;
u[5] = (8 + len) & 0xff;
u[6] = u[7] = 0;
memcpy(u + 8, msg, len);
size_t total = 14 + ip_len;
if (total < 60) {
memset(f + total, 0, 60 - total);
total = 60;
}
esp_err_t err = esp_eth_transmit_ctrl_vargs(s_eth, tx_ts, 2, f, (uint32_t)total);
if (err == ESP_OK && !(tx_ts->seconds | tx_ts->nanoseconds)) {
err = ESP_ERR_TIMEOUT; // sent, but no TX timestamp
}
return err;
}
esp_err_t ptp_hw_init(esp_eth_handle_t eth, esp_netif_t *netif)
{
s_eth = eth;
s_netif = netif;
bool on = true;
esp_err_t err = esp_eth_ioctl(eth, ETH_MAC_ESP_CMD_PTP_ENABLE, &on);
if (err != ESP_OK) {