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 }