這篇文章來源于DevicePlus.com英語網(wǎng)站的翻譯稿。
本文最初發(fā)布在deviceplus.jp網(wǎng)站上,而后被翻譯成英語。
目錄
前言
關(guān)于ESP32鬧鐘
創(chuàng)建日期和時間的“RTC”
在ESP32 LCD上顯示日期和時間
使用MP3模塊發(fā)出鬧鈴響聲
給鬧鐘主機接線
給ESP32鬧鐘分機接線
安裝庫和字體文件
Arduino IDE鬧鐘程序
分機程序
確認(rèn)運行情況
結(jié)論
相關(guān)文章
前言
您每天早上被什么鬧鐘吵醒?早上人們的一個常見問題是關(guān)掉鬧鐘并直接回去睡覺。
這一次,我們決定使用ESP32制作一個“喚醒鬧鐘”,應(yīng)該有助于解決這個問題。
設(shè)計步驟
預(yù)計完成時間:120分鐘
名稱 | 賣方 | 價格 |
ESP32-DevKitC(2 件) | 貿(mào)澤電子 | 約10.00美元 |
DS3231 模塊 | 亞馬遜 | 約3.00美元 |
LIR2032(紐扣充電電池) | 亞馬遜 | 約3.60美元 |
揚聲器 | 亞馬遜 | 約1.00~3.00美元 |
2.8英寸SPI連接器 320×240像素LCD屏幕 | 亞馬遜 | 約15.00美元 |
*除上述物品外,還需要一個輕觸開關(guān)、一個LED和一個約100Ω的電阻器。
關(guān)于ESP32鬧鐘
我們來使用ESP32完成一個目標(biāo)吧:像普通鬧鐘一樣在屏幕上顯示日期和時間,并在指定的時間響起。在設(shè)計制作這款“喚醒鬧鐘”時,我們需要將分機按鈕(用于關(guān)閉鬧鐘)與鬧鐘主機分開放置。之所以這樣設(shè)計,是因為想要您必須起床并走到分機按鈕處才能將鬧鐘關(guān)掉。
如下面的視頻所示,使用兩個ESP,一個用于主機,另一個用于分機。在視頻中,主機和分機是挨著的,但如果通過Wi-Fi連接,它們是可以在Wi-Fi范圍內(nèi)分開放置的。
將主機和分機分開放置時,請使用ESP32的Wi-Fi功能。兩個ESP32作為Web服務(wù)器和客戶端運行,并相互通信,如圖1所示。
圖 1 主機和分機之間的通信
創(chuàng)建日期和時間的“RTC”
對于Arduino等微控制器來說,通常能夠獲取在啟動程序后經(jīng)過的時間。但是,您獲得的時間通常不是很準(zhǔn)確,因為斷電時會重置經(jīng)過時間。
而如果使用ESP32,則可以通過Wi-Fi連接到互聯(lián)網(wǎng),以定期從互聯(lián)網(wǎng)上的NTP服務(wù)器獲取日期和時間,并將其設(shè)置到ESP32上。但Arduino Uno等部分微控制器沒有互聯(lián)網(wǎng)連接功能,因此,擁有一種可以更輕松地處理日期和時間的機制會很方便。
這就是為什么經(jīng)常使用一種被稱作“RTC”(實時時鐘)的IC。 RTC是基于周期性發(fā)出信號的元件來計時的IC。此外,通過將其連接到電池等外部電源,即使在微控制器斷電時也可以繼續(xù)計時。
許多產(chǎn)品都采用RTC,此次,我們將使用一種名為“DS3231”的RTC模塊。
在電子設(shè)計所用的RTC模塊當(dāng)中,DS3231模塊很受歡迎,且很容易獲得。由于接口是I2C,因此只需要4根線。除了RTC功能,還具有溫度傳感器功能(不過本文不會用到溫度傳感器)。
此外,在照片1所示的DS3231上,安裝一個名為“LIR2032”的紐扣電池,這樣即使在微控制器關(guān)閉的情況下也能繼續(xù)記錄日期和時間。LIR2032的電池尺寸與CR2032的相同,但不同的是它可充電。
照片1 DS3231模塊
在ESP32 LCD上顯示日期和時間
由于鬧鐘用于查看當(dāng)前日期和時間,因此需要以易于理解的方式顯示日期和時間。以下設(shè)備用于顯示日期和時間。
7段LED
LED 矩陣
OLED 顯示器
字符液晶顯示器
圖形液晶顯示器
根據(jù)設(shè)備的不同,有不同的庫和不同的編程方法。這還取決于它是否適合您想要制作的作品。例如,7段LED僅適合以低成本顯示數(shù)字,但不適合顯示詳情。其中,圖形液晶顯示器是最通用的,可以用于許多不同的項目,所以我這次決定使用它。
市面上有各種類型的液晶顯示器模塊,但對于今天的項目,我們將使用一個名為“ILI9341”的控制器,并使用SPI接口(照片2)。此外,液晶顯示器通常以2.2英寸/2.4英寸/2.8英寸尺寸出售,因此,請根據(jù)您正在做的作品類型進(jìn)行相應(yīng)調(diào)整。
照片2 控制器用ILI9341 2.8英寸液晶顯示器
使用MP3模塊發(fā)出鬧鐘鈴聲
既然是鬧鐘,在指定時間發(fā)出鈴聲是必需的。您可以將蜂鳴器連接到ESP32以產(chǎn)生單一鈴聲,但如果您愿意,也可以使用自己喜歡的鈴聲。為此,我們將使用一個名為“DFPlayer Mini”的模塊,它可以播放任何MP3數(shù)據(jù)(照片3)。
DFPlayer Mini是一個可以通過串口發(fā)送命令來播放microSD卡中MP3的模塊。可以將一個小型揚聲器連接到揚聲器輸出引腳以產(chǎn)生鈴聲。
照片3 DFPlayer Mini
給鬧鐘主機接線
讓我們進(jìn)入實際構(gòu)建吧。首先,給鬧鐘接線。
使用兩個面包板,一個配有ESP32和DS3231,另一個配有液晶顯示器(LCD)和DFPlayer Mini。各部件接線如圖2所示。
由于ESP32很寬,您只能在普通面包板的一面放一根跳線。因此,請改用電源線只在一側(cè)、多一排插孔的面包板(例如Sanhayato的SAD-101)。
ESP32和液晶顯示器通過SPI進(jìn)行連接。ESP32可以使用兩個 SPI(VSPI和HSPI),但使用的是VSPI(引腳18/19/23)(表1)。
ESP32和DS3231通過I2C進(jìn)行連接。在ESP32中,I2C可以分配給任何引腳,但我們使用標(biāo)準(zhǔn)引腳(SDA=21和SCL=22)(表 2)。
DFPlayer Mini 進(jìn)行串行連接。ESP32可以使用3個串口,但為此請同時使用引腳16和引腳17(表3)。此外,將揚聲器連接到DFPlayer Mini“SPK1”和“SPK2”兩個引腳。
圖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鬧鐘分機接線
接下來,我們將給分機接線。分機接線應(yīng)按圖3所示進(jìn)行。您所要做的就是將開關(guān)和 LED連接到ESP32。將開關(guān)的一側(cè)連接到ESP32的3V3引腳,另一側(cè)連接到引腳4。通過電阻器→LED再通過ESP32的引腳13連接到GND。
在讀取開關(guān)狀態(tài)的電路上插入一個上拉電阻或下拉電阻。但是,由于ESP32可以通過內(nèi)部電阻進(jìn)行上拉/下拉,因此省略了外部電阻。
圖3 分機接線
安裝庫和字體文件
完成接線工作后,您可以創(chuàng)建程序。首先,從安裝下面的各個庫開始。
Adafruit GFX
Adafruit ILI9341
RTCLib
DFRobotDFPlayerMini
安裝步驟如下:
啟動Arduino IDE。
選擇“Sketch”->“Include Library”->“Manage Library”菜單,以打開Library Manager。
在“Filter search”字段中輸入“Adafruit GFX”。
Adafruit GFX將在庫列表中顯示。單擊“Install”按鈕(圖 4)。
以相同的方式安裝每個庫。
有幾個名稱相似的RTCLib和DFPlayer庫。RTCLib 安裝“RTCLib by Adafruit”,而DFPlayer安裝“DFRobotDFPlayerMini by DFRobot”。
圖 4 Adafruit GFX庫安裝
此外,為通過大字符顯示時間,需要安裝字體文件。如果您下載并解壓縮以下zip文件,將可以獲得一個名為“FreeSans40pt7b.h”的文件。
打開Arduino IDE的標(biāo)準(zhǔn)草圖目標(biāo)文件夾,再打開“l(fā)ibraries”->“Adafruit_GFX_Library”->“Fonts”文件夾,將字體文件復(fù)制到那里。
https://www.h-fj.com/deviceplus/font.zip
Arduino IDE鬧鐘程序
接下來,在Arduino IDE中創(chuàng)建一個鬧鐘程序并將其寫入ESP32。程序內(nèi)容如清單1所示。
清單1:鬧鐘主機程序
程序內(nèi)容(放在這里)
但是,第17行到第21行需要改寫如下:
?第17/18行
根據(jù)您的Wi-Fi路由器的SSID/密碼重寫。
?第19行
指定要分配給ESS32的IP地址。根據(jù)您的Wi-Fi路由器的網(wǎng)絡(luò)配置自行決定IP地址。
在普通IP地址中,四組數(shù)字用句點分隔,但在這一行中,它是函數(shù)參數(shù)的形式,所以四組數(shù)字用逗號分隔。
?第20行
指定網(wǎng)絡(luò)默認(rèn)網(wǎng)關(guān)的IP地址。通常,它是Wi-Fi路由器的IP地址。用逗號分隔IP地址中的四組數(shù)字。
?第21行
根據(jù)分配給分機ESP32的IP地址重寫。
例如,如果您想按照表4所示進(jìn)行設(shè)置,請重寫第17行至第21行,如清單2所示。
項目 | 設(shè)定值 |
Wi-Fi路由器SSID | my_wifi |
Wi-Fi路由器密碼 | my_password |
分配給主機ESP32的IP地址 | 192.168.1.101 |
默認(rèn)網(wǎng)關(guān)IP地址 | 192.168.1.1 |
分配給分機ESP32的IP地址 | 192.168.1.102 |
表4:主機網(wǎng)絡(luò)設(shè)置示例
清單 2:重寫第17-21行的示例
分機程序
分機程序如清單3所示。
以與鬧鐘主機相同的方式重寫第5行到第9行。 但是,在第7行,指定分配給分機的IP地址。 此外,在第9行的“Main console IP address(主控臺IP地址)”中指定鬧鐘的IP地址。
清單 3:分機程序
程序內(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文章
7646瀏覽量
151947 -
RTC
+關(guān)注
關(guān)注
2文章
542瀏覽量
66919 -
Arduino
+關(guān)注
關(guān)注
188文章
6477瀏覽量
187661 -
ESP32
+關(guān)注
關(guān)注
18文章
977瀏覽量
17484
發(fā)布評論請先 登錄
相關(guān)推薦
評論