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