Skip to content

Instantly share code, notes, and snippets.

@alatalo
Created August 29, 2025 07:42
Show Gist options
  • Save alatalo/cdd7e4c48bdc5a3fbb875d33d4a6d590 to your computer and use it in GitHub Desktop.
Save alatalo/cdd7e4c48bdc5a3fbb875d33d4a6d590 to your computer and use it in GitHub Desktop.
Impulse NTP master clock for ESP32 + L298N and mechanical slave clocks (alternate polarity each pulse)
// Impulse NTP master clock for ESP32 + L298N (alternate polarity each pulse)
// Original by Wawa https://forum.arduino.cc/t/running-special-clock-with-motor-controller/1159828/106
// Refactored by Ville Alatalo https://github.com/alatalo 08/2025
// + minute-impulses
// + ENA for toggling coil
// + Finnish NTP + TZ
const char* WIFI_SSID = "Internet of Shit"; // WiFi SSID
const char* WIFI_PASS = ""; // WiFi password
const char* MY_TZ = "EET-2EEST,M3.5.0/3,M10.5.0/4"; // Helsinki, Finland
const char* MY_NTP_SERVER = "fi.pool.ntp.org"; // NTP pool, Finland
#include <WiFi.h>
#include "esp_sntp.h"
time_t now;
tm tm;
const int in1 = 25; // L298N IN1
const int in2 = 26; // L298N IN2
const int ena = 27; // L298N ENA (gate on-time)
const uint16_t pulseTime = 120; // coil on time (milliseconds)
const int STEP_SEC = 60; // clock steps (seconds)
const uint32_t MIN_INTERVAL_MS = 500; // when catching up, don't go faster than ~2 Hz
const uint32_t MAX_INTERVAL_MS = 120000; // cap long waits, mainly for safety
unsigned long prevMillis = 0;
unsigned long toggleTime = 0;
unsigned long intervalMs = 60000;
time_t ticktock = 0; // scheduled epoch-second for next pulse
bool ntpFlag = false;
bool toggle = false;
bool DST = false, prevDST = false;
void timeSyncCallback(struct timeval *tv) {
Serial.println(F("Time sync"));
ntpFlag = true;
}
void setup() {
pinMode(in1, OUTPUT);
pinMode(in2, OUTPUT);
pinMode(ena, OUTPUT);
digitalWrite(in1, LOW);
digitalWrite(in2, LOW);
digitalWrite(ena, LOW);
Serial.begin(115200);
Serial.println(F("\nESP32 NTP clock"));
sntp_set_time_sync_notification_cb(timeSyncCallback);
configTime(0, 0, MY_NTP_SERVER);
setenv("TZ", MY_TZ, 1);
tzset();
WiFi.persistent(false);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(200);
Serial.println(F("Connected, waiting for time sync"));
while (!ntpFlag) yield();
time(&now);
// Align next tick to the *next* minute boundary (or half-minute if STEP_SEC=30)
ticktock = (now - (now % STEP_SEC)) + STEP_SEC;
localtime_r(&now, &tm);
DST = tm.tm_isdst;
prevDST = DST;
Serial.println(DST ? F("Summer time") : F("Winter time"));
// First wait interval until the boundary
intervalMs = (unsigned long)((ticktock - now) * 1000L);
intervalMs = constrain(intervalMs, MIN_INTERVAL_MS, MAX_INTERVAL_MS);
prevMillis = millis();
}
void loop() {
time(&now);
localtime_r(&now, &tm);
// Handle DST change: fast-forward (to summer) or slow down/pause (to winter)
DST = tm.tm_isdst;
if (DST != prevDST) {
if (DST) {
// Summer time: advance 1 hour -> send extra pulses quickly
ticktock -= 3600;
Serial.println(F("DST -> Summer: fast-forwarding"));
} else {
// Winter time: fall back 1 hour -> pause progression
ticktock += 3600;
Serial.println(F("DST -> Winter: pausing an hour"));
}
prevDST = DST;
}
// Fire a pulse when due
if (millis() - prevMillis >= intervalMs) {
prevMillis = millis();
printf("%02u:%02u:%02u\n", tm.tm_hour, tm.tm_min, tm.tm_sec);
// schedule next target at +STEP_SEC
ticktock += STEP_SEC;
// compute corrected wait to next boundary
long dSec = (long)ticktock - (long)now;
intervalMs = (unsigned long)(dSec * 1000L);
intervalMs = constrain(intervalMs, MIN_INTERVAL_MS, MAX_INTERVAL_MS);
// alternate polarity and gate coil on for pulseTime
toggle = !toggle;
digitalWrite(in1, toggle);
digitalWrite(in2, !toggle);
digitalWrite(ena, HIGH); // coil on
Serial.println(F("Coil on"));
toggleTime = millis();
}
// Turn coil off after pulseTime
if (digitalRead(ena) == HIGH && (millis() - toggleTime > pulseTime)) {
digitalWrite(ena, LOW); // coil off
Serial.println(F("Coil off"));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment