ESP-IDF 6.0.1 SNTP 同步超时:DNS 竟然是罪魁祸首

ESP32 SNTP 时钟同步问题排查手册

背景

ESP32-S3 通过 WiFi 连接互联网后,需要从 NTP 服务器同步系统时间。项目使用 ESP-IDF 6.0.1 框架,目标是为日志记录、定时冻结等功能提供准确时间。

问题现象

I (4524) lua_demo: Initializing and starting SNTP...
I (4704) lua_demo: Waiting for system time to be set... (1/15)
I (6704) lua_demo: Waiting for system time to be set... (2/15)
...
I (32670) lua_demo: Waiting for system time to be set... (15/15)
W (32680) lua_demo: SNTP sync timeout, using system default time

SNTP 连续 15 次(每次 2 秒)重试全部超时,最终使用 1970 年默认时间。


排查过程

第 1 轮:怀疑 NTP 服务器不可达

尝试:换用不同 NTP 服务器域名

  • ntp.aliyun.com
  • pool.ntp.org
  • time.google.com

结果:全部超时。PC 端 ping ntp.aliyun.com 正常,说明服务器在线。


第 2 轮:怀疑 SNTP API 用法不对

ESP-IDF 6.0.1 引入了新 API esp_netif_sntp_init() 替代旧的 esp_sntp_init()

尝试过的方法(全部失败):

方法 结果
esp_sntp_init() + 手写轮询 超时
增加 vTaskDelay(3000) 等 DNS 就绪 超时
sntp_set_sync_mode(SNTP_SYNC_MODE_IMMED) 立即同步 API 不兼容
CONFIG_LWIP_SNTP_UPDATE_DELAY 1h→15s 超时
CONFIG_LWIP_SNTP_STARTUP_DELAY=n 去掉启动延迟 超时

正确 API(IDF 6.0.1):

#include "esp_netif_sntp.h"

esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG_MULTIPLE(3,
    ESP_SNTP_SERVER_LIST("ntp.aliyun.com", "pool.ntp.org", "time.google.com"));
esp_netif_sntp_init(&config);

int retry = 0;
while (esp_netif_sntp_sync_wait(pdMS_TO_TICKS(2000)) == ESP_ERR_TIMEOUT
       && ++retry < 15) {
    ESP_LOGI(TAG, "Waiting... (%d/15)", retry);
}

易错点:宏参数是 ESP_SNTP_SERVER_LIST(s1, s2, s3) 一个列表,不是 ESP_SNTP_SERVER(s1), ESP_SNTP_SERVER(s2)...,后者编译报错。


第 3 轮:怀疑 IPv6 干扰

ESP32 同时拿到 IPv4 和 IPv6 地址,NTP 请求可能走 IPv6 不通。

尝试

  • config.ip_family = AF_INETesp_sntp_config_t 没有此字段,编译报错
  • NTP 域名换成 IPv4 地址直连 → 仍然超时!

第 4 轮:关键突破 — DNS 日志

在 SNTP 等待循环中加 DNS 日志:

const ip_addr_t *dns = dns_getserver(0);
ESP_LOGI("DNS_CHECK", "Current DNS Server: %s", ipaddr_ntoa(dns));

发现

DNS_CHECK: Current DNS Server: 192.168.137.1

根因确认:路由器的 DNS(192.168.137.1)无法解析 NTP 域名!

虽然同设备用这个 DNS 可以访问 39.96.206.90:1883(MQTT,固定 IP)和 39.96.206.90:40071(文件下载),但 NTP 域名解析一直失败。


根本原因

┌─────────────────────────────────────────────────────────┐
│                    DNS 解析链路                           │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ESP32 ──DHCP──→ 路由器 DNS (192.168.137.1)             │
│              │                                          │
│              ├─ 39.96.206.90  (MQTT, 固定IP) ── ✅      │
│              ├─ 39.96.206.90  (FileBrowser) ──── ✅      │
│              └─ ntp.aliyun.com (需DNS) ──────── ❌      │
│                                                         │
│  路由器 DNS 不转发或不支持 NTP 域名解析                    │
│                                                         │
└─────────────────────────────────────────────────────────┘

最终解决方案

esp_netif_sntp_init() 之前,用 dns_setserver() 强行替换 DNS 为公共 DNS:

#include "lwip/dns.h"

