原创 用ESP32制作一个“唤醒闹钟”

2022-4-26 17:30 3085 12 12 分类: 智能硬件
用ESP32和RTC做一个“唤醒闹钟”吧!

最初于2019年12月3日发布

这篇文章来源于DevicePlus.com英语网站的翻译稿。

本文最初发布在deviceplus.jp网站上,而后被翻译成英语。


目录

  1. 前言
  2. 关于ESP32闹钟
  3. 创建日期和时间的“RTC”
  4. 在ESP32 LCD上显示日期和时间
  5. 使用MP3模块发出闹铃响声
  6. 给闹钟主机接线
  7. 给ESP32闹钟分机接线
  8. 安装库和字体文件
  9. Arduino IDE闹钟程序
  10. 分机程序
  11. 确认运行情况
  12. 结论
  13. 相关文章

前言

您每天早上被什么闹钟吵醒?早上人们的一个常见问题是关掉闹钟并直接回去睡觉。
这一次,我们决定使用ESP32制作一个“唤醒闹钟”,应该有助于解决这个问题。


设计步骤

预计完成时间:120分钟

名称 卖方 价格
ESP32-DevKitC(2 件) 贸泽电子 约10.00美元
DS3231 模块 亚马逊 约3.00美元
LIR2032(纽扣充电电池) 亚马逊 约3.60美元
扬声器 亚马逊 约1.00~3.00美元
2.8英寸SPI连接器 320×240像素LCD屏幕 亚马逊 约15.00美元

*除上述物品外,还需要一个轻触开关、一个LED和一个约100Ω的电阻器。

use ESP32 to make a wake up alarm clock


关于ESP32闹钟

我们来使用ESP32完成一个目标吧:像普通闹钟一样在屏幕上显示日期和时间,并在指定的时间响起。在设计制作这款“唤醒闹钟”时,我们需要将分机按钮(用于关闭闹钟)与闹钟主机分开放置。之所以这样设计,是因为想要您必须起床并走到分机按钮处才能将闹钟关掉。

如下面的视频所示,使用两个ESP,一个用于主机,另一个用于分机。在视频中,主机和分机是挨着的,但如果通过Wi-Fi连接,它们是可以在Wi-Fi范围内分开放置的。

将主机和分机分开放置时,请使用ESP32的Wi-Fi功能。两个ESP32作为Web服务器和客户端运行,并相互通信,如图1所示。

use ESP32 Wi-Fi function

图 1 主机和分机之间的通信


创建日期和时间的“RTC”

对于Arduino等微控制器来说,通常能够获取在启动程序后经过的时间。但是,您获得的时间通常不是很准确,因为断电时会重置经过时间。

而如果使用ESP32,则可以通过Wi-Fi连接到互联网,以定期从互联网上的NTP服务器获取日期和时间,并将其设置到ESP32上。但Arduino Uno等部分微控制器没有互联网连接功能,因此,拥有一种可以更轻松地处理日期和时间的机制会很方便。

这就是为什么经常使用一种被称作“RTC”(实时时钟)的IC。 RTC是基于周期性发出信号的元件来计时的IC。此外,通过将其连接到电池等外部电源,即使在微控制器断电时也可以继续计时。

许多产品都采用RTC,此次,我们将使用一种名为“DS3231”的RTC模块。

在电子设计所用的RTC模块当中,DS3231模块很受欢迎,且很容易获得。由于接口是I2C,因此只需要4根线。除了RTC功能,还具有温度传感器功能(不过本文不会用到温度传感器)。

此外,在照片1所示的DS3231上,安装一个名为“LIR2032”的纽扣电池,这样即使在微控制器关闭的情况下也能继续记录日期和时间。LIR2032的电池尺寸与CR2032的相同,但不同的是它可充电。

RTC real time clock

照片1 DS3231模块


在ESP32 LCD上显示日期和时间

