Skip to content
LSLab

Writing Simple Clock Firmware with Arduino

  • Intermediate
  • Article
  • Ages 16+
  • 14 min read
A USB-connected microcontroller board beside an LCD clock on a laboratory table.

Clock firmware can begin with the Arduino core, a display library and separate time, weather and settings components. The proposed structure schedules work around explicit states. Its goal is to make each component understandable and replaceable while keeping the clock face useful when a network operation fails or takes too long.

Contents
Begin with board and display examples that work independently.
  • Firmware board
  • Library-supported display

What you will learn

  • Divide code by responsibility instead of putting everything in rendering.
  • Describe scheduled work with bounded waits.
  • Design button menus, alarms and OTA updates with a recovery path.

Before you start

Read about time and weather and LCD configuration; understand basic functions and data structures.

Which tools provide a useful starting point?

For this prototype, choose the Arduino core for a supported board and begin with official examples. Arduino setup() runs at startup or reset. Espressif's SimpleTime example demonstrates WiFi.begin(), configTime() and getLocalTime() for networking and time.

Upload the time example to the selected board and inspect serial output, then try the display library's example independently. Integrate them after each works. Record core and library versions, board identity and display configuration so the working build can be reproduced.

Use serial configuration to check data flow during development. For daily use, buttons set alarms and lighting; selecting “Wi-Fi setup” opens a local settings page through an access point. Espressif provides station and access-point APIs; the form, location confirmation and exit from setup remain application work.

An example is not a finished product. A connection-waiting example demonstrates success, while a clock also needs cancellation, retries and user interaction during failure.

Integrate individually tested parts before exercising failure paths.
  • Display trial
  • Add network data
  • Recover settings

How should code and state be divided?

Give rendering a shared snapshot that includes the status of each source. The proposed boundaries are:

ComponentInputOutput
SettingsConfirmed choicesPlace, coordinates, zone, lighting and alarm schedule
Buttons and menuDebounced input eventsNavigation, draft edits, save or cancel
AlarmValid time and saved scheduleNext occurrence, ringing or snoozing state
OTAConfirmed update requestProgress, errors, version and boot-check state
TimeLocal clock and synchronizationTime and validity
WeatherCoordinates and selection versionValues, timestamps and status
DisplaySnapshot and screen profileClock face or setup view
PowerSignals supported by the hardwareLighting mode and activity policy

Preferences retains data across restarts and power loss and suits small settings. The proposal writes only confirmed changes. Running time and scheduling counters remain operational state; saving configuration does not replace backup timekeeping.

Separate saved settings from edits in progress. Canceling setup restores the confirmed values. Saving validates the place and coordinates before replacing active settings. This boundary lets the form be tested independently of the clock face.

Rendering reads a snapshot instead of fetching network data.
  • Time and validity
  • Weather and freshness
  • View snapshot
  • Renderer

How should the main loop operate?

Prioritize buttons and the clock face while network operations advance within deadlines. This is architectural pseudocode, not an uploadable sketch; the service functions still require implementation for the selected board and libraries.

setup:
  settings = loadConfirmedSettings()
  display.begin(screenProfile)
  buttons.begin(buttonProfile)
  drawUnavailableFace()
  alarm.restoreScheduleAndHandledOccurrence(settings)
  ota.runPendingBootSelfTest(settings, display, buttons, alarm)
  network.startIfConfigured(settings)

loop:
  events = buttons.pollDebouncedEvents()
  if alarm.isRinging():
    alarm.handleStopAndSnooze(events)
  else:
    menu.handle(events)
  network.tickUntilDeadline()
  timeService.tickIfDue()
  alarm.tick(timeService, monotonicClock, settings)
  weatherService.tickIfDue(settings.locationVersion)
  acceptOnlyValidCurrentLocationResults()
  ota.tickIfConfirmedAndAllowed(powerState, alarm, menu)
  view = makeSnapshot(timeService, weatherService, powerState, alarm, ota)
  redrawChangedRegions(view)
  lighting.applyPolicy(buttons, powerState)

A tick should perform ready work and return control. Naming a function tick does not turn a synchronous library into an asynchronous one. If HTTP blocks, give it a finite timeout and measure button response during the wait. If response is unacceptable, use a separate network task or an asynchronous interface with a clear data boundary.

