网络授时笔记

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的名称和密码。

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

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函数,最开始的两行代码如下所示:

++boot_count;
ESP_LOGI(TAG, "Boot count: %d", boot_count)

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

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 timetime(&now);
}

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

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

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中配置这个选项,所以不会执行。条件编译之后的语句如下所示:

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条语句,如下所示:

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条语句如下所示:

ESP_ERROR_CHECK( nvs_flash_init() );
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK( esp_event_loop_create_default() );

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

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

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

ESP_ERROR_CHECK(example_connect());

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

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

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

esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG(CONFIG_SNTP_TIME_SERVER);

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

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

config.sync_cb = time_sync_notification_cb;     // Note: This is only needed if we want

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

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

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

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

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

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

esp_netif_sntp_init(&config);

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

print_servers();

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

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 itchar 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);}}
}

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

I (6439) example: List of configured NTP servers:
I (6449) example: server 0: pool.ntp.org

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

// 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 定义了最多的获取次数。最后的两条语句如下所示:

ESP_ERROR_CHECK( example_disconnect() );
esp_netif_sntp_deinit();

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/710.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTTP-响应协议

HTTP的响应过程&#xff1f; 浏览器请求数据--》web服务器过程&#xff1a;请求过程 web服务器将响应数据-》到浏览器&#xff1a;响应过程 响应数据有哪些内容&#xff1f; 1.和请求数据类似。 2. 响应体中存储着web服务器返回给浏览器的响应数据。并且注意响应头和响应体之间…

【学习笔记】理解深度学习和机器学习的数学基础:数值计算

深度学习作为人工智能领域的一个重要分支&#xff0c;其算法的实现和优化离不开数值计算。数值计算在深度学习中扮演着至关重要的角色&#xff0c;它涉及到如何在计算机上高效、准确地解决数学问题。本文将介绍深度学习中数值计算的一些关键概念和挑战&#xff0c;以及如何应对…

JVM与Java体系结构

一、前言: Java语言和JVM简介: Java是目前最为广泛的软件开发平台之一。 JVM:跨语言的平台 随着Java7的正式发布&#xff0c;Java虚拟机的设计者们通过JSR-292规范基本实现在Java虚拟机平台上运行非Java语言编写的程序。 Java虚拟机根本不关心运行在其内部的程序到底是使用何…

计科高可用服务器架构实训(防火墙、双机热备,VRRP、MSTP、DHCP、OSPF)

一、项目介绍 需求分析&#xff1a; &#xff08;1&#xff09;总部和分部要求网络拓扑简单&#xff0c;方便维护&#xff0c;网络有扩展和冗余性&#xff1b; &#xff08;2&#xff09;总部分财务部&#xff0c;人事部&#xff0c;工程部&#xff0c;技术部&#xff0c;提供…

4.3.3 最优二叉树+二叉查找树

文章目录 基本概念哈夫曼方法应用&#xff1a;通信编码译码二叉查找树 基本概念 最优二叉树哈夫曼树 哈夫曼树&#xff1a;带权路径长度最短的树。 路径&#xff1a;一个结点到另一个结点的通路。 路径长度&#xff1a;路径上的分支数量。 树的路径长度&#xff1a;根到每个叶子…

Conda虚拟Python环境下安装包遇到的坑

明天下午要去参加Nvidia组织的一个开发者夏令营活动&#xff0c;按照2024 NVIDIA开发者社区夏令营环境配置指南(Win & Mac)_nvidia mac-CSDN博客提供的指引配置环境。里面建议的是用conda来配置Python虚拟环境&#xff0c;原本本机直接安装最直接&#xff0c;不过正好学习下…

MVC执行流程

&#xff08;1&#xff09;用户通过浏览器&#xff08;客户端&#xff09;向服务端&#xff08;后端&#xff09;发送请求&#xff0c;请求会被前端控制器DispatcherServlet拦截。 &#xff08;2&#xff09;DispatcherServlet拦截到请求后&#xff0c;会调用处理器映射器&…

Spring——依赖注入之p命名空间和c命名空间

p命名空间 其实就是Set注入 只不过p命名空间写法更简洁 p可以理解为 property标签的首字母p p命名空间依赖于set方法 依赖引入 使用前需要再配置文件头文件中引入p命名空间的依赖&#xff1a; ** xmlns:p“http://www.springframework.org/schema/p” ** 用法 在bean标签…

01-51单片机LED与独立按键

