Community project

Kid-Friendly Alarm Clock

ESP32
Photo of Kid-Friendly Alarm Clock
Generated with AI

abide-swoop-exert

Published October 29, 2026

This kid-friendly alarm clock combines a colorful 1.28-inch round display, precise timekeeping, and audio alerts to create an engaging wake-up experience. Built around an ESP32 microcontroller, the clock features a real-time clock module for accurate timekeeping, a Class-D amplifier with speaker for alarm sounds, and four push buttons for easy control.

The guide includes a complete wiring diagram showing all connections between the ESP32, display, RTC module, amplifier, speaker, and buttons. Assembly steps walk through component layout, soldering connections, and firmware deployment. Readers will also get the full Arduino sketch with Wi-Fi configuration, multiple theme options, snooze functionality, and web-based settings to customize alarm times and appearance.

Wiring diagram

Wiring diagram for Kid-Friendly Alarm Clock
Loading diagram…

Gather all the parts

QtyComponent
1

GC9A01 Round TFT LCD 1.28 inch 240x240

1.28 inch round IPS TFT LCD display module with GC9A01/GC9A01A driver IC. 240x240 RGB resolution, 4-wire SPI interface, and 3.3V/5V module input. Module-side labels are VCC, GND, DIN, CLK, CS, DC, RST, and BL/BLK. The display is write-only over SPI, so no MISO line is required for the LCD. Supported by Adafruit GC9A01A and TFT_eSPI libraries in Arduino-compatible firmware projects.

1

Adafruit DS3231 Precision RTC Breakout

DS3231 precision real-time clock breakout with integrated TCXO and crystal for accurate timekeeping over I2C.

1

Push Button

Momentary push button switch

1

Push Button

Momentary push button switch

1

Push Button

Momentary push button switch

1

MAX98357A I2S Class-D Mono Amplifier Breakout

I2S-input Class-D mono audio amplifier IC on a compact breakout board. Accepts I2S digital audio input (BCLK, LRC, DIN) and drives a small speaker or transducer directly. No I2C/SPI control bus is needed. The amplifier supply range is 2.5V-5.5V, and the I2S input pins are compatible with 3.3V logic. SD/MODE controls shutdown and channel selection; GAIN selects 3 dB, 6 dB, 9 dB, 12 dB, or 15 dB gain.

1

8Ω Speaker

8 Ω, 0.5–3 W

Generic small 8Ω 0.5-3W loudspeaker (~28mm typical). Pair with an I2S amp (MAX98357A) or class-D amp (TPA3116D2) for usable volume; do not drive directly from a GPIO pin. Audio output for music/voice playback.

1

Push Button

A small momentary button that turns the clock's temporary Wi-Fi settings page on or off.

Assemble it in 6 steps

1. Lay out the parts before applying power

Place the ESP32 DevKit V1, round display, RTC module, MAX98357A amplifier, speaker, and four push buttons on a breadboard. Keep the speaker and display facing outward; they will be the child-facing parts of the clock.

  • Use an ESP32 board with the usual two rows of header pins and ESP32-WROOM metal radio can.
  • Do not connect the battery or USB-C battery module in this first USB-powered build.
  • Do not let bare wires from different rows touch; a short circuit can make the board hot or damage it.

2. Wire the round color screen

Connect display VCC to ESP32 3V3 (power), GND to ESP32 GND (ground), SCK/SCL to GPIO18 (screen clock), MOSI/SDA/DIN to GPIO23 (screen picture data), CS to GPIO4 (screen select), DC to GPIO16 (screen command/data choice), RST to GPIO17 (screen reset), and BL to 3V3 (backlight power).

  • Some screen boards print SCL where this guide says SCK, and SDA or DIN where this guide says MOSI; those labels mean the same screen connections here.
  • Use 3V3, not the ESP32 5V/VIN pin, for this display's signal supply unless its own board clearly documents a level shifter. Swapped VCC and GND can damage the display.

3. Wire the clock module

Fit the matching backup coin cell in the DS3231 holder with its positive side facing the holder's positive marking. Connect RTC VIN to 3V3 (power), GND to GND (ground), SDA to GPIO21 (time data), and SCL to GPIO22 (time clock).

  • The coin cell keeps the time while USB power is unplugged.
  • Buy or use the Adafruit-style DS3231 shown in this plan; its I2C pins are safe when VIN is connected to 3V3.
  • Do not power an unknown cheap DS3231 board from 5V while its SDA and SCL wires go straight to the ESP32; 5V signals can damage the ESP32.

4. Wire the speaker amplifier and speaker

