ساعت یادگاری سالگرد ازدواج با نمایشگر E-Paper و برد ESP32
زمان خیلی زود میگذره، مخصوصا وقتی کنار کسی هستی که دوستش داری. خیلی وقتها دلمون میخواد یه یادگاری خاص از روز عروسی یا آشناییمون داشته باشیم که هر لحظه بهمون یادآوری کنه چقدر از اون روز قشنگ گذشته. ما تو تیم آیسیمدار تصمیم گرفتیم یه پروژه الکترونیکی جذاب و احساسی رو با هم بسازیم؛ یه ساعت رومیزی مینیمال که زمان سپری شده از روز عروسی یا هر تاریخ مهم دیگهای رو برات محاسبه و نمایش میده.
این پروژه فقط یه ساعت معمولی نیست، بلکه قراره عشق و خاطرات رو تو قالب اعداد به تصویر بکشه. ما برای ساخت این یادگاری از یه نمایشگر جوهر الکترونیکی (E-Paper) استفاده میکنیم که هم ظاهر فوقالعاده شیک و کلاسیکی داره و هم مصرف انرژیاش بهشدت پایینه. یعنی میتونی با خیال راحت روی میز بذاریش و تا مدتها از دیدنش لذت ببری بدون اینکه نگران خاموش شدنش باشی.
قلب تپنده این ساعت، یه برد کوچیک و قدرتمند به اسم XIAO ESP32-C3 هست که به راحتی برنامهریزی میشه و زمان دقیق رو پردازش میکنه. فرقی نمیکنه بخوای این ساعت رو برای خودت بسازی یا به عنوان یه هدیه خاص به کسی تقدیم کنی؛ تو این آموزش قدمبهقدم همراهت هستیم تا این ایده قشنگ رو به واقعیت تبدیل کنی. پس قطعات رو آماده کن و بریم سراغ ساختش!
تجسم یک حس نامرئی
قطعات موردنیاز
- برد توسعه Seeed Studio XIAO ESP32-C3
- نمایشگر جوهر الکترونیکی (E-Paper) تکرنگ 2.9 اینچی
- مجموعه پیچهای سایز M2
- مجموعه پیچهای سایز M3
طراحی و پرینت سهبعدی قاب ساعت
برای طراحی قاب این پروژه از نرمافزار Fusion 360 استفاده کردیم. بعد از اینکه طراحی تموم شد، فایلها رو با فرمت STL خروجی گرفتیم تا برای پرینت سهبعدی آماده بشن. تو هم میتونی این فایلها رو بگیری و با هر پرینتر سهبعدی که در دسترس داری، قاب رو چاپ کنی.
طراحی این قاب کاملا ساده و مینیمال در نظر گرفته شده تا با ظاهر تمیز و کلاسیک نمایشگرهای جوهر الکترونیکی همخوانی داشته باشه. میتونی از متریالها و رنگهای مختلفی برای پرینت استفاده کنی تا یه قاب محکم، زیبا و کاملا حرفهای داشته باشی.
مونتاژ قطعات و سرهم کردن ساعت
اول از همه، برد XIAO رو با دقت روی آداپتور PCB لحیم کن.
حالا برد XIAO رو طبق نقشه سیمکشی به ماژول E-Paper وصل کن. تو همین نقشه میتونی نحوه اتصال باتری رو هم ببینی. اگه دوست داری پروژهات قابل حمل باشه، میتونی طبق نقشه یه باتری بهش اضافه کنی. اما اگه نیازی به باتری نداری و میخوای ساعت رو همیشه روی میز بذاری، میتونی فقط از طریق کابل USB اون رو روشن نگه داری.
ماژول جوهر الکترونیکی (E-Ink) رو داخل قاب نمایشگر قرار بده و با استفاده از پیچهای 5 میلیمتری M2 اون رو محکم کن.
برد اصلی (XIAO PCB) رو هم سر جای خودش بذار و با پیچهای M3 فیکسش کن.
آنتن وایفای یا بلوتوث برد رو نصب کن تا خیالت از بابت ارتباطات راحت باشه.
اگه نیازه، یه مقدار چسب برای محکمتر شدن قطعات بزن و بعدش قاب پشتی رو کامل ببند.
در نهایت، ساعت رو با یه کابل به کامپیوترت وصل کن تا هم روشن بشه و هم بتونیم کدها رو روش آپلود کنیم.
برنامهنویسی و شخصیسازی کدهای ساعت
وقتشه بریم سراغ مغز متفکر پروژه. کدهای اصلی رو برات آماده کردیم.
#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <time.h>
#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold18pt7b.h>
#include <Fonts/FreeMonoBold12pt7b.h>
// ================= E-PAPER PINS =================
#define CS_PIN D1
#define DC_PIN D3
#define RST_PIN D0
#define BUSY_PIN D5
GxEPD2_BW<GxEPD2_290_BS, GxEPD2_290_BS::HEIGHT> display(
GxEPD2_290_BS(CS_PIN, DC_PIN, RST_PIN, BUSY_PIN)
);
// ================= WIFI =================
WebServer server(80);
Preferences prefs;
const char* apSSID = "WeddingClock_Setup";
const char* apPASS = "12345678";
unsigned long lastHourShown = 0;
uint8_t refreshCount = 0; // for ghosting control
// ================= HEART BITMAP =================
// 15x16 monochrome heart
const unsigned char image_cards_hearts_bits[] PROGMEM = {
0x1C, 0x38,
0x3E, 0x7C,
0x7F, 0xFE,
0xFF, 0xFF,
0xFF, 0xFF,
0x7F, 0xFE,
0x3F, 0xFC,
0x1F, 0xF8,
0x0F, 0xF0,
0x07, 0xE0,
0x03, 0xC0,
0x01, 0x80,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00
};
// ================= WEDDING TIME =================
// Oct 19, 2025 – 6:15 AM IST → 00:45 UTC
time_t getWeddingEpochUTC() {
struct tm t = {};
t.tm_year = 2025 - 1900;
t.tm_mon = 9;
t.tm_mday = 19;
t.tm_hour = 0;
t.tm_min = 45;
t.tm_sec = 0;
t.tm_isdst = 0;
return mktime(&t);
}
// ================= DISPLAY UI =================
void drawWeddingUI(unsigned long hoursPassed, unsigned long daysPassed) {
char hrsBuf[20];
char daysBuf[20];
sprintf(hrsBuf, "%09lu Hrs", hoursPassed);
sprintf(daysBuf, "%09lu Dys", daysPassed);
bool fullRefresh = (refreshCount >= 6);
if (fullRefresh) {
display.setFullWindow(); // anti-ghosting
refreshCount = 0;
} else {
display.setPartialWindow(0, 0, display.width(), display.height());
refreshCount++;
}
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.setTextColor(GxEPD_BLACK);
display.setTextWrap(false);
// Hours
display.setFont(&FreeMonoBold18pt7b);
display.setCursor(10, 73);
display.print(hrsBuf);
// Days
display.setCursor(10, 112);
display.print(daysBuf);
// Names
display.setFont(&FreeMonoBold12pt7b);
display.setCursor(56, 31);
display.print("Arjun");
display.setCursor(152, 31);
display.print("Amgitha");
// Heart
display.drawBitmap(
131, 17,
image_cards_hearts_bits,
15, 16,
GxEPD_BLACK
);
} while (display.nextPage());
}
// ================= TIME COUNTERS =================
void showTimeCounters() {
time_t now;
time(&now);
time_t weddingEpoch = getWeddingEpochUTC();
if (now < weddingEpoch) {
drawWeddingUI(0, 0);
return;
}
unsigned long seconds = now - weddingEpoch;
drawWeddingUI(seconds / 3600, seconds / 86400);
}
// ================= WIFI CONFIG PAGE =================
void handleRoot() {
String page =
"<!DOCTYPE html><html><head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'>"
"<style>"
"body{font-family:Arial;background:#f2f2f2;padding:20px;}"
".card{background:#fff;padding:20px;border-radius:14px;"
"max-width:400px;margin:auto;box-shadow:0 10px 25px rgba(0,0,0,.15);}"
"h2{text-align:center;}"
"select,input,button{width:100%;padding:12px;margin-top:10px;"
"border-radius:8px;border:1px solid #ccc;}"
"button{background:#000;color:#fff;font-weight:bold;}"
"</style></head><body>"
"<div class='card'><h2>Wedding Clock Wi-Fi</h2>"
"<form action='/connect'><label>Network</label><select name='ssid'>";
int n = WiFi.scanNetworks();
for (int i = 0; i < n; i++)
page += "<option value='" + WiFi.SSID(i) + "'>" + WiFi.SSID(i) + "</option>";
page +=
"</select><label>Password</label>"
"<input type='password' name='pass'>"
"<button type='submit'>Connect</button>"
"</form></div></body></html>";
server.send(200, "text/html", page);
}
void handleConnect() {
prefs.putString("ssid", server.arg("ssid"));
prefs.putString("pass", server.arg("pass"));
server.send(200, "text/html", "Connecting… Rebooting");
delay(1500);
ESP.restart();
}
// ================= WIFI LOGIC =================
bool connectToSavedWiFi() {
String ssid = prefs.getString("ssid", "");
String pass = prefs.getString("pass", "");
if (ssid == "") return false;
WiFi.begin(ssid.c_str(), pass.c_str());
unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED && millis() - start < 15000) {
delay(500);
}
return WiFi.status() == WL_CONNECTED;
}
void startConfigPortal() {
WiFi.softAP(apSSID, apPASS);
IPAddress ip = WiFi.softAPIP();
display.setFullWindow();
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.setFont(&FreeMonoBold12pt7b);
display.setCursor(10, 50);
display.print("Connect to:");
display.setCursor(10, 75);
display.print(apSSID);
display.setCursor(10, 100);
display.print(ip.toString());
} while (display.nextPage());
server.on("/", handleRoot);
server.on("/connect", handleConnect);
server.begin();
}
// ================= NTP =================
void syncTimeNTP() {
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
time_t now;
while (time(&now) < 100000) delay(500);
}
// ================= SETUP =================
void setup() {
Serial.begin(115200);
display.init(115200, true, 50, false);
display.setRotation(1);
prefs.begin("wifi", false);
if (!connectToSavedWiFi()) {
startConfigPortal();
return;
}
syncTimeNTP();
showTimeCounters();
}
// ================= LOOP =================
void loop() {
server.handleClient();
time_t now;
time(&now);
unsigned long hour = now / 3600;
if (hour != lastHourShown) {
lastHourShown = hour;
showTimeCounters();
}
delay(1000);
}
شخصیسازی ساعت (تغییر اسمها و تاریخ عروسی)
شخصیسازی این پروژه خیلی راحته. با تغییر دادن فقط چند خط کد، میتونی اسم خودت و پارتنرت رو بنویسی و تاریخ و زمان دقیق عروسی یا آشناییتون رو تنظیم کنی.
چطور اسمها رو روی نمایشگر تغییر بدیم؟
تو کدهای مربوط به نمایشگر، بخشی رو پیدا میکنی که وظیفه چاپ کردن اسمها رو داره.
display.setFont(&FreeMonoBold12pt7b);
display.setCursor(56, 31);
display.print("Arjun");
display.setCursor(152, 31);
display.print("Amgitha");
برای شخصیسازی، خیلی راحت اسمهای داخل کوتیشن رو پاک کن و اسمهای مدنظر خودت رو بنویس.
display.print("Alex");
display.print("Jamie");
اگه اسمهاتون خیلی طولانی یا کوتاهه، میتونی موقعیت مکاننما (Cursor) رو تو کدها یه کم جابهجا کنی تا نوشتهها دقیقا وسط صفحه بیفتن و ظاهر کار تمیز بمونه.
تنظیم تاریخ و زمان دقیق عروسی
تاریخ و زمان عروسی با استفاده از ساختار tm تعریف شده که ویرایشش رو خیلی امن و ساده میکنه. تو کدها بگرد و این تابع رو پیدا کن:
time_t getWeddingEpochUTC() {
struct tm t = {};
t.tm_year = 2025 - 1900;
t.tm_mon = 9; // October (0-based)
t.tm_mday = 19;
t.tm_hour = 0;
t.tm_min = 45;
t.tm_sec = 0;
t.tm_isdst = 0;
return mktime(&t);
}
برای مثال، این کدها تاریخ 19 اکتبر 2025 و ساعت 06:15 صبح به وقت هند رو نشون میدن که معادل ساعت 00:45 به وقت جهانی (UTC) هست.
تبدیل زمان محلی به زمان جهانی (UTC)
اگه میخوای زمان رو به وقت ایران تنظیم کنی، باید تفاوت ساعت محلی با UTC رو در نظر بگیری:
- زمان ایران = UTC + 3 ساعت و 30 دقیقه (در نیمه اول سال 4 ساعت و 30 دقیقه)
- بنابراین باید این اختلاف رو از زمان محلی خودت کم کنی تا به زمان جهانی برسی.
یه مثال ساده
فرض کن زمان مراسمت 1 دسامبر 2026 ساعت 4 عصر به وقت ایران بوده. تبدیلش به UTC اینطوری میشه:
- ساعت 12:30 ظهر به وقت UTC (با فرض 3.5 ساعت اختلاف)
کد بهروزشده به این شکل درمیاد:
t.tm_year = 2026 - 1900;
t.tm_mon = 11; // December
t.tm_mday = 1;
t.tm_hour = 10;
t.tm_min = 30;
البته اگه از همون اول زمان رو به وقت UTC داری، میتونی مستقیم واردش کنی و درگیر تبدیلها نشی.
جمعبندی و نتیجه نهایی ساعت
نتیجه کار یه ساعت فوقالعاده کممصرفه که همیشه روشنه و تو سکوت، مدت زمانی که از بهترین روز زندگیت گذشته رو برات میشماره. این ساعت هم طراحی مینیمالی داره، هم خیلی شخصیه و طوری ساخته شده که سالها بدون دردسر برات کار کنه.
این پروژه برای ما تو آیسیمدار خیلی جذاب بود؛ چون این دستگاه چیزی بیشتر از یه ساعت معمولیه. این یه یادآور کوچیک از یه لحظه خاصه که با دستای خودت ساختیش. امیدواریم از ساختنش لذت برده باشی.
تو میتونی اولین سازنده باشی!
اگه پروژه رو ساختی به اشتراک بزار و اعتبار هدیه بگیر
این آموزش با استانداردهای اختصاصی آیسیمدار بازنویسی و بهینهسازی شده و تمامی حقوق انتشار آن متعلق به این مجموعه است و در صورت کپی یا بازنشر پیگرد قانونی خواهد داشت.








































گفتگوهای کاربران
هنوز نظری ثبت نشده؛ تو شروع کن!
هنوز نظری ثبت نشده؛ تو شروع کن!