一、单片机概述 注意&#xff1a;个人学习笔记&#xff0c;里面涉及到的C语言和进程转换相关的知识在C语言部分已经写了&#xff0c;这里是默认都会的状态学习单片机。 1.什么是单片机 单片机&#xff0c;英文Micro Controller Unit&#xff0c;简称MCU。其内部集成了CPU、R…

Day04-后端Web基础——Maven基础

目录 Maven课程内容1. Maven初识1.1 什么是Maven?1.2 Maven的作用1.2.1 依赖管理1.2.2 项目构建1.2.3 统一项目结构 2. Maven概述2.1 Maven介绍2.2 Maven模型2.3 Maven仓库2.4 Maven安装2.4.1 下载2.4.2 安装步骤 3. IDEA集成Maven3.1 配置Maven环境3.1.2 全局设置 3.2 Maven项…

spring boot学习第二十三篇:Spring Boot集成RocketMQ

前置条件先安装好RocketMQ 希望在Window10安装rocketMQ并简单使用&#xff0c;可以参考如下文章&#xff1a; Window10安装rocketMQ并简单使用-CSDN博客 1、pom.xml文件里面加上依赖 <dependency><groupId>org.apache.rocketmq</groupId><artifactId&…

【Docker】入门教程

目录 一、Docker的安装 二、Docker的命令 Docker命令实验 1.下载镜像 2.启动容器 3.修改页面 4.保存镜像 5.分享社区 三、Docker存储 1.目录挂载 2.卷映射 四、Docker网络 1.容器间相互访问 2.Redis主从同步集群 3.启动MySQL 五、Docker Compose 1.命令式安装 …

Go语言之路————go环境的初始化

Go语言之路————go环境的初始化 前言一、Go的安装二、环境配置三、初始化一个新项目四、常用的一些指令 前言 我是一名多年Java开发人员&#xff0c;因为工作需要现在要学习go语言&#xff0c;Go语言之路是一个系列&#xff0c;记录着我从0开始接触Go&#xff0c;到后面能正…

【C语言系列】函数递归

函数递归 一、递归是什么&#xff1f;1.1尾递归 二、递归的限制条件三、递归举例3.1举例一&#xff1a;求n的阶乘3.2举例二&#xff1a;顺序打印一个整数的每一位 四、递归与迭代4.1举例三&#xff1a;求第n个斐波那契数 五、拓展学习青蛙跳台问题 一、递归是什么&#xff1f; …

编程题-二分查找

题目&#xff1a; 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1 解法一&#xff08;循环遍历查找&#xff09;&#xff…

关于大数据的基础知识(一)——定义特征结构要素

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///计算机爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于大数据的基础知识&#xff08;一&a…

【git】-2 分支管理

目录 一、分支的概念 二、查看、创建、切换分支 1、查看分支-git branch 2、创建分支- git branch 分支名 3、切换分支- git checkout 分支名 三、git指针 -实现分支和版本间的切换 四、普通合并分支 git merge 文件名 五、冲突分支合并 ​​​​​​【git】-初始gi…

Maven核心插件之maven-resources-plugin

前言 Maven 插件是 Maven 构建系统的重要组成部分&#xff0c;它们为 Maven 提供了丰富的功能和扩展能力&#xff0c;使得 Maven 不仅是一个构建工具&#xff0c;更是一个强大的项目管理平台。在 Maven 项目中&#xff0c;插件的使用通常通过配置 pom.xml 文件来完成。每个插件…

[云原生之旅] K8s-Portforward的另类用法, 立省两个端口

前言 此方法适用于Pod不需要大量连接的情况: 有多个pod在执行任务, 偶尔需要连接其中一个pod查看进度/日志;对pod执行一个脚本/命令; 不适用于大量连接建立的情况: pod启的数据库服务;pod启的Api服务;pod启的前端服务;pod启的Oss服务; Portforward简介 Portforward就是端…

MySQL进阶突击系列(05)突击MVCC核心原理 | 左右护法ReadView视图和undoLog版本链强强联合

2024小结&#xff1a;在写作分享上&#xff0c;这里特别感谢CSDN社区提供平台&#xff0c;支持大家持续学习分享交流&#xff0c;共同进步。社区诚意满满的干货&#xff0c;让大家收获满满。 对我而言&#xff0c;珍惜每一篇投稿分享&#xff0c;每一篇内容字数大概6000字左右&…