网络授时笔记

SNTP的全称是Simple Network Time Protocol,意思是简单网络时间协议,用来从网络中获取当前的时间,也可以称为网络授时。项目中会使用LwIP SNTP模块从服务器(pool.ntp.org)获取时间

我们使用sntp例程,sntp例程路径为D:\Espressif\frameworks\esp-idf-v5.1.3\examples\protocols\sntp。复制到实验文件夹,路径为D:\esp32c3\sntp,不用修改名字,使用VSCode打开sntp文件夹,程序不需要修改,直接配置好串口、目标芯片,menuconfig就可以下载看结果,menuconfig中,除了把flash大小修改为8MB以外,还需要添加你要连接wifi的名称和密码。

添加好之后,保存关闭,编译下载到开发板,看终端结果

cs 复制代码
I (10319) example: The current date/time in New York is: Wed Jan 31 06:54:54 2024
I (10319) example: The current date/time in Shanghai is: Wed Jan 31 19:54:54 2024
I (10329) example: Entering deep sleep for 10 seconds

只有一个c文件,点击打开main下面的sntp_example_main.c文件,可以先浏览一下这个c文件,一共有4个函数,分别是app_main()、obtain_time()、time_sync_notification_cb()、print_servers(),app_main()中调用了obtain_time()获取时间函数,obtain_time()函数中又调用了剩下的两个函数,后面两个函数只起到了终端打印信息提示作用,先看app_main函数,最开始的两行代码如下所示:

cs 复制代码
++boot_count;
ESP_LOGI(TAG, "Boot count: %d", boot_count)

前面我们已经知道,程序每10秒钟会唤醒一次,每次唤醒后,boot_count值会加1,并使用ESP_LOGI把boot_count的值打印到终端。这里用来表示唤醒和重启的不同,如果是重启,boot_count的值永远都是1

cs 复制代码
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
// Is time set? If not, tm_year will be (1970 - 1900).
if (timeinfo.tm_year < (2016 - 1900)) {
    ESP_LOGI(TAG, "Time is not set yet. Connecting to WiFi and getting time over NTP.");
    obtain_time();
    // update 'now' variable with current time
    time(&now);
}

1.time_t now定义一个64位变量now,使用time(&now)来获取系统当前时间,获取到的是一个64位的数字。

2.struct tm timeinfo定义一个结构体变量timeinfo,使用localtime_r(&now, &timeinfo)把64位的数字时间,各自提取到该结构体中的年月日时分秒等变量中。

cpp 复制代码
struct tm {  
  int tm_sec;   /* 秒 - 取值区间为[0,59] */
  int tm_min;   /* 分 - 取值区间为[0,59] */
  int tm_hour;  /* 时 - 取值区间为[0,23] */ 
  int tm_mday;  /* 一个月中的日期 - 取值区间为[1,31] */ 
  int tm_mon;   /* 月份(从一月开始,0代表一月)- 取值区间为[0,11] */
  int tm_year;  /* 年份,其值等于实际年份减去1900 */ 
  int tm_wday;  /* 星期--取值区间为[0,6],其中0代表星期天,1代表星期一,以此类推 */ 
  int tm_yday;  /* 从每年的1月1日开始的天数 -- 取值区间为[0,365],其中0代表1月1日,以此类推 */ 
  int tm_isdst; /*夏令时标识符,实行夏令时的时候,为1。不实行夏令时的进候,为0;不了解情况时,为-1。*/
}; 

使用上面提到的变量类型和函数,需要包含c语言的标准库函数<time.h>;

接下来,使用if判断结构体变量timeinfo中的成员tm_year的值。这条语句前面的注释,已经给到一个提示,如果tm_year的值没有设置过,将等于70。也就是说,如果开发板第一次执行这个程序,这个值将是70。如果现在是睡眠唤醒后执行到这里,今年是2024年,tm_year的值就是124,这个值需要加上1900才是当前的实际年份。obtain_time()函数用来从网络上获取当前时间。我们假设obtain_time函数已经执行完,继续看app_main函数。接下来的if条件编译没有在menuconfig中配置这个选项,所以不会执行。条件编译之后的语句如下所示:

cpp 复制代码
char strftime_buf[64];

// Set timezone to Eastern Standard Time and print local time
setenv("TZ", "EST5EDT,M3.2.0/2,M11.1.0", 1);
tzset();
localtime_r(&now, &timeinfo);
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
ESP_LOGI(TAG, "The current date/time in New York is: %s", strftime_buf);

// Set timezone to China Standard Time
setenv("TZ", "CST-8", 1);
tzset();
localtime_r(&now, &timeinfo);
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
ESP_LOGI(TAG, "The current date/time in Shanghai is: %s", strftime_buf);

上面代码中,先定义一个strftime_buf字符数组,用来存放最后将要打印的字符。

接下来的两个片段,分别设置为纽约时间和上海时间。

setenv()和tzset()设置时区。

strftime()函数根据timeinfo中的成员值把日期时间组合成一串字符,存储到strftime_buf数组中。

最后通过ESP_LOGI打印出strftime_buf数组中的字符串。

接下来的if语句,也不会执行,因为我们没有在menuconfig中设置SNTP_SYNC_MODE_SMOOTH。

最后的3条语句,如下所示:

