/ BTTF_Clock / BTTF / BTTF.ino
BTTF.ino
  1  // SPDX-FileCopyrightText: 2019 Anne Barela for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  // Time Circuit sketch for Adafruit 4-Digit 7-Segment Display w/I2C Backpack.
  6  // Modeled after Doc Brown's DeLorean time circuit from the "Back to the
  7  // Future" movies.  Uses three (3) each of the following displays:
  8  //   Red   : http://www.adafruit.com/products/878
  9  //   Green : http://www.adafruit.com/products/880
 10  //   Yellow: http://www.adafruit.com/products/879
 11  // A blue version is also available, but not used here.  Also uses two (2)
 12  // each 3mm discrete LEDs:
 13  //   Green : http://www.adafruit.com/products/779
 14  //   Red   : http://www.adafruit.com/products/777
 15  // Two yellow LEDs are used, but these aren't available in the shop -- they
 16  // can be purchased through Digi-Key, etc.  The same sources will likely
 17  // carry a 74HC138 3-to-8 line decoder w/inverting outputs (or, with some
 18  // changes to the code, a more common 74HC32 quad 2-input OR gate could
 19  // also be used, if you already have one on hand -- see comments later).
 20  // The resulting item is not 100% screen accurate.  The film prop used
 21  // 2-digit displays for day, hour and minute, the discrete LEDs for the
 22  // top (red) display were yellow, while the 3-character month displays were
 23  // back-painted glass fakes.  This demo was put together for fun, not
 24  // pedantry, and generally gets the idea across.  Everyone recognizes it!
 25  
 26  // These 4-digit displays can be assigned any of eight I2C addresses via
 27  // solder jumpers on the back.  But the full time circuit requires *nine*
 28  // displays.  A dirty hack exploits the fact that we only need one-way
 29  // (write only) access to the displays.  The I2C data line is connected to
 30  // all devices as normal...then the I2C clock drives the enable line of a
 31  // 74HC138, while the select lines choose among multiple ersatz I2C buses
 32  // (potentially up to 8, though we're only using 3 here).  Only that bus
 33  // then responds to the incoming data.  The 74HC138 was chosen for its
 34  // inverting outputs -- the idle state is high, consistent with I2C.
 35  
 36  // So, each bus contains three 4-digit displays, and within each bus they're
 37  // assigned address 0 (for MM.DD), 1 (YYYY) and 2 (HH:MM).  This simplifies
 38  // the code, as display addresses are the same across all dates.  A ChronoDot
 39  // (RTC_DS1307) clock chip is also used...bidirectional communication works
 40  // because this connects to the regular I2C clock & data lines, not one of
 41  // the 74HC138 outputs.  Altogether there's 10 devices attached to the I2C
 42  // data line.  I'm not sure what the recommended fanout is for the ATmega...
 43  // but if this runs into trouble, can always add a second 74HC138 for data,
 44  // using the same bus select bits.
 45  
 46  #include <Wire.h>
 47  #include <Adafruit_LEDBackpack.h>
 48  #include <Adafruit_GFX.h>
 49  #include <RTClib.h>
 50  
 51  Adafruit_7segment matrix[3] = {
 52    Adafruit_7segment(), Adafruit_7segment(), Adafruit_7segment() };
 53  RTC_DS1307 clock;
 54  // RTClib isn't pre-Y2K (or post-Y2.1K) compliant, so an ugly trick is used
 55  // to represent "fantasy" dates.  Because our clock displays don't show
 56  // seconds, that field in the DateTime class is borrowed to indicate a
 57  // century #.  Honest-to-goodness DateTimes will have a seconds() value of
 58  // 0 to 59.  If seconds >= 100, this field (-100) is the two-digit century.
 59  DateTime dest(55, 11,  5, 22,  4, 100 + 19), // Nov 5, 1955 10:04 PM
 60           last(85, 10, 26,  1, 24, 100 + 19); // Oct 26, 1985 1:24 AM
 61  
 62  void setup() {
 63    uint8_t m, b;
 64  
 65    clock.begin();
 66  
 67    // NOTE: all pin numbers used here are for the Teensy microcontroller
 68    // board, NOT a stock Arduino.  You may need to adapt this code to
 69    // your particular situation. (e.g. if making a prop that also uses
 70    // the Wave Shield to add sounds, you'd want to use an Arduino Uno and
 71    // then steer clear of all the SPI pins (10-13)).
 72  
 73    // Enable select lines for I2C multiplexing (only 2 are used here)
 74    pinMode( 9, OUTPUT);
 75    pinMode(10, OUTPUT);
 76  
 77    // Enable AM/PM LED outputs, set all LOW (off)
 78    for(b=11; b<=16; b++) {
 79      pinMode(b, OUTPUT);
 80      digitalWrite(b, LOW);
 81    }
 82  
 83    // Initialize all three displays on all three buses
 84    for(b=0; b<3; b++) {
 85      selectBus(b);
 86      for(m=0; m<3; m++) matrix[m].begin(0x70 + m);
 87    }
 88  }
 89  
 90  boolean dots = false; // For flashing colon on HH:MM times
 91  
 92  void loop() {
 93    displayDate(0, dest);        // Destination time
 94    displayDate(1, clock.now()); // Present time
 95    displayDate(2, last);        // Last time departed
 96  
 97    dots = !dots;                // Blink blink blink
 98    delay(500);
 99  }