HTTPS requests need server verification appropriate to the selected library. Espressif documents root certificates and certificate bundles for this purpose. Prepare valid time and certificate configuration before testing weather downloads. Keep verification failures distinct for diagnosis rather than disabling checks.

After failure, schedule a later retry and return control to the clock. After success, replace validated data and request a redraw. Weather need not be fetched for each render. Once basic failure paths are stable, add the alarm and OTA states below; any sleep policy must preserve alarm wake-up scheduling.

Network work must return control for responsive buttons.
  • Handle input
  • Advance ready work
  • Check deadlines
  • Return to loop

How can buttons handle setup and alarms?

The proposed interface has Menu, + and − buttons, with save and cancel choices shown on screen. Arduino describes switch bounce: a mechanical press can be read as several transitions. Its Debounce example uses millis() to track elapsed time; short presses and holds still require application logic.

StateMenu+−
Clock facePress to open menuTurn on lightView alarm schedule
Menu selectionPress to enter; hold to exitNext itemPrevious item
Editing a fieldPress for next field; hold to cancelIncrease valueDecrease value
Confirmation pagePress to choose Save or CancelChange selectionChange selection
RingingPress to dismiss this occurrenceSnoozeTurn on light, leave alarm ringing

The proposed menu contains Alarm, Backlight, Time and zone, Wi-Fi setup, Update and Information. Edit one field per page, keeping its name and selection visible. Once a Menu hold has canceled an edit, releasing the button must not produce a short-press action. Use elapsed-time checks for debounce and hold detection so input does not stall clock updates.

To set the alarm, open Alarm, enable it, choose the hour, minute, repeat days and snooze interval, then confirm Save. The first version has one schedule, repeating daily or on selected weekdays. Canceling or reaching the inactivity deadline discards pending edits. The face shows the saved schedule and its enabled state. Dismissal ends the current occurrence; disabling the alarm is a separate saved change.

The proposed settings record contains schedule identity, local hour, minute, repeat days, enabled state and snooze interval. Preferences stores confirmed settings. Runtime states are waiting for valid time, armed, ringing and snoozing. Without valid time, show “Set time first” and leave ringing unarmed. Buttons allow date, time and zone entry offline; weather remains unavailable.

Each occurrence has a key made from schedule identity, local date and scheduled time. Record the handled key before activating sound. This favors avoiding repeated ringing after a restart, but power loss between the steps can miss the sound; document and test that limitation.

Moving the clock backward during synchronization does not erase the key. The initial policy skips occurrences passed by a forward correction or zone change and schedules the next one. If local time repeats during a clock-rule change, the key allows only one occurrence.

Snooze uses a monotonic counter, which measures elapsed time independently of calendar corrections. In this proposal, restarting cancels an active snooze and displays a notice while retaining the original schedule. The first version defers deep sleep while snoozing. A configured ringing deadline stops sound automatically. Loss of networking does not prevent scheduling while local time is valid; complete power removal still needs a separate timekeeping solution.

Long Wi-Fi credentials and place names belong on the phone-accessible settings page. Selecting Wi-Fi setup deliberately opens that mode; the display shows how to connect and leave it. Alarm, time and light controls remain available through buttons without a phone. This is an interaction specification to implement, not a ready-made menu library.

Buttons edit a draft; confirmation replaces the alarm schedule.
  • Menu, + and − controls
  • Pending field and confirmation
  • Saved alarm schedule
  • Cancel to saved values

How can OTA deliver features and bug fixes?

An Over-the-Air update, or OTA update, replaces firmware over a network connection without a programming cable for each release. Espressif describes writing application firmware to a slot not selected for boot, verifying the image and then selecting it for the next boot. The layout needs at least two OTA application slots and an OTA data partition.

The proposal updates only the application, leaving the bootloader, partition table and settings partition unchanged. Choose a flash layout with room for both the current and next application before the initial wired upload. Record board identity, LCD profile and settings-schema version in the release record. A build for another display can be unsuitable even on the same controller family; the application must check compatibility before installation.