cpp 复制代码
const int deep_sleep_sec = 10;
ESP_LOGI(TAG, "Entering deep sleep for %d seconds", deep_sleep_sec);
esp_deep_sleep(1000000LL * deep_sleep_sec);

esp_deep_sleep()函数把ESP32-C3设置为睡眠状态,10秒后自动唤醒。

以上就是app_main函数的执行流程。

接下来我们看obtain_time函数,看看时间是怎么获取到的。

obtain_time函数的前3条语句如下所示:

cpp 复制代码
ESP_ERROR_CHECK( nvs_flash_init() );
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK( esp_event_loop_create_default() );

以上3条语句用来做连接wifi之前的准备工作。

之后有一个if条件编译,我们没有设置,也不会执行,这里直接跳过分析。

接下来的一条语句如下所示,用来连接wifi:

cpp 复制代码
ESP_ERROR_CHECK(example_connect());

接下来的if条件编译,也不会执行,会执行它的else条件编译后的语句,用来打印信息。

cpp 复制代码
ESP_LOGI(TAG, "Initializing and starting SNTP");

接下来的if条件编译,当服务器数量大于1才会执行,我们只设置了一个获取时间的服务器,所以也不会执行。会执行else条件编译后的语句:

cpp 复制代码
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG(CONFIG_SNTP_TIME_SERVER);

这条语句定义了一个esp_sntp_config_t类型变量config,并给该变量配置了默认值。

接下来定义了一个回调函数time_sync_notification_cb。

cpp 复制代码
config.sync_cb = time_sync_notification_cb;     // Note: This is only needed if we want

放到这个函数上面,单击右键选择"转到定义",函数原型如下所示:

cpp 复制代码
void time_sync_notification_cb(struct timeval *tv)
{
    ESP_LOGI(TAG, "Notification of a time synchronization event");
}

该函数的目的只是用来打印信息,提示发生时间同步事件。

点击软件最上面的向左的箭头←,回到obtain_time函数中刚才的位置。

接下来的if条件编译也不会执行。

再接下来的语句如下所示,初始化sntp。

cpp 复制代码
esp_netif_sntp_init(&config);

再接下来执行打印服务器名称的函数,如下所示:

cpp 复制代码
print_servers();

把鼠标放到这个函数上面单击右键,选择"转到定义",函数原型如下所示:

cpp 复制代码
static void print_servers(void)
{
    ESP_LOGI(TAG, "List of configured NTP servers:");

    for (uint8_t i = 0; i < SNTP_MAX_SERVERS; ++i){
        if (esp_sntp_getservername(i)){
            ESP_LOGI(TAG, "server %d: %s", i, esp_sntp_getservername(i));
        } else {
            // we have either IPv4 or IPv6 address, let's print it
            char buff[INET6_ADDRSTRLEN];
            ip_addr_t const *ip = esp_sntp_getserver(i);
            if (ipaddr_ntoa_r(ip, buff, INET6_ADDRSTRLEN) != NULL)
                ESP_LOGI(TAG, "server %d: %s", i, buff);
        }
    }
}

在我们输出终端打印内容的下面两条就是该函数打印出的内容

cpp 复制代码
I (6439) example: List of configured NTP servers:
I (6449) example: server 0: pool.ntp.org

点击软件最上面的向左的箭头←,回到obtain_time函数中刚才的位置。再接下来的几条语句如下所示:

cpp 复制代码
// wait for time to be set
time_t now = 0;
struct tm timeinfo = { 0 };
int retry = 0;
const int retry_count = 15;
while (esp_netif_sntp_sync_wait(2000 / portTICK_PERIOD_MS) == ESP_ERR_TIMEOUT && ++retry < retry_count) {
    ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
}
time(&now);
localtime_r(&now, &timeinfo);

esp_netif_sntp_sync_wait()函数用来从服务器获取时间。间隔2秒获取一次,直到成功获取到时间,retry_count 定义了最多的获取次数。最后的两条语句如下所示:

cpp 复制代码
ESP_ERROR_CHECK( example_disconnect() );
esp_netif_sntp_deinit();

example_disconnect()函数用来断开网络连接。

esp_netif_sntp_deinit()函数用来清除初始化sntp配置。

相关推荐
至为芯36 分钟前
IP6537至为芯支持双C口快充输出的45W降压SOC芯片
c语言·开发语言
小羊羊Python1 小时前
SoundMaze v1.0.1正式发布!
开发语言·c++
浩瀚地学1 小时前
【Java】JDK8的一些新特性
java·开发语言·经验分享·笔记·学习
l1t1 小时前
利用DeepSeek将python DLX求解数独程序格式化并改成3.x版本
开发语言·python·算法·数独
JeffDingAI1 小时前
【Datawhale学习笔记】深入大模型架构
笔记·学习
暖阳之下2 小时前
学习周报三十一
学习
a不是橘子2 小时前
03在Ubuntu中验证PV操作
笔记·ubuntu·操作系统·虚拟机·os·pv操作
tangyal2 小时前
渗透笔记1
笔记·网络安全·渗透
yugi9878383 小时前
基于遗传算法优化主动悬架模糊控制的Matlab实现
开发语言·matlab
fanged3 小时前
STM32(5)--HAL1(TODO)
笔记