Connect MAX98357A VIN to 3V3 (power), GND to GND (ground), BCLK to GPIO26 (sound clock), LRC to GPIO25 (sound timing), and DIN to GPIO27 (sound data). Connect the amplifier's SPK+ pin to the speaker POS terminal (speaker positive audio lead) and SPK− to the speaker NEG terminal (speaker negative audio lead).

  • An 8-ohm, 0.5–3 W bare speaker is a good safe choice for this small clock.
  • Keep the two speaker wires together and short for cleaner sound.
  • Never connect either speaker terminal to GND or 3V3. The amplifier drives both speaker wires, and grounding one can damage the amplifier.

5. Add the four control buttons

For each push button, use two legs on opposite sides of the button. Connect one side of the Set button to GPIO13 (signal) and its opposite side to GND (ground). Connect Up to GPIO14 and GND (signal and ground), Down to GPIO32 and GND (signal and ground), and Wi-Fi to GPIO33 and GND (signal and ground).

  • The buttons use the ESP32's built-in pull-up resistors, so no separate resistor is needed.
  • SET turns the alarm on or off, UP adds five minutes to the alarm, DOWN moves its hour back one, and SET becomes the five-minute snooze button while the alarm is sounding.
  • A four-leg tactile button has the two legs on each same side already joined together. Use one leg from each opposite side, otherwise the button will appear permanently pressed.

6. Power, deploy, and use the Wi-Fi settings

Check every 3V3 and GND wire once more, then connect the ESP32 to USB for power. In Schematik press Deploy. Press the Wi-Fi button when the clock is running; connect a phone to the Wi-Fi network named KidsClock using password clock123, then open 192.168.4.1 in the phone browser to set the time, alarm, and theme. Wi-Fi turns itself off after ten minutes or when the Wi-Fi button is pressed again.

  • Change the Wi-Fi password in the firmware before giving the finished clock to a child.
  • The screen updates only when the visible time or settings change, so it stays responsive without constantly redrawing.
  • If the screen remains black, unplug USB immediately and first check that its VCC goes to 3V3 and its GND goes to GND — swapped power can damage the screen.

Review all connections

1. Connections between "round_display" and "ESP32"

Functionround_displayESP32
powerVCC → Display power3V3
groundGND → Display groundGND
spiSCK → Display clockGPIO 18
spiMOSI → Display picture dataGPIO 23
spiCS → Display selectGPIO 4
digitalDC → Display command/data choiceGPIO 16
digitalRST → Display resetGPIO 17
powerBL → Display backlight always on3V3

2. Connections between "rtc" and "ESP32"

FunctionrtcESP32
powerVIN → RTC power so its I2C signals stay safe for the ESP323V3
groundGND → RTC groundGND
i2cSDA → RTC time dataGPIO 21
i2cSCL → RTC time clockGPIO 22

3. Connections between "audio_amp" and "ESP32"

Functionaudio_ampESP32
powerVIN → Audio amplifier power3V3
groundGND → Audio amplifier groundGND
dataBCLK → Audio bit clockGPIO 26
dataLRC → Audio left/right clockGPIO 25
dataDIN → Audio sound dataGPIO 27
dataSPK+ → 8Ω Speaker POSEXT
dataSPK- → 8Ω Speaker NEGEXT

4. Connections between "set_button" and "ESP32"

Functionset_buttonESP32
groundGND → Button groundGND
digitalSIGNAL → Set/menu input; also snoozes a ringing alarmGPIO 13

5. Connections between "up_button" and "ESP32"

Functionup_buttonESP32
groundGND → Button groundGND
digitalSIGNAL → Up/next inputGPIO 14

6. Connections between "down_button" and "ESP32"

Functiondown_buttonESP32
groundGND → Button groundGND
digitalSIGNAL → Down/previous inputGPIO 32

7. Connections between "wifi_button" and "ESP32"

Functionwifi_buttonESP32
groundGND → Button groundGND
digitalSIGNAL → Wi-Fi settings inputGPIO 33

Deploy the firmware

#include <Arduino.h>
#include <SPI.h>
#include <Wire.h>
#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <driver/i2s.h>
#include <Adafruit_GFX.h>
#include <Adafruit_GC9A01A.h>
#include <RTClib.h>