由于闹钟用于查看当前日期和时间,因此需要以易于理解的方式显示日期和时间。以下设备用于显示日期和时间。

  • 7段LED
  • LED 矩阵
  • OLED 显示器
  • 字符液晶显示器
  • 图形液晶显示器

根据设备的不同,有不同的库和不同的编程方法。这还取决于它是否适合您想要制作的作品。例如,7段LED仅适合以低成本显示数字,但不适合显示详情。其中,图形液晶显示器是最通用的,可以用于许多不同的项目,所以我这次决定使用它。

市面上有各种类型的液晶显示器模块,但对于今天的项目,我们将使用一个名为“ILI9341”的控制器,并使用SPI接口(照片2)。此外,液晶显示器通常以2.2英寸/2.4英寸/2.8英寸尺寸出售,因此,请根据您正在做的作品类型进行相应调整。

LCD display

照片2 控制器用ILI9341 2.8英寸液晶显示器


使用MP3模块发出闹钟铃声

既然是闹钟,在指定时间发出铃声是必需的。您可以将蜂鸣器连接到ESP32以产生单一铃声,但如果您愿意,也可以使用自己喜欢的铃声。为此,我们将使用一个名为“DFPlayer Mini”的模块,它可以播放任何MP3数据(照片3)。
DFPlayer Mini是一个可以通过串口发送命令来播放microSD卡中MP3的模块。可以将一个小型扬声器连接到扬声器输出引脚以产生铃声。

DFPlayer Mini to play MP3 in a microSD card

照片3 DFPlayer Mini


给闹钟主机接线

让我们进入实际构建吧。首先,给闹钟接线。

使用两个面包板,一个配有ESP32和DS3231,另一个配有液晶显示器(LCD)和DFPlayer Mini。各部件接线如图2所示。

由于ESP32很宽,您只能在普通面包板的一面放一根跳线。因此,请改用电源线只在一侧、多一排插孔的面包板(例如Sanhayato的SAD-101)。

ESP32和液晶显示器通过SPI进行连接。ESP32可以使用两个 SPI(VSPI和HSPI),但使用的是VSPI(引脚18/19/23)(表1)。

ESP32和DS3231通过I2C进行连接。在ESP32中,I2C可以分配给任何引脚,但我们使用标准引脚(SDA=21和SCL=22)(表 2)。

DFPlayer Mini 进行串行连接。ESP32可以使用3个串口,但为此请同时使用引脚16和引脚17(表3)。此外,将扬声器连接到DFPlayer Mini“SPK1”和“SPK2”两个引脚。

wiring alarm clock body

图2 闹钟主机接线

ESP32 引脚 液晶显示器引脚
5V VCC
GND GND
5 CS
4 RESET
2 DC
23 MOSI
18 SCK
19 MISO

表 1:ESP32 与液晶显示器的连接

 

ESP32 引脚 DS3231 引脚
5V VCC
GND GND
21 SDA
22 SCL

表 2:ESP32与DS3231的连接

 

ESP32 引脚 DFPlayer Mini 引脚
5V VCC
GND GND
16 TX
17 RX

表 3:ESP32与DFPlayer Mini的连接


给ESP32闹钟分机接线

接下来,我们将给分机接线。分机接线应按图3所示进行。您所要做的就是将开关和 LED连接到ESP32。将开关的一侧连接到ESP32的3V3引脚,另一侧连接到引脚4。通过电阻器→LED再通过ESP32的引脚13连接到GND。

在读取开关状态的电路上插入一个上拉电阻或下拉电阻。但是,由于ESP32可以通过内部电阻进行上拉/下拉,因此省略了外部电阻。

wiring for the extension

图3 分机接线


安装库和字体文件

完成接线工作后,您可以创建程序。首先,从安装下面的各个库开始。

  • Adafruit GFX
  • Adafruit ILI9341
  • RTCLib
  • DFRobotDFPlayerMini

