Software Part 2

Now you can copy and paste all the code (further down this page) into a new sketch. Before uploading to the board, you’ll want to edit this section near the top (around line 38):

#define YEAR  2061 // Date and time of some future event
#define MONTH    7 // (ostensibly your demise)
#define DAY     28 // But here it's set for Halley's comet
#define HOUR    12 // Example time here is set for noon,
#define MINUTE   0 // DON'T use leading zero or value is octal!

This sets the date and time of some future event to count down toward.

HOUR should be in 24-hour format; e.g. midnight is “0”, noon is “12”, while 3pm is “15” (12+3). MINUTE is 0 to 59. For both of these values, do not use a leading zero; e.g. for 9:09 am, use “9” for each, not “09”. (Numbers with leading zeros are interpreted by the computer as octal — a different representation, like hexadecimal or binary.)

After uploading this sketch to the Pro Trinket, you should be greeted with a (hopefully very large) number — the minutes until your set date — and the rightmost decimal will be blinking. One second on, one second off. If not, or if having other problems, see the troubleshooting tips at the bottom of this page.

// Mindfulness clock.  Counts down minutes to some event; presumably one's
// demise.  The display is purposefully not divided into years and days and
// so forth, just an absolute number of minutes, counting down.  It's a
// mindfulness thing; makes you think.  Minutes you're never getting back.
// STOP WATCHING INTERNET CAT VIDEOS AND GET SOMETHING DONE!

// GLOBAL STUFF ------------------------------------------------------------

// PARTS USED:
// - Adafruit Pro Trinket microcontroller, the 5V or 3V versions work
//   equally well, whichever is in stock (adafru.it/2000 or 2010).  Other
//   ATmega 328P-based microcontrollers may work (e.g. Arduino Uno), but
//   NOT 32u4-based boards like the Feather 32u4 or Arduino Leonardo,
//   nor ATtiny-based boards.  Code uses specific hardware features.
// - Adafruit DS3231 Precision RTC Breakout (http://adafru.it/3013).  Other
//   DS3231-based models like the ChronoDot (255) can also work.
// - CR1220 lithium battery (or whatever battery your RTC requires)
// - TWO 4-digit, 7-segment LED Backpacks (adafru.it/877) with LED displays
//   in your choice of colors (red is always very dramatic!)

// NOTES:
// - The two 7-segment displays will be placed side-by-side to create a
//   contiguous 8-digit display.  Bridge the 'A0' pads on the back of ONE
//   display to change its I2C address.
// - Install the LED displays correctly: display goes on the BLANK side of
//   the backpack board and does NOT cover the driver chip.  Make sure the
//   DECIMAL POINTS match the positions on the silkscreen, or it won't work.
// - Connect 'SQW' pin on the RTC board to Digital Pin 3 on microcontroller.
// - The DS3231 dates are valid through year 2100, this code through 2099.
//   Behavior is undefined past those dates and probably won't work, but
//   call it a feature...you and your Arduino have survived over 80 years.

#include <Wire.h>
#include <RTClib.h>
#include <Adafruit_GFX.h>
#include <Adafruit_LEDBackpack.h>

#define YEAR  2061 // Date and time of some future event
#define MONTH    7 // (ostensibly your demise)
#define DAY     28 // But here it's set for Halley's comet
#define HOUR    12 // Example time here is set for noon,
#define MINUTE   0 // DON'T use leading zero or value is octal!

// GLOBAL STUFF ------------------------------------------------------------

RTC_DS3231 rtc;

struct {
  uint8_t           addr;         // I2C address
  Adafruit_7segment seg7;         // 7segment object
} disp[] = {
  { 0x70, Adafruit_7segment() },  // High digits
  { 0x71, Adafruit_7segment() }   // Low digits
};

// These are declared 'volatile' because they change in an interrupt handler
volatile uint32_t minutes = 0;    // Time remaining (counts down)
volatile uint8_t  seconds = 0;    // Next-minute counter (counts up)
volatile boolean  hzFlag  = true; // true = refresh display

// UTILITY FUNCTIONS -------------------------------------------------------

// This function is called when a high-to-low transition is detected on
// digital pin 3 -- this is connected to the SQW pin on the RTC, which
// provides a super-precise 1-second square wave; won't drift the way
// delay() or millis() would (could even sleep MCU if desired).
// The displays are NOT refreshed inside this function -- it's poor form
// to try doing too much inside an interrupt routine, especially when the
// functions used in updating the displays (via the Wire library) may
// themselves rely on other interrupts.  Instead, a global flag is set here
// and the loop() routine polls this & updates the display when ready.
void interrupt1hz(void) {
  if(++seconds >= 60) {
    seconds = 0;
    if(minutes) minutes--;
  }
  hzFlag = true;
}

// Number of days since 2000/01/01, valid for 2000-2099.  Essentially a
// copy of the date2days() function in RTClib, but it's been declared static
// there and is inaccessible through the API.  So, a duplicate...
uint16_t date2days(uint16_t y, uint8_t m, uint8_t d) {
  static const uint8_t daysInMonth[] PROGMEM =
    { 31,28,31,30,31,30,31,31,30,31,30,31 };
  if(y >= 2000) y -= 2000;
  uint16_t days = d;
  for(uint8_t i=1; i<m; ++i) days += pgm_read_byte(daysInMonth + i - 1);
  if((m > 2) && (!(y & 3))) days++;        // Leap day for current year
  return days + 365 * y + (y + 3) / 4 - 1; // Add leap days for prior years
}

