這篇文章來(lái)源于DevicePlus.com英語(yǔ)網(wǎng)站的翻譯稿。
本文最初發(fā)布在deviceplus.jp網(wǎng)站上,而后被翻譯成英語(yǔ)。
目錄
前言
關(guān)于ESP32鬧鐘
創(chuàng)建日期和時(shí)間的“RTC”
在ESP32 LCD上顯示日期和時(shí)間
使用MP3模塊發(fā)出鬧鈴響聲
給鬧鐘主機(jī)接線
給ESP32鬧鐘分機(jī)接線
安裝庫(kù)和字體文件
Arduino IDE鬧鐘程序
分機(jī)程序
確認(rèn)運(yùn)行情況
結(jié)論
相關(guān)文章
前言
您每天早上被什么鬧鐘吵醒?早上人們的一個(gè)常見(jiàn)問(wèn)題是關(guān)掉鬧鐘并直接回去睡覺(jué)。
這一次,我們決定使用ESP32制作一個(gè)“喚醒鬧鐘”,應(yīng)該有助于解決這個(gè)問(wèn)題。
設(shè)計(jì)步驟
預(yù)計(jì)完成時(shí)間:120分鐘
名稱 | 賣方 | 價(jià)格 |
ESP32-DevKitC(2 件) | 貿(mào)澤電子 | 約10.00美元 |
DS3231 模塊 | 亞馬遜 | 約3.00美元 |
LIR2032(紐扣充電電池) | 亞馬遜 | 約3.60美元 |
揚(yáng)聲器 | 亞馬遜 | 約1.00~3.00美元 |
2.8英寸SPI連接器 320×240像素LCD屏幕 | 亞馬遜 | 約15.00美元 |
*除上述物品外,還需要一個(gè)輕觸開(kāi)關(guān)、一個(gè)LED和一個(gè)約100Ω的電阻器。
關(guān)于ESP32鬧鐘
我們來(lái)使用ESP32完成一個(gè)目標(biāo)吧:像普通鬧鐘一樣在屏幕上顯示日期和時(shí)間,并在指定的時(shí)間響起。在設(shè)計(jì)制作這款“喚醒鬧鐘”時(shí),我們需要將分機(jī)按鈕(用于關(guān)閉鬧鐘)與鬧鐘主機(jī)分開(kāi)放置。之所以這樣設(shè)計(jì),是因?yàn)橄胍仨毱鸫膊⒆叩椒謾C(jī)按鈕處才能將鬧鐘關(guān)掉。
如下面的視頻所示,使用兩個(gè)ESP,一個(gè)用于主機(jī),另一個(gè)用于分機(jī)。在視頻中,主機(jī)和分機(jī)是挨著的,但如果通過(guò)Wi-Fi連接,它們是可以在Wi-Fi范圍內(nèi)分開(kāi)放置的。
將主機(jī)和分機(jī)分開(kāi)放置時(shí),請(qǐng)使用ESP32的Wi-Fi功能。兩個(gè)ESP32作為Web服務(wù)器和客戶端運(yùn)行,并相互通信,如圖1所示。
圖 1 主機(jī)和分機(jī)之間的通信
創(chuàng)建日期和時(shí)間的“RTC”
對(duì)于Arduino等微控制器來(lái)說(shuō),通常能夠獲取在啟動(dòng)程序后經(jīng)過(guò)的時(shí)間。但是,您獲得的時(shí)間通常不是很準(zhǔn)確,因?yàn)閿嚯姇r(shí)會(huì)重置經(jīng)過(guò)時(shí)間。
而如果使用ESP32,則可以通過(guò)Wi-Fi連接到互聯(lián)網(wǎng),以定期從互聯(lián)網(wǎng)上的NTP服務(wù)器獲取日期和時(shí)間,并將其設(shè)置到ESP32上。但Arduino Uno等部分微控制器沒(méi)有互聯(lián)網(wǎng)連接功能,因此,擁有一種可以更輕松地處理日期和時(shí)間的機(jī)制會(huì)很方便。
這就是為什么經(jīng)常使用一種被稱作“RTC”(實(shí)時(shí)時(shí)鐘)的IC。 RTC是基于周期性發(fā)出信號(hào)的元件來(lái)計(jì)時(shí)的IC。此外,通過(guò)將其連接到電池等外部電源,即使在微控制器斷電時(shí)也可以繼續(xù)計(jì)時(shí)。
許多產(chǎn)品都采用RTC,此次,我們將使用一種名為“DS3231”的RTC模塊。
在電子設(shè)計(jì)所用的RTC模塊當(dāng)中,DS3231模塊很受歡迎,且很容易獲得。由于接口是I2C,因此只需要4根線。除了RTC功能,還具有溫度傳感器功能(不過(guò)本文不會(huì)用到溫度傳感器)。
此外,在照片1所示的DS3231上,安裝一個(gè)名為“LIR2032”的紐扣電池,這樣即使在微控制器關(guān)閉的情況下也能繼續(xù)記錄日期和時(shí)間。LIR2032的電池尺寸與CR2032的相同,但不同的是它可充電。
照片1 DS3231模塊
在ESP32 LCD上顯示日期和時(shí)間
由于鬧鐘用于查看當(dāng)前日期和時(shí)間,因此需要以易于理解的方式顯示日期和時(shí)間。以下設(shè)備用于顯示日期和時(shí)間。
7段LED
LED 矩陣
OLED 顯示器
字符液晶顯示器
圖形液晶顯示器
根據(jù)設(shè)備的不同,有不同的庫(kù)和不同的編程方法。這還取決于它是否適合您想要制作的作品。例如,7段LED僅適合以低成本顯示數(shù)字,但不適合顯示詳情。其中,圖形液晶顯示器是最通用的,可以用于許多不同的項(xiàng)目,所以我這次決定使用它。
市面上有各種類型的液晶顯示器模塊,但對(duì)于今天的項(xiàng)目,我們將使用一個(gè)名為“ILI9341”的控制器,并使用SPI接口(照片2)。此外,液晶顯示器通常以2.2英寸/2.4英寸/2.8英寸尺寸出售,因此,請(qǐng)根據(jù)您正在做的作品類型進(jìn)行相應(yīng)調(diào)整。
照片2 控制器用ILI9341 2.8英寸液晶顯示器
使用MP3模塊發(fā)出鬧鐘鈴聲
既然是鬧鐘,在指定時(shí)間發(fā)出鈴聲是必需的。您可以將蜂鳴器連接到ESP32以產(chǎn)生單一鈴聲,但如果您愿意,也可以使用自己喜歡的鈴聲。為此,我們將使用一個(gè)名為“DFPlayer Mini”的模塊,它可以播放任何MP3數(shù)據(jù)(照片3)。
DFPlayer Mini是一個(gè)可以通過(guò)串口發(fā)送命令來(lái)播放microSD卡中MP3的模塊??梢詫⒁粋€(gè)小型揚(yáng)聲器連接到揚(yáng)聲器輸出引腳以產(chǎn)生鈴聲。
照片3 DFPlayer Mini
給鬧鐘主機(jī)接線
讓我們進(jìn)入實(shí)際構(gòu)建吧。首先,給鬧鐘接線。
使用兩個(gè)面包板,一個(gè)配有ESP32和DS3231,另一個(gè)配有液晶顯示器(LCD)和DFPlayer Mini。各部件接線如圖2所示。
由于ESP32很寬,您只能在普通面包板的一面放一根跳線。因此,請(qǐng)改用電源線只在一側(cè)、多一排插孔的面包板(例如Sanhayato的SAD-101)。
ESP32和液晶顯示器通過(guò)SPI進(jìn)行連接。ESP32可以使用兩個(gè) SPI(VSPI和HSPI),但使用的是VSPI(引腳18/19/23)(表1)。
ESP32和DS3231通過(guò)I2C進(jìn)行連接。在ESP32中,I2C可以分配給任何引腳,但我們使用標(biāo)準(zhǔn)引腳(SDA=21和SCL=22)(表 2)。
DFPlayer Mini 進(jìn)行串行連接。ESP32可以使用3個(gè)串口,但為此請(qǐng)同時(shí)使用引腳16和引腳17(表3)。此外,將揚(yáng)聲器連接到DFPlayer Mini“SPK1”和“SPK2”兩個(gè)引腳。
圖2 鬧鐘主機(jī)接線
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鬧鐘分機(jī)接線
接下來(lái),我們將給分機(jī)接線。分機(jī)接線應(yīng)按圖3所示進(jìn)行。您所要做的就是將開(kāi)關(guān)和 LED連接到ESP32。將開(kāi)關(guān)的一側(cè)連接到ESP32的3V3引腳,另一側(cè)連接到引腳4。通過(guò)電阻器→LED再通過(guò)ESP32的引腳13連接到GND。
在讀取開(kāi)關(guān)狀態(tài)的電路上插入一個(gè)上拉電阻或下拉電阻。但是,由于ESP32可以通過(guò)內(nèi)部電阻進(jìn)行上拉/下拉,因此省略了外部電阻。
圖3 分機(jī)接線
安裝庫(kù)和字體文件
完成接線工作后,您可以創(chuàng)建程序。首先,從安裝下面的各個(gè)庫(kù)開(kāi)始。
Adafruit GFX
Adafruit ILI9341
RTCLib
DFRobotDFPlayerMini
安裝步驟如下:
啟動(dòng)Arduino IDE。
選擇“Sketch”->“Include Library”->“Manage Library”菜單,以打開(kāi)Library Manager。
在“Filter search”字段中輸入“Adafruit GFX”。
Adafruit GFX將在庫(kù)列表中顯示。單擊“Install”按鈕(圖 4)。
以相同的方式安裝每個(gè)庫(kù)。
有幾個(gè)名稱相似的RTCLib和DFPlayer庫(kù)。RTCLib 安裝“RTCLib by Adafruit”,而DFPlayer安裝“DFRobotDFPlayerMini by DFRobot”。
圖 4 Adafruit GFX庫(kù)安裝
此外,為通過(guò)大字符顯示時(shí)間,需要安裝字體文件。如果您下載并解壓縮以下zip文件,將可以獲得一個(gè)名為“FreeSans40pt7b.h”的文件。
打開(kāi)Arduino IDE的標(biāo)準(zhǔn)草圖目標(biāo)文件夾,再打開(kāi)“l(fā)ibraries”->“Adafruit_GFX_Library”->“Fonts”文件夾,將字體文件復(fù)制到那里。
https://www.h-fj.com/deviceplus/font.zip
Arduino IDE鬧鐘程序
接下來(lái),在Arduino IDE中創(chuàng)建一個(gè)鬧鐘程序并將其寫入ESP32。程序內(nèi)容如清單1所示。
清單1:鬧鐘主機(jī)程序
程序內(nèi)容(放在這里)
但是,第17行到第21行需要改寫如下:
?第17/18行
根據(jù)您的Wi-Fi路由器的SSID/密碼重寫。
?第19行
指定要分配給ESS32的IP地址。根據(jù)您的Wi-Fi路由器的網(wǎng)絡(luò)配置自行決定IP地址。
在普通IP地址中,四組數(shù)字用句點(diǎn)分隔,但在這一行中,它是函數(shù)參數(shù)的形式,所以四組數(shù)字用逗號(hào)分隔。
?第20行
指定網(wǎng)絡(luò)默認(rèn)網(wǎng)關(guān)的IP地址。通常,它是Wi-Fi路由器的IP地址。用逗號(hào)分隔IP地址中的四組數(shù)字。
?第21行
根據(jù)分配給分機(jī)ESP32的IP地址重寫。
例如,如果您想按照表4所示進(jìn)行設(shè)置,請(qǐng)重寫第17行至第21行,如清單2所示。
項(xiàng)目 | 設(shè)定值 |
Wi-Fi路由器SSID | my_wifi |
Wi-Fi路由器密碼 | my_password |
分配給主機(jī)ESP32的IP地址 | 192.168.1.101 |
默認(rèn)網(wǎng)關(guān)IP地址 | 192.168.1.1 |
分配給分機(jī)ESP32的IP地址 | 192.168.1.102 |
表4:主機(jī)網(wǎng)絡(luò)設(shè)置示例
清單 2:重寫第17-21行的示例
分機(jī)程序
分機(jī)程序如清單3所示。
以與鬧鐘主機(jī)相同的方式重寫第5行到第9行。 但是,在第7行,指定分配給分機(jī)的IP地址。 此外,在第9行的“Main console IP address(主控臺(tái)IP地址)”中指定鬧鐘的IP地址。
清單 3:分機(jī)程序
程序內(nèi)容
#include #include #include #include #include #include #include #include #include "time.h" #include #include #include #include #include
// Initial setup
const char *ssid = “Wi-Fi SSID”;
const char *pass = “Wi-Fi password”;
IPAddress ip(IP address assigned to main unit);
IPAddress gateway(IP address of default gateway);
const char* notify_url = “http://IP address of extension unit/alarm”;
const char* adjust_time = “04:00:00”;
#define DF_VOLUME 30
// Constants, etc.
#define ALARM_SIG 25
#define TFT_DC 2
#define TFT_CS 5
#define TFT_RST 4
#define TFT_WIDTH 320
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
RTC_DS3231 rtc;
HardwareSerial hs(1);
DFRobotDFPlayerMini myDFPlayer;
WebServer server(80);
char old_date[15];
char old_time[9];
char old_alarm[15];
char alarm_time[9];
char wdays[7][4] = { “Sun”, “Mon”, “Tue”, “Wed”, “Thu”, “Fri”, “Sat” };
bool alarm_checked = false;
bool alarm_on = false;
bool ntp_adjusted = false;
int alarm_ctr;
// Set date and time using NTP
void setTimeByNTP() {
struct tm t;
configTime(9 * 3600L, 0, “ntp.nict.jp”, “time.google.com”, “ntp.jst.mfeed.ad.jp”);
if (!getLocalTime(&t)) {
Serial.println(“getLocalTime Error”);
return;
}
rtc.adjust(DateTime(t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec));
}
// Display string on LCD
void showMessage(char* s_new, char* s_old, int y0, int height) {
int16_t x1, y1;
uint16_t w, w2, h;
int x, y;
if (strcmp(s_new, s_old) != 0) {
tft.getTextBounds(s_old, 0, 0, &x1, &y1, &w, &h);
w2 = w * 11 / 10;
tft.fillRect((TFT_WIDTH – w2) / 2 , y0 – (height / 2) + 1, w2, height, ILI9341_BLACK),
tft.getTextBounds(s_new, 0, 0, &x1, &y1, &w, &h);
tft.setCursor((TFT_WIDTH – w) / 2, y0 + (h / 2) – 1);
tft.print(s_new);
strcpy(s_old, s_new);
}
}
// Main settings page
void handleRoot() {
int i;
String html =
“n”
“
n”
“n”
“ n”
“ n”
“n”
“n”
“ n”
“
n”
“n”; for (i = 0; i < 24; i++) { html += “html += String(i); html += “”>”; html += String(i); html += “n”; } html += “(h)n”;
html += “n”; for (i = 0; i < 60; i++) { html += “html += String(i); html += “”>”; html += String(i); html += “n”; } html += “(min) n”;
html += “n”;
html += “
n”;
html += “ n”;
html += “ n”;
html += “n”;
html += “
n”;
html += “ n”;
html += “n”;
html += “n”;
server.send(200, “text/html”, html);
}
// Set alarm
void handleSetAlarm() {
int i, hour, min, sec;
bool is_off = false;
String s_hour = “”, s_min = “”, s_sec = “”;
// Get “off/hour/min/sec” parameters from URL
for (i = 0; i < server.args(); i++) {
if (server.argName(i).compareTo(“off”) == 0) {
is_off = true;
break;
}
else if (server.argName(i).compareTo(“hour”) == 0) {
s_hour = server.arg(i);
}
else if (server.argName(i).compareTo(“min”) == 0) {
s_min = server.arg(i);
}
else if (server.argName(i).compareTo(“sec”) == 0) {
s_sec = server.arg(i);
}
}
// Turn off alarm if “off” parameter is set if (is_off) {
strcpy(alarm_time, “Off”);
server.send(200, “text/plain; charset=utf-8”, “Alarm turned off.”);
}
// Set alarm time else if (s_hour.length() > 0 && s_min.length() > 0) {
hour = s_hour.toInt();
min = s_min.toInt();
if (s_sec.length() > 0) {
sec = s_sec.toInt();
}
else {
sec = 0;
}
if (hour >= 0 && hour <= 23 && min >= 0 && min <= 59 && sec >= 0 && sec <= 59) {
sprintf(alarm_time, “%02d:%02d:%02d”, hour, min, sec);
String msg = “Alarm set to “;
msg.concat(alarm_time);
msg.concat(” .”);
server.send(200, “text/plain; charset=utf-8”, msg);
}
else {
server.send(200, “text/plain; charset=utf-8”, “Incorrect date/time.”);
}
}
else {
server.send(200, “text/plain; charset=utf-8”, “Incorrect parameters.”);
}
}
/ Stop alarm
void handleStopAlarm() {
myDFPlayer.pause();
alarm_on = false;
tft.drawRect(30, 180, 260, 40, ILI9341_BLACK);
server.send(200, “text/plain”, “Alarm stop”);
}
// If an invalid URL is specified
void handleNotFound() {
String message = “Not Found : “;
message += server.uri();
server.send(404, “text/plain”, message);
}
// Setup
void setup() {
int16_t x1, y1;
uint16_t w, h;
Serial.begin(115200);
strcpy(old_date, “00000000000000”);
strcpy(old_time, “00000000”);
strcpy(old_alarm, “00000000000000”);
strcpy(alarm_time, “Off”);
// Initialize display
tft.begin();
tft.setRotation(3);
tft.fillScreen(ILI9341_BLACK);
tft.setTextColor(ILI9341_WHITE);
tft.setFont(&FreeSans12pt7b);
String s = “Initializing…”;
tft.getTextBounds(s, 0, 0, &x1, &y1, &w, &h);
tft.setCursor(0, h);
tft.println(s);
// Connect to WiFi
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(“.”);
}
WiFi.config(ip, gateway, WiFi.subnetMask(), IPAddress(8, 8, 8, 8), IPAddress(8, 8, 4, 4));
Serial.println(“”);
Serial.println(“WiFi Connected.”);
tft.println(“WiFi Connected.”);
// Initialize DFPlayer
hs.begin(9600, SERIAL_8N1, 16, 17);
int count = 0;
while (count < 10) {
if (!myDFPlayer.begin(hs)) {
count++;
Serial.print(“DFPlayer initialize attempt “);
Serial.println(count);
}
else {
break;
}
}
if (count < 10) {
Serial.println(“DFPlayer Initialized.”);
tft.println(“DFPlayer Initialized.”);
myDFPlayer.pause();
myDFPlayer.volume(DF_VOLUME);
}
else {
Serial.println(“DFPlayer Error.”);
tft.println(“DFPlayer Error.”);
while(1);
}
// Initialize RTC
if (!rtc.begin()) {
Serial.println(“Couldn’t find RTC”);
while (1);
}
Serial.println(“RTC Initialized”);
tft.println(“RTC Initialized.”);
// Get current date/time via NTP and set to RTC
setTimeByNTP();
// Initialize web server
server.on(“/”, handleRoot);
server.on(“/set”, handleSetAlarm);
server.on(“/stop”, handleStopAlarm);
server.onNotFound(handleNotFound);
server.begin();
// Fill display with black
tft.fillScreen(ILI9341_BLACK);
}
void loop() {
char new_time[9], new_date[15], new_alarm[15];
// Launch web server
server.handleClient();
// Display current date/time on LCD
DateTime now = rtc.now();
sprintf(new_date, “%04d/%02d/%02d “, now.year(), now.month(), now.day());
strcat(new_date, wdays[now.dayOfTheWeek()]);
sprintf(new_time, “%02d:%02d:%02d”, now.hour(), now.minute(), now.second());
strcpy(new_alarm, “Alarm “);
strcat(new_alarm, alarm_time);
tft.setFont(&FreeSans18pt7b);
tft.setTextColor(ILI9341_WHITE);
showMessage(new_date, old_date, 40, 28);
showMessage(new_alarm, old_alarm, 200, 28);
tft.setFont(&FreeSans40pt7b);
showMessage(new_time, old_time, 120, 64);
// Check if current time is time set for alarm
if (strstr(new_time, alarm_time) != NULL) {
if (!alarm_checked) {
// If it’s alarm time, ring out then send message to extension unit
myDFPlayer.loop(1);
alarm_checked = true;
alarm_on = true;
alarm_ctr = 0;
HTTPClient http;
http.begin(notify_url);
int httpCode = http.GET();
}
}
else {
alarm_checked = false;
}
// While alarm is sounding, make red frame flash around alarm time on display
if (alarm_on) {
if (alarm_ctr == 0) {
審核編輯黃宇
-
微控制器
+關(guān)注
關(guān)注
48文章
7953瀏覽量
155127 -
RTC
+關(guān)注
關(guān)注
2文章
622瀏覽量
68880 -
Arduino
+關(guān)注
關(guān)注
190文章
6498瀏覽量
192207 -
ESP32
+關(guān)注
關(guān)注
21文章
1017瀏覽量
19276
發(fā)布評(píng)論請(qǐng)先 登錄
在做一個(gè)微功耗的esp32,因?yàn)?b class='flag-5'>esp32需要500mA工作電流,可以用2個(gè)LDO電源對(duì)系統(tǒng)供電嗎?
請(qǐng)問(wèn)怎樣設(shè)置待機(jī)喚醒為RTC鬧鐘事件喚醒?
HAL庫(kù)RTC鬧鐘喚醒能實(shí)現(xiàn)嗎?
STM32F103上的RTC鬧鐘中斷功能以及用鬧鐘中斷喚醒STM32的待機(jī)模式
從深度睡眠喚醒時(shí)DacAudio ESP32不工作的原因?
esp32c3不能操縱RTC引腳而只能保持它們或被喚醒嗎?
ESP32深度睡眠被鎖定無(wú)法喚醒的原因?
深度睡眠喚醒時(shí)的RTC重置問(wèn)題如何解決?
esp32c3不能操縱RTC引腳而只能保持它們或被喚醒嗎?
STM32實(shí)現(xiàn)對(duì)RTC鬧鐘喚醒的設(shè)計(jì)
STM32 RTC鬧鐘的中斷和喚醒待機(jī)模式
MM32F013x——RTC鬧鐘定時(shí)喚醒

ESP32 Feather RTC與SD開(kāi)源分享

樂(lè)鑫ESP-RTC實(shí)時(shí)音視頻通信方案

評(píng)論