Built-in project · ESP32 DevKit V1

ESP32 NTP clock on a TM1637 display

An ESP32 gets the time from an NTP server with NTPClient and shows hours and minutes on a TM1637 four-digit display, the colon blinking every second. The whole build, an ESP32 DevKit and 1 more part, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Beginner About 20 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $10.50. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • Getting the time over WiFi with NTP
  • UTC and time zone offsets in seconds
  • Driving a TM1637 four-digit display
  • Blinking a colon without delay
  • Waiting for a connection in setup
  • Difficulty: Beginner
  • Time: About 20 minutes
  • Board: ESP32 DevKit V1
  • Parts: 3

How it works

The ESP32 joins WiFi, then asks an NTP server for the time. NTP answers in UTC, so the sketch adds UTC_OFFSET, set to minus 5 hours, to get New York winter time.

The TM1637 display shows hours and minutes with a colon that blinks every half second. Between answers the clock counts on from millis, and it asks the server again every 10 minutes. In Mokxi the network is simulated, so it starts at 19:00.

Parts list

3 parts, plus the jumper wires. They retail for about $10.50; on Mokxi they're free.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Four-digit display, TM1637 pin DIOESP32 DevKit V1 pin D19
Four-digit display, TM1637 pin CLKESP32 DevKit V1 pin D18
Four-digit display, TM1637 pin VCC3.3 V
Four-digit display, TM1637 pin GNDGround

The note on the canvas

Word for word, as it sits beside the circuit in the editor.

Try it
1. Press Run. The display shows ---- while the ESP32 joins WiFi.
2. NTPClient gets the time and the display shows 19:00, the colon blinking: New Year's Eve in New York (UTC minus 5 hours).
3. Change UTC_OFFSET to your own time zone in seconds and Run again. On a real ESP32 it shows the real time; Mokxi's simulated network answers with its own clock, which starts at 2026-01-01 00:00 UTC.

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · ESP32 DevKit V1
/*
 * ESP32 NTP clock: the time of day from the internet on a TM1637 four-digit
 * display, with the colon blinking once a second.
 *
 *   TM1637   CLK -> GPIO 18 (D18), DIO -> GPIO 19 (D19)
 *            VCC -> 3V3, GND -> GND
 *
 * Libraries: NTPClient (arduino-libraries) and TM1637Display (avishorp),
 * both in the Arduino Library Manager.
 *
 * The board joins WiFi, then NTPClient asks an NTP server for the time over
 * UDP and asks again every 10 minutes. Between answers it counts on from
 * millis(), so the display changes every minute without a request.
 *
 * Set UTC_OFFSET to your time zone in seconds: -5 hours is New York in
 * winter (EST), 3600 is Central Europe in winter, 19800 is India. NTP itself
 * knows nothing of time zones or daylight saving: it answers UTC.
 *
 * In Mokxi the network is SIMULATED and reaches no NTP server, so the answer
 * is the simulation's own clock: 2026-01-01 00:00:00 UTC when you press Run,
 * plus the seconds since. With the offset below the display starts at 19:00,
 * New Year's Eve in New York. On a real ESP32 it shows the real time.
 */
#include <WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <TM1637Display.h>

const char *SSID = "Mokxi-Lab";
const char *PASSWORD = "breadboard";

const long UTC_OFFSET = -5L * 3600;           // seconds; -5 h is EST
const unsigned long RESYNC_MS = 10UL * 60000; // ask the server every 10 minutes

const int CLK_PIN = 18;
const int DIO_PIN = 19;

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", UTC_OFFSET, RESYNC_MS);
TM1637Display display(CLK_PIN, DIO_PIN);

// "----" while there is no time yet.
const uint8_t DASHES[] = {SEG_G, SEG_G, SEG_G, SEG_G};

int shownMinute = -1;
bool colon = false;
unsigned long ticked = 0;

void setup()
{
    Serial.begin(115200);
    display.setBrightness(5);
    display.setSegments(DASHES);

    Serial.print("Connecting to ");
    Serial.println(SSID);
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println();
    Serial.print("WiFi connected, IP ");
    Serial.println(WiFi.localIP());

    timeClient.begin();
    while (!timeClient.update()) {
        Serial.println("No answer from the NTP server, trying again");
        delay(1000);
    }
    Serial.print("Time set: ");
    Serial.println(timeClient.getFormattedTime());
}

