Arduino ESP32 获取网络时间并同步本地RTC时钟

Arduino ESP32 获取网络时间并同步本地RTC时钟


  • 🎉 在 ArduinoESP32核心支持库当中已经包含相关的获取时间的库,获取网络时间后,就可以不依赖网络,重复去获取时间,如果长时间运行,可以设置间隔时间同步NTP时间,只要访问本地时间的相关函数能正常调用,就没有问题。
  • 🔖使用读取本地时间,好处就是不需要频繁去获取NTP时间,占用网络资源,最大节省资源,适合低功耗下运行,保证时间运行准确。只要开机运行获取一次网络时间后,就可以关闭网络,后面读取本地时间,可以最大限度的不依赖网络来获取时间。
  • 🔖在访问本地时间的时候,有些看似不重要的细节,往往很容易掉到坑里去。

🧲实施条件

  • 🌿ESP32需要在:WiFi.mode(WIFI_STA);模式下,配网并接入网络。
  • 🌿使用下面函数从网络时间服务器上获取并设置时间:
configTime(long gmtOffset_sec, int daylightOffset_sec, const char* server1, const char* server2 = nullptr, const char* server3 = nullptr)
  • 🔖参数说明:
  • gmtOffset_sec 参数就是用来修正时区的,比如对于我们东八区(UTC/GMT+08:00)来说该参数就需要填写 8 * 3600 ;
  • daylightOffset_sec使用夏令时 daylightOffset_sec 就填写3600,否则就填写0;

通过网络时间服务器获得的时间是世界协调时间(UTC)/格林尼治时间(GMT),不同地区的时间可以通过时区换算.

  • 设置完成后就可以使用下面函数读取当前时间了:
bool getLocalTime(struct tm * info, uint32_t ms = 5000)
  • 📄参数说明:
  • ms 为该操作超时时间,超时则返回false;
    info 是一个 struct tm 结构体对象,用于接收当前时间;

获取成功后芯片会使用RTC时钟保持时间的更新,这时候,就可以不依赖网络了,可以关闭网络,运行时读取本地同步过的时间。

🎯测试例程一

  • 有网状态下,更新时间。
/**
  ESP32 
*/

#include <WiFi.h>

#define NTP1  "ntp1.aliyun.com"
#define NTP2  "ntp2.aliyun.com"
#define NTP3  "ntp3.aliyun.com"

//填写WIFI入网信息
const char* ssid     = "########";     // WIFI账户
const char* password = "********"; // WIFI密码

void setClock() {
  struct tm timeinfo;
    if (!getLocalTime(&timeinfo))
    {//如果获取失败,就开启联网模式,获取时间
        Serial.println("Failed to obtain time");
     //    WiFi.disconnect(false);
        WiFi.mode(WIFI_STA);//开启网络  
       WiFi.begin(ssid, password);
         while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.print(".");
    }
     configTime(8 * 3600, 0, NTP1, NTP2,NTP3);
        return;
    }
    Serial.println(&timeinfo, "%F %T %A"); // 格式化输出:2021-10-24 23:00:44 Sunday
    Serial.print(asctime(&timeinfo));//默认打印格式:Mon Oct 25 11:13:29 2021
  //   WiFi.disconnect(true);//断开网络连接,关闭网络
}

void setup()
{
    Serial.begin(115200);
    Serial.println();
 //设置ESP32工作模式为无线终端模式
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.print(".");
    }
    Serial.println("WiFi connected!");
 configTime(8 * 3600, 0, NTP1, NTP2,NTP3);
      setClock();
    // 从网络时间服务器上获取并设置时间
    // 获取成功后芯片会使用RTC时钟保持时间的更新
   
//    WiFi.disconnect(true);//断开wifi网络
//    WiFi.mode(WIFI_OFF);//关闭网络
    Serial.println("WiFi disconnected!");
}

void loop()
{
  
  Serial.println("Waiting 10s before the next round...");
  delay(10000);
  setClock();
}

  • 🎉串口打印信息
    在这里插入图片描述
🧲默认的时间格式输出:asctime(&timeinfo),如果像将该数据传递给其他地方使用,可以使用char*变量来接收。例如:char* timelist=asctime(&timeinfo);需要注意的是,并不能使用const char* timelist2 =(&timeinfo, "%F %T %A");来接收这种格式化的数据,得到的将是%F %T %A的字符串结果。

🎄struct tm结构体与格式化输出

通过一个结构体将时间数据拆解成段,满足不同需求的显示。

  • struct tm结构体
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~11;
int tm_year; // 年,其值等于实际年份减去1900;
int tm_wday; // 星期,取值0~6,0为周日,1为周一,依此类推;
int tm_yday; // 年中的日期,取值0~365,0代表1月1日,1代表1月2日,依此类推;
int tm_isdst; // 夏令时标识符,实行夏令时的时候,tm_isdst为正;不实行夏令时的进候,tm_isdst为0;不了解情况时,tm_isdst()为负
};

  • 格式化输出(只能在申明tm结构体函数内调用执行)