安装步骤如下:

  1. 启动Arduino IDE。
  2. 选择“Sketch”->“Include Library”->“Manage Library”菜单,以打开Library Manager。
  3. 在“Filter search”字段中输入“Adafruit GFX”。
  4. Adafruit GFX将在库列表中显示。单击“Install”按钮(图 4)。
  5. 以相同的方式安装每个库。

有几个名称相似的RTCLib和DFPlayer库。RTCLib 安装“RTCLib by Adafruit”,而DFPlayer安装“DFRobotDFPlayerMini by DFRobot”。

install library and font files

图 4 Adafruit GFX库安装

此外,为通过大字符显示时间,需要安装字体文件。如果您下载并解压缩以下zip文件,将可以获得一个名为“FreeSans40pt7b.h”的文件。

打开Arduino IDE的标准草图目标文件夹,再打开“libraries”->“Adafruit_GFX_Library”->“Fonts”文件夹,将字体文件复制到那里。
https://www.h-fj.com/deviceplus/font.zip


Arduino IDE闹钟程序

接下来,在Arduino IDE中创建一个闹钟程序并将其写入ESP32。程序内容如清单1所示。

清单1:闹钟主机程序
程序内容(放在这里)

但是,第17行到第21行需要改写如下:

・第17/18行
根据您的Wi-Fi路由器的SSID/密码重写。
・第19行
指定要分配给ESS32的IP地址。根据您的Wi-Fi路由器的网络配置自行决定IP地址。
在普通IP地址中,四组数字用句点分隔,但在这一行中,它是函数参数的形式,所以四组数字用逗号分隔。
・第20行
指定网络默认网关的IP地址。通常,它是Wi-Fi路由器的IP地址。用逗号分隔IP地址中的四组数字。
・第21行
根据分配给分机ESP32的IP地址重写。

例如,如果您想按照表4所示进行设置,请重写第17行至第21行,如清单2所示。

项目 设定值
Wi-Fi路由器SSID my_wifi
Wi-Fi路由器密码 my_password
分配给主机ESP32的IP地址 192.168.1.101
默认网关IP地址 192.168.1.1
分配给分机ESP32的IP地址 192.168.1.102

表4:主机网络设置示例
清单 2:重写第17-21行的示例


分机程序

分机程序如清单3所示。

以与闹钟主机相同的方式重写第5行到第9行。 但是,在第7行,指定分配给分机的IP地址。 此外,在第9行的“Main console IP address(主控台IP地址)”中指定闹钟的IP地址。

清单 3:分机程序

