学习日记——ESP8266SNTP
SNTP基本知识
1、定义
SNTP是简单网络时间协议,而NTP网络时间协议就是网络计算机上同步计算时间的协议,具有高度的精确性,实际上也用不到这么高精度的算法。所以就在NTP上简化了以下变成SNTP,SNTP协议主要被用来同步因特网上计算机的时间。
2、工作方式
SNTP采用客户端/服务器的工作方式,可以采用单播或者广播的模式。
单播即客户端定期直接与SNTP服务器交互获取时间差进行校时。广播则是SNTP服务器定期向指定的多播地址发送时间信息,SNTP客户端通过监听这些多播地址来获取时间信息进行同步。UNIX网络编程中有SNTP广播方式的部分实现代码。
3、SNTP校时原理
SNTP协议主要是通过记录客户端向服务器发送数据包时的时间戳t1,服务器端接收到该数据包时的时间戳t2,服务器向客户端回应时的时间戳t3和最后客户端接收到服务器回应时的时间戳t4来计算客户端时间和服务器端时间的偏差,从而进行校时操作,如下图所示:
则t1与t2之间的时间差为((T2-T1)+ (T3-T4))/2
数据包在网络上的传播时间是 (T2-T1) + (T4-T3)
SNTP接口
1、sntp_setserver
功能:通过 IP 地址设置 SNTP 服务器,⼀共最多⽀持设置 3 个 SNTP 服务器
函数定义:void sntp_setserver(unsigned char idx, ip_addr_t *addr)
参数:
unsigned char idx
:SNTP 服务器编号,最多⽀持 3 个 SNTP 服务器(0 ~ 2);0 号为主服务器,1 号和 2 号为备⽤服务器。
ip_addr_t *addr
:IP 地址;⽤户需⾃⾏确保,传⼊的是合法 SNTP 服务器
返回:⽆
2、sntp_getserver
功能:查询 SNTP 服务器的 IP 地址,对应的设置接⼝为:sntp_setserver
函数定义: ip_addr_t sntp_getserver(unsigned char idx)
参数: unsigned char idx
:SNTP 服务器编号,最多⽀持 3 个 SNTP 服务器(0 ~ 2)
返回:IP 地址
3、sntp_setservername
功能:通过域名设置 SNTP 服务器,⼀共最多⽀持设置 3 个 SNTP 服务器
函数定义:void sntp_setservername(unsigned char idx, char *server)
参数
unsigned char idx
:SNTP 服务器编号,最多⽀持 3 个 SNTP 服务器(0 ~ 2);0 号为主服务器,1 号和 2 号为备⽤服务器。
char *server
:域名;⽤户需⾃⾏确保,传⼊的是合法 SNTP 服务器
返回:无
4、sntp_getservername
功能:查询 SNTP 服务器的域名,仅⽀持查询通过 sntp_setservername
设置的 SNTP 服务器
函数定义:char * sntp_getservername(unsigned char idx)
参数: unsigned char idx
:SNTP 服务器编号,最多⽀持 3 个 SNTP 服务器(0 ~ 2)
返回:服务器域名
5、sntp_init
功能:SNTP 初始化
函数定义:void sntp_init(void)
参数:⽆
返回:⽆
6、sntp_stop
功能:SNTP 关闭
函数定义:void sntp_stop(void)
参数:⽆
返回:⽆
7、sntp_get_current_timestamp
功能:查询当前距离基准时间 (1970.01.01 00:00:00 GMT + 8) 的时间戳,单位:秒
函数定义:uint32 sntp_get_current_timestamp()
参数:⽆
返回:距离基准时间的时间戳
8、sntp_get_real_time
功能:查询实际时间 (GMT + 8)
函数定义:char* sntp_get_real_time(long t)
参数:long t:与基准时间相距的时间戳
返回:实际时间
9、sntp_set_timezone
功能:设置时区信息
注意:调⽤本接⼝前,请先调⽤ sntp_stop
函数定义:bool sntp_set_timezone (sint8 timezone)
参数:sint8 timezone
:时区值,参数范围:-11 ~ 13
返回:
true:成功
false:失败
示例:
sntp_stop();if( true == sntp_set_timezone(-5) ) {sntp_init();}
10、.sntp_get_timezone
功能:查询时区信息
函数定义:sint8 sntp_get_timezone (void)
参数:⽆
返回:时区值,参数范围:-11 ~ 13
例程
首先在user_init函数中初始化串口,等待串口稳定后,使OLED显示初始化的内容,并且将ESP8266设置成STA模式,并且进行1秒钟的重复定时。这个user_init函数与之前UDP通信TCP通信的函数一样。区别在于需要OLED显示的内容不同。
void ICACHE_FLASH_ATTR user_init(void)
{uart_init(115200,115200); // 初始化串口波特率os_delay_us(10000); // 等待串口稳定os_printf("\t Project:\t%s\r\n", ProjectName);os_printf("\t SDK version:\t%s", system_get_sdk_version());// OLED显示初始化OLED_Init(); // OLED初始化OLED_ShowString(0,0," "); // Internet TimeOLED_ShowString(0,2,"Clock = "); // Clock:时钟OLED_ShowString(0,4,"Temp = "); // Temperature:温度OLED_ShowString(0,6,"Humid = "); // Humidity:湿度LED_Init_JX(); // LED初始化ESP8266_STA_Init_JX(); // ESP8266_STA初始化OS_Timer_IP_Init_JX(1000,1); // 1秒重复定时(获取IP地址)
}
设置好STA相关参数后,获取IP地址,如果成功获取到IP地址后,就初始换SNTP。
void ICACHE_FLASH_ATTR ESP8266_STA_Init_JX()
{struct station_config STA_Config; // STA参数结构体struct ip_info ST_ESP8266_IP; // STA信息结构体// 设置ESP8266的工作模式wifi_set_opmode(0x01); // 设置为STA模式,并保存到Flash/*// 设置STA模式下的IP地址【ESP8266默认开启DHCP Client,接入WIFI时会自动分配IP地址】
wifi_station_dhcpc_stop(); // 关闭 DHCP ClientIP4_ADDR(&ST_ESP8266_IP.ip,192,168,8,35); // 配置IP地址IP4_ADDR(&ST_ESP8266_IP.gw,192,168,8,1); // 配置网关地址IP4_ADDR(&ST_ESP8266_IP.netmask,255,255,255,0); // 配置子网掩码wifi_set_ip_info(STATION_IF,&ST_ESP8266_IP); // 设置STA模式下的IP地址*/// 结构体赋值,配置STA模式参数os_memset(&STA_Config, 0, sizeof(struct station_config)); // STA参数结构体 = 0os_strcpy(STA_Config.ssid,ESP8266_STA_SSID); // 设置WIFI名os_strcpy(STA_Config.password,ESP8266_STA_PASS); // 设置WIFI密码wifi_station_set_config(&STA_Config); // 设置STA参数,并保存到Flash// wifi_station_connect(); // ESP8266连接WIFI
}
初始化SNTP函数。ESP8266最多设置三个SNTP服务器,其中SNTP服务器0是主服务器,剩下的两个是备用服务器。我们可以调用sntp_setservername(0, "us.pool.ntp.org");
和sntp_setserver(2, addr);
API来设置服务器。参数1是SNTP的优先级。第一个API的参数2是SNTP的域名第二个API的参数2是指SNTP服务器的32位IP地址,在这个API之前调用ipaddr_aton("210.72.145.44", addr);
API将字符串转换成点分二进制位32位的IP地址。之后调用sntp_init();
这个API初始化SNTP。最后设置了1秒钟的重复定时。
void ICACHE_FLASH_ATTR ESP8266_SNTP_Init_JX(void)
{ip_addr_t * addr = (ip_addr_t *)os_zalloc(sizeof(ip_addr_t));sntp_setservername(0, "us.pool.ntp.org"); // 服务器_0【域名】sntp_setservername(1, "ntp.sjtu.edu.cn"); // 服务器_1【域名】ipaddr_aton("210.72.145.44", addr); // 点分十进制 => 32位二进制sntp_setserver(2, addr); // 服务器_2【IP地址】os_free(addr); // 释放addrsntp_init(); // SNTP初始化APIOS_Timer_SNTP_Init_JX(1000,1); // 1秒重复定时(SNTP)
}
在SNTP定时回调函数中使用TimeStamp = sntp_get_current_timestamp();
获取当前距离基准时间的时间戳,基准时间为1970.01.01 00:00:00 GMT+8。如果这个时间戳不等于0,就说明我们的SNTP获取网络时间成功。我们调用下一个APIStr_RealTime = sntp_get_real_time(TimeStamp);
参数为距离基准时间的时间戳,来获取当前时间的真实时间。接着串口打印时间戳和实际时间。之后我们根据获取到的实际时间用OLED显示。并且OLED显示读取到的温湿度
void ICACHE_FLASH_ATTR OS_Timer_SNTP_cb(void * arg)
{// 字符串整理 相关变量u8 C_Str = 0; // 字符串字节计数char A_Str_Data[20] = {0}; // 【"日期"】字符串数组char *T_A_Str_Data = A_Str_Data; // 缓存数组指针char A_Str_Clock[10] = {0}; // 【"时间"】字符串数组char * Str_Head_Week; // 【"星期"】字符串首地址char * Str_Head_Month; // 【"月份"】字符串首地址char * Str_Head_Day; // 【"日数"】字符串首地址char * Str_Head_Clock; // 【"时钟"】字符串首地址char * Str_Head_Year; // 【"年份"】字符串首地址uint32 TimeStamp; // 时间戳char * Str_RealTime; // 实际时间的字符串// 查询当前距离基准时间(1970.01.01 00:00:00 GMT+8)的时间戳(单位:秒)TimeStamp = sntp_get_current_timestamp();if(TimeStamp) // 判断是否获取到偏移时间{//os_timer_disarm(&OS_Timer_SNTP); // 关闭SNTP定时器// 查询实际时间(GMT+8):东八区(北京时间)Str_RealTime = sntp_get_real_time(TimeStamp);// 【实际时间】字符串 == "周 月 日 时:分:秒 年"os_printf("SNTP_TimeStamp = %d\r\n",TimeStamp); // 时间戳os_printf("\r\nSNTP_InternetTime = %s",Str_RealTime); // 实际时间// 时间字符串整理,OLED显示【"日期"】、【"时间"】字符串 // 【"年份" + ' '】填入日期数组Str_Head_Year = Str_RealTime; // 设置起始地址while( *Str_Head_Year ) // 找到【"实际时间"】字符串的结束字符'\0'Str_Head_Year ++ ;// 【注:API返回的实际时间字符串,最后还有一个换行符,所以这里 -5】Str_Head_Year -= 5 ; // 获取【"年份"】字符串的首地址T_A_Str_Data[4] = ' ' ;os_memcpy(T_A_Str_Data, Str_Head_Year, 4); // 【"年份" + ' '】填入日期数组T_A_Str_Data += 5; // 指向【"年份" + ' '】字符串的后面的地址// 获取【日期】字符串的首地址Str_Head_Week = Str_RealTime; // "星期" 字符串的首地址Str_Head_Month = os_strstr(Str_Head_Week, " ") + 1; // "月份" 字符串的首地址Str_Head_Day = os_strstr(Str_Head_Month, " ") + 1; // "日数" 字符串的首地址Str_Head_Clock = os_strstr(Str_Head_Day, " ") + 1; // "时钟" 字符串的首地址// 【"月份" + ' '】填入日期数组C_Str = Str_Head_Day - Str_Head_Month; // 【"月份" + ' '】的字节数os_memcpy(T_A_Str_Data, Str_Head_Month, C_Str); // 【"月份" + ' '】填入日期数组T_A_Str_Data += C_Str; // 指向【"月份" + ' '】字符串的后面的地址// 【"日数" + ' '】填入日期数组C_Str = Str_Head_Clock - Str_Head_Day; // 【"日数" + ' '】的字节数os_memcpy(T_A_Str_Data, Str_Head_Day, C_Str); // 【"日数" + ' '】填入日期数组T_A_Str_Data += C_Str; // 指向【"日数" + ' '】字符串的后面的地址// 【"星期" + ' '】填入日期数组C_Str = Str_Head_Month - Str_Head_Week - 1; // 【"星期"】的字节数os_memcpy(T_A_Str_Data, Str_Head_Week, C_Str); // 【"星期"】填入日期数组T_A_Str_Data += C_Str; // 指向【"星期"】字符串的后面的地址// OLED显示【"日期"】、【"时钟"】字符串*T_A_Str_Data = '\0'; // 【"日期"】字符串后面添加'\0'OLED_ShowString(0,0,A_Str_Data); // OLED显示日期os_memcpy(A_Str_Clock, Str_Head_Clock, 8); // 【"时钟"】字符串填入时钟数组A_Str_Clock[8] = '\0';OLED_ShowString(64,2,A_Str_Clock); // OLED显示时间}// 每5秒,读取/显示温湿度数据//-----------------------------------------------------------------------------------------C_Read_DHT11 ++ ; // 读取DHT11计时if(C_Read_DHT11>=5) // 5秒计时{C_Read_DHT11 = 0; // 计时=0if(DHT11_Read_Data_Complete() == 0) // 读取DHT11温湿度{DHT11_NUM_Char(); // DHT11数据值转成字符串OLED_ShowString(64,4,DHT11_Data_Char[1]); // DHT11_Data_Char[0] == 【温度字符串】OLED_ShowString(64,6,DHT11_Data_Char[0]); // DHT11_Data_Char[1] == 【湿度字符串】}else{OLED_ShowString(64,4,"----"); // Temperature:温度OLED_ShowString(64,6,"----"); // Humidity:湿度}}//-----------------------------------------------------------------------------------------
}
现象
下载程序,打开串口助手,复位ESP8266,等待几秒后。我们可以看到串口助手成功获取到距离基准时间的时间戳。
根据时间戳成功获取当前的实际时间,与我们电脑的网络时间一直,说明我们的ESP8266成功获取到我们准确的实际时间,ESP8266的OLED上可以显示当前的时间以及温湿度。我从OLED上看到时间会跟着时间时间一秒一秒的增加。
参考链接
https://www.bilibili.com/video/BV1dJ411S723?p=43
https://www.cnblogs.com/jiangzhaowei/p/8830583.html
https://zhidao.baidu.com/question/572961879.html
学习日记——ESP8266SNTP相关推荐
- java的圆周率_java学习日记,圆周率的打印
前段时间看到听说学习java每天写技术贴会对自己提升很大,我现在学习java也就2个周,算不上技术贴,就写写学习日记吧. 昨天师傅给我出了一道题,说是试试用java打印圆周率. 刚开始我的思路是,如果 ...
- GPU(CUDA)学习日记(十一)------ 深入理解CUDA线程层次以及关于设置线程数的思考
GPU(CUDA)学习日记(十一)------ 深入理解CUDA线程层次以及关于设置线程数的思考 标签: cuda存储线程结构网格 2012-12-07 16:30 6298人阅读 评论(4)收藏 举 ...
- GPU(CUDA)学习日记(十三)------ CUDA内存简介
GPU(CUDA)学习日记(十三)------ CUDA内存简介 标签: cuda存储线程结构 2012-12-07 16:53 2902人阅读 评论(0)收藏 举报 分类: GPU(16) CUDA ...
- GPU(CUDA)学习日记(九)------ CUDA存储器模型
GPU(CUDA)学习日记(九)------ CUDA存储器模型 标签: cuda存储bindingcache编程api 2012-09-27 10:53 1677人阅读 评论(1) 收藏 举报 分类 ...
- 语言余一个负数_C语言学习日记(8)——整数溢出
小时候喜欢玩电子计算器,觉得很神奇,想要算什么数,立刻就能算出来.当时看着计算器的液晶屏幕就想,如果算一个很大的数,超过了屏幕上面数字的位数,会怎么样呢?试了以后结果是INF,那就是无穷大了.虽然当时 ...
- SpringMVC学习日记 1.Spring框架
SpringMVC学习日记 1.Spring框架 Spring简介 Spring框架是一个开源框架,由Rod Johnson组织和开发,生产目的在于简化企业级应用的开发. 主要特性 非侵入(no-in ...
- 微信开发学习日记(一):快速阅读5本书,掌握整体背景
2015年1月开始学习微信开发. 已经有多年开发经验了,从网上文章来看,微信开发主要是接口,然后是业务逻辑,不是很难.所以,我比较强调学习效率.一天学一点,是不能满足我的快速学习欲望的. ...
- const 指针_C语言学习日记(11)——const与指针
对于一个普通指针typet *p.p有三个最基本的能力,第一就是可以通过p = &obj来指向一个type类型对象,并随意切换指向对象.第二就是通过value = *p来读取它指向的对象的值. ...
- 十五的学习日记20160926-你不知道的JS笔记/
十五的学习日记20160926 JavaScript 一个用于检测正负值的函数,可以用它辨别-0值. 我觉得挺好用,以后可以写到自己的工具库里. //函数:检查传入参数是否为正数.Number=> ...
最新文章
- 详解BSCI实验四:配置BGP
- python制作adobe photoshop插件_Adobe Photoshop风格的后期处理和OpenCV
- 视频监控行业解决方案
- python能做游戏吗-python能开发游戏吗
- 云效 Flow——Java构建并通过云效上传二方库到 Maven 私有仓库
- SDN第二章 win10远程桌面Ubuntu
- WPF任务栏同步进度
- LeetCode 1256. 加密数字(bitset)
- A_Pancers团队项目设计完善编码测试
- Unity2020.1新功能探路:Profiler相关更新
- ffmpeg h264 解码 转
- Android 文件管理器 Android文件管理器源代码
- mysql 执行计划 视频_实战讲解MySQL执行计划,面试官当场要了我
- 《单细胞生物》教学反思
- SpringData JPA整理
- 公开SNS社区即时找朋友链的源代码和部署方案(续六)
- ctf MISC disordered_zip
- 20210406森林里的兔子
- Paul Graham:如何做好天使投资
- python中字符串的处理_Python中字符串的处理技巧分享
热门文章
- Linux学习资源推荐
- 初一计算机课怎么上,如何上好初中信息技术课
- 公共数据 | CHFS中国家庭金融调查数据
- 西门子V20变频器PID控制恒压供水的具体步骤
- C语言easyx制作华容道游戏,huarongdao 用EasyX编写的华容道程序 - 下载 - 搜珍网
- asp.net 2.0控件开发电子书 下载
- 名词解释语言表达的6c原则,货币银行学中的“6C原则”是什么意思
- 16GB大内存该怎么玩儿?
- 中国石油大学《化工过程设备设计》第三阶段在线作业
- Python爬虫——使用线程池爬取同程旅行景点数据并做数据可视化