An illustrative update flow can use the following sequence.

  1. Open Update to see the installed version, offered version and notes describing added features or fixed bugs. Checking for a release only displays information; installation needs separate confirmation.
  2. Allow installation with stable USB power, no settings edit, no ringing or snooze, and enough time before the next alarm. Otherwise show why the update is deferred and keep the clock face available.
  3. Download from the configured release endpoint over HTTPS with server verification. Espressif also supports image-signature verification when configured. The proposed product requires images signed by a trusted release key; key handling, certificates and signature checks must be implemented before enabling this feature.
  4. Check target board, hardware profile, size and image validity before accepting the build. An incomplete download, verification failure or profile mismatch reports an error, retains the current application and offers retry rather than booting an unaccepted image.
  5. After accepting the image, recheck power and alarm conditions before restarting. On first boot, test settings reads, display rendering, button handling and alarm initialization before confirming that the new application works.

Rollback returns to a previous application if the new build fails its checks. Espressif requires CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE for this behavior; the application calls esp_ota_mark_app_valid_cancel_rollback() after successful diagnostics. OTA download support does not establish that the selected Arduino core build enables rollback. Build and test a suitable bootloader or explicitly mark automatic recovery as unsupported. Keep wired programming access for recovery when OTA is unavailable.

Boot diagnostics check local functions and must not depend on Internet access or fresh weather. For a settings-schema change, the proposal reads the old record and creates a separate migrated copy, confirming it only after checks pass. Preserve a record the previous firmware can still read after rollback. Test application recovery together with settings rather than expecting Preferences alone to handle schema changes.

Restarting for OTA interrupts operation. Document that limitation and defer updates around scheduled alarms using a configured exclusion window. A requirement to ring through every power failure or update needs an independent alarm mechanism; this design does not establish that guarantee.

Retain the previous build during download; confirm the new build after boot checks.
  • Verified release download
  • New application destination
  • Previous application for recovery
  • Check before acceptance

Real-world example

Start with a face using fictional data. Add network time, disconnect Wi-Fi and check continued time display. Add a saved weather response to test missing fields before using live HTTP. Finally, save a location and restart to check recovery.

If the light button responds only after an HTTP timeout, the blocking path has been identified. Reduce the bounded wait or move networking away from button handling, then repeat the same scenario. This is a proposed development sequence rather than firmware already tested on hardware.

Continue with a saved alarm. Disable Wi-Fi before it is due, press + to snooze and Menu to dismiss the next ring. Then test an OTA build with a deliberate settings-read failure in a test environment: the new build must remain unconfirmed, and the previous build and schedule must be checked after recovery. These are proposed tests, not hardware results.

Common misunderstandings

A single file is always easiest to develop. Dividing code by responsibility helps isolate changes to a display or API, as in the example above.

Saving every value restores running time. Persistent settings and continuous timekeeping are different mechanisms.

Frequently asked questions

Must the display driver be written from scratch?

Prefer a module with library support for its actual configuration. Write a driver only when the hardware requirement makes that necessary.

Does every new feature need a programming cable?

After an initial wired build with OTA support, the user can confirm updates through the menu. A cable remains necessary for recovery beyond OTA or changes to the flash layout.

Can this pseudocode be uploaded directly?

No. It defines the work structure; a real sketch still needs board configuration, drivers, connections and certificates.

Terms in this article

English and Vietnamese terms, with the meanings used here. Hover, tap or Tab to a term with a dotted underline for an explanation as you read.

Preferences
A library retaining small settings across restarts.
setup()
The initialization function run once when a sketch starts.
HTTPS
HTTP over TLS; server verification requires appropriate certificate configuration.
Over-the-Air updateOTA
A firmware update delivered over a network rather than a programming cable for each release.
Application rollbackRollback
A mechanism returning to a previous application when a new build is not confirmed, if enabled and a valid previous build exists.
DebounceChống dội nút
Filtering mechanical switch transitions to recognize a stable button action.

This article is not marked as viewed

Spotted something wrong or unsafe?

Tell the administrator by email. The message already has the article title and address.

Report this article
See all articles of the topic

Sources

These sources are written for older readers. If they feel hard, read them with an adult.