程序内容

  1. #include
  2. #include
  3. #include
  4. #include <Fonts/FreeSans12pt7b.h>
  5. #include <Fonts/FreeSans18pt7b.h>
  6. #include <Fonts/FreeSans40pt7b.h>
  7. #include
  8. #include
  9. #include "time.h"
  10. #include
  11. #include
  12. #include
  13. #include
  14. #include
  15. // Initial setup
  16. const char *ssid = "Wi-Fi SSID";
  17. const char *pass = "Wi-Fi password";
  18. IPAddress ip(IP address assigned to main unit);
  19. IPAddress gateway(IP address of default gateway);
  20. const char* notify_url = "http://IP address of extension unit/alarm";
  21. const char* adjust_time = "04:00:00";
  22. #define DF_VOLUME 30
  23. // Constants, etc.
  24. #define ALARM_SIG 25
  25. #define TFT_DC 2
  26. #define TFT_CS 5
  27. #define TFT_RST 4
  28. #define TFT_WIDTH 320
  29. Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
  30. RTC_DS3231 rtc;
  31. HardwareSerial hs(1);
  32. DFRobotDFPlayerMini myDFPlayer;
  33. WebServer server(80);
  34. char old_date[15];
  35. char old_time[9];
  36. char old_alarm[15];
  37. char alarm_time[9];
  38. char wdays[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
  39. bool alarm_checked = false;
  40. bool alarm_on = false;
  41. bool ntp_adjusted = false;
  42. int alarm_ctr;
  43. // Set date and time using NTP
  44. void setTimeByNTP() {
  45. struct tm t;
  46. configTime(9 * 3600L, 0, "ntp.nict.jp", "time.google.com", "ntp.jst.mfeed.ad.jp");
  47. if (!getLocalTime(&t)) {
  48. Serial.println("getLocalTime Error");
  49. return;
  50. }
  51. rtc.adjust(DateTime(t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec));
  52. }
  53. // Display string on LCD
  54. void showMessage(char* s_new, char* s_old, int y0, int height) {
  55. int16_t x1, y1;
  56. uint16_t w, w2, h;
  57. int x, y;
  58. if (strcmp(s_new, s_old) != 0) {
  59. tft.getTextBounds(s_old, 0, 0, &x1, &y1, &w, &h);
  60. w2 = w * 11 / 10;
  61. tft.fillRect((TFT_WIDTH - w2) / 2 , y0 - (height / 2) + 1, w2, height, ILI9341_BLACK),
  62. tft.getTextBounds(s_new, 0, 0, &x1, &y1, &w, &h);
  63. tft.setCursor((TFT_WIDTH - w) / 2, y0 + (h / 2) - 1);
  64. tft.print(s_new);
  65. strcpy(s_old, s_new);
  66. }
  67. }
  68. // Main settings page
  69. void handleRoot() {
  70. int i;
  71. String html =
  72. "<!DOCTYPE html>\n"
  73. "<html>\n"
  74. "<head>\n"
  75. "<meta charset="utf-8">\n"
  76. "<title>Alarm clock settings</title>\n"
  77. "</head>\n"
  78. "<body>\n"
  79. "<form method="get" action="/set">\n"
  80. "<p>\n"
  81. "<select name="hour">\n";
  82. for (i = 0; i < 24; i++) {
  83. html += "<option value="";
  84. html += String(i);
  85. html += "">";
  86. html += String(i);
  87. html += "</option>\n";
  88. }
  89. html += "</select>(h)\n";
  90. html += "<select name="min">\n";
  91. for (i = 0; i < 60; i++) {
  92. html += "<option value="";
  93. html += String(i);
  94. html += "">";
  95. html += String(i);
  96. html += "</option>\n";
  97. }
  98. html += "</select>(min) \n";
  99. html += "<input type="submit" name="submit" value="Alarm setting" />\n";
  100. html += "</p>\n";
  101. html += "</form>\n";
  102. html += "<form method="get" action="/set">\n";
  103. html += "<input type="hidden" name="off" value="1">\n";
  104. html += "<p><input type="submit" name="submit" value="Alarm off" /></p>\n";
  105. html += "</form>\n";
  106. html += "</body>\n";
  107. html += "</html>\n";
  108. server.send(200, "text/html", html);
  109. }
  110. // Set alarm
  111. void handleSetAlarm() {
  112. int i, hour, min, sec;
  113. bool is_off = false;
  114. String s_hour = "", s_min = "", s_sec = "";
  115. // Get "off/hour/min/sec" parameters from URL
  116. for (i = 0; i < server.args(); i++) {
  117. if (server.argName(i).compareTo("off") == 0) {
  118. is_off = true;
  119. break;
  120. }
  121. else if (server.argName(i).compareTo("hour") == 0) {
  122. s_hour = server.arg(i);
  123. }
  124. else if (server.argName(i).compareTo("min") == 0) {
  125. s_min = server.arg(i);
  126. }
  127. else if (server.argName(i).compareTo("sec") == 0) {
  128. s_sec = server.arg(i);
  129. }
  130. }
  131. // Turn off alarm if "off" parameter is set if (is_off) {
  132. strcpy(alarm_time, "Off");
  133. server.send(200, "text/plain; charset=utf-8", "Alarm turned off.");
  134. }
  135. // Set alarm time else if (s_hour.length() > 0 && s_min.length() > 0) {
  136. hour = s_hour.toInt();
  137. min = s_min.toInt();
  138. if (s_sec.length() > 0) {
  139. sec = s_sec.toInt();
  140. }
  141. else {
  142. sec = 0;
  143. }
  144. if (hour >= 0 && hour <= 23 && min >= 0 && min <= 59 && sec >= 0 && sec <= 59) {
  145. sprintf(alarm_time, "%02d:%02d:%02d", hour, min, sec);
  146. String msg = "Alarm set to ";
  147. msg.concat(alarm_time);
  148. msg.concat(" .");
  149. server.send(200, "text/plain; charset=utf-8", msg);
  150. }
  151. else {
  152. server.send(200, "text/plain; charset=utf-8", "Incorrect date/time.");
  153. }
  154. }
  155. else {
  156. server.send(200, "text/plain; charset=utf-8", "Incorrect parameters.");
  157. }
  158. }
  159. / Stop alarm
  160. void handleStopAlarm() {
  161. myDFPlayer.pause();
  162. alarm_on = false;
  163. tft.drawRect(30, 180, 260, 40, ILI9341_BLACK);
  164. server.send(200, "text/plain", "Alarm stop");
  165. }
  166. // If an invalid URL is specified
  167. void handleNotFound() {
  168. String message = "Not Found : ";
  169. message += server.uri();
  170. server.send(404, "text/plain", message);
  171. }
  172. // Setup
  173. void setup() {
  174. int16_t x1, y1;
  175. uint16_t w, h;
  176. Serial.begin(115200);
  177. strcpy(old_date, "00000000000000");
  178. strcpy(old_time, "00000000");
  179. strcpy(old_alarm, "00000000000000");
  180. strcpy(alarm_time, "Off");
  181. // Initialize display
  182. tft.begin();
  183. tft.setRotation(3);
  184. tft.fillScreen(ILI9341_BLACK);
  185. tft.setTextColor(ILI9341_WHITE);
  186. tft.setFont(&FreeSans12pt7b);
  187. String s = "Initializing...";
  188. tft.getTextBounds(s, 0, 0, &x1, &y1, &w, &h);
  189. tft.setCursor(0, h);
  190. tft.println(s);
  191. // Connect to WiFi
  192. WiFi.begin(ssid, pass);
  193. while (WiFi.status() != WL_CONNECTED) {
  194. delay(500);
  195. Serial.print(".");
  196. }
  197. WiFi.config(ip, gateway, WiFi.subnetMask(), IPAddress(8, 8, 8, 8), IPAddress(8, 8, 4, 4));
  198. Serial.println("");
  199. Serial.println("WiFi Connected.");
  200. tft.println("WiFi Connected.");
  201. // Initialize DFPlayer
  202. hs.begin(9600, SERIAL_8N1, 16, 17);
  203. int count = 0;
  204. while (count < 10) {
  205. if (!myDFPlayer.begin(hs)) {
  206. count++;
  207. Serial.print("DFPlayer initialize attempt ");
  208. Serial.println(count);
  209. }
  210. else {
  211. break;
  212. }
  213. }
  214. if (count < 10) {
  215. Serial.println("DFPlayer Initialized.");
  216. tft.println("DFPlayer Initialized.");
  217. myDFPlayer.pause();
  218. myDFPlayer.volume(DF_VOLUME);
  219. }
  220. else {
  221. Serial.println("DFPlayer Error.");
  222. tft.println("DFPlayer Error.");
  223. while(1);
  224. }
  225. // Initialize RTC
  226. if (!rtc.begin()) {
  227. Serial.println("Couldn't find RTC");
  228. while (1);
  229. }
  230. Serial.println("RTC Initialized");
  231. tft.println("RTC Initialized.");
  232. // Get current date/time via NTP and set to RTC
  233. setTimeByNTP();
  234. // Initialize web server
  235. server.on("/", handleRoot);
  236. server.on("/set", handleSetAlarm);
  237. server.on("/stop", handleStopAlarm);
  238. server.onNotFound(handleNotFound);
  239. server.begin();
  240. // Fill display with black
  241. tft.fillScreen(ILI9341_BLACK);
  242. }
  243. void loop() {
  244. char new_time[9], new_date[15], new_alarm[15];
  245. // Launch web server
  246. server.handleClient();
  247. // Display current date/time on LCD
  248. DateTime now = rtc.now();
  249. sprintf(new_date, "%04d/%02d/%02d ", now.year(), now.month(), now.day());
  250. strcat(new_date, wdays[now.dayOfTheWeek()]);
  251. sprintf(new_time, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
  252. strcpy(new_alarm, "Alarm ");
  253. strcat(new_alarm, alarm_time);
  254. tft.setFont(&FreeSans18pt7b);
  255. tft.setTextColor(ILI9341_WHITE);
  256. showMessage(new_date, old_date, 40, 28);
  257. showMessage(new_alarm, old_alarm, 200, 28);
  258. tft.setFont(&FreeSans40pt7b);
  259. showMessage(new_time, old_time, 120, 64);
  260. // Check if current time is time set for alarm
  261. if (strstr(new_time, alarm_time) != NULL) {
  262. if (!alarm_checked) {
  263. // If it's alarm time, ring out then send message to extension unit
  264. myDFPlayer.loop(1);
  265. alarm_checked = true;
  266. alarm_on = true;
  267. alarm_ctr = 0;
  268. HTTPClient http;
  269. http.begin(notify_url);
  270. int httpCode = http.GET();
  271. }
  272. }
  273. else {
  274. alarm_checked = false;
  275. }
  276. // While alarm is sounding, make red frame flash around alarm time on display
  277. if (alarm_on) {
  278. if (alarm_ctr == 0) {
  279. tft.drawRect(30, 180, 260, 40, ILI9341_RED);
  280. }
  281. else if (alarm_ctr == ALARM_SIG) {
  282. tft.drawRect(30, 180, 260, 40, ILI9341_BLACK);
  283. }
  284. alarm_ctr++;
  285. alarm_ctr %= ALARM_SIG * 2;
  286. }
  287. // Update date/time using NTP at a specific time everyday
  288. if (strstr(new_time, adjust_time) != NULL) {
  289. if (!ntp_adjusted) {
  290. setTimeByNTP();
  291. ntp_adjusted = true;
  292. }
  293. }
  294. else {
  295. ntp_adjusted = false;
  296. }
  297. delay(20);
  298. }

 

确认运行情况

程序写入主机/分机后,我们来确认一下运行情况。步骤如下:

    1. 准备一张32GB或以下的microSD卡,并创建一个名为“mp3”的文件夹。
    2. 准备好要用作闹钟铃声的MP3文件,将其命名为“0001.mp3”,并将其保存在mp3文件夹中。
    3. 将microSD卡插入DFPlayer Mini。
    4. 启动主机和分机。
    5. 初始化和Wi-Fi设置完成后,当前日期和时间将显示在主机上。
    6. 在与主机/分机处于同一网络的电脑上启动Web浏览器,并连接到地址“http: // 主机IP地址/”。
    7. 闹钟设置表单将显示在Web浏览器上,如照片4所示。指定时间并单击“Alarm Settings”按钮。
alarm clock settings

照片 4 闹钟设置

  1. 闹钟响起的时间设置为“Alarm set to 10:00:00”。
  2. 确认设置闹钟时主机会响起铃声。
  3. 确认设置闹钟时分机的LED亮起。
  4. 按分机上的开关检查闹钟是否关闭。

如要取消闹钟设置,请使用Web浏览器连接到地址“http: //主机IP地址/”,然后单击“Alarm Off”按钮。


结论

ESP32可以通过Wi-Fi连接到网络。不仅可以联网,还可以像今天的项目一样,与网络上的各种适用的电子产品连接使用。


来源:rohm

文章评论0条评论)

登录后参与讨论
我要评论
0
12
关闭 站长推荐上一条 /2 下一条