// SETUP() FUNCTION - RUNS ONCE AT STARTUP ---------------------------------

void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);   // LED on = initializing

  for(uint8_t i=0; i<2; i++) {       // Initialize displays
    disp[i].seg7.begin(disp[i].addr);
    disp[i].seg7.clear();
    disp[i].seg7.writeDisplay();
  }

  if(!rtc.begin()) {                 // Initialize RTC
    Serial.println("Couldn't find RTC");
    for(boolean b; ; b = !b) {       // Blinking = RTC error
      digitalWrite(LED_BUILTIN, b);
      delay(250);
    }
  }

  // Determine number of minutes until YEAR/MONTH/DAY/etc.
  DateTime now = rtc.now();
  uint32_t d1  = date2days(YEAR, MONTH, DAY),                   // End day
           d2  = date2days(now.year(), now.month(), now.day()); // Today
  if(d2 > d1) { // Date already passed
    minutes = 0;
  } else {
    int32_t s1 = (HOUR * 60 + MINUTE) * 60L,
            s2 = (now.hour() * 60 + now.minute()) * 60L + now.second();
    if((d2 == d1) && (s2 >= s1)) { // Same day, hour/min/sec has passed
      minutes = 0;
    } else {
      s1     -= s2; // Seconds time difference, may be negative, this is OK
      seconds = 60 - (s1 % 60); // Counts up 0-60 for next minute mark
      minutes = (d1 - d2) * 24 * 60 + s1 / 60; // Minutes remaining
    }
  }

  // Enable 1 Hz square wave output from RTC.
  // rtc.writeSqwPinMode((Ds3231SqwPinMode)SquareWave1HZ); *should* handle
  // this, but for whatever reason is not, so fiddle registers directly...
  uint8_t ctrl;
  Wire.beginTransmission(DS3231_ADDRESS);
  Wire.write((byte)DS3231_CONTROL);
  Wire.endTransmission();
  Wire.requestFrom(DS3231_ADDRESS, (byte)1);
  ctrl = Wire.read() & 0b11100011; // 1 Hz, INTCN off
  Wire.beginTransmission(DS3231_ADDRESS);
  Wire.write((byte)DS3231_CONTROL);
  Wire.write((byte)ctrl);
  Wire.endTransmission();

  // Attach interrupt on pin 3 to 1Hz output from RTC
  pinMode(3, INPUT_PULLUP); // Is open drain on RTC
  attachInterrupt(1, interrupt1hz, FALLING);
  digitalWrite(13, LOW); // LED off = successful init
}

// LOOP() FUNCTION - RUNS FOREVER ------------------------------------------

void loop() {
  while(!hzFlag); // Wait for interrupt to indicate elapsed second

  uint16_t hi = minutes / 10000, // Value on left (high digits) display
           lo = minutes % 10000; // Value on right (low digits) display
  disp[0].seg7.print(hi, DEC);   // Write values to each display...
  disp[1].seg7.print(lo, DEC);

  // print() does not zero-pad the displays; this may produce a gap
  // between the upper and lower sections.  Add zeros where needed...
  if(hi) {
    if(lo < 1000) {
      disp[1].seg7.writeDigitNum(0, 0);
      if(lo < 100) {
        disp[1].seg7.writeDigitNum(1, 0);
        if(lo < 10) {
          disp[1].seg7.writeDigitNum(3, 0);
        }
      }
    }
  } else {
   disp[0].seg7.clear(); // Clear 'hi' display
  }

  // Flash rightmost dot (1 sec on, 1 sec off)
  disp[1].seg7.writeDigitNum(4, lo % 10, seconds & 1);

  disp[0].seg7.writeDisplay(); // Push data to displays
  disp[1].seg7.writeDisplay();

  hzFlag = false; // Clear flag for next interrupt
}

I get an error when compiling the sketch!

  • Confirm the correct board is selected (“Pro Trinket 5V/16MHz (USB)” or “Pro Trinket 3V/12MHz (USB)”, whichever you’re using).
  • Confirm all three libraries are correctly installed: RTClib, Adafruit_GFX and Adafruit_LEDBackpack.

The displays are blank. I get nothing.

  • Did you install the wire from SQW on the clock board to digital pin 3 on the Pro Trinket?
  • There may be a power or communication problem between the Pro Trinket and the displays. Check all four connections: BUS, GND, A4/SDA/D and A5/SCL/C, make sure the latter two aren’t reversed.
  • Look the circuit over for accidental solder bridges or cold solder joints.
  • Are the displays correctly assembled per these directions?

Both displays are showing the same four digits. I get two blinking decimals, or no blinking decimal.

Bridge the “A0” pads on the back of one display with a bit of solder. This assigns each display a unique I2C address.

The blinking decimal is in the middle, and the digits aren’t counting correctly.

The LED displays are mounted in the wrong positions. No need to dismantle anything, just switch the “0x70” and “0x71” on these two lines (around line 52) and re-upload:

  { 0x70, Adafruit_7segment() },  // High digits
  { 0x71, Adafruit_7segment() }   // Low digits
Last updated on Jul 01, 2017 Published on Aug 01, 2016