100  
101  // This function enables one of the 3 (but potentially up to 8) I2C buses
102  // by setting the select lines on the 74HC138 (only 2 lines are used in the
103  // circuit, the third is tied to ground).  A 74HC32 quad OR gate could also
104  // be used, but each bus will require its own select line (set corresponding
105  // output HIGH to disable, LOW to enable).  I wanted to use the least pins
106  // so that others remain free for the addition of a possible keypad later.
107  // (Could also free up pins using a port expander or shift register for
108  // the AM/PM LEDs.)
109  void selectBus(uint8_t b) {
110    digitalWrite(10, (b & 1) ? HIGH : LOW);
111    digitalWrite( 9, (b & 2) ? HIGH : LOW);
112    // Can add third bit here if more buses are needed
113  }
114  
115  // Show contents of DateTime object on the three displays across one bus
116  void displayDate(uint8_t b, DateTime d) {
117    int x;
118  
119    selectBus(b);
120  
121    // Write MM.DD (zero padded) to display #0
122    x = d.month();
123    matrix[0].writeDigitNum(0, x / 10);
124    matrix[0].writeDigitNum(1, x % 10, true);
125    x = d.day();
126    matrix[0].writeDigitNum(3, x / 10);
127    matrix[0].writeDigitNum(4, x % 10);
128  
129    // Write year to display #1 (4-digit, zero-padded)
130    // Great Scott!  RTClib isn't pre-Y2K compliant, so 'second'
131    // is borrowed as a flag to indicate fantasy dates.
132    if((x = d.second()) >= 100) x = d.year() - 2000 + x * 100;
133    else                        x = d.year();
134    matrix[1].writeDigitNum(0, (x / 1000) % 10);
135    matrix[1].writeDigitNum(1, (x /  100) % 10);
136    matrix[1].writeDigitNum(3, (x /   10) % 10);
137    matrix[1].writeDigitNum(4,  x         % 10);
138  
139    // Write time to display #2 (HH:MM, zero-padded + AM/PM indicator)
140    x = d.hour();
141    if(x < 12) { // AM
142      digitalWrite(b * 2 + 11, HIGH); // Upper LED on
143      digitalWrite(b * 2 + 12, LOW);  // Lower LED off
144    } else {     // PM
145      digitalWrite(b * 2 + 11, LOW);  // Upper LED off
146      digitalWrite(b * 2 + 12, HIGH); // Lower LED on
147    }
148    if(x > 12) x -= 12; // Convert 24-hour to 12-hour time
149    matrix[2].writeDigitNum(0, x / 10);
150    matrix[2].writeDigitNum(1, x % 10);
151    x = d.minute();
152    matrix[2].writeDigitNum(3, x / 10);
153    matrix[2].writeDigitNum(4, x % 10);
154    matrix[2].drawColon(dots);
155  
156    // Refresh all three displays on current bus
157    for(x=0; x<3; x++) matrix[x].writeDisplay();
158  }