This project is a simple concept where I combine two things I need to get done in the morning into one device: waking up on time, doing self-care, and taking my medication. It has all the elements of an alarm clock, a monitor to show the time, a way to change the alarm time, and a snooze button. The arcade buttons serve as a more satisfying interactive element that are the snooze buttons.
Front view of the device.
All of the switches and buttons have their own slot in the housing. It's a tight fit so I don't need glue necessarily to keep them in there. The top switch is for alarm activation, and the botton three in order (top to bottom) is time +, time -, and confirm. These buttons are solely for the purpose of setting the alarm time.
Inner look at the electronics. It's a little gnarly but this is the neatest I could get it without going insane.
Where I got the display to work until! The I2C buses are not connecting well explaining the 00:00 Time and the unresponsive switches. I was happy to get something on the display at all.
1e. Moving image (one or more), a .gif, .mp4, or .mov file. Your movie can be as short as a second or two if that's all that needed to show the interactivity of your device, but should go longer as is necessary. It can feature voice narration if you like, but does not need to.
Upload instructions: Do not merely link to an outside hosting service like YouTube and do not embed a YouTube or other video service frame into your page. Rather, the videos are inserted by uploading a video file to this Google Drive folder and then under the Insert tab on the right, selecting "Drive," then clicking on the Shared drive called "60-223 f25." By keeping video files in that Shared drive, we ensure that this documentation page will remain intact for the long term.
If the movie is not audibly narrated, a caption should describe the action of the movie so that a blind person would still be able to understand what is happening.
(Note that videos don't have captions or alt-text options on Google Sites, so you just need to write your own text boxes below them.)
HAVE NOT DONE THIS YET BECAUSE DEVICE IS NOT WORKING :(
In this video, I show operation of the machine in decimal mode, flipping the right three switches from off to on, which makes the decimal sum 15. Then, I flip the bottom center switch from the left to the right, which turns the machine into hexadecimal mode; once, I do that, the 15 appears instead as "F," which is the equivalent hexadecimal representation. Then, I switch only the fourth switch from the left to on, and all of the other ones off; once I do that, the sum is 16 in decimal, which appears as "10" in hexadecimal on the display. Finally, I switch the mode switch back to the left, and the display shows "16" since that's the decimal representation of that number.
2. Process images and review
The prototype wiring was purposefully done on a smaller breadboard so that I could minimize the size of the final device.
Using a 128x64 OLED Display as my stand-in display until my actual display arrived, I wired everything up and realized that this project would have me wrestling with a LOT of wires and organizing would become a top priority.
On Monday (Two days before my project was due!), I recieved my display and the ESP32. I hadn't realized that working with the ESP32 would be a completely different beast from the Arduino I was familiar with. Almost all of my time before the due date was spent on trying to figure out the wiring and setup for these new things.
One of my first big mistakes was trying to get something going without fully understanding the pinouts and technology of my Qualia ESP32-S3. Given that there are only two digital and two analog pinouts, I initially thought that I could use those for my 1. buzzer 2. two LEDs 3. 6 buttons, but I realized after much time that those wouldn't be enough pins even if I set up a resistance ladder for 2 pinouts for 6 buttons.
I printed out the housing for my alarm clock and started putting everything together (even though I hadn't got my display to start working...) but I knew that I had to get moving somehow. With the black background and red and blue arcade buttons, the device ended up looking really retro which I loved.
Upper view of the inner electronic setup. I think there could have been a better way to organize the wires and place boards where they could fit snugly but the display wiring was very bulky and got rid of a lot of space to work with.
Starting this project, I didn't realize how time-consuming even the simplest of steps would be and how costly it would be to retrack your steps if you make a mistake that could have been avoided if I had taken my time. Starting with the prototype without my most essential parts (the display, the dev board), I attempted to focus on what I could do, which was writing out the logic for my program that I thought would translate over to the ESP32 well. Using an Arduino UNO I set up the button functions I needed with four tactile buttons (Time +, Time -, Snooze, Confirm) and used a buzzer and LED to perform basic outputs as I needed. As the demo day grew closer without my parts, I set to soldering and heat-shrinking the electrical components I needed for my final product, such as the LEDs, the resistors, and the many buttons I needed. I was a little stuck at this point, because my housing hadn't been printed out (which I wasn't counting on it being right the first time) so I didn't know how long to make the wire connections from the protoboard to where the components would be inserted. Once I got my display and my dev board, I realized I probably wouldn't be able to finish the project in time. Trying to understand how the new display worked that was completely different from the OLED or LCD displays I had worked with before on top of a new dev board system was very confusing, and the guides I'd try to follow online simply were outdated or didn't work with my display. After a lot of struggling and trying all sorts of ways, I ended up figuring out that a big part of my issues were the constantly switching ports of the Qualia as it went back and forth between boot mode and not-boot mode. Flashing the drive in a few different methods I eventually got the bootloader to appear reliably, and I could finally get to uploading my code and have the ESP32 receive and keep the data I was sending, which meant I could finally upload a visual onto my display. I got to this point a few days after demo day.
3. Discussion
Coda, a classmate of mine, said "I think it was just the display issues you had but otherwise really solid." I agree with their comment, I thought it was unfortunate that I wasn't able to get the display to work as I had everything else (code, logic, etc) worked out. I was trying to have something to show for the other alarm clock elements (like the snooze button or the LEDs) but given that the display code wasn't compiling, I couldn't show those parts. I felt like I had very little time to prepare given that I received my parts Monday when demo day was Wednesday. That being said I take pride in how quickly I got everything together as Coda said.
Tiffany, another classmate, said "Thoughts on button on the top of the box instead of the front and have a bigger display?" I initially had designed my alarm clock to be this way to fit the aesthetic of a typical alarm clock, but I thought that having the buttons on the front would make more intuitive sense for the user as to what button fulfills what functions instead of having the core elements of the alarm clock being spread out through the device. If I had gone with smaller buttons, I would have had gone for what Tiffany has said.
Self critique: If I were to do this project again from the start, I think I would have a much smoother ride as I would have in mind the technical limitations / electrical components I would have to use without having to do over my circuits and CAD. I didn't understand how an ESP-32 really worked until after the demo day when I found out how to reliably access the bootloader on my laptop and compile my Arduino code so that something would appear on my display. On the note of foresight, drafting out the electrical schematic while working on the soldering was a huge help, as I kept forgetting where I was and what my most optimal connection route would be. I'm proud of what I got done in two days, but if I had prepared better beforehand I could have shown a better final product.
What you learned about your own abilities and/or limitations:
I realized that trying to learn about a new board / hardware piece shouldn't involve "poking around to see what you find" as almost always there's a guide out there that can take you where you want to get to. I think I spent too long focused on the problem of my display not working (hardware) that I couldn't see that a bigger problem lied in my board settings and libraries that I were using. I only ade great progress once I found a forum where someone had posted a guide to follow on how to get an RGB-666 display working with the Qualia ESP32-S3. I enjoyed CADing the housing for my electronics and I think I can afford to be more creative once I become familiar with wiring up the different components on the inside in a clean manner. I got to familiarize myself with a lot of new concepts (expander, resistance ladders, flashing a USB, bootloaders, multiple I2C addresses...) and very much enjoyed the aha moments where I understood how things connected together. I think next time I will make sure that my prototype is working exactly as I intend to (with the board I want to use, and not a stand-in) and then start drafting my circuits and casings. I think because I was rushing to get something done I went back and forth when I should have been more patient and methodical.
Next steps. I want to build a more compact version of the alarm clock once I figure out how to wire things up while taking into account the spatial limitations of the housing. I dont' like having to weave between wires or tugging on them to see where they connect to once I put everything together in place. Having distinct markers to show what wire is connected to what module would be great for visibility and debugging.
4. Technical information
4b. Code submission, embedded into the project page
#include <Wire.h>
#include <RTClib.h>
#include <Arduino_GFX_Library.h>
#include <Adafruit_MCP23X17.h>
Arduino_XCA9554SWSPI *expander = new Arduino_XCA9554SWSPI(
PCA_TFT_RESET, PCA_TFT_CS, PCA_TFT_SCK, PCA_TFT_MOSI,
&Wire, 0x3F);
//Project Title: The Reminding Alarm Clock
//Name: Jessica Bae
//This code sets up the alarm clock. You can set the alarm time by long-pressing the "confirm" button and editing the time as necessary.
//Products Used: Rectangle Bar RGB TTL TFT Display - 4.58 320x960 No Touchscreen [HD458002C40] : Adafruit Industries, Unique & fun DIY electronics and kits
//Adafruit Qualia ESP32-S3 for TTL RGB-666 Displays : Adafruit Industries, Unique & fun DIY electronics and kits
//Tutorial Referenced: adafruit-qualia-esp32-s3-for-rgb666-displays.pdf
/* ============================================================
HARDWARE PINMAP — QUALIA ESP32‑S3 + MCP23017 + DS3231 RTC
============================================================
-------------------------
QUALIA ESP32‑S3 I2C BUS
-------------------------
SDA → GPIO 5
SCL → GPIO 6
(Both require 4.7k pull‑ups to 3.3V)
-------------------------
DS3231 REAL‑TIME CLOCK
-------------------------
VCC → 3.3V
GND → GND
SDA → GPIO 5 (shared I2C bus)
SCL → GPIO 6 (shared I2C bus)
-------------------------
MCP23017 I/O EXPANDER
-------------------------
VDD → 3.3V
VSS → GND
SDA → GPIO 5 (shared I2C bus)
SCL → GPIO 6 (shared I2C bus)
RESET → 3.3V (must be tied HIGH)
A0 → GND (I2C address = 0x20)
A1 → GND
A2 → GND
-------------------------
MCP23017 INPUT BUTTONS
-------------------------
GPB0 → BTN_SELF
GPB1 → BTN_MED
GPB2 → BTN_ALARM_SWITCH
GPB3 → BTN_TIME_UP
GPB4 → BTN_TIME_DOWN
GPB5 → BTN_CONFIRM
(All buttons wired to GND on press)
(Configured as INPUT_PULLUP in code)
-------------------------
MCP23017 OUTPUT LEDS
-------------------------
GPA6 → LED_SELF
GPA7 → LED_MED
-------------------------
BUZZER (DIRECT TO ESP32)
-------------------------
BUZZER → GPIO 43
GND → GND
-------------------------
QUALIA DISPLAY EXPANDER
-------------------------
NOTE: The XCA9554 expander on the Qualia board
is only for the display subsystem.
============================================================ */
// RGB Panel Timing
Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel(
TFT_DE, TFT_VSYNC, TFT_HSYNC, TFT_PCLK,
TFT_R1, TFT_R2, TFT_R3, TFT_R4, TFT_R5,
TFT_G0, TFT_G1, TFT_G2, TFT_G3, TFT_G4, TFT_G5,
TFT_B1, TFT_B2, TFT_B3, TFT_B4, TFT_B5,
1, 50, 2, 44, // hsync
1, 16, 2, 18 // vsync
);
// Display object
Arduino_RGB_Display *gfx = new Arduino_RGB_Display(
320, 960,
rgbpanel,
0,
true,
expander,
GFX_NOT_DEFINED,
HD458002C40_init_operations,
sizeof(HD458002C40_init_operations),
80);
// i2c devices
RTC_DS3231 rtc;
Adafruit_MCP23X17 ioexp;
// Buttons (GPB0–GPB5)
#define BTN_SELF 0
#define BTN_MED 1
#define BTN_ALARM 2
#define BTN_TIME_UP 3
#define BTN_TIME_DN 4
#define BTN_CONFIRM 5
// LEDs (GPA6–GPA7)
#define LED_SELF 6
#define LED_MED 7
// Buzzer
#define BUZZER 43
// ==== ALARM VARIABLES ====
int alarmHour = 8;
int alarmMinute = 0;
bool alarmActive = false;
bool alarmEnabled = true;
bool medDone = false;
bool selfDone = false;
bool snoozing = false;
uint32_t snoozeEnd = 0;
const uint32_t SNOOZE_DURATION = 5 * 60 * 1000; // 5 minutes
bool editingTime = false;
bool editingHour = true; // true = editing hour, false = editing minute
uint32_t confirmPressStart = 0;
bool confirmHeld = false;
uint32_t flashTimer = 0;
bool flashState = false;
DateTime dt;
// ==== READ BUTTON ====
bool readButton(uint8_t pin) {
static uint32_t lastRead[8] = { 0 };
bool pressed = (ioexp.digitalRead(pin) == LOW);
if (pressed && (millis() - lastRead[pin] > 200)) {
lastRead[pin] = millis();
return true;
}
return false;
}
// ==== ALARM SOUND ====
void gentleAlarm() {
tone(BUZZER, 600, 200);
delay(300);
tone(BUZZER, 800, 200);
delay(600);
}
void drawUI() {
gfx->fillScreen(RGB565_BLACK);
gfx->setTextColor(RGB565_WHITE);
gfx->setTextSize(3);
gfx->setCursor(20, 40);
gfx->printf("Time: %02d:%02d", dt.hour(), dt.minute());
gfx->setCursor(20, 120);
// Flashing during edit mode
if (editingTime && editingHour) {
if (millis() % 600 < 300)
gfx->printf("Alarm: :%02d", alarmMinute);
else
gfx->printf("Alarm: %02d:%02d", alarmHour, alarmMinute);
}
else if (editingTime && !editingHour) {
if (millis() % 600 < 300)
gfx->printf("Alarm: %02d: ", alarmHour);
else
gfx->printf("Alarm: %02d:%02d", alarmHour, alarmMinute);
}
else {
gfx->printf("Alarm: %02d:%02d", alarmHour, alarmMinute);
}
gfx->setCursor(20, 200);
gfx->printf("Alarm Enabled: %s", alarmEnabled ? "YES" : "NO");
gfx->setCursor(20, 260);
gfx->printf("Med Done: %s", medDone ? "YES" : "NO");
gfx->setCursor(20, 320);
gfx->printf("Self Done: %s", selfDone ? "YES" : "NO");
if (snoozing) {
gfx->setCursor(20, 400);
gfx->setTextColor(RGB565_YELLOW);
gfx->println("SNOOZING...");
}
}
// ============================================================
// FLASHING REMINDER SCREEN
// ============================================================
void drawFlashScreen() {
flashState = !flashState;
uint16_t bg = flashState ? RGB565_RED : RGB565_BLACK;
gfx->fillScreen(bg);
gfx->setTextSize(4);
gfx->setTextColor(RGB565_WHITE);
gfx->setCursor(20, 100);
gfx->println("TASKS REMAINING:");
gfx->setTextSize(3);
if (!medDone) {
gfx->setCursor(20, 200);
gfx->println("- Medication");
}
if (!selfDone) {
gfx->setCursor(20, 260);
gfx->println("- Self Care");
}
}
// ==== SETUP ====
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println("Booting...");
// I2C on Qualia pins
Wire.begin(5, 6);
Serial.println("I2c starting");
gfx->begin();
gfx->setRotation(1);
Serial.println(ioexp.begin_I2C(0x20) ? "MCP OK" : "MCP FAIL");
expander->pinMode(PCA_TFT_BACKLIGHT, OUTPUT);
expander->digitalWrite(PCA_TFT_BACKLIGHT, HIGH);
// RTC
rtc.begin();
// IO Expander
ioexp.begin_I2C(0x20);
// Configure expander pins
ioexp.pinMode(BTN_MED, INPUT_PULLUP);
ioexp.pinMode(BTN_SELF, INPUT_PULLUP);
ioexp.pinMode(BTN_ALARM, INPUT_PULLUP);
ioexp.pinMode(BTN_TIME_UP, INPUT_PULLUP);
ioexp.pinMode(BTN_TIME_DN, INPUT_PULLUP);
ioexp.pinMode(BTN_CONFIRM, INPUT_PULLUP);
ioexp.pinMode(LED_MED, OUTPUT);
ioexp.pinMode(LED_SELF, OUTPUT);
pinMode(BUZZER, OUTPUT);
noTone(BUZZER);
drawUI();
}
// ==== MAIN LOOP ====
void loop() {
dt = rtc.now();
alarmEnabled = (ioexp.digitalRead(BTN_ALARM) == LOW);
// --- Detect long press on CONFIRM to enter edit mode ---
if (ioexp.digitalRead(BTN_CONFIRM) == LOW) {
if (!confirmHeld) {
confirmHeld = true;
confirmPressStart = millis();
}
// If held for > 1 second, enter edit mode
if (!editingTime && (millis() - confirmPressStart > 1000)) {
editingTime = true;
editingHour = true; // start with hour
Serial.println("Entered TIME EDIT MODE");
}
} else {
confirmHeld = false;
}
// =====================================================
// TIME EDIT MODE
// =====================================================
if (editingTime) {
// --- Editing HOUR ---
if (editingHour) {
if (readButton(BTN_TIME_UP)) {
alarmHour = (alarmHour + 1) % 24;
Serial.println("Hour increased");
}
if (readButton(BTN_TIME_DN)) {
alarmHour = (alarmHour + 23) % 24;
Serial.println("Hour decreased");
}
// Press CONFIRM to switch to minute editing
if (readButton(BTN_CONFIRM)) {
editingHour = false;
Serial.println("Switching to MINUTE edit");
}
}
// --- Editing MINUTE ---
else {
if (readButton(BTN_TIME_UP)) {
alarmMinute = (alarmMinute + 1) % 60;
Serial.println("Minute increased");
}
if (readButton(BTN_TIME_DN)) {
alarmMinute = (alarmMinute + 59) % 60;
Serial.println("Minute decreased");
}
// Press CONFIRM again to exit edit mode
if (readButton(BTN_CONFIRM)) {
editingTime = false;
Serial.println("Exited TIME EDIT MODE");
}
}
// Draw UI with flashing indicator
drawUI();
delay(150);
return; // Skip normal alarm logic while editing
}
// Alarm trigger
if (alarmEnabled && !snoozing && !alarmActive && dt.hour() == alarmHour && dt.minute() == alarmMinute) {
alarmActive = true;
medDone = false;
selfDone = false;
}
// Snooze button behavior
if (alarmActive && !snoozing) {
if (readButton(BTN_MED) || readButton(BTN_SELF)) {
Serial.println("Initial snooze pressed.");
noTone(BUZZER);
snoozing = true;
snoozeEnd = millis() + SNOOZE_DURATION;
}
}
// Snooze expiration
if (snoozing && millis() > snoozeEnd) {
snoozing = false;
}
// Alarm behavior
if (alarmActive && !snoozing) {
gentleAlarm();
ioexp.digitalWrite(LED_MED, medDone ? LOW : HIGH);
ioexp.digitalWrite(LED_SELF, selfDone ? LOW : HIGH);
if (medDone && selfDone) {
Serial.println("All done!");
alarmActive = false;
noTone(BUZZER);
}
}
// Flashing reminder screen
if (alarmActive && snoozing) {
if (millis() - flashTimer > 1000) {
flashTimer = millis();
drawFlashScreen();
}
} else {
drawUI();
}
delay(100);
}