7.4. Alarm Clock

Alarm clock project which enables you to set the time using 2 pushbuttons for when to trigger the alarm. The RTC (Real Time Clock) is automatically updated and synchronized using NTP (Network Time Protocol) server so the board knows when to trigger the alarm.
At the end of this example you will learn:
- How to get local time and synchronize the boards RTC
- How to display time on LCD
- How to set and trigger an alarm
Parts required:
- Soldered NULA Mini Board
- Breadboard
- 1 x Buzzer
- 16x2 LCD Display
- 2 x Pushbutton
- 1 x Qwiic cable
- Some jumper wires

Putting the components together
- Place the buzzer on the breadboard, connect the positive pin on GPIO 19, and other pin to GND.
- For each pushbutton, place it on the breadboard, connect one terminal of the button to the pin declared in code, and the other terminal to GND using jumper wires.
- Connect the LCD using Qwiic cable

Code Example
Import necessary libraries. ntptime is used to get time and update the RTC from ntp server.
from machine import Pin, PWM, I2C
from LCD import LCD_I2C
import time
import ntptime
import network
Declare I2C pins and initialize the LCD display.
i2c = I2C(0, scl=Pin(7), sda=Pin(6))
lcd = LCD_I2C(i2c)
# Initialize sensor over Qwiic
# lcd = LCD_I2C()
lcd.backlight()
lcd.begin()
Declare ssid and password for the network you want to connect to.
ssid = ""
password = ""
Variable that will keep track of last time the RTC was synchronized with NTP.
synced_at = 0
Declare pins for two pushbuttons and buzzer. We also set how long the alarm will sound without stopping it manually.
HOUR_BTN_PIN = 4
MINUTES_BTN_PIN = 5
BUZZER_PIN = 19
BUZZER_FREQ = 2000
ALARM_MAX_SEC = 60
Initialize the Pin objects for buttons.
hour_btn = Pin(HOUR_BTN_PIN, Pin.IN, Pin.PULL_UP)
minutes_btn = Pin(MINUTES_BTN_PIN, Pin.IN, Pin.PULL_UP)
Define variables to keep track of the last button states and last time they were pressed, and set DEBOUNCE_MS for button debounce time.
hour_btn_last = 1
minutes_btn_last = 1
hour_btn_last_time = 0
minutes_btn_last_time = 0
DEBOUNCE_MS = 200
Initialize the buzzer as PWM object.
buzzer = PWM(Pin(BUZZER_PIN))
buzzer.freq(BUZZER_FREQ)
buzzer.duty(0)
Function which handles WiFi connection, once connected display message on LCD display.
def connect_wifi(ssid, password):
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
lcd.setCursor(0, 0)
lcd.print("Connecting to WiFi")
wlan.connect(ssid, password)
while not wlan.isconnected():
pass
lcd.clear()
lcd.print("WiFi Connected")
Function that tries to fetch time from ntp using ntptime.settime() method. Method also at the same time updates the on board RTC.
def sync_time():
try:
ntptime.settime()
global synced_at
synced_at = time.ticks_ms()
except Exception as e:
print("Failed to synchronize time:", e)
Function to update the display, showing current time set for alarm.
def print_alarm_time(hour, minute):
lcd.clear()
lcd.setCursor(1, 0)
lcd.print("Alarm set for:")
lcd.setCursor(5, 1)
lcd.print("{:02}:{:02}".format(hour, minute))
Plays the alarm sound when triggered.
def play_alarm():
for _ in range(3):
buzzer.duty(512)
time.sleep(0.1)
buzzer.duty(0)
time.sleep(0.1)
Here we connect to WiFi and update the time from NTP server. Also, we define hour and minute variables to hold the alarm values.
connect_wifi(ssid, password)
time.sleep(2)
sync_time()
alarm_hour = 0
alarm_minute = 0
alarm_set = False
reset_alarm = False
print_alarm_time(alarm_hour, alarm_minute)
Main loop updates the time from NTP every minute. Using time.localtime(time.time() + 3600) we fetch the time from RTC with a +1 UTC offset. Buttons for hour and minutes are constantly checked to see if they are pressed, when pressed increment the value. When alarm triggers it sounds the alarm every 2 seconds until one minute has passed or user stopped the alarm by pressing on either of the buttons.
while True:
# Sync time every minute
if time.ticks_diff(time.ticks_ms(), synced_at) >= 60000:
sync_time()
if reset_alarm:
alarm_hour = 0
alarm_minute = 0
reset_alarm = False
current_time = time.localtime(time.time() + 3600)
hour = current_time[3]
minute = current_time[4]
# Check for hour button press
if hour_btn.value() == 0 and hour_btn_last == 1:
if time.ticks_diff(time.ticks_ms(), hour_btn_last_time) > DEBOUNCE_MS:
alarm_hour = (alarm_hour + 1) % 24
print_alarm_time(alarm_hour, alarm_minute)
hour_btn_last_time = time.ticks_ms()
hour_btn_last = hour_btn.value()
# Check for minutes button press
if minutes_btn.value() == 0 and minutes_btn_last == 1:
if time.ticks_diff(time.ticks_ms(), minutes_btn_last_time) > DEBOUNCE_MS:
alarm_minute = (alarm_minute + 1) % 60
print_alarm_time(alarm_hour, alarm_minute)
minutes_btn_last_time = time.ticks_ms()
minutes_btn_last = minutes_btn.value()
# Check for alarm
if hour == alarm_hour and minute == alarm_minute:
lcd.clear()
lcd.print("ALARM!")
alarm_running = True
alarm_start_time = time.ticks_ms()
while True:
# Play alarm sound every 2 seconds
if time.ticks_diff(time.ticks_ms(), alarm_start_time) % 2000 < 100:
play_alarm()
if hour_btn.value() == 0 or minutes_btn.value() == 0:
lcd.clear()
lcd.print("Alarm stopped.")
buzzer.duty(0)
break
if time.ticks_diff(time.ticks_ms(), alarm_start_time) >= ALARM_MAX_SEC * 1000:
lcd.clear()
lcd.print("Alarm expired!")
buzzer.duty(0)
break
reset_alarm = True
time.sleep(1) # Small delay after alarm
# Small delay to avoid busy loop
time.sleep(0.1)