gokul/six-digit-logic-clock
This code defines the physical layout and schematic connections for a multi-layer PCB featuring components like RTC modules (DS3231MZ), microcontroller (ATMEGA328P), LED display driver (MAX7219), and various passive and discrete components, with detailed footprints, pinouts, and placement for a clock and control interface.
- Version
- 0.1.2
- License
- unset
- Stars
- 0
clock_firmware.ino
#include <SPI.h>
#include <Wire.h>
// MiniCore board settings: ATmega328P, 8 MHz internal oscillator, BOD 2.7 V.
constexpr uint8_t PIN_MAX_LOAD = 10; // PB2
constexpr uint8_t PIN_HOUR = 3; // PD3, active low
constexpr uint8_t PIN_MINUTE = 2; // PD2, active low
constexpr uint8_t RTC_ADDRESS = 0x68;
struct ClockTime {
uint8_t hour;
uint8_t minute;
uint8_t second;
};
struct DebouncedButton {
uint8_t pin;
bool stableState = HIGH;
bool lastSample = HIGH;
uint32_t changedAt = 0;
bool pressed() {
const bool sample = digitalRead(pin);
const uint32_t now = millis();
if (sample != lastSample) {
lastSample = sample;
changedAt = now;
}
if (sample != stableState && now - changedAt >= 25) {
stableState = sample;
return stableState == LOW;
}
return false;
}
};
DebouncedButton hourButton{PIN_HOUR};
DebouncedButton minuteButton{PIN_MINUTE};
uint8_t fromBcd(uint8_t value) {
return static_cast<uint8_t>((value >> 4) * 10 + (value & 0x0F));
}
uint8_t toBcd(uint8_t value) {
return static_cast<uint8_t>(((value / 10) << 4) | (value % 10));
}
void maxWrite(uint8_t address, uint8_t value) {
digitalWrite(PIN_MAX_LOAD, LOW);
SPI.transfer(address);
SPI.transfer(value);
digitalWrite(PIN_MAX_LOAD, HIGH);
}
void rtcWriteRegister(uint8_t address, uint8_t value) {
Wire.beginTransmission(RTC_ADDRESS);
Wire.write(address);
Wire.write(value);
Wire.endTransmission();
}
uint8_t rtcReadRegister(uint8_t address) {
Wire.beginTransmission(RTC_ADDRESS);
Wire.write(address);
Wire.endTransmission(false);
Wire.requestFrom(RTC_ADDRESS, static_cast<uint8_t>(1));
return Wire.available() ? Wire.read() : 0xFF;
}
bool rtcReadTime(ClockTime &time) {
Wire.beginTransmission(RTC_ADDRESS);
Wire.write(0x00);
if (Wire.endTransmission(false) != 0) return false;
if (Wire.requestFrom(RTC_ADDRESS, static_cast<uint8_t>(3)) != 3) return false;
time.second = fromBcd(Wire.read() & 0x7F);
time.minute = fromBcd(Wire.read() & 0x7F);
const uint8_t hours = Wire.read();
if (hours & 0x40) {
const uint8_t hour12 = fromBcd(hours & 0x1F);
time.hour = static_cast<uint8_t>((hour12 % 12) + ((hours & 0x20) ? 12 : 0));
} else {
time.hour = fromBcd(hours & 0x3F);
}
return time.hour < 24 && time.minute < 60 && time.second < 60;
}
void rtcWriteTime(const ClockTime &time) {
Wire.beginTransmission(RTC_ADDRESS);
Wire.write(0x00);
Wire.write(toBcd(time.second));
Wire.write(toBcd(time.minute));
Wire.write(toBcd(time.hour)); // 24-hour format
Wire.endTransmission();
}
uint8_t monthFromBuildDate(const char *date) {
constexpr char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
for (uint8_t month = 0; month < 12; ++month) {
if (date[0] == months[month * 3] && date[1] == months[month * 3 + 1] &&
date[2] == months[month * 3 + 2]) {
return static_cast<uint8_t>(month + 1);
}
}
return 1;
}
void setRtcFromBuildTime() {
const char *time = __TIME__;
const char *date = __DATE__;
const uint8_t hour = static_cast<uint8_t>((time[0] - '0') * 10 + time[1] - '0');
const uint8_t minute = static_cast<uint8_t>((time[3] - '0') * 10 + time[4] - '0');
const uint8_t second = static_cast<uint8_t>((time[6] - '0') * 10 + time[7] - '0');
const uint8_t day = static_cast<uint8_t>((date[4] == ' ' ? 0 : date[4] - '0') * 10 + date[5] - '0');
const uint8_t month = monthFromBuildDate(date);
const uint8_t year = static_cast<uint8_t>((date[9] - '0') * 10 + date[10] - '0');
Wire.beginTransmission(RTC_ADDRESS);
Wire.write(0x00);
Wire.write(toBcd(second));
Wire.write(toBcd(minute));
Wire.write(toBcd(hour));
Wire.write(toBcd(1)); // Day-of-week is not displayed.
Wire.write(toBcd(day));
Wire.write(toBcd(month));
Wire.write(toBcd(year));
Wire.endTransmission();
rtcWriteRegister(0x0F, rtcReadRegister(0x0F) & ~0x80); // Clear OSF.
}
void displayTime(const ClockTime &time) {
maxWrite(1, time.second % 10);
maxWrite(2, time.second / 10);
maxWrite(3, time.minute % 10);
maxWrite(4, time.minute / 10);
maxWrite(5, time.hour % 10);
maxWrite(6, time.hour < 10 ? 0x0F : time.hour / 10); // Blank leading zero.
}
void setup() {
pinMode(PIN_MAX_LOAD, OUTPUT);
digitalWrite(PIN_MAX_LOAD, HIGH);
pinMode(PIN_HOUR, INPUT_PULLUP);
pinMode(PIN_MINUTE, INPUT_PULLUP);
Wire.begin();
Wire.setClock(100000);
SPI.begin();
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
maxWrite(0x0F, 0x00); // Display test off.
maxWrite(0x09, 0x3F); // Code-B decode on digits 0 through 5.
maxWrite(0x0B, 0x05); // Scan exactly six digits.
maxWrite(0x0A, 0x04); // Moderate brightness.
maxWrite(0x0C, 0x01); // Normal operation.
ClockTime now{};
const bool oscillatorStopped = (rtcReadRegister(0x0F) & 0x80) != 0;
if (oscillatorStopped || !rtcReadTime(now)) setRtcFromBuildTime();
}
void loop() {
ClockTime now{};
if (!rtcReadTime(now)) {
maxWrite(0x0C, 0x00);
delay(100);
return;
}
bool changed = false;
if (hourButton.pressed()) {
now.hour = static_cast<uint8_t>((now.hour + 1) % 24);
changed = true;
}
if (minuteButton.pressed()) {
now.minute = static_cast<uint8_t>((now.minute + 1) % 60);
now.second = 0;
changed = true;
}
if (changed) rtcWriteTime(now);
maxWrite(0x0C, 0x01);
displayTime(now);
delay(10);
}