// 在 esp_netif_sntp_init(&config) 之前执行
ip_addr_t dns_server;
ipaddr_aton("119.29.29.29", &dns_server);   // 腾讯公共 DNS
dns_setserver(0, &dns_server);

// 验证
const ip_addr_t *dns = dns_getserver(0);
ESP_LOGI("DNS_CHECK", "DNS Server: %s", ipaddr_ntoa(dns));

结果:

I (4524) lua_demo: Initializing and starting SNTP...
I (5300) DNS_CHECK: DNS Server: 119.29.29.29
I (6300) lua_demo: SNTP synced: 2026-05-22 14:30:15 CST

2 秒内同步成功!


关键知识点

1. ESP32 DNS 来源

WiFi DHCP 过程自动获取 DNS,默认走路由器:

192.168.137.1 (路由器) → 路由器自己的 DNS 上游

2. dns_setserver() 的优先级

dns_setserver(0, ...) ← 最高优先级,覆盖 DHCP 分配
dns_setserver(1, ...) ← 备用
dns_setserver(2, ...) ← 第二备用

注意:必须在 esp_netif_init() 之后调用。

3. 常用国内公共 DNS

DNS 服务器 IP 提供方
腾讯 DNS 119.29.29.29 腾讯云 DNSPod
阿里 DNS 223.5.5.5 阿里云
百度 DNS 180.76.76.76 百度
114 DNS 114.114.114.114 南京信风
谷歌 DNS 8.8.8.8 Google (可能慢)

4. ESP-IDF 6.0.1 SNTP 正确 API

// ✅ 正确:新 API
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG_MULTIPLE(n,
    ESP_SNTP_SERVER_LIST("s1", "s2", "s3"));
esp_netif_sntp_init(&config);
esp_netif_sntp_sync_wait(timeout);

// ❌ 不要参考 v5.x 旧代码
esp_sntp_init();
sntp_get_sync_status();

排查方法论总结

排查步骤 方法 本次结果
1. 确认服务器可达 PC 端 ping/测试 ✅ 通
2. 确认 DNS 正常 加日志打印 dns_getserver() ❌ 发现是路由器 DNS
3. 排除代码问题 参考官方示例 ✅ API 正确
4. 确认网络层 MQTT/HTTP 同服务器正常 ✅ IP 通,DNS 问题
5. 替换 DNS 源 dns_setserver() ✅ 解决

核心教训:嵌入式网络问题不要只换 NTP 服务器,先用 dns_getserver() 看看实际 DNS 是谁。


完整代码

#include "esp_netif_sntp.h"
#include "lwip/dns.h"

static void sntp_time_sync(void)
{
    setenv("TZ", "CST-8", 1);
    tzset();
    ESP_LOGI(TAG, "Timezone set: CST-8 (UTC+8)");

    vTaskDelay(pdMS_TO_TICKS(3000));

    // ★ 关键:替换为公共 DNS
    ip_addr_t dns_server;
    ipaddr_aton("119.29.29.29", &dns_server);
    dns_setserver(0, &dns_server);

    ESP_LOGI(TAG, "Initializing and starting SNTP...");

    esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG_MULTIPLE(3,
        ESP_SNTP_SERVER_LIST("ntp.aliyun.com", "pool.ntp.org", "time.google.com"));
    esp_netif_sntp_init(&config);

    int retry = 0;
    const int retry_count = 15;
    while (esp_netif_sntp_sync_wait(pdMS_TO_TICKS(2000)) == ESP_ERR_TIMEOUT
           && ++retry < retry_count) {
        ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
    }

    time_t now;
    struct tm timeinfo;
    time(&now);
    localtime_r(&now, &timeinfo);

    if (timeinfo.tm_year > (2016 - 1900)) {
        ESP_LOGI(TAG, "SNTP synced: %04d-%02d-%02d %02d:%02d:%02d CST",
                 timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
                 timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
    } else {
        ESP_LOGW(TAG, "SNTP sync timeout, using system default time");
    }
}

参考

  • ESP-IDF SNTP 官方示例
  • esp_netif_sntp.h 源码:components/esp_netif/include/esp_netif_sntp.h
  • lwip/dns.h 源码:components/lwip/lwip/src/include/lwip/dns.h
posted @ 2026-05-22 15:04  口嗨养生博  阅读(2)  评论(0)    收藏  举报