Serial.println(&timeinfo, "%F %T %A"); // 格式化输出:2021-10-24 23:00:44 Sunday Serial.print(asctime(&timeinfo));//默认打印格式:Mon Oct 25 11:13:29 2021

%a 星期几的简写
%A 星期几的全称
%b 月分的简写
%B 月份的全称
%c 标准的日期的时间串
%C 年份的后两位数字
%d 十进制表示的每月的第几天
%D 月//%e 在两字符域中,十进制表示的每月的第几天
%F 年--%g 年份的后两位数字,使用基于周的年
%G 年分,使用基于周的年
%h 简写的月份名
%H 24小时制的小时
%I 12小时制的小时
%j 十进制表示的每年的第几天
%m 十进制表示的月份
%M 十时制表示的分钟数
%p 本地的AM或PM的等价显示
%r 12小时的时间
%R 显示小时和分钟:hh:mm
%S 十进制的秒数
%t 水平制表符
%T 显示时分秒:hh:mm:ss
%u 每周的第几天,星期一为第一天 (值从06,星期一为0%U 第年的第几周,把星期日做为第一天(值从053%V 每年的第几周,使用基于周的年
%w 十进制表示的星期几(值从06,星期天为0%W 每年的第几周,把星期一做为第一天(值从053%x 标准的日期串
%X 标准的时间串
%y 不带世纪的十进制年份(值从099%Y 带世纪部分的十进制年份
%z,%Z 时区名称,如果不能得到时区名称则返回空字符

📑strftime函数
  • 🥕原函数:size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr)
  • str – 这是指向目标数组的指针,用来复制产生的 C 字符串。
  • maxsize – 这是被复制到 str 的最大字符数。
  • format – 这是 C 字符串,包含了普通字符和特殊格式说明符的任何组合。这些格式说明符由函数替换为表示 tm 中所指定时间的相对应值。格式说明符是:
说明符	替换为	实例
%a	缩写的星期几名称	Sun
%A	完整的星期几名称	Sunday
%b	缩写的月份名称	Mar
%B	完整的月份名称	March
%c	日期和时间表示法	Sun Aug 19 02:56:02 2012
%d	一月中的第几天(01-3119
%H	24 小时格式的小时(00-2314
%I	12 小时格式的小时(01-1205
%j	一年中的第几天(001-366231
%m	十进制数表示的月份(01-1208
%M	分(00-5955
%p	AM 或 PM 名称	PM
%S	秒(00-6102
%U	一年中的第几周,以第一个星期日作为第一周的第一天(00-5333
%w	十进制数表示的星期几,星期日表示为 00-64
%W	一年中的第几周,以第一个星期一作为第一周的第一天(00-5334
%x	日期表示法	08/19/12
%X	时间表示法	02:50:06
%y	年份,最后两个数字(00-9901
%Y	年份	2012
%Z	时区的名称或缩写	CDT
%%	一个 % 符号	%
  • ✅以下内容更新时间(2024-1-2 12:23:29)

📒测试例程二

  • 🔖软件RTC时间
  • 🔖所需库需要自行安装。
#include <WiFi.h>
#include <NTPClient.h>
#include <RTClib.h>

// 网络时间相关定义
const char *ssid = "########";  // 填写WiFi账号
const char *password = "********";   // WiFi密码

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

WiFiUDP udp;
NTPClient timeClient(udp);
RTC_Millis rtc; // 使用ESP32软件RTC_Millis实现

DateTime getNTPTime() {
  timeClient.update();
  time_t rawTime = timeClient.getEpochTime();
  return DateTime(rawTime);
}

void setupWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("WiFi Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }
  timeClient.begin();
  timeClient.setTimeOffset(28800); // 设置时区偏移量,这里为8小时=28800秒
}

void setup() {
  Serial.begin(115200);
  setupWiFi();

  // 同步RTC时间
  rtc.begin(getNTPTime());
}



void loop() {
  // put your main code here, to run repeatedly:
delay(1000);
DateTime now = rtc.now();

    Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.day(), DEC);
    Serial.print(" (");
    Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
    Serial.print(") ");
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println();
}

在这里插入图片描述

📙例程三:本地设置RTC时间(更新内容日期:2024-5-22)

#include <WiFi.h>
#include <sys/time.h>

// 网络时间相关定义,(没有使用到网络)
const char *ssid = "########";  // 填写WiFi账号
const char *password = "********";   // WiFi密码

 struct tm timeinfo 
 = {
   .tm_sec  = 30,        /* 秒,范围从 0 到 59 */
   .tm_min  = 20,        /* 分,范围从 0 到 59  */
   .tm_hour   = 16,        /* 小时,范围从 0 到 23 */
   .tm_mday   =22,       /* 一月中的第几天,范围从 1 到 31 */
   .tm_mon    =5-1,         /* 月份,范围从 0 到 11   */
   .tm_year   = 124,       /* 自 1900 起的年数  */
   .tm_wday   = 2,        /* 一周中的第几天,范围从 0 到 6*/
   .tm_yday   =143,        /* 一年中的第几天,范围从 0 到 365*/
   .tm_isdst  =0,       /* 夏令时*/    
};

void timePrint() {
  getLocalTime(&timeinfo,0);
  if (timeinfo.tm_year >= 117) Serial.println(&timeinfo, "%B %d %Y %H:%M:%S (%A)");
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
    time_t t = mktime(&timeinfo);//转为时间戳
    printf("Setting time: %s", asctime(&timeinfo));
    struct timeval now = { .tv_sec = t };
    settimeofday(&now, NULL);//设定RTC时间
}

void loop() {
  // put your main code here, to run repeatedly:
 delay(1000);
timePrint();//打印时间信息
}

  • 串口打印信息:
    在这里插入图片描述

📘例程四:利用settimeofday设置时间和gettimeofday()函数获取时间

#include <WiFi.h>
#include <sys/time.h>

// 网络时间相关定义,(没有使用到网络)
const char *ssid = "########";  // 填写WiFi账号
const char *password = "********";   // WiFi密码

tm timeinfo = {
   .tm_sec  = 30,        /* 秒,范围从 0 到 59 */
   .tm_min  = 20,        /* 分,范围从 0 到 59  */
   .tm_hour   = 16,        /* 小时,范围从 0 到 23 */
   .tm_mday   =22,       /* 一月中的第几天,范围从 1 到 31 */
   .tm_mon    =5-1,         /* 月份,范围从 0 到 11   */
   .tm_year   = 124,       /* 自 1900 起的年数  */
   .tm_wday   = 2,        /* 一周中的第几天,范围从 0 到 6*/
   .tm_yday   =143,        /* 一年中的第几天,范围从 0 到 365*/
   .tm_isdst  =0,       /* 夏令时*/    
};

void setTime(tm timeStruct) {
  time_t t = mktime(&timeStruct);
    printf("Setting time: %s", asctime(&timeStruct));

    struct timeval now = { .tv_sec = t };
    settimeofday(&now, NULL);
   
}

void getTime(struct tm *timeStruct) {
//struct tm now;

 // getLocalTime(&timeStruct,0);
 // if (timeStruct.tm_year >= 117) Serial.println(&timeStruct, "%B %d %Y %H:%M:%S (%A)");
  struct timeval tv;
gettimeofday(&tv, NULL);
time_t now = tv.tv_sec;
// 将时间戳转换为本地时间
//struct tm * timeinfo;
//timeStruct = localtime(&now);//不要启用这个函数,不走时。
localtime_r(&now, timeStruct);
strftime(strftime_buf, sizeof(strftime_buf), "%c", timeStruct);
printf( "The current date/time: %s", strftime_buf);
}


void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);

  setTime(timeinfo);
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(1000);
  getTime(&timeinfo);
printf("当前的本地时间和日期:%s", asctime(&timeinfo));
    Serial.print("星期: ");
    Serial.println(timeinfo.tm_wday);
    Serial.print("日期: ");
    Serial.println(timeinfo.tm_mday);
    Serial.print("月份: ");
    Serial.println(timeinfo.tm_mon);
    Serial.print("年份: ");
    Serial.println(timeinfo.tm_year);
    Serial.print("小时: ");
    Serial.println(timeinfo.tm_hour);
    Serial.print("分钟: ");
    Serial.println(timeinfo.tm_min);
    Serial.print("秒数: ");
    Serial.println(timeinfo.tm_sec);
}

  • 📜打印效果:
    在这里插入图片描述
  • 🌟注意:这里直接打印的是tm 结构体成员值。如果想准确的输出时间信息,参照tm 时间结构体注释说明,,进行调整。
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 到 11                */
   int tm_year;        /* 自 1900 起的年数                */
   int tm_wday;        /* 一周中的第几天,范围从 0 到 6                */
   int tm_yday;        /* 一年中的第几天,范围从 0 到 365                    */
   int tm_isdst;       /* 夏令时                        */    
};

  • 🧨例如:
Serial.printf("%04d",1900+timeinfo.tm_year);//年
Serial.printf("%02d",timeinfo.tm_mon+1);//月
Serial.printf("%d",timeinfo.tm_wday+1);//星期
  • 21
    点赞
  • 202
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值