First, I’m a 74 year old senior doing my first Ardruino project. I did the hardware for my wife’s clock, but not the code. A college student offered to help me. In exchange I’m helping him build a Citation II pcb upgrade I have designed and built. So, the clock design is running with the code he provided. The clock is a normal LED 7 segment clock , with Westminster chime running off an mp3 player.
The only problem is the hour set switch doesn’t function. All other function work fine. I would also like to add if possible an TSOP 1838 IR sensor remote to set the clock beside switches, beside the sitwches.
if anyone can help me I would be greatly appreciated. Oh, James is back at college, junior year and can’t help me.
Here is my code for reference
#include <I2C_RTC.h>
/*
Lee's Clock: Hopefully the final revision.
In order to use the buttons:
Hold 'set' to adjust the 'minutes' and 'brightness'.
Press 'set' to change between twelve and twenty four hour time.
As of right now the 'hours' button doesn't work so i forced the minutes to cycle to the next hour.
If you hold any of the first three buttons; it will begin to add faster.
Set the brightness first and then minutes because holding 'set' pauses the clock.
Make the minutes one higher than the current time and let go of the 'set' button when the time on the standard you are using hits zero seconds.
I advise against putting any battery on the RTC module as it will consistently overcharge
*/
#include <Wire.h>
#include <RTClib.h>
DS3231 rtc;
// Shift Register Hardware Pin Definitions
const int reg_clock = 13; // SRCLK
const int reg_latch = 10; // RCLK
const int reg_data = 11; // SER (Data In)
const int reg_OE = 9; // OE (Output Enable - Hardware PWM Control)
// Exact physical pinout matching your custom PCB panel traces
const int btn_bright = 2; // J6 Pin 5 -> Arduino Pin 2
const int btn_min = 3; // J6 Pin 4 -> Arduino Pin 3 (Minutes Button with Hour Rollover)
const int btn_set = 5; // J6 Pin 2 -> Arduino Pin 5 (Set Button)
// Common Anode segment patterns (1 = Segment ON / Sink to GND)
const byte digitPatterns[] = {
0b01111110, // 0
0b00110000, // 1
0b01101101, // 2
0b01111001, // 3
0b00110011, // 4
0b01011011, // 5
0b01011111, // 6
0b01110000, // 7
0b01111111, // 8
0b01111011 // 9
};
// Global Time variables
int hours = 12, minutes = 14, seconds = 0; // Pre-set to 12:14:00 for easy testing
bool is24Hour = false;
// Chime Flags
bool chimeDoneThisMinute = false;
int lastCheckedMinute = -1;
// Asynchronous Chime Engine State Variables (Zero Delays)
bool introMelodyActive = false;
unsigned long chimeStateTimer = 0;
int strikeCount = 0;
int totalStrikesNeeded = 0;
// Your exact custom active-low brightness PWM levels
const int brightnessPWM[] = {255, 192, 128, 64, 0};
int currentBrightStage = 2;
// Non-blocking button and chime tracking state counters
int setHoldCounter = 0;
int minHoldCounter = 0;
int rtcRefreshTimer = 0;
bool setModeActive = false;
// Tracking variables to prevent display lag and electrical noise
int lastDispSec = -1, lastDispMin = -1, lastDispHr = -1;
void updateDisplayRouting() {
int displayHours = hours;
bool isPM = (displayHours >= 12);
if (!is24Hour) {
displayHours = displayHours % 12;
if (displayHours == 0) displayHours = 12;
}
int onesSec = seconds % 10;
int tensSec = seconds / 10;
int onesMin = minutes % 10;
int tensMin = minutes / 10;
int onesHr = displayHours % 10;
int tensHr = displayHours / 10;
byte seg0 = digitPatterns[onesSec];
byte seg1 = digitPatterns[tensSec];
byte seg2 = (tensHr == 0) ? 0b00000000 : digitPatterns[tensHr];
byte seg3 = digitPatterns[onesHr];
byte seg4 = digitPatterns[tensMin];
byte seg5 = digitPatterns[onesMin];
if (setModeActive) {
seg2 |= 0b10000000;
seg3 |= 0b10000000;
}
if (!is24Hour && isPM) {
seg5 |= 0b10000000;
}
digitalWrite(reg_latch, LOW);
shiftOut(reg_data, reg_clock, MSBFIRST, seg5);
shiftOut(reg_data, reg_clock, MSBFIRST, seg4);
shiftOut(reg_data, reg_clock, MSBFIRST, seg3);
shiftOut(reg_data, reg_clock, MSBFIRST, seg2);
shiftOut(reg_data, reg_clock, MSBFIRST, seg1);
shiftOut(reg_data, reg_clock, MSBFIRST, seg0);
digitalWrite(reg_latch, HIGH);
}
void readRTC() {
hours = now.hour();
minutes = now.minute();
seconds = now.second();
}
// RESTORED: Uses Native Hardware Serial for your pins 0 and 1 connection
void triggerTrack(byte trackNumber) {
Serial.write((uint8_t)'t');
delayMicroseconds(50);
Serial.write((uint8_t)trackNumber);
}
void checkChimesAsynchronous() {
if (minutes != lastCheckedMinute) {
chimeDoneThisMinute = false;
lastCheckedMinute = minutes;
}
if (!chimeDoneThisMinute) {
if (minutes == 15) {
triggerTrack(1);
chimeDoneThisMinute = true;
}
else if (minutes == 30) {
triggerTrack(2);
chimeDoneThisMinute = true;
}
else if (minutes == 45) {
triggerTrack(3);
chimeDoneThisMinute = true;
}
else if (minutes == 0) {
triggerTrack(4); // Start hourly intro melody immediately
introMelodyActive = true;
chimeStateTimer = millis();
totalStrikesNeeded = hours % 12;
if (totalStrikesNeeded == 0) totalStrikesNeeded = 12;
strikeCount = 0;
chimeDoneThisMinute = true;
}
}
if (introMelodyActive) {
unsigned long elapsed = millis() - chimeStateTimer;
// Step 1: Wait out the 26-second intro song
if (strikeCount == 0 && elapsed >= 26000) {
triggerTrack(5); // First hour strike
strikeCount = 1;
chimeStateTimer = millis();
}
// Step 2: Cycle through individual hourly strikes every 3 seconds
else if (strikeCount > 0 && strikeCount < totalStrikesNeeded && elapsed >= 3000) {
triggerTrack(5); // Consecutive hour strike
strikeCount++;
chimeStateTimer = millis();
}
// Step 3: Turn off tracking engine when total count completes
else if (strikeCount >= totalStrikesNeeded) {
introMelodyActive = false;
}
}
}
void handleInputsNoMillis() {
bool setPressed = (digitalRead(btn_set) == LOW);
bool minPressed = (digitalRead(btn_min) == LOW);
bool brightPressed = (digitalRead(btn_bright) == LOW);
// --- SET BUTTON INITIAL PRESS AND MODE TRANSITION ---
if (setPressed) {
if (!setModeActive) {
setHoldCounter++;
if (setHoldCounter > 30) { // ~600ms hold requirement reached
setModeActive = true;
}
}
if (setModeActive) {
// 1. Brightness Toggle
if (brightPressed) {
currentBrightStage = (currentBrightStage + 1) % 5;
analogWrite(reg_OE, brightnessPWM[currentBrightStage]);
delay(250); // Small mechanical debounce
}
// 2. Minutes Step with Automatic Hour Rollover
if (minPressed) {
minHoldCounter++;
// Advance instantly on first press. If held past 25 frames (~500ms), scroll every 6 frames (~120ms)
if (minHoldCounter == 1 || (minHoldCounter > 25 && minHoldCounter % 6 == 0)) {
seconds = 0;
minutes++;
// Rollover minutes into hours smoothly
if (minutes >= 60) {
minutes = 0;
hours++;
if (hours >= 24) hours = 0;
}
updateDisplayRouting();
}
} else {
minHoldCounter = 0; // Reset scroll acceleration instantly on button release
}
}
}
else {
// --- SET BUTTON RELEASE DETECTED ---
if (setHoldCounter > 0) {
if (setModeActive) {
setModeActive = false;
rtc.adjust(DateTime(2026, 8, 13, hours, minutes, 0)); // Save strictly on release
}
else if (setHoldCounter > 1 && setHoldCounter <= 30) {
is24Hour = !is24Hour; // Short tap changes 12/24 mode formatting
}
setHoldCounter = 0;
minHoldCounter = 0;
updateDisplayRouting();
}
}
}
void setup() {
pinMode(reg_clock, OUTPUT); pinMode(reg_latch, OUTPUT); pinMode(reg_data, OUTPUT); pinMode(reg_OE, OUTPUT);
analogWrite(reg_OE, brightnessPWM[currentBrightStage]);
pinMode(btn_bright, INPUT_PULLUP);
pinMode(btn_min, INPUT_PULLUP);
pinMode(btn_set, INPUT_PULLUP);
Wire.begin();
Serial.begin(38400); // Start native Hardware Serial on pins 0/1 at 38400 baud
if (!rtc.begin()) {
while (1);
}
// TESTING FEATURE: Forces the RTC memory directly to 12:14:00 on every boot/reset
// so you can instantly verify the 12:15:00 chime sound!
rtc.adjust(DateTime(2026, 8, 13, 12, 14, 0));
readRTC();
updateDisplayRouting();
}
void loop() {
handleInputsNoMillis();
if (!setModeActive) {
checkChimesAsynchronous();
// Query the RTC hardware via I2C once every 10 loops (~200ms)
rtcRefreshTimer++;
if (rtcRefreshTimer >= 10) {
readRTC();
rtcRefreshTimer = 0;
}
}
// Conditional refresh gate blocks high-frequency trace noise from locking up buttons
if (seconds != lastDispSec || minutes != lastDispMin || hours != lastDispHr || setModeActive) {
updateDisplayRouting();
lastDispSec = seconds;
lastDispMin = minutes;
lastDispHr = hours;
}
delay(20);
}#include <I2C_RTC.h>
/*
Lee's Clock: Hopefully the final revision.
In order to use the buttons:
Hold 'set' to adjust the 'minutes' and 'brightness'.
Press 'set' to change between twelve and twenty four hour time.
As of right now the 'hours' button doesn't work so i forced the minutes to cycle to the next hour.
If you hold any of the first three buttons; it will begin to add faster.
Set the brightness first and then minutes because holding 'set' pauses the clock.
Make the minutes one higher than the current time and let go of the 'set' button when the time on the standard you are using hits zero seconds.
I advise against putting any battery on the RTC module as it will consistently overcharge
*/
#include <Wire.h>
#include <RTClib.h>
DS3231 rtc;
// Shift Register Hardware Pin Definitions
const int reg_clock = 13; // SRCLK
const int reg_latch = 10; // RCLK
const int reg_data = 11; // SER (Data In)
const int reg_OE = 9; // OE (Output Enable - Hardware PWM Control)
// Exact physical pinout matching your custom PCB panel traces
const int btn_bright = 2; // J6 Pin 5 -> Arduino Pin 2
const int btn_min = 3; // J6 Pin 4 -> Arduino Pin 3 (Minutes Button with Hour Rollover)
const int btn_set = 5; // J6 Pin 2 -> Arduino Pin 5 (Set Button)
// Common Anode segment patterns (1 = Segment ON / Sink to GND)
const byte digitPatterns[] = {
0b01111110, // 0
0b00110000, // 1
0b01101101, // 2
0b01111001, // 3
0b00110011, // 4
0b01011011, // 5
0b01011111, // 6
0b01110000, // 7
0b01111111, // 8
0b01111011 // 9
};
// Global Time variables
int hours = 12, minutes = 14, seconds = 0; // Pre-set to 12:14:00 for easy testing
bool is24Hour = false;
// Chime Flags
bool chimeDoneThisMinute = false;
int lastCheckedMinute = -1;
// Asynchronous Chime Engine State Variables (Zero Delays)
bool introMelodyActive = false;
unsigned long chimeStateTimer = 0;
int strikeCount = 0;
int totalStrikesNeeded = 0;
// Your exact custom active-low brightness PWM levels
const int brightnessPWM[] = {255, 192, 128, 64, 0};
int currentBrightStage = 2;
// Non-blocking button and chime tracking state counters
int setHoldCounter = 0;
int minHoldCounter = 0;
int rtcRefreshTimer = 0;
bool setModeActive = false;
// Tracking variables to prevent display lag and electrical noise
int lastDispSec = -1, lastDispMin = -1, lastDispHr = -1;
void updateDisplayRouting() {
int displayHours = hours;
bool isPM = (displayHours >= 12);
if (!is24Hour) {
displayHours = displayHours % 12;
if (displayHours == 0) displayHours = 12;
}
int onesSec = seconds % 10;
int tensSec = seconds / 10;
int onesMin = minutes % 10;
int tensMin = minutes / 10;
int onesHr = displayHours % 10;
int tensHr = displayHours / 10;
byte seg0 = digitPatterns[onesSec];
byte seg1 = digitPatterns[tensSec];
byte seg2 = (tensHr == 0) ? 0b00000000 : digitPatterns[tensHr];
byte seg3 = digitPatterns[onesHr];
byte seg4 = digitPatterns[tensMin];
byte seg5 = digitPatterns[onesMin];
if (setModeActive) {
seg2 |= 0b10000000;
seg3 |= 0b10000000;
}
if (!is24Hour && isPM) {
seg5 |= 0b10000000;
}
digitalWrite(reg_latch, LOW);
shiftOut(reg_data, reg_clock, MSBFIRST, seg5);
shiftOut(reg_data, reg_clock, MSBFIRST, seg4);
shiftOut(reg_data, reg_clock, MSBFIRST, seg3);
shiftOut(reg_data, reg_clock, MSBFIRST, seg2);
shiftOut(reg_data, reg_clock, MSBFIRST, seg1);
shiftOut(reg_data, reg_clock, MSBFIRST, seg0);
digitalWrite(reg_latch, HIGH);
}
void readRTC() {
hours = now.hour();
minutes = now.minute();
seconds = now.second();
}
// RESTORED: Uses Native Hardware Serial for your pins 0 and 1 connection
void triggerTrack(byte trackNumber) {
Serial.write((uint8_t)'t');
delayMicroseconds(50);
Serial.write((uint8_t)trackNumber);
}
void checkChimesAsynchronous() {
if (minutes != lastCheckedMinute) {
chimeDoneThisMinute = false;
lastCheckedMinute = minutes;
}
if (!chimeDoneThisMinute) {
if (minutes == 15) {
triggerTrack(1);
chimeDoneThisMinute = true;
}
else if (minutes == 30) {
triggerTrack(2);
chimeDoneThisMinute = true;
}
else if (minutes == 45) {
triggerTrack(3);
chimeDoneThisMinute = true;
}
else if (minutes == 0) {
triggerTrack(4); // Start hourly intro melody immediately
introMelodyActive = true;
chimeStateTimer = millis();
totalStrikesNeeded = hours % 12;
if (totalStrikesNeeded == 0) totalStrikesNeeded = 12;
strikeCount = 0;
chimeDoneThisMinute = true;
}
}
if (introMelodyActive) {
unsigned long elapsed = millis() - chimeStateTimer;
// Step 1: Wait out the 26-second intro song
if (strikeCount == 0 && elapsed >= 26000) {
triggerTrack(5); // First hour strike
strikeCount = 1;
chimeStateTimer = millis();
}
// Step 2: Cycle through individual hourly strikes every 3 seconds
else if (strikeCount > 0 && strikeCount < totalStrikesNeeded && elapsed >= 3000) {
triggerTrack(5); // Consecutive hour strike
strikeCount++;
chimeStateTimer = millis();
}
// Step 3: Turn off tracking engine when total count completes
else if (strikeCount >= totalStrikesNeeded) {
introMelodyActive = false;
}
}
}
void handleInputsNoMillis() {
bool setPressed = (digitalRead(btn_set) == LOW);
bool minPressed = (digitalRead(btn_min) == LOW);
bool brightPressed = (digitalRead(btn_bright) == LOW);
// --- SET BUTTON INITIAL PRESS AND MODE TRANSITION ---
if (setPressed) {
if (!setModeActive) {
setHoldCounter++;
if (setHoldCounter > 30) { // ~600ms hold requirement reached
setModeActive = true;
}
}
if (setModeActive) {
// 1. Brightness Toggle
if (brightPressed) {
currentBrightStage = (currentBrightStage + 1) % 5;
analogWrite(reg_OE, brightnessPWM[currentBrightStage]);
delay(250); // Small mechanical debounce
}
// 2. Minutes Step with Automatic Hour Rollover
if (minPressed) {
minHoldCounter++;
// Advance instantly on first press. If held past 25 frames (~500ms), scroll every 6 frames (~120ms)
if (minHoldCounter == 1 || (minHoldCounter > 25 && minHoldCounter % 6 == 0)) {
seconds = 0;
minutes++;
// Rollover minutes into hours smoothly
if (minutes >= 60) {
minutes = 0;
hours++;
if (hours >= 24) hours = 0;
}
updateDisplayRouting();
}
} else {
minHoldCounter = 0; // Reset scroll acceleration instantly on button release
}
}
}
else {
// --- SET BUTTON RELEASE DETECTED ---
if (setHoldCounter > 0) {
if (setModeActive) {
setModeActive = false;
rtc.adjust(DateTime(2026, 8, 13, hours, minutes, 0)); // Save strictly on release
}
else if (setHoldCounter > 1 && setHoldCounter <= 30) {
is24Hour = !is24Hour; // Short tap changes 12/24 mode formatting
}
setHoldCounter = 0;
minHoldCounter = 0;
updateDisplayRouting();
}
}
}
void setup() {
pinMode(reg_clock, OUTPUT); pinMode(reg_latch, OUTPUT); pinMode(reg_data, OUTPUT); pinMode(reg_OE, OUTPUT);
analogWrite(reg_OE, brightnessPWM[currentBrightStage]);
pinMode(btn_bright, INPUT_PULLUP);
pinMode(btn_min, INPUT_PULLUP);
pinMode(btn_set, INPUT_PULLUP);
Wire.begin();
Serial.begin(38400); // Start native Hardware Serial on pins 0/1 at 38400 baud
if (!rtc.begin()) {
while (1);
}
// TESTING FEATURE: Forces the RTC memory directly to 12:14:00 on every boot/reset
// so you can instantly verify the 12:15:00 chime sound!
rtc.adjust(DateTime(2026, 8, 13, 12, 14, 0));
readRTC();
updateDisplayRouting();
}
void loop() {
handleInputsNoMillis();
if (!setModeActive) {
checkChimesAsynchronous();
// Query the RTC hardware via I2C once every 10 loops (~200ms)
rtcRefreshTimer++;
if (rtcRefreshTimer >= 10) {
readRTC();
rtcRefreshTimer = 0;
}
}
// Conditional refresh gate blocks high-frequency trace noise from locking up buttons
if (seconds != lastDispSec || minutes != lastDispMin || hours != lastDispHr || setModeActive) {
updateDisplayRouting();
lastDispSec = seconds;
lastDispMin = minutes;
lastDispHr = hours;
}
delay(20);
}
Thanks for looking at my code and helping me.
lee Mitchell
You have to use the <> tool above to copy/paste code in order to maintain indentation.
Out of curiosity I asked chatGPT if it could rewrite the code with indentation which it did.
It also complained the code was written twice and even commented on it offering to fix it all !!
#include <I2C_RTC.h>
/*
Lee's Clock: Hopefully the final revision.
In order to use the buttons:
Hold 'set' to adjust the 'minutes' and 'brightness'.
Press 'set' to change between twelve and twenty four hour time.
As of right now the 'hours' button doesn't work so i forced the
minutes to cycle to the next hour.
If you hold any of the first three buttons; it will begin to add faster.
Set the brightness first and then minutes because holding 'set' pauses the clock.
Make the minutes one higher than the current time and let go of the 'set'
button when the time on the standard you are using hits zero seconds.
I advise against putting any battery on the RTC module as it will consistently overcharge
*/
#include <Wire.h>
#include <RTClib.h>
DS3231 rtc;
// ============================================================================
// Shift Register Hardware Pin Definitions
// ============================================================================
const int reg_clock = 13; // SRCLK
const int reg_latch = 10; // RCLK
const int reg_data = 11; // SER (Data In)
const int reg_OE = 9; // OE (Output Enable - Hardware PWM Control)
// ============================================================================
// Button Pin Definitions
// ============================================================================
const int btn_bright = 2; // J6 Pin 5 -> Arduino Pin 2
const int btn_min = 3; // J6 Pin 4 -> Arduino Pin 3
const int btn_set = 5; // J6 Pin 2 -> Arduino Pin 5
// ============================================================================
// Common Anode Segment Patterns
// 1 = Segment ON / Sink to GND
// ============================================================================
const byte digitPatterns[] = {
0b01111110, // 0
0b00110000, // 1
0b01101101, // 2
0b01111001, // 3
0b00110011, // 4
0b01011011, // 5
0b01011111, // 6
0b01110000, // 7
0b01111111, // 8
0b01111011 // 9
};
// ============================================================================
// Global Time Variables
// ============================================================================
int hours = 12;
int minutes = 14;
int seconds = 0; // Pre-set to 12:14:00 for easy testing
bool is24Hour = false;
// ============================================================================
// Chime Flags
// ============================================================================
bool chimeDoneThisMinute = false;
int lastCheckedMinute = -1;
// ============================================================================
// Asynchronous Chime Engine State Variables
// ============================================================================
bool introMelodyActive = false;
unsigned long chimeStateTimer = 0;
int strikeCount = 0;
int totalStrikesNeeded = 0;
// ============================================================================
// Brightness PWM Levels
// ============================================================================
const int brightnessPWM[] = {
255,
192,
128,
64,
0
};
int currentBrightStage = 2;
// ============================================================================
// Button and RTC Tracking Variables
// ============================================================================
int setHoldCounter = 0;
int minHoldCounter = 0;
int rtcRefreshTimer = 0;
bool setModeActive = false;
// ============================================================================
// Display Tracking Variables
// ============================================================================
int lastDispSec = -1;
int lastDispMin = -1;
int lastDispHr = -1;
// ============================================================================
// Update Display
// ============================================================================
void updateDisplayRouting() {
int displayHours = hours;
bool isPM = (displayHours >= 12);
// Convert to 12-hour format if required
if (!is24Hour) {
displayHours = displayHours % 12;
if (displayHours == 0) {
displayHours = 12;
}
}
int onesSec = seconds % 10;
int tensSec = seconds / 10;
int onesMin = minutes % 10;
int tensMin = minutes / 10;
int onesHr = displayHours % 10;
int tensHr = displayHours / 10;
byte seg0 = digitPatterns[onesSec];
byte seg1 = digitPatterns[tensSec];
byte seg2 = (tensHr == 0)
? 0b00000000
: digitPatterns[tensHr];
byte seg3 = digitPatterns[onesHr];
byte seg4 = digitPatterns[tensMin];
byte seg5 = digitPatterns[onesMin];
// Indicate set mode
if (setModeActive) {
seg2 |= 0b10000000;
seg3 |= 0b10000000;
}
// Indicate PM in 12-hour mode
if (!is24Hour && isPM) {
seg5 |= 0b10000000;
}
digitalWrite(reg_latch, LOW);
shiftOut(reg_data, reg_clock, MSBFIRST, seg5);
shiftOut(reg_data, reg_clock, MSBFIRST, seg4);
shiftOut(reg_data, reg_clock, MSBFIRST, seg3);
shiftOut(reg_data, reg_clock, MSBFIRST, seg2);
shiftOut(reg_data, reg_clock, MSBFIRST, seg1);
shiftOut(reg_data, reg_clock, MSBFIRST, seg0);
digitalWrite(reg_latch, HIGH);
}
// ============================================================================
// Read Time From RTC
// ============================================================================
void readRTC() {
hours = now.hour();
minutes = now.minute();
seconds = now.second();
}
// ============================================================================
// Trigger Audio Track
// Uses Native Hardware Serial on pins 0 and 1
// ============================================================================
void triggerTrack(byte trackNumber) {
Serial.write((uint8_t)'t');
delayMicroseconds(50);
Serial.write((uint8_t)trackNumber);
}
// ============================================================================
// Asynchronous Chime Engine
// ============================================================================
void checkChimesAsynchronous() {
// Detect a new minute
if (minutes != lastCheckedMinute) {
chimeDoneThisMinute = false;
lastCheckedMinute = minutes;
}
// Trigger quarter-hour and hourly chimes
if (!chimeDoneThisMinute) {
if (minutes == 15) {
triggerTrack(1);
chimeDoneThisMinute = true;
}
else if (minutes == 30) {
triggerTrack(2);
chimeDoneThisMinute = true;
}
else if (minutes == 45) {
triggerTrack(3);
chimeDoneThisMinute = true;
}
else if (minutes == 0) {
// Start hourly intro melody immediately
triggerTrack(4);
introMelodyActive = true;
chimeStateTimer = millis();
totalStrikesNeeded = hours % 12;
if (totalStrikesNeeded == 0) {
totalStrikesNeeded = 12;
}
strikeCount = 0;
chimeDoneThisMinute = true;
}
}
// ==========================================================================
// Hourly Strike Sequence
// ==========================================================================
if (introMelodyActive) {
unsigned long elapsed = millis() - chimeStateTimer;
// Step 1:
// Wait out the 26-second intro song
if (strikeCount == 0 && elapsed >= 26000) {
triggerTrack(5);
strikeCount = 1;
chimeStateTimer = millis();
}
// Step 2:
// Cycle through individual hourly strikes every 3 seconds
else if (
strikeCount > 0 &&
strikeCount < totalStrikesNeeded &&
elapsed >= 3000
) {
triggerTrack(5);
strikeCount++;
chimeStateTimer = millis();
}
// Step 3:
// Turn off tracking engine when total count completes
else if (strikeCount >= totalStrikesNeeded) {
introMelodyActive = false;
}
}
}
// ============================================================================
// Handle Buttons
// ============================================================================
void handleInputsNoMillis() {
bool setPressed = (digitalRead(btn_set) == LOW);
bool minPressed = (digitalRead(btn_min) == LOW);
bool brightPressed = (digitalRead(btn_bright) == LOW);
// ==========================================================================
// SET BUTTON INITIAL PRESS AND MODE TRANSITION
// ==========================================================================
if (setPressed) {
if (!setModeActive) {
setHoldCounter++;
// ~600ms hold requirement reached
if (setHoldCounter > 30) {
setModeActive = true;
}
}
// ==========================================================================
// SET MODE ACTIVE
// ==========================================================================
if (setModeActive) {
// ------------------------------------------------------------------------
// 1. Brightness Toggle
// ------------------------------------------------------------------------
if (brightPressed) {
currentBrightStage = (currentBrightStage + 1) % 5;
analogWrite(
reg_OE,
brightnessPWM[currentBrightStage]
);
// Small mechanical debounce
delay(250);
}
// ------------------------------------------------------------------------
// 2. Minutes Step With Automatic Hour Rollover
// ------------------------------------------------------------------------
if (minPressed) {
minHoldCounter++;
/*
Advance instantly on first press.
If held past 25 frames (~500ms),
scroll every 6 frames (~120ms).
*/
if (
minHoldCounter == 1 ||
(minHoldCounter > 25 && minHoldCounter % 6 == 0)
) {
seconds = 0;
minutes++;
// Rollover minutes into hours smoothly
if (minutes >= 60) {
minutes = 0;
hours++;
if (hours >= 24) {
hours = 0;
}
}
updateDisplayRouting();
}
}
else {
// Reset scroll acceleration instantly on button release
minHoldCounter = 0;
}
}
}
// ==========================================================================
// SET BUTTON RELEASE DETECTED
// ==========================================================================
else {
if (setHoldCounter > 0) {
if (setModeActive) {
setModeActive = false;
// Save strictly on release
rtc.adjust(
DateTime(
2026,
8,
13,
hours,
minutes,
0
)
);
}
else if (
setHoldCounter > 1 &&
setHoldCounter <= 30
) {
// Short tap changes 12/24 hour mode formatting
is24Hour = !is24Hour;
}
setHoldCounter = 0;
minHoldCounter = 0;
updateDisplayRouting();
}
}
}
// ============================================================================
// Setup
// ============================================================================
void setup() {
// Shift register pins
pinMode(reg_clock, OUTPUT);
pinMode(reg_latch, OUTPUT);
pinMode(reg_data, OUTPUT);
pinMode(reg_OE, OUTPUT);
analogWrite(
reg_OE,
brightnessPWM[currentBrightStage]
);
// Button pins
pinMode(btn_bright, INPUT_PULLUP);
pinMode(btn_min, INPUT_PULLUP);
pinMode(btn_set, INPUT_PULLUP);
Wire.begin();
// Start native Hardware Serial on pins 0/1 at 38400 baud
Serial.begin(38400);
if (!rtc.begin()) {
while (1);
}
/*
TESTING FEATURE:
Forces the RTC memory directly to 12:14:00
on every boot/reset so you can instantly
verify the 12:15:00 chime sound!
*/
rtc.adjust(
DateTime(
2026,
8,
13,
12,
14,
0
)
);
readRTC();
updateDisplayRouting();
}
// ============================================================================
// Main Loop
// ============================================================================
void loop() {
handleInputsNoMillis();
// Only run the clock/chime system when not in set mode
if (!setModeActive) {
checkChimesAsynchronous();
/*
Query the RTC hardware via I2C
once every 10 loops (~200ms).
*/
rtcRefreshTimer++;
if (rtcRefreshTimer >= 10) {
readRTC();
rtcRefreshTimer = 0;
}
}
/*
Conditional refresh gate blocks high-frequency
trace noise from locking up buttons.
*/
if (
seconds != lastDispSec ||
minutes != lastDispMin ||
hours != lastDispHr ||
setModeActive
) {
updateDisplayRouting();
lastDispSec = seconds;
lastDispMin = minutes;
lastDispHr = hours;
}
delay(20);
}
The only problem is the hour set switch doesn’t function. All other function work fine. I would also like to add if possible an TSOP 1838 IR sensor remote to set the clock beside switches, beside the sitwches.
Sorry, but there is apparently no mechanism supplied in the sketch that directly changes the hour. From the code, I would suspect that the hour set button would be on pin 4 (since the other set buttons are 2,3 and 5).
It seems that the only way to change the hour using the given sketch would be to keep incrementing minutes until the hour jumps up to the desired value.
Anything seems possible when you don't know what you're talking about.
That is how it presently works that way. To set the hours you fast advance the minutes to the hour you want to set.
