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);
}