void loop()
{
    timeClient.update(); // asks the server only when RESYNC_MS has passed

    // Blink the colon: on for half a second, off for half a second.
    unsigned long now = millis();
    if (now - ticked >= 500) {
        ticked = now;
        colon = !colon;
        int hhmm = timeClient.getHours() * 100 + timeClient.getMinutes();
        display.showNumberDecEx(hhmm, colon ? 0b01000000 : 0, true);

        if (timeClient.getMinutes() != shownMinute) {
            shownMinute = timeClient.getMinutes();
            Serial.print("The time is ");
            Serial.println(timeClient.getFormattedTime());
        }
    }
    delay(10);
}

Code walkthrough

The key functions of the sketch, and what each one does.

setup sketch.ino

Shows four dashes on the display, joins WiFi and waits until it is connected. Then it starts the NTP client and keeps asking until a time arrives. That is why the dashes show until the time is known.

void setup()
{
    Serial.begin(115200);
    display.setBrightness(5);
    display.setSegments(DASHES);

    Serial.print("Connecting to ");
    Serial.println(SSID);
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println();
    Serial.print("WiFi connected, IP ");
    Serial.println(WiFi.localIP());

    timeClient.begin();
    while (!timeClient.update()) {
        Serial.println("No answer from the NTP server, trying again");
        delay(1000);
    }
    Serial.print("Time set: ");
    Serial.println(timeClient.getFormattedTime());
}

loop sketch.ino

Calls update, which only contacts the server when the resync time has passed. Every 500 ms it flips the colon and shows hours times 100 plus minutes on the display. It prints the time once each time the minute changes.

void loop()
{
    timeClient.update(); // asks the server only when RESYNC_MS has passed

    // Blink the colon: on for half a second, off for half a second.
    unsigned long now = millis();
    if (now - ticked >= 500) {
        ticked = now;
        colon = !colon;
        int hhmm = timeClient.getHours() * 100 + timeClient.getMinutes();
        display.showNumberDecEx(hhmm, colon ? 0b01000000 : 0, true);

        if (timeClient.getMinutes() != shownMinute) {
            shownMinute = timeClient.getMinutes();
            Serial.print("The time is ");
            Serial.println(timeClient.getFormattedTime());
        }
    }
    delay(10);
}

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The time is wrong This project
    Set UTC_OFFSET to your time zone in seconds. NTP knows nothing about time zones or daylight saving.
  • The display shows nothing sensible This project
    Check that CLK is on D18 and DIO on D19, and that VCC is on 3.3 V.
  • It shows dashes forever This project
    The WiFi or the NTP server is not answering. Watch the Serial Monitor for the retry messages.
  • The display does nothing. Four-digit display
    Connect CLK and DIO to two digital pins, plus VCC and GND. It is not I2C, so no address and no Wire library. Use a TM1637 library and give it the same two pins.
  • Digits look wrong or in a strange order. Four-digit display
    Check the library's segment mapping and digit order. The colon is bit 7 of the second digit. Brightness is set separately and can be 0 to 7.
  • A pin does not work as an input or output, or the board will not boot or upload. ESP32 DevKit V1
    GPIO 34, 35, 36 and 39 are input only with no pull-ups. GPIO 6 to 11 are used by flash, so avoid them. GPIO 0, 2 and 12 are strapping pins and can block boot if held wrong.
  • A 5 V sensor or signal damages the board or reads wrong. ESP32 DevKit V1
    ESP32 pins are 3.3 V and not 5 V tolerant. Use a level shifter or a divider for 5 V outputs. Power 3.3 V sensors from the 3V3 pin.

Questions and answers

Why does it show 19:00 and not my time?

Mokxi's simulated network reaches no NTP server, so NTPClient gets the simulation's own clock: 2026-01-01 00:00:00 UTC at Run, plus the seconds since. The sketch's -5 hour offset makes that 19:00. A real ESP32 shows the real time.

How do I set my time zone?

Change UTC_OFFSET to your offset from UTC in seconds: 3600 for Central Europe in winter, 19800 for India, -28800 for US Pacific in winter. NTP itself always answers in UTC.

Does it handle daylight saving time?

No. NTPClient adds a fixed offset, so change UTC_OFFSET when the clocks change, or use the ESP32's configTzTime() with a POSIX TZ string.

How often does it ask the server?

Every 10 minutes (RESYNC_MS). In between, update() counts on from millis() and sends nothing.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.