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