IV-12 Vacuum Fluorescent Display Clock | |
June 2019
The Arduino SketchAdditional LibrariesRTC lib
/* IV-12 Vacuum Fluorescent Display (VFD) Real Time Clock - (c) vwlowen.co.uk * */ #include <Wire.h> #include "RTClib.h" // https://github.com/adafruit/RTClib #include <SPI.h> #include <IRremote.h> // https://github.com/z3t0/Arduino-IRremote #include <EEPROM.h> // RTC_DS1307 rtc; // Choose the RTC clock module here. RTC_DS3231 rtc; // In practice, either works. // These defines are codes from the IR remote, as displayed on the Serial monitor #define IR_HOURS_UP 0xFD30CF // 1 set hours+ / day+ #define IR_MINUTES_UP 0xFDB04F // 2 set mins / month+ #define IR_YEAR_UP 0xFD708F // 3 Year Up #define IR_HOURS_DOWN 0xFD08F7 // 4 set hour- / day- #define IR_MINUTES_DOWN 0xFD8877 // 5 set mins- / month- #define IR_YEAR_DOWN 0xFD48B7 // 6 Year Down #define IR_EDIT_TIME 0xFD28D7 // 7 Enter/exit Time edit mode #define IR_EDIT_ALARM 0xFDA857 // 8 Enter/exit Alarm edit mode #define IR_EDIT_DATE 0xFD6897 // 9 Enter/exit Date edit mode #define IR_SHOW_TEMP 0xFD10EF // 0 Show Temperature #define IR_ALARM 0xFD20DF // EQ Alarm set/reset #define IR_SHOW_MOOD_LEDS 0xFD00FF // Play/Pause - show mood leds. #define IR_LEDS_OFF 0xFD906F // Rewind button: edit LEDS Off time #define IR_LEDS_ON 0xFD50AF // Fast Forward: edit LEDS On time const int setHours_pin = 2; // These three inputs duplicate IR.. const int setMins_pin = 3; // ... codes so are,therefore... const int cancel_alarm_pin = 4; // ... optional. const int mood_leds_out = 5; // Output to switch mood LEDs MOSFET const int alarm_output = 6; // Output to sound Alarm (if Alalrm is set) const int blank = 9; // Output to HV5812P-G 'BLANK' input. const int strobe = 10; // Output to HV5812P-G 'STROBE' input. const int IR_pin = A0; // Input from TSOP38238 IR receiver. const int alarm_active = A1; // Output for 'Alarm is set' LED. const int LDR_pin = A2; // Input for LDR voltage measurement. const int TMP_36 = A3; // Optional TMP36 Temperature sensor #define NUM_DIGITS 6 // Define number of display digits. #define BLANK_MIN 0 // Define PWM range day/night to dim VFD. #define BLANK_MAX 240 #define LDR_MIN 0 #define LDR_MAX 800 #define ALARM_TONE 180 // Define PWM "tone" for Alarm. #define DISPLAY_TIMEOUT 5000 // Display will revert to showing the Time after this delay if... // ...set Hours, Minutes or Year key on remote is not pressed. IRrecv irrecv(IR_pin); decode_results results; // Define the bit-pattern for each tube Grid. const byte grids[6] = { B00000001, // Hx10 or Dx10 B00000010, // H or D B00000100, // M x 10 or Mx10 B00001000, // M or M B00010000, // S x 10 or Yx10 B00100000 // S or Y }; // gfedcba const byte numbers[16] = { // Define the bit-pattern for each chracter and 7-segment number. B00111111, // 0 B00000110, // 1 B01011011, // 2 B01001111, // 3 B01100110, // 4 B01101101, // 5 B01111101, // 6 B00000111, // 7 B01111111, // 8 B01101111, // 9 B00000000, // 10 (Blank) B01110111, // 11 (A Edit Alarm indicator) B01100001, // 12 (c degrees c indicator) B01000000, // 13 (- minus indicator) B01111000 // 14 (t Edit Time indicator) }; byte theTemps[6]; byte theTime[6]; // Each read of the RTC is stored here. Hx10 ... sec. byte theAlarm[6]; // The Alarm time is stored here. byte theDate[6]; // The date is stored here byte ledsOff[6]; // Mood LEDs OFF time stored here byte ledsOn[6]; // Mood LEDs ON time stored here byte years, months, days, hours, mins, secs; // Time and Date values read from the RTC. byte alarmAddressHr = 0; // EEPROM addresses to save/restore Alarm Hours. byte alarmAddressMin = 1; byte moodAddress = 2; // EEPROM address to save if mood LEDs are off or on. byte ledsAddressOffHr = 3; // EEPROM addresses for timed mood LEDs byte ledsAddressOffMin = 4; byte ledsAddressOnHr = 5; byte ledsAddressOnMin = 6; byte alarmHours, alarmMins; // Variables to hold various hours and minutes. byte ledsOffHours, ledsOffMins; byte ledsOnHours, ledsOnMins; unsigned long settingTimer = 0; // Timer for incrementing hours & minutes at 250ms intervals. unsigned long displayTempTimer = 0; unsigned long displayTimeTimer = 0; // Timer for displaying time during edit. unsigned long displayDateTimer = 0; // Timer for displaying date from IR remote unsigned long displayAlarmTimer = 0; // Timer for displaying temperature from remote unsigned long cancelAlarm = 0; // Timer to de-bounce Cancel Alarm button; unsigned long displayLedsOffTimer = 0; unsigned long displayLedsOnTimer = 0; bool alarm_is_set = false; bool alarm_is_sounding = false; bool editTime = false; // Flags set when appropriate IR code is received. bool editAlarm = false; bool editDate = false; bool showTemp = false; bool editLedsOff = false; bool editLedsOn = false; bool tempReadOnce = false; bool thisFlag = false; byte moodLeds = 0; // Mood LEDs default is OFF. int grid = 0; // Define first grid to be activated/updated (grid 0 is Hours x 10). void resetAll() { editTime = false; // Clear all previous IR flags when a new one is received. editAlarm = false; editDate = false; showTemp = false; editLedsOff = false; editLedsOn = false; tempReadOnce = false; } void setup() { TCCR1B = (TCCR1B & 0b11111000) | 0x1; // Set Fast PWM frequency on pin 9 [and 10] (for dimming display) Serial.begin(115200); irrecv.enableIRIn(); // Start the receiver Serial.println("Enabled IRin"); SPI.begin(); if (! rtc.begin()) { Serial.println("Couldn't find RTC"); while (1); } pinMode(alarm_active, OUTPUT); pinMode(alarm_output, OUTPUT); pinMode(mood_leds_out, OUTPUT); pinMode(setHours_pin, INPUT_PULLUP); pinMode(setMins_pin, INPUT_PULLUP); pinMode(cancel_alarm_pin, INPUT_PULLUP); pinMode(strobe, OUTPUT); pinMode(blank, OUTPUT); analogWrite(blank, BLANK_MIN); // Set maximum VFD display brightness EEPROM.get(moodAddress, moodLeds); digitalWrite(mood_leds_out, moodLeds); } void loop() { if (showTemp & !tempReadOnce) { int reading = analogRead(TMP_36); float voltage = (reading / 1023.0) * 5000; int temperature = (int) (voltage - 500) * 0.1; theTemps[0] = 10; // blank temperature < 0 ? theTemps[1] = 13 : theTemps[1] = 10; // minus or blank theTemps[2] = temperature / 10 % 10; theTemps[3] = temperature / 1 % 10; theTemps[4] = 12; // 'c' theTemps[5] = 10; // blank tempReadOnce = true; } static int raw; static int count; int displayBlanking; raw = raw + analogRead(LDR_pin); // Measure ambient light; count++; if (count >= 20) { raw = raw / 20; count = 0; if (moodLeds == 1) { displayBlanking = BLANK_MIN; } else { displayBlanking = constrain(map(raw, LDR_MIN, LDR_MAX, BLANK_MAX, BLANK_MIN), BLANK_MIN, BLANK_MAX); raw = 0; } } results.value = 0; // Clear last IR Remote code. if (irrecv.decode(&results)) { // Get IR Remote code, if any. Serial.println(results.value, HEX); // Print value on Serial monitor for use in #defines above. irrecv.resume(); // Get ready to receive the next value. } // Enumerate Infrared results and set appropriate flag. switch (results.value) { case IR_SHOW_MOOD_LEDS: moodLeds == 0? moodLeds = 1 : moodLeds = 0; digitalWrite(mood_leds_out, moodLeds); EEPROM.put(moodAddress, moodLeds); break; case IR_LEDS_OFF: thisFlag = editLedsOff; // Save value of this IR flag resetAll(); // Reset all flags to 'false' editLedsOff = !thisFlag; // Restore value of this flag. displayLedsOffTimer = millis(); // Start display timer. break; case IR_LEDS_ON: thisFlag = editLedsOn; resetAll(); editLedsOn = !thisFlag; displayLedsOnTimer = millis(); break; case IR_ALARM: alarm_is_set = !alarm_is_set; results.value = 0; break; case IR_EDIT_TIME: thisFlag = editTime; resetAll(); editTime = !thisFlag; displayTimeTimer = millis(); break; case IR_EDIT_ALARM: thisFlag = editAlarm; resetAll(); editAlarm = !thisFlag; displayAlarmTimer = millis(); break; case IR_EDIT_DATE: thisFlag = editDate; resetAll(); editDate = !thisFlag; displayDateTimer = millis(); break; case IR_SHOW_TEMP: thisFlag = showTemp; resetAll(); showTemp = !thisFlag; tempReadOnce = false; displayTempTimer = millis(); break; } // Enumerate display Timers and reset flag if timed out. if (millis() > (displayLedsOffTimer + DISPLAY_TIMEOUT)) { // Reset IR flag id display timer has timed out. editLedsOff = false; } if (millis() > (displayLedsOnTimer + DISPLAY_TIMEOUT)) { editLedsOn = false; } if (millis() > (displayTimeTimer + DISPLAY_TIMEOUT)) { editTime = false; } if (millis() > (displayAlarmTimer + DISPLAY_TIMEOUT)) { editAlarm = false; } if (millis() > (displayDateTimer + DISPLAY_TIMEOUT)) { editDate = false; } if (millis() > (displayTempTimer + DISPLAY_TIMEOUT)) { showTemp = false; } if ((digitalRead(cancel_alarm_pin) == LOW) && (millis() > (cancelAlarm + 2000))) { alarm_is_set = !alarm_is_set; cancelAlarm = millis(); } if (alarm_is_set) { analogWrite(alarm_active, 255); // Light LED if alarm is active. } else analogWrite(alarm_active, 0); // Get date and time from RTC and Alarm time & Mood LEDs on/off times from EEPROM DateTime now = rtc.now(); // Get time from RTC. years = now.year() - 2000; months = now.month(); days = now.day(); hours = now.hour(); mins = now.minute(); secs = now.second(); EEPROM.get(alarmAddressHr, alarmHours); // Get Alarm time from EEPROM. EEPROM.get(alarmAddressMin, alarmMins); EEPROM.get(ledsAddressOffHr, ledsOffHours); EEPROM.get(ledsAddressOffMin, ledsOffMins); EEPROM.get(ledsAddressOnHr, ledsOnHours); EEPROM.get(ledsAddressOnMin, ledsOnMins); if (editLedsOff) { ledsOff[0] = ledsOffHours / 10; // Split the hours and minutes into separate ledsOff[1] = ledsOffHours % 10; // digits for displaying on the IV-12 tubes. ledsOff[2] = ledsOffMins / 10; ledsOff[3] = ledsOffMins % 10; ledsOff[4] = 10; ledsOff[5] = 10; } if (editLedsOn) { ledsOn[0] = ledsOnHours / 10; ledsOn[1] = ledsOnHours % 10; ledsOn[2] = ledsOnMins / 10; ledsOn[3] = ledsOnMins % 10; ledsOn[4] = 10; ledsOn[5] = 10; } if (editAlarm) { theAlarm[0] = alarmHours / 10; theAlarm[1] = alarmHours % 10; theAlarm[2] = alarmMins / 10; theAlarm[3] = alarmMins % 10; theAlarm[4] = 10; //'Blank' (All Segments Off) theAlarm[5] = 11; //'A' (Alarm Time) } theTime[0] = hours / 10; theTime[1] = hours % 10; theTime[2] = mins / 10; theTime[3] = mins % 10; if (editTime) { theTime[4] = 10; // 'Blank' theTime[5] = 14; // 't' // If editing the time, show a 't'... } else { // ... instead of seconds. theTime[4] = secs / 10; theTime[5] = secs % 10; } if (editDate) { theDate[0] = days / 10; theDate[1] = days % 10; theDate[2] = months / 10; theDate[3] = months % 10; theDate[4] = years / 10; theDate[5] = years % 10; } // If leds Off time and leds On time are different, check if either matches // the current hours and minutes and, if they match, set mood LEDs accordingly! if ((ledsOffHours != ledsOnHours) || (ledsOffMins != ledsOnMins)) { if ((hours == ledsOffHours) && (mins == ledsOffMins) & (secs < 2)) { moodLeds = 0; digitalWrite(mood_leds_out, moodLeds); } if ((hours == ledsOnHours) && (mins == ledsOnMins) & (secs < 2)) { moodLeds = 1; digitalWrite(mood_leds_out, moodLeds); } } // Sound the Alarm if times match if (((hours == alarmHours) && (mins == alarmMins) && (secs < 2) & alarm_is_set) || alarm_is_sounding) { analogWrite(alarm_output, ALARM_TONE); alarm_is_sounding = true; }; if (!alarm_is_set) { analogWrite(alarm_output, 0); alarm_is_sounding = false; } // Enumerate all the IR flags. If set to 'true', test IR results for hours and minutes up and down if (editLedsOff) { if (results.value == IR_HOURS_UP) { //Set Hour ledsOffHours++; if (ledsOffHours >= 24) ledsOffHours = 0; ledsOff[0] = ledsOffHours / 10; ledsOff[1] = ledsOffHours % 10; EEPROM.put(ledsAddressOffHr, ledsOffHours); displayLedsOffTimer = millis(); } if (results.value == IR_HOURS_DOWN) { //Set Hour ledsOffHours--; if (ledsOffHours < 0) ledsOffHours = 23; ledsOff[0] = ledsOffHours / 10; ledsOff[1] = ledsOffHours % 10; EEPROM.put(ledsAddressOffHr, ledsOffHours); displayLedsOffTimer = millis(); } if (results.value == IR_MINUTES_UP) { // Set Minute ledsOffMins++; if (ledsOffMins >= 60) ledsOffMins = 0; ledsOff[2] = ledsOffMins / 10; ledsOff[3] = ledsOffMins % 10; EEPROM.put(ledsAddressOffMin, ledsOffMins); displayLedsOffTimer = millis(); } if (results.value == IR_MINUTES_DOWN) { // Set Minute ledsOffMins--; if (ledsOffMins < 0) ledsOffMins = 23; ledsOff[2] = ledsOffMins / 10; ledsOff[3] = ledsOffMins % 10; EEPROM.put(ledsAddressOffMin, ledsOffMins); displayLedsOffTimer = millis(); } } else if (editLedsOn) { if (results.value == IR_HOURS_UP) { //Set Hour ledsOnHours++; if (ledsOnHours >= 24) ledsOnHours = 0; ledsOn[0] = ledsOnHours / 10; ledsOn[1] = ledsOnHours % 10; EEPROM.put(ledsAddressOnHr, ledsOnHours); displayLedsOnTimer = millis(); } if (results.value == IR_HOURS_DOWN) { //Set Hour ledsOnHours--; if (ledsOnHours < 0) ledsOnHours = 23; ledsOn[0] = ledsOnHours / 10; ledsOn[1] = ledsOnHours % 10; EEPROM.put(ledsAddressOnHr, ledsOnHours); displayLedsOnTimer = millis(); } if (results.value == IR_MINUTES_UP) { // Set Minute ledsOnMins++; if (ledsOnMins >= 60) ledsOnMins = 0; ledsOn[2] = ledsOnMins / 10; ledsOn[3] = ledsOnMins % 10; EEPROM.put(ledsAddressOnMin, ledsOnMins); displayLedsOnTimer = millis(); } if (results.value == IR_MINUTES_DOWN) { // Set Minute ledsOnMins--; if (ledsOnMins < 0) ledsOnMins = 23; ledsOn[2] = ledsOnMins / 10; ledsOn[3] = ledsOnMins % 10; EEPROM.put(ledsAddressOnMin, ledsOnMins); displayLedsOnTimer = millis(); } } else if (editAlarm) { if (results.value == IR_HOURS_UP) { //Set Hour alarmHours++; if (alarmHours >= 24) alarmHours = 0; theAlarm[0] = alarmHours / 10; theAlarm[1] = alarmHours % 10; EEPROM.put(alarmAddressHr, alarmHours); displayAlarmTimer = millis(); } if (results.value == IR_HOURS_DOWN) { //Set Hour alarmHours--; if (alarmHours < 0) alarmHours = 23; theAlarm[0] = alarmHours / 10; theAlarm[1] = alarmHours % 10; EEPROM.put(alarmAddressHr, alarmHours); displayAlarmTimer = millis(); } if (results.value == IR_MINUTES_UP) { // Set Minute alarmMins++; if (alarmMins >= 60) alarmMins = 0; theAlarm[2] = alarmMins / 10; theAlarm[3] = alarmMins % 10; EEPROM.put(alarmAddressMin, alarmMins); displayAlarmTimer = millis(); } if (results.value == IR_MINUTES_DOWN) { // Set Minute alarmMins--; if (alarmMins < 0) alarmMins = 23; theAlarm[2] = alarmMins / 10; theAlarm[3] = alarmMins % 10; EEPROM.put(alarmAddressMin, alarmMins); displayAlarmTimer = millis(); } } else if (editDate) { // If date/timeflag is set, set the date. if (results.value == IR_HOURS_UP){ //Set Day days++; if (days > 31) days = 1; theDate[0] = days / 10; theDate[1] = days % 10; rtc.adjust(DateTime(years + 2000, months, days, hours, mins, secs)); // Retain existing seconds value. displayDateTimer = millis(); } if (results.value == IR_HOURS_DOWN){ //Set Day days--; if (days < 1) days = 31; theDate[0] = days / 10; theDate[1] = days % 10; rtc.adjust(DateTime(years + 2000, months, days, hours, mins, secs)); // Retain existing seconds value. displayDateTimer = millis(); } if (results.value == IR_MINUTES_UP) { // Set Month months++; if (months >= 13) months = 1; theDate[2] = months / 10; theDate[3] = months % 10; rtc.adjust(DateTime(years + 2000, months, days, hours, mins, 0)); // Reset seconds to zero when minutes are set. displayDateTimer = millis(); } if (results.value == IR_MINUTES_DOWN) { // Set Month months--; if (months < 1) months = 12; theDate[2] = months / 10; theDate[3] = months % 10; rtc.adjust(DateTime(years + 2000, months, days, hours, mins, 0)); // Reset seconds to zero when minutes are set. displayDateTimer = millis(); } if (results.value == IR_YEAR_UP) { // years++; if (years > 99) years = 0; theDate[4] = years / 10; theDate[5] = years % 10; rtc.adjust(DateTime(years + 2000, months, days, hours, mins, 0)); displayDateTimer = millis(); } if (results.value == IR_YEAR_DOWN) { years--; if (years < 0) years = 99; theDate[4] = years / 10; theDate[5] = years % 10; rtc.adjust(DateTime(years + 2000, months, days, hours, mins, 0)); displayDateTimer = millis(); } } else if ((editTime) || // If Edit Time flag (from IR) is set, set the Time. (digitalRead(setHours_pin) == LOW) || // Manual buttons can set the time without setting IR edit mode. (digitalRead(setMins_pin) == LOW)) { if ((results.value == IR_HOURS_UP) || ((digitalRead(setHours_pin) == LOW) && (millis() > (settingTimer + 250)))){ hours++; if (hours >= 24) hours = 0; theTime[0] = hours / 10; theTime[1] = hours % 10; rtc.adjust(DateTime(years + 2000, months, days, hours, mins, secs)); // Retain existing seconds value. displayTimeTimer = millis(); settingTimer = millis(); } if (results.value == IR_HOURS_DOWN) { hours--; if (hours < 0) hours = 23; theTime[0] = hours / 10; theTime[1] = hours % 10; rtc.adjust(DateTime(years + 2000, months, days, hours, mins, secs)); // Retain existing seconds value. displayTimeTimer = millis(); settingTimer = millis(); } if ((results.value == IR_MINUTES_UP) || ((digitalRead(setMins_pin) == LOW) && (millis() > (settingTimer + 250)))) { mins++; if (mins >= 60) mins = 0; theTime[2] = mins / 10; theTime[3] = mins % 10; secs = 0; // Reset seconds to zero when minutes are set. theTime[4] = secs / 10; theTime[5] = secs % 10; rtc.adjust(DateTime(years + 2000, months, days, hours, mins, secs)); displayTimeTimer = millis(); settingTimer = millis(); } if (results.value == IR_MINUTES_DOWN) { mins--; if (mins < 0) mins = 59; theTime[2] = mins / 10; theTime[3] = mins % 10; secs = 0; // Reset seconds to zero when minutes are set. theTime[4] = secs / 10; theTime[5] = secs % 10; rtc.adjust(DateTime(years + 2000, months, days, hours, mins, secs)); displayTimeTimer = millis(); settingTimer = millis(); } } // Depending on which IR flag is set (if any) display appropriate values on IV-12 tubes. analogWrite(blank, 255); // Blank display while updating registers if (editLedsOff) { show(ledsOff); } else if (editLedsOn) { show(ledsOn); } else if (showTemp) { show(theTemps); } else if (editAlarm) { show(theAlarm); } else if (editDate) { show(theDate); } else show(theTime); analogWrite(blank, displayBlanking); // Set display brightness according to LDR value. grid++; // Advance to the next grid (ie next tube) next time around. if (grid > (NUM_DIGITS - 1)) grid = 0; // Grids are 0 to 5. } void show(byte theDisplay[]) { digitalWrite(strobe, LOW); SPI.transfer(grids[grid]); SPI.transfer(numbers[theDisplay[grid]]); digitalWrite(strobe, HIGH); }
|