#define TFT_SCK 18
#define TFT_MOSI 23
#define TFT_CS 4
#define TFT_DC 16
#define TFT_RST 17
#define RTC_SDA 21
#define RTC_SCL 22
#define I2S_BCLK 26
#define I2S_LRC 25
#define I2S_DIN 27
#define SET_BUTTON_PIN 13
#define UP_BUTTON_PIN 14
#define DOWN_BUTTON_PIN 32
#define WIFI_BUTTON_PIN 33
const char *AP_NAME="KidsClock", *AP_PASSWORD="clock123";
const uint32_t WIFI_TIMEOUT=600000UL, SNOOZE_TIME=300000UL;
Adafruit_GC9A01A tft(TFT_CS,TFT_DC,TFT_MOSI,TFT_SCK,TFT_RST); RTC_DS3231 rtc; WebServer web(80); Preferences store;
enum Theme { SPACE,OCEAN,SUN }; Theme theme; uint8_t alarmHour,alarmMinute; bool alarmEnabled,wifiOn,ringing; uint32_t wifiSince,snoozeUntil,lastMinute=UINT32_MAX,lastAlarm=UINT32_MAX,lastPress[4]={0};
uint16_t bg(){return theme==SPACE?0x10A4:theme==OCEAN?0x0437:0xFD20;} void save(){store.putUChar("th",theme);store.putUChar("ah",alarmHour);store.putUChar("am",alarmMinute);store.putBool("ae",alarmEnabled);}
void paintTheme(){tft.fillScreen(bg());if(theme==SPACE){for(int i=0;i<16;i++)tft.fillCircle((i*37+9)%230+5,(i*61+7)%220+5,1,GC9A01A_WHITE);tft.fillCircle(191,43,23,GC9A01A_YELLOW);tft.fillCircle(179,174,46,GC9A01A_BLUE);tft.fillCircle(163,159,15,GC9A01A_CYAN);}else if(theme==OCEAN){tft.fillCircle(190,43,23,GC9A01A_YELLOW);tft.fillRoundRect(0,174,240,66,10,GC9A01A_BLUE);for(int x=0;x<240;x+=30)tft.drawLine(x,180,x+15,174,GC9A01A_CYAN);tft.fillTriangle(24,73,65,88,24,103,GC9A01A_YELLOW);tft.fillTriangle(24,73,24,103,7,88,GC9A01A_YELLOW);tft.fillCircle(42,88,3,GC9A01A_BLACK);}else{tft.fillCircle(190,45,28,GC9A01A_YELLOW);tft.fillRoundRect(0,175,240,65,10,0x3660);tft.fillCircle(35,181,28,0x07E0);tft.fillCircle(207,184,35,0x07E0);}}
void draw(bool force=false){DateTime n=rtc.now();uint32_t mk=n.unixtime()/60;if(!force&&mk==lastMinute)return;lastMinute=mk;paintTheme();char tm[6],al[25];snprintf(tm,sizeof(tm),"%02d:%02d",n.hour(),n.minute());snprintf(al,sizeof(al),"ALARM %s %02d:%02d",alarmEnabled?"ON":"OFF",alarmHour,alarmMinute);int16_t x,y;uint16_t w,h;tft.setTextSize(5);tft.getTextBounds(tm,0,0,&x,&y,&w,&h);tft.fillRoundRect((240-w)/2-7,88,w+14,h+14,10,GC9A01A_BLACK);tft.setTextColor(GC9A01A_WHITE);tft.setCursor((240-w)/2,95);tft.print(tm);tft.setTextSize(1);tft.setCursor(62,153);tft.print(al);tft.setCursor(48,165);tft.print(wifiOn?"Wi-Fi settings are ON":"SET: alarm  UP: +5 min  DOWN: -1 hr");}
void audioSetup(){i2s_config_t c={};c.mode=(i2s_mode_t)(I2S_MODE_MASTER|I2S_MODE_TX);c.sample_rate=16000;c.bits_per_sample=I2S_BITS_PER_SAMPLE_16BIT;c.channel_format=I2S_CHANNEL_FMT_RIGHT_LEFT;c.communication_format=I2S_COMM_FORMAT_I2S_MSB;c.dma_buf_count=8;c.dma_buf_len=128;c.tx_desc_auto_clear=true;i2s_pin_config_t p={};p.bck_io_num=I2S_BCLK;p.ws_io_num=I2S_LRC;p.data_out_num=I2S_DIN;p.data_in_num=I2S_PIN_NO_CHANGE;i2s_driver_install(I2S_NUM_0,&c,0,nullptr);i2s_set_pin(I2S_NUM_0,&p);i2s_zero_dma_buffer(I2S_NUM_0);}
void beep(){static uint16_t phase=0;int16_t out[256];for(int i=0;i<128;i++){int16_t v=(phase++%80<40)?8500:-8500;out[i*2]=v;out[i*2+1]=v;}size_t written;i2s_write(I2S_NUM_0,out,sizeof(out),&written,0);}void stopAlarm(){ringing=false;i2s_zero_dma_buffer(I2S_NUM_0);draw(true);}
String page(){String s="<!doctype html><meta name=viewport content='width=device-width,initial-scale=1'><style>body{font-family:sans-serif;max-width:430px;margin:auto;padding:20px;background:#112;color:white}input,select,button{box-sizing:border-box;width:100%;padding:10px;font-size:18px;margin:5px 0}button{background:#28a;color:white;border:0;border-radius:8px}</style><h1>Kids Clock</h1><form action=/save>Theme <select name=th><option value=0";s+=(theme==SPACE?" selected":"");s+=">Space</option><option value=1";s+=(theme==OCEAN?" selected":"");s+=">Ocean</option><option value=2";s+=(theme==SUN?" selected":"");s+=">Sunshine</option></select>Alarm hour <input name=h type=number min=0 max=23 value="+String(alarmHour)+">Alarm minute <input name=m type=number min=0 max=59 value="+String(alarmMinute)+"><label><input name=e type=checkbox ";s+=(alarmEnabled?"checked":"");s+=">Alarm enabled</label><button>Save</button></form><form action=/time><button name=epoch id=time>Set time from phone</button></form><p>Network: <b>KidsClock</b><br>Password: <b>clock123</b><br>Wi-Fi closes after 10 minutes.</p><script>document.getElementById('time').value=Math.floor(Date.now()/1000)</script>";return s;}
void wifiStart(){if(wifiOn)return;WiFi.mode(WIFI_AP);WiFi.softAP(AP_NAME,AP_PASSWORD);web.begin();wifiOn=true;wifiSince=millis();draw(true);}void wifiStop(){if(!wifiOn)return;web.stop();WiFi.softAPdisconnect(true);WiFi.mode(WIFI_OFF);wifiOn=false;draw(true);}bool pressed(uint8_t p,uint8_t i){if(digitalRead(p)!=LOW||millis()-lastPress[i]<250)return false;lastPress[i]=millis();return true;}
void buttons(){if(pressed(WIFI_BUTTON_PIN,3)){if(wifiOn)wifiStop();else wifiStart();}if(pressed(SET_BUTTON_PIN,0)){if(ringing){snoozeUntil=millis()+SNOOZE_TIME;stopAlarm();}else{alarmEnabled=!alarmEnabled;save();draw(true);}}if(!ringing&&pressed(UP_BUTTON_PIN,1)){alarmMinute=(alarmMinute+5)%60;save();draw(true);}if(!ringing&&pressed(DOWN_BUTTON_PIN,2)){alarmHour=(alarmHour+23)%24;save();draw(true);}}
void alarmCheck(){DateTime n=rtc.now();uint32_t mk=n.unixtime()/60;if(alarmEnabled&&n.hour()==alarmHour&&n.minute()==alarmMinute&&mk!=lastAlarm&&millis()>snoozeUntil){lastAlarm=mk;ringing=true;tft.fillScreen(GC9A01A_RED);tft.setTextColor(GC9A01A_WHITE);tft.setTextSize(4);tft.setCursor(33,92);tft.print("WAKE UP!");tft.setTextSize(2);tft.setCursor(38,145);tft.print("Press SET: snooze");}}
void setup(){pinMode(SET_BUTTON_PIN,INPUT_PULLUP);pinMode(UP_BUTTON_PIN,INPUT_PULLUP);pinMode(DOWN_BUTTON_PIN,INPUT_PULLUP);pinMode(WIFI_BUTTON_PIN,INPUT_PULLUP);store.begin("kidsclock",false);theme=(Theme)store.getUChar("th",SPACE);alarmHour=store.getUChar("ah",7);alarmMinute=store.getUChar("am",0);alarmEnabled=store.getBool("ae",true);Wire.begin(RTC_SDA,RTC_SCL);rtc.begin();SPI.begin(TFT_SCK,-1,TFT_MOSI,TFT_CS);tft.begin();tft.setRotation(0);audioSetup();web.on("/",[](){web.send(200,"text/html",page());});web.on("/save",[](){theme=(Theme)constrain(web.arg("th").toInt(),0,2);alarmHour=constrain(web.arg("h").toInt(),0,23);alarmMinute=constrain(web.arg("m").toInt(),0,59);alarmEnabled=web.hasArg("e");save();draw(true);web.sendHeader("Location","/");web.send(303);});web.on("/time",[](){uint32_t e=web.arg("epoch").toInt();if(e>1700000000UL)rtc.adjust(DateTime(e));draw(true);web.sendHeader("Location","/");web.send(303);});draw(true);}
void loop(){buttons();if(wifiOn){web.handleClient();if(millis()-wifiSince>WIFI_TIMEOUT)wifiStop();}alarmCheck();if(ringing){beep();delay(1);return;}draw();delay(20);}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace, diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik