RTC 实时时钟
本节课来学习使用 ESP32 中的 RTC 模块。
实验原理
RTC 全称为实时时钟(Real-time Clock),是一种与 CPU 互不干扰,独立于 CPU 运行的计时设备。
RTC 主要用于在计算机系统关机时,保存计算机系统时钟,以便在下次计算机系统开机时能够从 RTC 中恢复出正确的时间。
RTC 的应用场景非常广泛,例如实现时间戳功能、自动唤醒、计时器等。在一些需要记录时间的项目中,RTC 可以作为重要的时间标记。
硬件电路设计
物料清单(BOM 表):
材料名称 | 数量 |
---|---|
带有 IIC 模块的 LCD1602 液晶屏 | 1 |
杜邦线(跳线) | 若干 |
将材料按照下图相连:
注意
本实验需要用到 I2C 驱动代码,代码在 第 12 节课 中。
软件程序设计
学习 Arduino 中的 RTC 之前,我们还需要学习一下 C 语言中的 结构体
。
结构体(Struct)
也是一种数据类型,但与整数(int)、小数(float double)、字符(char)这些基本的数据类型不同的是,结构体是一种用于组织和存储数据的自定义数据类型,它允许你将不同数据类型的变量组合在一起,形成一个更复杂的数据结构。
在实际生活中,事物的属性远比基本的数据类型复杂多变,比如一个人的身份信息就包括名字、身份证号、年龄、身高、体重、联系方式等属性。在 C 和 C++ 中,结构体通常用于表示具有相关属性的数据集合。
结构体的定义如下:
struct Person {
char name[50];
int age;
float height;
};
值得一提的是,结构体的定义只是定义了一种数据类型,也就是模板。我们可以根据这个模板创建出很多具体的结构变量,代码如下:
struct Person me = {
"罗大富",
29,
180.5
}
这样,变量 me
就可以表示一个名字叫做罗大富、年龄 29、身高 180.5 的人了。
我们也可以使用 点运算符 .
加属性名的方式按照自己喜欢的顺序赋值,代码如下:
struct Person another = {
.height = 149,
.name = "如花",
.age = 15
}
在实例化之后,我们可以使用 结构体变量名 + . + 属性名
的方式修改或者访问其中的值,例如:
// 访问结构体变量的成员
int age = me.age;
float height = another.height;
char name = me.name;
// 修改结构体变量的成员
me.age = 18;
me.height = 190.5;
me.name = "张三";
在 Arduino ESP32 核心支持库当中已经包含相关的获取时间的库,获取网络时间后,就可以不依赖网络,重复去获取时间,如果长时间运行,可以设置间隔时间同步 NTP 时间,只要访问本地时间的相关函数能正常调用,就没有问题。
使用读取本地时间,好处就是不需要频繁去获取 NTP 时间,占用网络资源,最大节省资源,适合低功耗下运行,保证时间运行准确。只要开机运行获取一次网络时间后,就可以关闭网络,后面读取本地时间,可以最大限度的不依赖网络来获取时间。
首先,我们需要使用 configTime
函数从网络时间服务器上获取并设置时间:
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;server1
,server2
,server3
: NTP 服务器的地址。可以提供多个服务器地址,以备备用,在这里我们使用阿里云 NTP 服务器就可以获取本地时间了
设置完成后就可以使用 getLocalTime
函数读取当前时间了:
bool getLocalTime(struct tm * info, uint32_t ms = 5000)
其中参数的含义:
info
:是一个struct tm
结构体对象,用于接收当前时间;ms
:操作超时时间,超时则返回 false;
struct tm
是 C/C++ 标准库 <time.h>
中定义的一个结构体,通常用于处理时间和日期的相关操作,比如获取当前时间、格式化时间、计算时间差等。它包含以下成员:
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() 为负
};
获取成功后芯片会使用 RTC 时钟保持时间的更新,这时候,就可以不依赖网络了,可以关闭网络,运行时读取本地同步过的时间并且进行格式化输出,输出方法很简单,代码如下:
// 定义时间信息
struct tm timeinfo;
Serial.println(&timeinfo);
// & 符号用于获取结构体 timeinfo 的地址。
提示
在 Arduino 的 Serial.println()
函数中,第一个参数是指向要打印的数据的指针,第二个参数是一个格式化字符串。
在这里,&timeinfo
获取了 timeinfo
结构体的地址,然后 Serial.println()
函数使用这个地址来访问 timeinfo
结构体的内容,按照指定的格式进行打印。
我们也可以通过格式化字符串方法对输出进行格式化:
%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
每周的第几天,星期一为第一天 (值从 0 到 6,星期一为 0)%U
第年的第几周,把星期日做为第一天(值从 0 到 53)%V
每年的第几周,使用基于周的年%w
十进制表示的星期几(值从 0 到 6,星期天为 0)%W
每年的第几周,把星期一做为第一天(值从 0 到 53)%x
标准的日期串%X
标准的时间串%y
不带世纪的十进制年份(值从 0 到 99)%Y
带世纪部分的十进制年份
因此,如果想要在 LCD 液晶屏上显示当前时间的话,代码如下:
#include <WiFi.h>
#include <LiquidCrystal_I2C.h>
#define NTP "ntp.aliyun.com"
// 初始化 LCD1602
LiquidCrystal_I2C lcd(0x27, 16, 2);
// 定义时间信息
struct tm timeinfo;
// WiFi 账号密码
const char *ssid = "你的 WiFi 名称";
const char *password = "你的 WiFi 密码";
// 连接 WiFi
void wifi_init(){
// 设置 ESP32 工作模式
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("正在连接 WiFi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.println("WiFi 连接成功");
}
// 设置并获取本地时间
void time_init() {
// 获取本地时间
if (!getLocalTime(&timeinfo)) {
Serial.println("获取本地时间失败");
// 连接 WiFi
wifi_init();
// 从 NTP 服务器获取时间并设置
configTime(8*3600, 0, NTP);
return;
}
// 输出时间
// Serial.println(timeinfo.tm_year)
Serial.println(&timeinfo, "%F %A %T");
// 清屏
lcd.clear();
// 显示内容
lcd.setCursor(0, 0);
lcd.print(&timeinfo, "%F");
lcd.setCursor(13, 0);
lcd.print(&timeinfo, "%a");
lcd.setCursor(8,1);
lcd.print(&timeinfo, "%T");
}
void setup() {
Serial.begin(115200);
wifi_init();
// 初始化 LCD 对象
lcd.init();
lcd.backlight();
}
void loop() {
time_init();
delay(1000);
}