From 0ae58a8ea5252dbb5df950e1a3dab3c5a2635b7b Mon Sep 17 00:00:00 2001 From: Luke McKenzie Date: Wed, 25 Apr 2018 23:11:33 -0500 Subject: [PATCH 01/13] (Unfinished, see stopping place) Support for LED control; use eeprom update instead of write --- README.md | 1 + sixtube_lm/sixtube_lm.ino | 84 ++++++++++++++++++++++++++------------- 2 files changed, 58 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 9cf2d5e..79437ea 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ _Note: Many variations are possible, depending on your clock's hardware; but thi | 22. Last day of work week | 0–6 (Sunday–Saturday) | | 23. Work starts at | Time of day. | | 24. Work ends at | Time of day. | +| 25. LED behavior | 0 = always off
1 = always on
2 = on, but follow day-off and night-off if enabled
3 = off, but on when alarm/timer sounds
4 = off, but on with switched relay (if equipped – great for radios!)
(Clocks with LED control only, UNDB v5.x+) | To reset the options menu settings to "factory" defaults, hold **Select** while connecting the clock to power. diff --git a/sixtube_lm/sixtube_lm.ino b/sixtube_lm/sixtube_lm.ino index 6cbe769..42d5fcb 100644 --- a/sixtube_lm/sixtube_lm.ino +++ b/sixtube_lm/sixtube_lm.ino @@ -67,6 +67,7 @@ const word switchDur = 7200; //sec - when alarm triggers switched relay, it's sw const word piezoPulse = 500; //ms - used with piezo via tone() const word relayPulse = 200; //ms - used with pulsed relay +//Soft power switches const byte enableSoftAlarmSwitch = 1; // 1 = yes. Alarm can be switched on and off when clock is displaying the alarm time (fnIsAlarm). // 0 = no. Alarm will be permanently on. Use with switched relay if the appliance has its own switch on this relay circuit. @@ -74,7 +75,13 @@ const byte enableSoftPowerSwitch = 1; //works with switched relay only // 1 = yes. Relay can be switched on and off directly when clock is displaying time of day (fnIsTime). This is useful if connecting an appliance (e.g. radio) that doesn't have its own switch, or if replacing the clock unit in a clock radio where the clock does all the switching (e.g. Telechron). // 0 = no. Use if the connected appliance has its own power switch (independent of this relay circuit) or does not need to be manually switched. -const byte unoffDur = 10; //when display is dim/off, a press will light the tubes for this many seconds +//LED circuit control +const char ledPin = -1; +// If running a v5.0 board with constantly powered LEDs, leave this set to -1 (disabled). +// If running a v5.x board, LED control pin is X. An extra menu option will appear to let end user control LEDs. + +//When display is dim/off, a press will light the tubes for how long? +const byte unoffDur = 10; //sec // How long (in ms) are the button hold durations? const word btnShortHold = 1000; //for setting the displayed feataure @@ -118,7 +125,7 @@ Some are skipped when they wouldn't apply to a given clock's hardware config, se 23 Alarm days 24 Alarm snooze 25 Timer interval mode - skipped when no piezo and relay is switch (start=0) -( 26 is available ) + 26 LED circuit behavior 27 Night-off 28-29 Night start, mins 30-31 Night end, mins @@ -138,11 +145,11 @@ Some are skipped when they wouldn't apply to a given clock's hardware config, se //Options menu options' EEPROM locations and default/min/max values. //Options' numbers may be changed by reordering these arrays (and changing readme accordingly). //Although these arrays are 0-index, the option number displayed (and listed in readme) is 1-index. (search for "fn-fnOpts+1") -//1-index option number: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 -const byte optsLoc[] = {16,17,18,19,20,22,23,24,42,39,43,25,40,21,44,41,27, 28, 30,32,33,34, 35, 37}; -const word optsDef[] = { 2, 1, 0, 0, 5, 0, 0, 9, 0,61, 0, 0,61, 0, 0,61, 0,1320, 360, 0, 1, 5, 480,1020}; -const word optsMin[] = { 1, 1, 0, 0, 0, 0, 0, 0, 0,49, 0, 0,49, 0, 0,49, 0, 0, 0, 0, 0, 0, 0, 0}; -const word optsMax[] = { 2, 5, 3, 1,20, 6, 2,60, 1,88, 1, 1,88, 4, 1,88, 2,1439,1439, 2, 6, 6,1439,1439}; +//1-index option number: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 +const byte optsLoc[] = {16,17,18,19,20,22,23,24,42,39,43,25,40,21,44,41,27, 28, 30,32,33,34, 35, 37,26}; +const word optsDef[] = { 2, 1, 0, 0, 5, 0, 0, 9, 0,61, 0, 0,61, 0, 0,61, 0,1320, 360, 0, 1, 5, 480,1020, 1}; +const word optsMin[] = { 1, 1, 0, 0, 0, 0, 0, 0, 0,49, 0, 0,49, 0, 0,49, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const word optsMax[] = { 2, 5, 3, 1,20, 6, 2,60, 1,88, 1, 1,88, 4, 1,88, 2,1439,1439, 2, 6, 6,1439,1439, 4}; //RTC objects DS3231 ds3231; //an object to access the ds3231 specifically (temp, etc) @@ -183,7 +190,6 @@ char scrollRemain = 0; //"frames" of scroll – 0=not scrolling, >0=coming in, < void setup(){ Serial.begin(9600); Wire.begin(); - initOutputs(); initInputs(); delay(100); //prevents the below from firing in the event there's a capacitor stabilizing the input, which can read low falsely initEEPROM(readInput(mainSel)==LOW); //Do a hard init of EEPROM if button is held; else do a soft init to make sure vals in range @@ -202,6 +208,7 @@ void setup(){ writeEEPROM(25,0,false); //turn off timer interval mode } if(!enableSoftAlarmSwitch) writeEEPROM(2,1,false); //force alarm on if software switch is disabled + initOutputs(); //depends on some EEPROM settings } unsigned long pollLast = 0; //every 50ms @@ -449,6 +456,8 @@ void ctrlEvt(byte ctrl, byte evt){ if(relayPin>=0 && relayMode==0 && readEEPROM(43,false)==1) { //if switched relay, and timer signal is set to it if(relayPin!=127) digitalWrite(relayPin,HIGH); Serial.print(millis(),DEC); Serial.println(F(" Relay on, timer set")); + if(readEEPROM(26,false)==4) switchLEDs(1); //LEDs following switch relay + //TODO will this cancel properly? especially if alarm interrupts? } clearSet(); break; case fnIsDayCount: //set like date, save in eeprom like finishOpt @@ -608,23 +617,18 @@ word readEEPROM(int loc, bool isWord){ } void writeEEPROM(int loc, int val, bool isWord){ if(isWord) { - Serial.print(F("EEPROM write word:")); - Serial.print(F(" loc ")); Serial.print(loc,DEC); - if(EEPROM.read(loc)!=highByte(val)) { - EEPROM.write(loc,highByte(val)); - Serial.print(F(" val ")); Serial.print(highByte(val),DEC); - } else Serial.print(F(" unchanged (no write).")); - Serial.print(F(" loc ")); Serial.print(loc+1,DEC); - if(EEPROM.read(loc+1)!=lowByte(val)) { - EEPROM.write(loc+1,lowByte(val)); - Serial.print(F(" val ")); Serial.print(lowByte(val),DEC); - } else Serial.print(F(" unchanged (no write).")); + // Serial.print(F("EEPROM write word:")); + // Serial.print(F(" loc ")); Serial.print(loc,DEC); Serial.print(F(" val ")); + // if(EEPROM.read(loc)!=highByte(val)) { Serial.print(highByte(val),DEC); } else { Serial.print(F("no change")); } + EEPROM.update(loc,highByte(val)); + // Serial.print(F(" loc ")); Serial.print(loc+1,DEC); Serial.print(F(" val ")); + // if(EEPROM.read(loc+1)!=lowByte(val)) { Serial.print(lowByte(val),DEC); } else { Serial.print(F("no change")); } + EEPROM.update(loc+1,lowByte(val)); } else { - Serial.print(F("EEPROM write byte:")); - Serial.print(F(" loc ")); Serial.print(loc,DEC); - if(EEPROM.read(loc)!=val) { EEPROM.write(loc,val); - Serial.print(F(" val ")); Serial.print(val,DEC); - } else Serial.print(F(" unchanged (no write).")); + // Serial.print(F("EEPROM write byte:")); + // Serial.print(F(" loc ")); Serial.print(loc,DEC); Serial.print(F(" val ")); + // if(EEPROM.read(loc)!=val) { Serial.print(val,DEC); } else { Serial.print(F("no change")); } + EEPROM.update(loc,val); } Serial.println(); } @@ -828,9 +832,11 @@ void switchAlarm(char dir){ void switchPower(char dir){ if(enableSoftPowerSwitch && relayPin>=0 && relayMode==0) { //if switched relay, and soft switch enabled //signalStop(); could use this instead of the below to turn the radio off + if(dir==0) dir = !digitalRead(relayPin); if(relayPin!=127) { - digitalWrite(relayPin,(dir==1?HIGH:(dir==-1?LOW:!digitalRead(relayPin)))); + digitalWrite(relayPin,(dir==1?HIGH:LOW)); } + if(readEEPROM(26,false)==4) switchLEDs(dir); //LEDs following switch relay Serial.print(millis(),DEC); Serial.print(F(" Relay ")); if(dir==0) { Serial.print(F("toggled")); if(relayPin!=127) { Serial.print(digitalRead(relayPin)==HIGH?F(" on"):F(" off")); } } @@ -1007,7 +1013,7 @@ void updateDisplay(){ editDisplay(tod.second(),5,5,true,false); default: break; }//end switch - } + } //end if fn running } //end updateDisplay() void editDisplay(word n, byte posStart, byte posEnd, bool leadingZeros, bool fade){ @@ -1060,6 +1066,8 @@ void initOutputs() { for(byte i=0; i<3; i++) { pinMode(anodes[i],OUTPUT); } if(piezoPin>=0) pinMode(piezoPin, OUTPUT); if(relayPin>=0 && relayPin!=127) pinMode(relayPin, OUTPUT); + if(ledPin>=0) pinMode(ledPin, OUTPUT); + updateLEDs(); //set to initial value } void cycleDisplay(){ @@ -1171,8 +1179,10 @@ void signalStart(byte sigFn, byte sigDur, word pulseDur){ //make some noise! or if(getSignalOutput()==1 && relayPin>=0 && relayMode==0) { //switched relay: turn it on now if(relayPin!=127) digitalWrite(relayPin,HIGH); Serial.print(millis(),DEC); Serial.println(F(" Relay on, signalStart")); + if(readEEPROM(26,false)==4) switchLEDs(1); //LEDs following switch relay } } + if(readEEPROM(26,false)==3) switchLEDs(1); //LEDs following signal } void signalStop(){ //stop current signal and clear out signal timer if applicable signalRemain = 0; snoozeRemain = 0; @@ -1180,7 +1190,9 @@ void signalStop(){ //stop current signal and clear out signal timer if applicabl if(getSignalOutput()==1 && relayPin>=0 && relayMode==0) { //switched relay: turn it off now if(relayPin!=127) digitalWrite(relayPin,LOW); Serial.print(millis(),DEC); Serial.println(F(" Relay off, signalStop")); + if(readEEPROM(26,false)==4) switchLEDs(0); //LEDs following switch relay } + if(readEEPROM(26,false)==3) switchLEDs(0); //LEDs following relay } //beep start and stop should only be called by signalStart/signalStop and checkRTC void signalPulseStart(word pulseDur){ @@ -1216,4 +1228,22 @@ word getHz(byte note){ } char getSignalOutput(){ //for current signal: time, timer, or (default) alarm: 0=piezo, 1=relay return readEEPROM((signalSource==fnIsTime?44:(signalSource==fnIsTimer?43:42)),false); -} \ No newline at end of file +} + +void updateLEDs(){ + //Run whenever something is changed that might affect the LED state + if(ledPin>=0) switch(readEEPROM(26,false)){ + case 0: //always off + digitalWrite(ledPin,LOW); break; + case 1: //always on + digitalWrite(ledPin,HIGH); break; + case 2: //on, but follow day-off and night-off + digitalWrite(ledPin,(displayDim<2?LOW:HIGH)); break; + case 3: //off, but on when alarm/timer sounds + digitalWrite(ledPin,(signalRemain && (signalSource==fnIsAlarm || signalSource==fnIsTimer)?HIGH:LOW)); break; + case 4: //off, but on with switched relay + if(relayPin>=0 && relayPin<127 && relayMode==0) digitalWrite(ledPin,digitalRead(relayPin)); break; + //Stopping place: Change all the updateLEDs to not have a polarity, add some more, and hide menu 25 when not equipped, and value 4 when not equipped + default: break; + } +} //end updateLEDs \ No newline at end of file From dc16152700280daa29630888f4ebe0001dcbbd99 Mon Sep 17 00:00:00 2001 From: Luke McKenzie Date: Tue, 22 May 2018 21:51:09 -0500 Subject: [PATCH 02/13] Temp option + persistent display + write display to console, for testing ; further (untested) work on LED circuit switching --- README.md | 3 +- sixtube_lm/sixtube_lm.ino | 141 +++++++++++++++++++++++++------------- 2 files changed, 95 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 79437ea..05dd8ad 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ _Note: Many variations are possible, depending on your clock's hardware; but thi | **Alarm** | `_7 00 1_` | Shows alarm time (always in 24hr format) and on/off status on 5th tube (1=on, 0=off) and by display brightness (bright=on, dim=off). Use **Adjust** to switch on/off. Hold **Select** to set time (same way as **Time**). When alarm sounds, press **Select** to snooze, or hold for 1sec (followed by a short beep) to silence the alarm for the day. Options menu lets you restrict the alarm to your workweek or weekend only. In a power outage, the alarm will remain set, but it will not sound if power is disconnected at alarm time. | | **Timer** | `__ __ _0` | A countdown timer, in hours, minutes, and seconds; or `0` when stopped. Can be set to the minute, up to 18 hours. Begins running as soon as you set it, and will continue to run in the background if you change to a different function. To cancel while running, hold **Select**. When timer runs out, press **Select** to silence. If power is lost, the timer will reset to `0`. Can be configured to work as an interval timer in the options menu (10), or as an appliance timer instead ([relay in switched mode](#hardware-configuration)). | | **Day counter** | `_1 23 __` | Shows the number of days until/since a date you specify. Set the same way as **Date.** | -| **Temperature** | `__ 38 25` | (Disabled by default.) Shows the temperature of the onboard DS3231 chip (e.g. 38.25°C – I think). May not be very useful as it tends to read higher than ambient temperature and its tolerance is low. | +| **Temperature** | `__ 38 25` | Shows the temperature of the onboard DS3231 chip (e.g. 38.25°C – I think). May not be very useful as it tends to read higher than ambient temperature and its tolerance is low. Negative temperatures indicated with leading zeroes. | | **Tube tester** | `88 88 88` | (Disabled by default.) Cycles through all the digits on all the tubes. | ### Options Menu @@ -58,6 +58,7 @@ _Note: Many variations are possible, depending on your clock's hardware; but thi | 23. Work starts at | Time of day. | | 24. Work ends at | Time of day. | | 25. LED behavior | 0 = always off
1 = always on
2 = on, but follow day-off and night-off if enabled
3 = off, but on when alarm/timer sounds
4 = off, but on with switched relay (if equipped – great for radios!)
(Clocks with LED control only, UNDB v5.x+) | +| 26. Temperature format | 0 = Celsius
1 = Fahrenheit
(Clocks with temperature function enabled only) | To reset the options menu settings to "factory" defaults, hold **Select** while connecting the clock to power. diff --git a/sixtube_lm/sixtube_lm.ino b/sixtube_lm/sixtube_lm.ino index 42d5fcb..e8f69e0 100644 --- a/sixtube_lm/sixtube_lm.ino +++ b/sixtube_lm/sixtube_lm.ino @@ -24,7 +24,8 @@ const byte fnIsDayCount = 4; const byte fnIsTemp = 5; const byte fnIsTubeTester = 6; //cycles all digits on all tubes 1/second, similar to anti-cathode-poisoning cleaner // functions enabled in this clock, in their display order. Only fnIsTime is required -const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsAlarm, fnIsTimer, fnIsDayCount}; //, fnIsTemp, fnIsTubeTester +const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsAlarm, fnIsTimer, fnIsDayCount, fnIsTemp}; //, fnIsTemp, fnIsTubeTester +// To control which of these display persistently vs. switch back to Time after a few seconds, search "Temporary-display mode timeout" // These are the RLB board connections to Arduino analog input pins. // S1/PL13 = Reset @@ -58,7 +59,7 @@ const char piezoPin = 10; const char relayPin = 127; // If running a v5.0 board with only a piezo output, leave these set to 10 and -1 (disabled) respectively - unless removing the piezo to drive a relay instead, in which case, reverse them. // If running a v5.x board with both piezo and relay, piezo is 10, relay is X. Extra menu options will appear to let end user decide which functions control which outputs. -// Relay toggles are written to the serial console for testing. To test without relay present, set relayPin to 127. +// To test without relay/load present, set relayPin to 127 to have relay commands written to console instead. const byte relayMode = 0; //If relay is equipped, what does it do? // 0 = switched mode: the relay will be switched to control an appliance like a radio or light fixture. If used with timer, it will switch on while timer is running (like a "sleep" function). If used with alarm, it will switch on when alarm trips; specify duration of this in switchDur. // 1 = pulsed mode: the relay will be pulsed, like the beeper is, to control an intermittent signaling device like a solenoid or indicator lamp. Specify pulse duration in relayPulse. @@ -76,9 +77,10 @@ const byte enableSoftPowerSwitch = 1; //works with switched relay only // 0 = no. Use if the connected appliance has its own power switch (independent of this relay circuit) or does not need to be manually switched. //LED circuit control -const char ledPin = -1; +const char ledPin = 127; // If running a v5.0 board with constantly powered LEDs, leave this set to -1 (disabled). // If running a v5.x board, LED control pin is X. An extra menu option will appear to let end user control LEDs. +// To test without LED circuit present, set relayPin to 127 to have LED switch commands written to console instead. //When display is dim/off, a press will light the tubes for how long? const byte unoffDur = 10; //sec @@ -125,7 +127,7 @@ Some are skipped when they wouldn't apply to a given clock's hardware config, se 23 Alarm days 24 Alarm snooze 25 Timer interval mode - skipped when no piezo and relay is switch (start=0) - 26 LED circuit behavior + 26 LED circuit behavior - skipped when no led pin 27 Night-off 28-29 Night start, mins 30-31 Night end, mins @@ -140,16 +142,17 @@ Some are skipped when they wouldn't apply to a given clock's hardware config, se 42 Alarm signal, 0=beeper, 1=relay - skipped when no relay (start=0) or no piezo (start=0) 43 Timer signal - skipped when no relay (start=0) or no piezo (start=1) 44 Strike signal - skipped when no pulse relay (start=0) or no piezo (start=1) + 45 Temperature format - skipped when fnIsTemp is not in fnsEnabled */ //Options menu options' EEPROM locations and default/min/max values. //Options' numbers may be changed by reordering these arrays (and changing readme accordingly). //Although these arrays are 0-index, the option number displayed (and listed in readme) is 1-index. (search for "fn-fnOpts+1") -//1-index option number: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 -const byte optsLoc[] = {16,17,18,19,20,22,23,24,42,39,43,25,40,21,44,41,27, 28, 30,32,33,34, 35, 37,26}; -const word optsDef[] = { 2, 1, 0, 0, 5, 0, 0, 9, 0,61, 0, 0,61, 0, 0,61, 0,1320, 360, 0, 1, 5, 480,1020, 1}; -const word optsMin[] = { 1, 1, 0, 0, 0, 0, 0, 0, 0,49, 0, 0,49, 0, 0,49, 0, 0, 0, 0, 0, 0, 0, 0, 0}; -const word optsMax[] = { 2, 5, 3, 1,20, 6, 2,60, 1,88, 1, 1,88, 4, 1,88, 2,1439,1439, 2, 6, 6,1439,1439, 4}; +//1-index option number: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 +const byte optsLoc[] = {16,17,18,19,20,22,23,24,42,39,43,25,40,21,44,41,27, 28, 30,32,33,34, 35, 37,26,45}; +const word optsDef[] = { 2, 1, 0, 0, 5, 0, 0, 9, 0,61, 0, 0,61, 0, 0,61, 0,1320, 360, 0, 1, 5, 480,1020, 1, 0}; +const word optsMin[] = { 1, 1, 0, 0, 0, 0, 0, 0, 0,49, 0, 0,49, 0, 0,49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const word optsMax[] = { 2, 5, 3, 1,20, 6, 2,60, 1,88, 1, 1,88, 4, 1,88, 2,1439,1439, 2, 6, 6,1439,1439, 4, 1}; //RTC objects DS3231 ds3231; //an object to access the ds3231 specifically (temp, etc) @@ -208,6 +211,7 @@ void setup(){ writeEEPROM(25,0,false); //turn off timer interval mode } if(!enableSoftAlarmSwitch) writeEEPROM(2,1,false); //force alarm on if software switch is disabled + //if LED circuit is not switched (v5.0 board), the LED menu setting (eeprom 26) doesn't matter initOutputs(); //depends on some EEPROM settings } @@ -455,8 +459,8 @@ void ctrlEvt(byte ctrl, byte evt){ timerRemain = timerInitial; //set actual timer going if(relayPin>=0 && relayMode==0 && readEEPROM(43,false)==1) { //if switched relay, and timer signal is set to it if(relayPin!=127) digitalWrite(relayPin,HIGH); - Serial.print(millis(),DEC); Serial.println(F(" Relay on, timer set")); - if(readEEPROM(26,false)==4) switchLEDs(1); //LEDs following switch relay + else { Serial.print(millis(),DEC); Serial.println(F(" Relay on, timer set")); } + updateLEDs(); //LEDs following switch relay //TODO will this cancel properly? especially if alarm interrupts? } clearSet(); break; @@ -541,7 +545,15 @@ void fnOptScroll(char dir){ || ((piezoPin<0 && relayMode==0) && (optLoc==21||optLoc==25)) //no piezo, and relay is switch: no strike or timer interval mode || ((relayPin<0 || piezoPin<0) && (optLoc==42||optLoc==43||optLoc==44)) //no relay or no piezo: no alarm/timer/strike signal || ((relayMode==0) && (optLoc==44)) //relay is switch: no strike signal - ) fnOptScroll(dir); + || ((ledPin<0) && (optLoc==26)) //no led pin: no led control + ) { + fnOptScroll(dir); + } + if(optLoc==45) { //temp not in fnsEnabled: skip temp format option (and calib if we get to it TODO) + bool found = 0; + for(byte fnct=0; fnct=fnOpts){ if(pollLast>inputLast+(timeoutSet*1000)) { fnSetPg = 0; fn = fnIsTime; force=true; } //Time out after 2 mins } - //Temporary-display mode timeout: if we're *not* in a permanent one (time, day counter, tester, or running/signaling timer) - else if(fn!=fnIsTime && fn!=fnIsTubeTester && fn!=fnIsDayCount && !(fn==fnIsTimer && (timerRemain>0 || signalRemain>0))){ + //Temporary-display mode timeout: if we're *not* in a permanent one (time, day counter, temp, tester, or running/signaling timer) + else if(fn!=fnIsTime && fn!=fnIsTubeTester && fn!=fnIsDayCount && fn!=fnIsTemp && !(fn==fnIsTimer && (timerRemain>0 || signalRemain>0))){ if(pollLast>inputLast+(timeoutTempFn*1000)) { fnSetPg = 0; fn = fnIsTime; force=true; } } //Stop a signal beep if it's time to @@ -833,15 +846,15 @@ void switchPower(char dir){ if(enableSoftPowerSwitch && relayPin>=0 && relayMode==0) { //if switched relay, and soft switch enabled //signalStop(); could use this instead of the below to turn the radio off if(dir==0) dir = !digitalRead(relayPin); - if(relayPin!=127) { - digitalWrite(relayPin,(dir==1?HIGH:LOW)); + if(relayPin!=127) digitalWrite(relayPin,(dir==1?HIGH:LOW)); + else { + Serial.print(millis(),DEC); + Serial.print(F(" Relay ")); + if(dir==0) { Serial.print(F("toggled")); if(relayPin!=127) { Serial.print(digitalRead(relayPin)==HIGH?F(" on"):F(" off")); } } + else Serial.print(dir==1?F("switched on"):F("switched off")); + Serial.println(F(", switchPower")); } - if(readEEPROM(26,false)==4) switchLEDs(dir); //LEDs following switch relay - Serial.print(millis(),DEC); - Serial.print(F(" Relay ")); - if(dir==0) { Serial.print(F("toggled")); if(relayPin!=127) { Serial.print(digitalRead(relayPin)==HIGH?F(" on"):F(" off")); } } - else Serial.print(dir==1?F("switched on"):F("switched off")); - Serial.println(F(", switchPower")); + updateLEDs(); //LEDs following switch relay } } @@ -1001,6 +1014,8 @@ void updateDisplay(){ break; case fnIsTemp: //thermometer int temp; temp = ds3231.getTemperature()*100; + if(readEEPROM(45,false)==1) temp = temp*1.8 + 3200; + //TODO another option to apply offset editDisplay(abs(temp)/100,1,3,(temp<0?true:false),true); //leading zeros if negative editDisplay(abs(temp)%100,4,5,true,true); break; @@ -1013,7 +1028,26 @@ void updateDisplay(){ editDisplay(tod.second(),5,5,true,false); default: break; }//end switch - } //end if fn running + } //end if fn running + + if(true) { //DEBUG MODE: when display's not working, just write it to the console, with time + if(tod.hour()<10) Serial.print(F("0")); + Serial.print(tod.hour(),DEC); + Serial.print(F(":")); + if(tod.minute()<10) Serial.print(F("0")); + Serial.print(tod.minute(),DEC); + Serial.print(F(":")); + if(tod.second()<10) Serial.print(F("0")); + Serial.print(tod.second(),DEC); + Serial.print(F(" ")); + for(byte i=0; i9) Serial.print(F("-")); //blanked tube + else Serial.print(displayNext[i],DEC); + } + Serial.println(); + } + } //end updateDisplay() void editDisplay(word n, byte posStart, byte posEnd, bool leadingZeros, bool fade){ @@ -1066,7 +1100,7 @@ void initOutputs() { for(byte i=0; i<3; i++) { pinMode(anodes[i],OUTPUT); } if(piezoPin>=0) pinMode(piezoPin, OUTPUT); if(relayPin>=0 && relayPin!=127) pinMode(relayPin, OUTPUT); - if(ledPin>=0) pinMode(ledPin, OUTPUT); + if(ledPin>=0 && ledPin!=127) pinMode(ledPin, OUTPUT); updateLEDs(); //set to initial value } @@ -1178,21 +1212,19 @@ void signalStart(byte sigFn, byte sigDur, word pulseDur){ //make some noise! or //piezo or pulsed relay: checkRTC will handle it if(getSignalOutput()==1 && relayPin>=0 && relayMode==0) { //switched relay: turn it on now if(relayPin!=127) digitalWrite(relayPin,HIGH); - Serial.print(millis(),DEC); Serial.println(F(" Relay on, signalStart")); - if(readEEPROM(26,false)==4) switchLEDs(1); //LEDs following switch relay + else { Serial.print(millis(),DEC); Serial.println(F(" Relay on, signalStart")); } } } - if(readEEPROM(26,false)==3) switchLEDs(1); //LEDs following signal + updateLEDs(); //LEDs following signal or relay } void signalStop(){ //stop current signal and clear out signal timer if applicable signalRemain = 0; snoozeRemain = 0; signalPulseStop(); //piezo or pulsed relay: stop now if(getSignalOutput()==1 && relayPin>=0 && relayMode==0) { //switched relay: turn it off now if(relayPin!=127) digitalWrite(relayPin,LOW); - Serial.print(millis(),DEC); Serial.println(F(" Relay off, signalStop")); - if(readEEPROM(26,false)==4) switchLEDs(0); //LEDs following switch relay + else { Serial.print(millis(),DEC); Serial.println(F(" Relay off, signalStop")); } } - if(readEEPROM(26,false)==3) switchLEDs(0); //LEDs following relay + updateLEDs(); //LEDs following relay } //beep start and stop should only be called by signalStart/signalStop and checkRTC void signalPulseStart(word pulseDur){ @@ -1202,8 +1234,8 @@ void signalPulseStart(word pulseDur){ } else if(getSignalOutput()==1 && relayPin>=0 && relayMode==1) { //pulsed relay if(relayPin!=127) digitalWrite(relayPin,HIGH); + else { Serial.print(millis(),DEC); Serial.println(F(" Relay on, signalPulseStart")); } signalPulseStopTime = millis()+relayPulse; //always use relayPulse in case timing is important for connected device - Serial.print(millis(),DEC); Serial.println(F(" Relay on, signalPulseStart")); } } void signalPulseStop(){ @@ -1212,8 +1244,8 @@ void signalPulseStop(){ } else if(getSignalOutput()==1 && relayPin>=0 && relayMode==1) { //pulsed relay if(relayPin!=127) digitalWrite(relayPin,LOW); + else { Serial.print(millis(),DEC); Serial.println(F(" Relay off, signalPulseStop")); } signalPulseStopTime = 0; - Serial.print(millis(),DEC); Serial.println(F(" Relay off, signalPulseStart")); } } word getSignalPitch(){ //for current signal: time, timer, or (default) alarm @@ -1231,19 +1263,32 @@ char getSignalOutput(){ //for current signal: time, timer, or (default) alarm: 0 } void updateLEDs(){ - //Run whenever something is changed that might affect the LED state - if(ledPin>=0) switch(readEEPROM(26,false)){ - case 0: //always off - digitalWrite(ledPin,LOW); break; - case 1: //always on - digitalWrite(ledPin,HIGH); break; - case 2: //on, but follow day-off and night-off - digitalWrite(ledPin,(displayDim<2?LOW:HIGH)); break; - case 3: //off, but on when alarm/timer sounds - digitalWrite(ledPin,(signalRemain && (signalSource==fnIsAlarm || signalSource==fnIsTimer)?HIGH:LOW)); break; - case 4: //off, but on with switched relay - if(relayPin>=0 && relayPin<127 && relayMode==0) digitalWrite(ledPin,digitalRead(relayPin)); break; - //Stopping place: Change all the updateLEDs to not have a polarity, add some more, and hide menu 25 when not equipped, and value 4 when not equipped - default: break; - } + //Run whenever something is changed that might affect the LED state: initial (initOutputs), signal start/stop, relay on/off, setting change + if(ledPin>=0) { + switch(readEEPROM(26,false)){ + case 0: //always off + if(ledPin!=127) digitalWrite(ledPin,LOW); + else { Serial.println(F("LEDs off always")); } + break; + case 1: //always on + if(ledPin!=127) digitalWrite(ledPin,HIGH); + else { Serial.println(F("LEDs on always")); } + break; + case 2: //on, but follow day-off and night-off + if(ledPin!=127) digitalWrite(ledPin,(displayDim<2?LOW:HIGH)); + else { Serial.print(displayDim<2?F("LEDs off"):F("LEDs on")); Serial.println(F(" per dim state")); } + break; + case 3: //off, but on when alarm/timer sounds + if(ledPin!=127) digitalWrite(ledPin,(signalRemain && (signalSource==fnIsAlarm || signalSource==fnIsTimer)?HIGH:LOW)); + else { Serial.print(signalRemain && (signalSource==fnIsAlarm || signalSource==fnIsTimer)?F("LEDs on"):F("LEDs off")); Serial.println(F(" per alarm/timer")); } + break; + case 4: //off, but on with switched relay + if(relayPin>=0 && relayMode==0) { + if(ledPin!=127) digitalWrite(ledPin,digitalRead(relayPin)); + else { Serial.println(F("LEDs follow switched relay")); } //can't actually say in case relayPin is 127 + } + break; + default: break; + } //end switch + } //if ledPin } //end updateLEDs \ No newline at end of file From 9348917a5b8991e9aee46de9c4ebfc567c4791d8 Mon Sep 17 00:00:00 2001 From: Luke McKenzie Date: Thu, 5 Jul 2018 20:35:25 -0500 Subject: [PATCH 03/13] External config files, option number changes, v8 config file set up for input mod testing --- README.md | 68 ++++---- sixtube_lm/configs/v5-4tube.h | 76 +++++++++ sixtube_lm/configs/v5-6tube-rotary.h | 76 +++++++++ sixtube_lm/configs/v5-6tube.h | 76 +++++++++ sixtube_lm/configs/v8-6tube-relayswitch.h | 83 +++++++++ sixtube_lm/sixtube_lm.ino | 195 +++++++--------------- 6 files changed, 404 insertions(+), 170 deletions(-) create mode 100644 sixtube_lm/configs/v5-4tube.h create mode 100644 sixtube_lm/configs/v5-6tube-rotary.h create mode 100644 sixtube_lm/configs/v5-6tube.h create mode 100644 sixtube_lm/configs/v8-6tube-relayswitch.h diff --git a/README.md b/README.md index 05dd8ad..0c4e1c9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Nixie clocks](https://i.imgur.com/FemMWax.jpg) -**A digital clock with perpetual calendar, alarm, countdown timer/appliance timer, and day counter.** Written for the Arduino Nano at the heart of [RLB Designs'](http://rlb-designs.com/) Universal Nixie Driver Board (UNDB) v5.0, featuring a DS3231 real-time clock, and driving up to 6 digits multiplexed in pairs via two SN74141 driver chips. +**A digital clock with perpetual calendar, alarm, countdown timer/appliance timer, and day counter.** Written for the Arduino Nano at the heart of [RLB Designs'](http://rlb-designs.com/) Universal Nixie Driver Board (UNDB), featuring a DS3231 real-time clock, and driving up to 6 digits multiplexed in pairs via two SN74141 driver chips. Includes support for LED and relay control for UNDB v8+. [The latest release can be downloaded here.](https://github.com/clockspot/arduino-nixie/releases/latest) Skip to [Hardware Configuration](#hardware-configuration) for details on how to tweak the sketch. @@ -31,40 +31,46 @@ _Note: Many variations are possible, depending on your clock's hardware; but thi * Use **Adjust** to go to the option number you want to set (see table below); press **Select** to open it for setting (display will flash); use **Adjust** to set; and **Select** to save. * When all done, hold **Select** to exit the options menu. -| Option | Settings | -| --- | --- | -| 1. Time format | 1 = 12-hour
2 = 24-hour
(time-of-day display only; setting times is always done in 24h) | -| 2. Date format | 1 = month/date/weekday
2 = date/month/weekday
3 = month/date/year
4 = date/month/year
5 = year/month/date
Note that four-tube clocks will display only the first two values in each of these options. | -| 3. Display date during time? | 0 = never
1 = date instead of seconds
2 = full date (as above) every minute at :30 seconds
3 = same as 2, but scrolls in and out | -| 4. Leading zero in hour, date, and month? | 0 = no
1 = yes | -| 5. Digit fade | 0–20 (in hundredths of a second) | -| 6. Auto DST | Add 1h for daylight saving time between these dates (at 2am):
0 = off
1 = second Sunday in March to first Sunday in November (US/CA)
2 = last Sunday in March to last Sunday in October (UK/EU)
3 = first Sunday in April to last Sunday in October (MX)
4 = last Sunday in September to first Sunday in April (NZ)
5 = first Sunday in October to first Sunday in April (AU)
6 = third Sunday in October to third Sunday in February (BZ) | -| 7. Alarm days | 0 = every day
1 = work week only (per settings below)
2 = weekend only | -| 8. Alarm snooze | 0–60 minutes. 0 disables snooze. | -| 9. Alarm signal | 0 = beeper
1 = relay (if in switch mode, will stay on for 2 hours)
(Clocks with both beeper and relay only) | -| 10. Alarm beeper pitch | [Note number on a piano keyboard](https://en.wikipedia.org/wiki/Piano_key_frequencies), from 49 (A4) to 88 (C8). Some are louder than others!
(Clocks with beeper only) | -| 11. Timer signal | 0 = beeper
1 = relay (if in switch mode, will stay on until timer runs down)
(Clocks with both beeper and relay only) | -| 12. Timer interval mode | 0 = count down and stop
1 = count down and restart (interval mode)
(Clocks with beeper and/or pulse relay only) | -| 13. Timer beeper pitch | Set the same way as the alarm pitch, above
(Clocks with beeper only) | -| 14. Strike | Make noise on the hour:
0 = off
1 = single beep
2 = pips
3 = strike the hour (1 to 12)
4 = ship's bell (hour and half hour)
Will not sound during day-off/night-off (except when off starts at top of hour)
(Clocks with beeper or pulse relay only) | -| 15. Strike signal | 0 = beeper
1 = relay
(Clocks with both beeper and pulse relay only) | -| 16. Strike beeper pitch | Set the same way as the alarm signal pitch, above. If using the pips, 63 (987 Hz) is closest to the real BBC pips frequency (1000 Hz).
(Clocks with beeper only) | -| 17. Night-off | To save tube life and/or preserve your sleep, dim or shut off tubes nightly when you're not around or sleeping.
0 = none (tubes fully on at night)
1 = dim tubes at night
2 = shut off tubes at night
When off, you can press **Select** to illuminate the tubes briefly. | -| 18. Night starts at | Time of day. | -| 19. Night ends at | Time of day. Set to 0:00 to use the alarm time. At this time (whether night-off/alarm is enabled or not), all tubes will briefly cycle through all digits at full brightness to help prevent [cathode poisoning](http://www.tube-tester.com/sites/nixie/different/cathode%20poisoning/cathode-poisoning.htm). | -| 20. Day-off | To further save tube life, shut off tubes during the day when you're not around.
0 = none (tubes fully on during the day)
1 = clock at work (shut off all day on weekends)
2 = clock at home (shut off during work hours)
When off, you can press **Select** to illuminuate the tubes briefly. | -| 21. First day of work week | 0–6 (Sunday–Saturday) | -| 22. Last day of work week | 0–6 (Sunday–Saturday) | -| 23. Work starts at | Time of day. | -| 24. Work ends at | Time of day. | -| 25. LED behavior | 0 = always off
1 = always on
2 = on, but follow day-off and night-off if enabled
3 = off, but on when alarm/timer sounds
4 = off, but on with switched relay (if equipped – great for radios!)
(Clocks with LED control only, UNDB v5.x+) | -| 26. Temperature format | 0 = Celsius
1 = Fahrenheit
(Clocks with temperature function enabled only) | +| | Option | Settings | +| --- | --- | --- | +| | **General** | | +| 1 | Time format | 1 = 12-hour
2 = 24-hour
(time-of-day display only; setting times is always done in 24h) | +| 2 | Date format | 1 = month/date/weekday
2 = date/month/weekday
3 = month/date/year
4 = date/month/year
5 = year/month/date
Note that four-tube clocks will display only the first two values in each of these options. | +| 3 | Display date during time? | 0 = never
1 = date instead of seconds
2 = full date (as above) every minute at :30 seconds
3 = same as 2, but scrolls in and out
4 = full date every 5 minutes at :30 seconds
3 = same as 4, but scrolls in and out | +| 4 | Leading zero in hour, date, and month? | 0 = no
1 = yes | +| 5 | Digit fade | 0–20 (in hundredths of a second) | +| 6 | Auto DST | Add 1h for daylight saving time between these dates (at 2am):
0 = off
1 = second Sunday in March to first Sunday in November (US/CA)
2 = last Sunday in March to last Sunday in October (UK/EU)
3 = first Sunday in April to last Sunday in October (MX)
4 = last Sunday in September to first Sunday in April (NZ)
5 = first Sunday in October to first Sunday in April (AU)
6 = third Sunday in October to third Sunday in February (BZ) | +| 7 | LED behavior | 0 = always off
1 = always on
2 = on, but follow day-off and night-off if enabled
3 = off, but on when alarm/timer sounds
4 = off, but on with switched relay (if equipped – great for radios!)
(Clocks with LED control only, UNDB v8+) | +| 8 | Temperature format | 0 = Celsius
1 = Fahrenheit
(Clocks with temperature function enabled only) | +| 9 | Anti-cathode poisoning | Briefly cycles all digits to prevent [cathode poisoning](http://www.tube-tester.com/sites/nixie/different/cathode%20poisoning/cathode-poisoning.htm):
0 = once a day, at "Night ends at" time
1 = once a day, at midnight
2 = every hour, at minute :01
3 = every minute, at second :01 | +| | **Alarm** | | +| 10 | Alarm days | 0 = every day
1 = work week only (per settings below)
2 = weekend only | +| 11 | Alarm signal | 0 = beeper
1 = relay (if in switch mode, will stay on for 2 hours)
(Clocks with both beeper and relay only) | +| 12 | Alarm beeper pitch | [Note number on a piano keyboard](https://en.wikipedia.org/wiki/Piano_key_frequencies), from 49 (A4) to 88 (C8). Some are louder than others!
(Clocks with beeper only) | +| 13 | Alarm snooze | 0–60 minutes. 0 disables snooze. | +| | **Timer** | | +| 20 | Timer interval mode | 0 = count down and stop
1 = count down and restart (interval mode)
(Clocks with beeper and/or pulse relay only) | +| 21 | Timer signal | 0 = beeper
1 = relay (if in switch mode, will stay on until timer runs down)
(Clocks with both beeper and relay only) | +| 22 | Timer beeper pitch | Set the same way as the alarm pitch, above
(Clocks with beeper only) | +| | **Strike** | | +| 30 | Strike | Make noise on the hour:
0 = off
1 = single beep
2 = pips
3 = strike the hour (1 to 12)
4 = ship's bell (hour and half hour)
Will not sound during day-off/night-off (except when off starts at top of hour)
(Clocks with beeper or pulse relay only) | +| 31 | Strike signal | 0 = beeper
1 = relay
(Clocks with both beeper and pulse relay only) | +| 32 | Strike beeper pitch | Set the same way as the alarm signal pitch, above. If using the pips, 63 (987 Hz) is closest to the real BBC pips frequency (1000 Hz).
(Clocks with beeper only) | +| | **Night-off and day-off** | | +| 40 | Night-off | To save tube life and/or preserve your sleep, dim or shut off tubes nightly when you're not around or sleeping.
0 = none (tubes fully on at night)
1 = dim tubes at night
2 = shut off tubes at night
When off, you can press **Select** to illuminate the tubes briefly. | +| 41 | Night starts at | Time of day. | +| 42 | Night ends at | Time of day. Set to 0:00 to use the alarm time. At this time (whether night-off/alarm is enabled or not), all tubes will briefly cycle through all digits at full brightness to help prevent . | +| 43 | Day-off | To further save tube life, shut off tubes during the day when you're not around.
0 = none (tubes fully on during the day)
1 = clock at work (shut off all day on weekends)
2 = clock at home (shut off during work hours)
When off, you can press **Select** to illuminuate the tubes briefly. | +| 44 | First day of work week | 0–6 (Sunday–Saturday) | +| 45 | Last day of work week | 0–6 (Sunday–Saturday) | +| 46 | Work starts at | Time of day. | +| 47 | Work ends at | Time of day. | To reset the options menu settings to "factory" defaults, hold **Select** while connecting the clock to power. ## Hardware Configuration -A number of hardware-related settings are specified in consts at the top of the sketch, which can be edited to suit the clock's hardware configuration. These include: +A number of hardware-related settings are specified in config files, one of which is included at the top of the sketch (so you can easily maintain multiple clocks with different hardware). These settings include: * **How many tubes** in the display module. Default is 6; small display adjustments are made for 4-tube clocks. * **Which functions** are enabled. Default is all but temperature and tube tester. @@ -79,7 +85,7 @@ A number of hardware-related settings are specified in consts at the top of the * **Soft power switch** enabled (switched relay only): default is yes; appliance can be switched manually with Adjust while viewing the time of day. Change to no if the appliance has its own power switch (independent of this relay circuit) or does not need to be manually switched. * **Various other durations** for things like scrolling speed, set mode timeouts, short and long button holds, "hold to set faster" thresholds, etc. -You can also set the **defaults for the options menu** to better suit the clock's intended use. +You can also set the **defaults for the options menu** (in main code, currently) to better suit the clock's intended use. **To compile the edited sketch:** You will need to add the [ooPinChangeInt](https://code.google.com/archive/p/oopinchangeint/downloads) and [NorthernWidget DS3231](https://github.com/NorthernWidget/DS3231) libraries to your Arduino IDE. diff --git a/sixtube_lm/configs/v5-4tube.h b/sixtube_lm/configs/v5-4tube.h new file mode 100644 index 0000000..67a9775 --- /dev/null +++ b/sixtube_lm/configs/v5-4tube.h @@ -0,0 +1,76 @@ +const byte displaySize = 4; //number of tubes in display module. Small display adjustments are made for 4-tube clocks + +// available clock functions, and unique IDs (between 0 and 200) +const byte fnIsTime = 0; +const byte fnIsDate = 1; +const byte fnIsAlarm = 2; +const byte fnIsTimer = 3; +const byte fnIsDayCount = 4; +const byte fnIsTemp = 5; +const byte fnIsTubeTester = 6; //cycles all digits on all tubes 1/second, similar to anti-cathode-poisoning cleaner +// functions enabled in this clock, in their display order. Only fnIsTime is required +const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsAlarm, fnIsTimer, fnIsDayCount}; //, fnIsTemp, fnIsTubeTester +// To control which of these display persistently vs. switch back to Time after a few seconds, search "Temporary-display mode timeout" + +// These are the UNDB v5 board connections to Arduino analog input pins. +// S1/PL13 = Reset +// S2/PL5 = A1 +// S3/PL6 = A0 +// S4/PL7 = A6 +// S5/PL8 = A3 +// S6/PL9 = A2 +// S7/PL14 = A7 +// A6-A7 are analog-only pins that aren't quite as responsive and require a physical pullup resistor (1K to +5V), and can't be used with rotary encoders because they don't support pin change interrupts. + +// What input is associated with each control? +const byte mainSel = A2; //main select button - must be equipped +const byte mainAdjUp = A1; //main up/down buttons or rotary encoder - must be equipped +const byte mainAdjDn = A0; +const byte altSel = 0; //alt select button - if unequipped, set to 0 + +// What type of adj controls are equipped? +// 1 = momentary buttons. 2 = quadrature rotary encoder. +const byte mainAdjType = 2; + +// In normal running mode, what do the controls do? +// -1 = nothing/switch, -2 = cycle through functions, fn in fnsEnabled[] = go to that function +// If using soft alarm/power switch per below, the control(s) set to -1 will do the switching. +const char mainSelFn = -2; +const char mainAdjFn = -1; +const byte altSelFn = -1; + +//What are the signal pin(s) connected to? +const char piezoPin = 10; +const char relayPin = -1; //don't change - not available until UNDB v8 +const byte relayMode = 0; //don't change - not available until UNDB v8 +const word signalDur = 180; //sec - when pulsed signal is going, pulses are sent once/sec for this period (e.g. 180 = 3min) +const word switchDur = 7200; //sec - when alarm triggers switched relay, it's switched on for this period (e.g. 7200 = 2hr) +const word piezoPulse = 500; //ms - used with piezo via tone() +const word relayPulse = 200; //ms - used with pulsed relay + +//Soft power switches +const byte enableSoftAlarmSwitch = 1; +// 1 = yes. Alarm can be switched on and off when clock is displaying the alarm time (fnIsAlarm). +// 0 = no. Alarm will be permanently on. Use with switched relay if the appliance has its own switch on this relay circuit. +const byte enableSoftPowerSwitch = 0; //don't change - not available until UNDB v8 + +//LED circuit control +const char ledPin = -1; //don't change - not available until UNDB v8 + +//When display is dim/off, a press will light the tubes for how long? +const byte unoffDur = 10; //sec + +// How long (in ms) are the button hold durations? +const word btnShortHold = 1000; //for setting the displayed feataure +const word btnLongHold = 3000; //for for entering options menu +const byte velThreshold = 150; //ms +// When an adj up/down input (btn or rot) follows another in less than this time, value will change more (10 vs 1). +// Recommend ~150 for rotaries. If you want to use this feature with buttons, extend to ~400. + +// What is the "frame rate" of the tube cleaning and display scrolling? up to 65535 ms +const word cleanSpeed = 200; //ms +const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to give the illusion of a slow scroll that doesn't pause, use (timeoutTempFn*1000)/(displaySize+1) - e.g. 714 for displaySize=6 and timeoutTempFn=5 + +// What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec +const unsigned long timeoutSet = 120; //sec +const unsigned long timeoutTempFn = 5; //sec \ No newline at end of file diff --git a/sixtube_lm/configs/v5-6tube-rotary.h b/sixtube_lm/configs/v5-6tube-rotary.h new file mode 100644 index 0000000..9f5b83e --- /dev/null +++ b/sixtube_lm/configs/v5-6tube-rotary.h @@ -0,0 +1,76 @@ +const byte displaySize = 6; //number of tubes in display module. Small display adjustments are made for 4-tube clocks + +// available clock functions, and unique IDs (between 0 and 200) +const byte fnIsTime = 0; +const byte fnIsDate = 1; +const byte fnIsAlarm = 2; +const byte fnIsTimer = 3; +const byte fnIsDayCount = 4; +const byte fnIsTemp = 5; +const byte fnIsTubeTester = 6; //cycles all digits on all tubes 1/second, similar to anti-cathode-poisoning cleaner +// functions enabled in this clock, in their display order. Only fnIsTime is required +const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsAlarm, fnIsTimer, fnIsDayCount}; //, fnIsTemp, fnIsTubeTester +// To control which of these display persistently vs. switch back to Time after a few seconds, search "Temporary-display mode timeout" + +// These are the UNDB v5 board connections to Arduino analog input pins. +// S1/PL13 = Reset +// S2/PL5 = A1 +// S3/PL6 = A0 +// S4/PL7 = A6 +// S5/PL8 = A3 +// S6/PL9 = A2 +// S7/PL14 = A7 +// A6-A7 are analog-only pins that aren't quite as responsive and require a physical pullup resistor (1K to +5V), and can't be used with rotary encoders because they don't support pin change interrupts. + +// What input is associated with each control? +const byte mainSel = A2; //main select button - must be equipped +const byte mainAdjUp = A0; //main up/down buttons or rotary encoder - must be equipped +const byte mainAdjDn = A1; +const byte altSel = 0; //alt select button - if unequipped, set to 0 + +// What type of adj controls are equipped? +// 1 = momentary buttons. 2 = quadrature rotary encoder. +const byte mainAdjType = 1; + +// In normal running mode, what do the controls do? +// -1 = nothing/switch, -2 = cycle through functions, fn in fnsEnabled[] = go to that function +// If using soft alarm/power switch per below, the control(s) set to -1 will do the switching. +const char mainSelFn = -2; +const char mainAdjFn = -1; +const byte altSelFn = -1; + +//What are the signal pin(s) connected to? +const char piezoPin = 10; +const char relayPin = -1; //don't change - not available until UNDB v8 +const byte relayMode = 0; //don't change - not available until UNDB v8 +const word signalDur = 180; //sec - when pulsed signal is going, pulses are sent once/sec for this period (e.g. 180 = 3min) +const word switchDur = 7200; //sec - when alarm triggers switched relay, it's switched on for this period (e.g. 7200 = 2hr) +const word piezoPulse = 500; //ms - used with piezo via tone() +const word relayPulse = 200; //ms - used with pulsed relay + +//Soft power switches +const byte enableSoftAlarmSwitch = 1; +// 1 = yes. Alarm can be switched on and off when clock is displaying the alarm time (fnIsAlarm). +// 0 = no. Alarm will be permanently on. Use with switched relay if the appliance has its own switch on this relay circuit. +const byte enableSoftPowerSwitch = 0; //don't change - not available until UNDB v8 + +//LED circuit control +const char ledPin = -1; //don't change - not available until UNDB v8 + +//When display is dim/off, a press will light the tubes for how long? +const byte unoffDur = 10; //sec + +// How long (in ms) are the button hold durations? +const word btnShortHold = 1000; //for setting the displayed feataure +const word btnLongHold = 3000; //for for entering options menu +const byte velThreshold = 150; //ms +// When an adj up/down input (btn or rot) follows another in less than this time, value will change more (10 vs 1). +// Recommend ~150 for rotaries. If you want to use this feature with buttons, extend to ~400. + +// What is the "frame rate" of the tube cleaning and display scrolling? up to 65535 ms +const word cleanSpeed = 200; //ms +const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to give the illusion of a slow scroll that doesn't pause, use (timeoutTempFn*1000)/(displaySize+1) - e.g. 714 for displaySize=6 and timeoutTempFn=5 + +// What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec +const unsigned long timeoutSet = 120; //sec +const unsigned long timeoutTempFn = 5; //sec \ No newline at end of file diff --git a/sixtube_lm/configs/v5-6tube.h b/sixtube_lm/configs/v5-6tube.h new file mode 100644 index 0000000..22135b7 --- /dev/null +++ b/sixtube_lm/configs/v5-6tube.h @@ -0,0 +1,76 @@ +const byte displaySize = 6; //number of tubes in display module. Small display adjustments are made for 4-tube clocks + +// available clock functions, and unique IDs (between 0 and 200) +const byte fnIsTime = 0; +const byte fnIsDate = 1; +const byte fnIsAlarm = 2; +const byte fnIsTimer = 3; +const byte fnIsDayCount = 4; +const byte fnIsTemp = 5; +const byte fnIsTubeTester = 6; //cycles all digits on all tubes 1/second, similar to anti-cathode-poisoning cleaner +// functions enabled in this clock, in their display order. Only fnIsTime is required +const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsAlarm, fnIsTimer, fnIsDayCount}; //, fnIsTemp, fnIsTubeTester +// To control which of these display persistently vs. switch back to Time after a few seconds, search "Temporary-display mode timeout" + +// These are the UNDB v5 board connections to Arduino analog input pins. +// S1/PL13 = Reset +// S2/PL5 = A1 +// S3/PL6 = A0 +// S4/PL7 = A6 +// S5/PL8 = A3 +// S6/PL9 = A2 +// S7/PL14 = A7 +// A6-A7 are analog-only pins that aren't quite as responsive and require a physical pullup resistor (1K to +5V), and can't be used with rotary encoders because they don't support pin change interrupts. + +// What input is associated with each control? +const byte mainSel = A2; //main select button - must be equipped +const byte mainAdjUp = A1; //main up/down buttons or rotary encoder - must be equipped +const byte mainAdjDn = A0; +const byte altSel = 0; //alt select button - if unequipped, set to 0 + +// What type of adj controls are equipped? +// 1 = momentary buttons. 2 = quadrature rotary encoder. +const byte mainAdjType = 2; + +// In normal running mode, what do the controls do? +// -1 = nothing/switch, -2 = cycle through functions, fn in fnsEnabled[] = go to that function +// If using soft alarm/power switch per below, the control(s) set to -1 will do the switching. +const char mainSelFn = -2; +const char mainAdjFn = -1; +const byte altSelFn = -1; + +//What are the signal pin(s) connected to? +const char piezoPin = 10; +const char relayPin = -1; //don't change - not available until UNDB v8 +const byte relayMode = 0; //don't change - not available until UNDB v8 +const word signalDur = 180; //sec - when pulsed signal is going, pulses are sent once/sec for this period (e.g. 180 = 3min) +const word switchDur = 7200; //sec - when alarm triggers switched relay, it's switched on for this period (e.g. 7200 = 2hr) +const word piezoPulse = 500; //ms - used with piezo via tone() +const word relayPulse = 200; //ms - used with pulsed relay + +//Soft power switches +const byte enableSoftAlarmSwitch = 1; +// 1 = yes. Alarm can be switched on and off when clock is displaying the alarm time (fnIsAlarm). +// 0 = no. Alarm will be permanently on. Use with switched relay if the appliance has its own switch on this relay circuit. +const byte enableSoftPowerSwitch = 0; //don't change - not available until UNDB v8 + +//LED circuit control +const char ledPin = -1; //don't change - not available until UNDB v8 + +//When display is dim/off, a press will light the tubes for how long? +const byte unoffDur = 10; //sec + +// How long (in ms) are the button hold durations? +const word btnShortHold = 1000; //for setting the displayed feataure +const word btnLongHold = 3000; //for for entering options menu +const byte velThreshold = 150; //ms +// When an adj up/down input (btn or rot) follows another in less than this time, value will change more (10 vs 1). +// Recommend ~150 for rotaries. If you want to use this feature with buttons, extend to ~400. + +// What is the "frame rate" of the tube cleaning and display scrolling? up to 65535 ms +const word cleanSpeed = 200; //ms +const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to give the illusion of a slow scroll that doesn't pause, use (timeoutTempFn*1000)/(displaySize+1) - e.g. 714 for displaySize=6 and timeoutTempFn=5 + +// What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec +const unsigned long timeoutSet = 120; //sec +const unsigned long timeoutTempFn = 5; //sec \ No newline at end of file diff --git a/sixtube_lm/configs/v8-6tube-relayswitch.h b/sixtube_lm/configs/v8-6tube-relayswitch.h new file mode 100644 index 0000000..9fdf211 --- /dev/null +++ b/sixtube_lm/configs/v8-6tube-relayswitch.h @@ -0,0 +1,83 @@ +const byte displaySize = 6; //number of tubes in display module. Small display adjustments are made for 4-tube clocks + +// available clock functions, and unique IDs (between 0 and 200) +const byte fnIsTime = 0; +const byte fnIsDate = 1; +const byte fnIsAlarm = 2; +const byte fnIsTimer = 3; +const byte fnIsDayCount = 4; +const byte fnIsTemp = 5; +const byte fnIsTubeTester = 6; //cycles all digits on all tubes 1/second, similar to anti-cathode-poisoning cleaner +// functions enabled in this clock, in their display order. Only fnIsTime is required +const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsAlarm, fnIsTimer, fnIsDayCount}; //, fnIsTemp, fnIsTubeTester +// To control which of these display persistently vs. switch back to Time after a few seconds, search "Temporary-display mode timeout" + +// These are the RLB board connections to Arduino analog input pins. +// S1/PL13 = Reset +// S2/PL5 = A1 +// S3/PL6 = A0 +// S4/PL7 = A6 +// S5/PL8 = A3 +// S6/PL9 = A2 +// S7/PL14 = A7 +// A6-A7 are analog-only pins that aren't quite as responsive and require a physical pullup resistor (1K to +5V), and can't be used with rotary encoders because they don't support pin change interrupts. + +// What input is associated with each control? +const byte mainSel = A1; //main select button - must be equipped +const byte mainAdjUp = A6; //main up/down buttons or rotary encoder - must be equipped +const byte mainAdjDn = A7; +const byte altSel = A0; //alt select button - if unequipped, set to 0 + +// What type of adj controls are equipped? +// 1 = momentary buttons. 2 = quadrature rotary encoder. +// If using rotary encoder, make sure it is connected to A0/A1 which can support digital input, not A6/A7. +const byte mainAdjType = 1; + +// In normal running mode, what do the controls do? +// -1 = nothing/switch, -2 = cycle through functions, fn in fnsEnabled[] = go to that function +// If using soft alarm/power switch per below, the control(s) set to -1 will do the switching. +const char mainSelFn = -2; +const char mainAdjFn = -1; +const byte altSelFn = fnIsAlarm; //go straight to alarm + +//What are the signal pin(s) connected to? +const char piezoPin = 10; +const char relayPin = A3; +// -1 to disable feature (no relay item equipped); A3 if equipped (UNDB v8) +const byte relayMode = 0; //If relay is equipped, what does it do? +// 0 = switched mode: the relay will be switched to control an appliance like a radio or light fixture. If used with timer, it will switch on while timer is running (like a "sleep" function). If used with alarm, it will switch on when alarm trips; specify duration of this in switchDur. +// 1 = pulsed mode: the relay will be pulsed, like the beeper is, to control an intermittent signaling device like a solenoid or indicator lamp. Specify pulse duration in relayPulse. +const word signalDur = 180; //sec - when pulsed signal is going, pulses are sent once/sec for this period (e.g. 180 = 3min) +const word switchDur = 7200; //sec - when alarm triggers switched relay, it's switched on for this period (e.g. 7200 = 2hr) +const word piezoPulse = 500; //ms - used with piezo via tone() +const word relayPulse = 200; //ms - used with pulsed relay + +//Soft power switches +const byte enableSoftAlarmSwitch = 1; +// 1 = yes. Alarm can be switched on and off when clock is displaying the alarm time (fnIsAlarm). +// 0 = no. Alarm will be permanently on. Use with switched relay if the appliance has its own switch on this relay circuit. +const byte enableSoftPowerSwitch = 1; //works with switched relay only +// 1 = yes. Relay can be switched on and off directly when clock is displaying time of day (fnIsTime). This is useful if connecting an appliance (e.g. radio) that doesn't have its own switch, or if replacing the clock unit in a clock radio where the clock does all the switching (e.g. Telechron). +// 0 = no. Use if the connected appliance has its own power switch (independent of this relay circuit) or does not need to be manually switched. + +//LED circuit control +const char ledPin = A2; +// -1 to disable feature; A2 if equipped (UNDB v8) + +//When display is dim/off, a press will light the tubes for how long? +const byte unoffDur = 10; //sec + +// How long (in ms) are the button hold durations? +const word btnShortHold = 1000; //for setting the displayed feataure +const word btnLongHold = 3000; //for for entering options menu +const byte velThreshold = 150; //ms +// When an adj up/down input (btn or rot) follows another in less than this time, value will change more (10 vs 1). +// Recommend ~150 for rotaries. If you want to use this feature with buttons, extend to ~400. + +// What is the "frame rate" of the tube cleaning and display scrolling? up to 65535 ms +const word cleanSpeed = 200; //ms +const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to give the illusion of a slow scroll that doesn't pause, use (timeoutTempFn*1000)/(displaySize+1) - e.g. 714 for displaySize=6 and timeoutTempFn=5 + +// What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec +const unsigned long timeoutSet = 120; //sec +const unsigned long timeoutTempFn = 5; //sec \ No newline at end of file diff --git a/sixtube_lm/sixtube_lm.ino b/sixtube_lm/sixtube_lm.ino index e8f69e0..a697a8e 100644 --- a/sixtube_lm/sixtube_lm.ino +++ b/sixtube_lm/sixtube_lm.ino @@ -4,105 +4,23 @@ // based on original sketch by Robin Birtles (rlb-designs.com) and Chris Gerekos // based on http://arduinix.com/Main/Code/ANX-6Tube-Clock-Crossfade.txt + +////////// Hardware configuration ////////// +//Include the config file that matches your hardware setup. If needed, duplicate an existing one. + +//#include "configs/v5-6tube.h" //UNDB v5, 6 tubes +//#include "configs/v5-6tube-rotary.h" //UNDB v5, 6 tubes, rotary encoder instead of buttons +//#include "configs/v5-4tube.h" //UNDB v5, 4 tubes +#include "configs/v8-6tube-relayswitch.h" //UNDB v8, 6 tubes, switching relay + + +////////// Other includes, global consts, and vars ////////// #include #include #include #include #include - -////////// Hardware configuration consts ////////// - -const byte displaySize = 6; //number of tubes in display module. Small display adjustments are made for 4-tube clocks - -// available clock functions, and unique IDs (between 0 and 200) -const byte fnIsTime = 0; -const byte fnIsDate = 1; -const byte fnIsAlarm = 2; -const byte fnIsTimer = 3; -const byte fnIsDayCount = 4; -const byte fnIsTemp = 5; -const byte fnIsTubeTester = 6; //cycles all digits on all tubes 1/second, similar to anti-cathode-poisoning cleaner -// functions enabled in this clock, in their display order. Only fnIsTime is required -const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsAlarm, fnIsTimer, fnIsDayCount, fnIsTemp}; //, fnIsTemp, fnIsTubeTester -// To control which of these display persistently vs. switch back to Time after a few seconds, search "Temporary-display mode timeout" - -// These are the RLB board connections to Arduino analog input pins. -// S1/PL13 = Reset -// S2/PL5 = A1 -// S3/PL6 = A0 -// S4/PL7 = A6 -// S5/PL8 = A3 -// S6/PL9 = A2 -// S7/PL14 = A7 -// A6-A7 are analog-only pins that aren't quite as responsive and require a physical pullup resistor (1K to +5V), and can't be used with rotary encoders because they don't support pin change interrupts. - -// What input is associated with each control? -const byte mainSel = A2; //main select button - must be equipped -const byte mainAdjUp = A1; //main up/down buttons or rotary encoder - must be equipped -const byte mainAdjDn = A0; -const byte altSel = 0; //alt select button - if unequipped, set to 0 - -// What type of adj controls are equipped? -// 1 = momentary buttons. 2 = quadrature rotary encoder. -const byte mainAdjType = 1; - -// In normal running mode, what do the controls do? -// -1 = nothing/switch, -2 = cycle through functions, fn in fnsEnabled[] = go to that function -// If using soft alarm/power switch per below, the control(s) set to -1 will do the switching. -const char mainSelFn = -2; -const char mainAdjFn = -1; -const byte altSelFn = -1; - -//What are the signal pin(s) connected to? -const char piezoPin = 10; -const char relayPin = 127; -// If running a v5.0 board with only a piezo output, leave these set to 10 and -1 (disabled) respectively - unless removing the piezo to drive a relay instead, in which case, reverse them. -// If running a v5.x board with both piezo and relay, piezo is 10, relay is X. Extra menu options will appear to let end user decide which functions control which outputs. -// To test without relay/load present, set relayPin to 127 to have relay commands written to console instead. -const byte relayMode = 0; //If relay is equipped, what does it do? -// 0 = switched mode: the relay will be switched to control an appliance like a radio or light fixture. If used with timer, it will switch on while timer is running (like a "sleep" function). If used with alarm, it will switch on when alarm trips; specify duration of this in switchDur. -// 1 = pulsed mode: the relay will be pulsed, like the beeper is, to control an intermittent signaling device like a solenoid or indicator lamp. Specify pulse duration in relayPulse. -const word signalDur = 180; //sec - when pulsed signal is going, pulses are sent once/sec for this period (e.g. 180 = 3min) -const word switchDur = 7200; //sec - when alarm triggers switched relay, it's switched on for this period (e.g. 7200 = 2hr) -const word piezoPulse = 500; //ms - used with piezo via tone() -const word relayPulse = 200; //ms - used with pulsed relay - -//Soft power switches -const byte enableSoftAlarmSwitch = 1; -// 1 = yes. Alarm can be switched on and off when clock is displaying the alarm time (fnIsAlarm). -// 0 = no. Alarm will be permanently on. Use with switched relay if the appliance has its own switch on this relay circuit. -const byte enableSoftPowerSwitch = 1; //works with switched relay only -// 1 = yes. Relay can be switched on and off directly when clock is displaying time of day (fnIsTime). This is useful if connecting an appliance (e.g. radio) that doesn't have its own switch, or if replacing the clock unit in a clock radio where the clock does all the switching (e.g. Telechron). -// 0 = no. Use if the connected appliance has its own power switch (independent of this relay circuit) or does not need to be manually switched. - -//LED circuit control -const char ledPin = 127; -// If running a v5.0 board with constantly powered LEDs, leave this set to -1 (disabled). -// If running a v5.x board, LED control pin is X. An extra menu option will appear to let end user control LEDs. -// To test without LED circuit present, set relayPin to 127 to have LED switch commands written to console instead. - -//When display is dim/off, a press will light the tubes for how long? -const byte unoffDur = 10; //sec - -// How long (in ms) are the button hold durations? -const word btnShortHold = 1000; //for setting the displayed feataure -const word btnLongHold = 3000; //for for entering options menu -const byte velThreshold = 150; //ms -// When an adj up/down input (btn or rot) follows another in less than this time, value will change more (10 vs 1). -// Recommend ~150 for rotaries. If you want to use this feature with buttons, extend to ~400. - -// What is the "frame rate" of the tube cleaning and display scrolling? up to 65535 ms -const word cleanSpeed = 200; //ms -const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to give the illusion of a slow scroll that doesn't pause, use (timeoutTempFn*1000)/(displaySize+1) - e.g. 714 for displaySize=6 and timeoutTempFn=5 - -// What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec -const unsigned long timeoutSet = 120; //sec -const unsigned long timeoutTempFn = 5; //sec - - -////////// Other global consts and vars ////////// - /*EEPROM locations for non-volatile clock settings Don't change which location is associated with which setting; they should remain permanent to avoid the need for EEPROM initializations after code upgrades; and they are used directly in code. Most values in the clock are 1-byte; if 2-byte, high byte is loc, low byte is loc+1. @@ -143,16 +61,18 @@ Some are skipped when they wouldn't apply to a given clock's hardware config, se 43 Timer signal - skipped when no relay (start=0) or no piezo (start=1) 44 Strike signal - skipped when no pulse relay (start=0) or no piezo (start=1) 45 Temperature format - skipped when fnIsTemp is not in fnsEnabled + 46 Anti-cathode poisoning */ -//Options menu options' EEPROM locations and default/min/max values. -//Options' numbers may be changed by reordering these arrays (and changing readme accordingly). -//Although these arrays are 0-index, the option number displayed (and listed in readme) is 1-index. (search for "fn-fnOpts+1") -//1-index option number: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 -const byte optsLoc[] = {16,17,18,19,20,22,23,24,42,39,43,25,40,21,44,41,27, 28, 30,32,33,34, 35, 37,26,45}; -const word optsDef[] = { 2, 1, 0, 0, 5, 0, 0, 9, 0,61, 0, 0,61, 0, 0,61, 0,1320, 360, 0, 1, 5, 480,1020, 1, 0}; -const word optsMin[] = { 1, 1, 0, 0, 0, 0, 0, 0, 0,49, 0, 0,49, 0, 0,49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; -const word optsMax[] = { 2, 5, 3, 1,20, 6, 2,60, 1,88, 1, 1,88, 4, 1,88, 2,1439,1439, 2, 6, 6,1439,1439, 4, 1}; +//Options menu numbers (displayed in UI and readme), EEPROM locations, and default/min/max values. +//Option numbers/order can be changed (though try to avoid for user convenience); +//but option locs should be maintained so EEPROM doesn't have to be reset after an upgrade. +// General Alarm Timer Strike Night-off and day-off +const byte optsNum[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11,12,13, 20,21,22, 30,31,32, 40, 41, 42,43,44,45, 46, 47}; +const byte optsLoc[] = {16,17,18,19,20,22,26,45,46, 23,42,39,24, 25,43,40, 21,44,41, 27, 28, 30,32,33,34, 35, 37}; +const word optsDef[] = { 2, 1, 0, 0, 5, 0, 1, 0, 0, 0, 0,61, 9, 0, 0,61, 0, 0,61, 0,1320, 360, 0, 1, 5, 480,1020}; +const word optsMin[] = { 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,49, 0, 0, 0,49, 0, 0,49, 0, 0, 0, 0, 0, 0, 0, 0}; +const word optsMax[] = { 2, 5, 3, 1,20, 6, 4, 1, 3, 2, 1,88,60, 1, 1,88, 4, 1,88, 2,1439,1439, 2, 6, 6,1439,1439}; //RTC objects DS3231 ds3231; //an object to access the ds3231 specifically (temp, etc) @@ -251,11 +171,8 @@ void initInputs(){ //TODO are there no "loose" pins left floating after this? per https://electronics.stackexchange.com/q/37696/151805 pinMode(A0, INPUT_PULLUP); pinMode(A1, INPUT_PULLUP); - pinMode(A2, INPUT_PULLUP); - pinMode(A3, INPUT_PULLUP); - //4 and 5 used for I2C - pinMode(A6, INPUT); digitalWrite(A6, HIGH); - pinMode(A7, INPUT); digitalWrite(A7, HIGH); + //pinMode(A2, INPUT_PULLUP); + //pinMode(A3, INPUT_PULLUP); //rotary encoder init if(mainAdjType==2) AdaEncoder mainRot = AdaEncoder('a',mainAdjUp,mainAdjDn); } @@ -458,8 +375,8 @@ void ctrlEvt(byte ctrl, byte evt){ timerInitial = fnSetVal*60; //timerRemain is seconds, but timer is set by minute timerRemain = timerInitial; //set actual timer going if(relayPin>=0 && relayMode==0 && readEEPROM(43,false)==1) { //if switched relay, and timer signal is set to it - if(relayPin!=127) digitalWrite(relayPin,HIGH); - else { Serial.print(millis(),DEC); Serial.println(F(" Relay on, timer set")); } + digitalWrite(relayPin,HIGH); + Serial.print(millis(),DEC); Serial.println(F(" Relay on, timer set")); updateLEDs(); //LEDs following switch relay //TODO will this cancel properly? especially if alarm interrupts? } @@ -492,7 +409,7 @@ void ctrlEvt(byte ctrl, byte evt){ else { //options menu setting - to/from EEPROM - byte opt = fn-fnOpts; //current option number, 0-index (for opts[] arrays) + byte opt = fn-fnOpts; //current option index if(evt==2 && ctrl==mainSel) { //mainSel short hold: exit options menu btnStop(); @@ -846,14 +763,12 @@ void switchPower(char dir){ if(enableSoftPowerSwitch && relayPin>=0 && relayMode==0) { //if switched relay, and soft switch enabled //signalStop(); could use this instead of the below to turn the radio off if(dir==0) dir = !digitalRead(relayPin); - if(relayPin!=127) digitalWrite(relayPin,(dir==1?HIGH:LOW)); - else { - Serial.print(millis(),DEC); - Serial.print(F(" Relay ")); - if(dir==0) { Serial.print(F("toggled")); if(relayPin!=127) { Serial.print(digitalRead(relayPin)==HIGH?F(" on"):F(" off")); } } - else Serial.print(dir==1?F("switched on"):F("switched off")); - Serial.println(F(", switchPower")); - } + digitalWrite(relayPin,(dir==1?HIGH:LOW)); + Serial.print(millis(),DEC); + Serial.print(F(" Relay ")); + if(dir==0) { Serial.print(F("toggled")); Serial.print(digitalRead(relayPin)==HIGH?F(" on"):F(" off")); } + else Serial.print(dir==1?F("switched on"):F("switched off")); + Serial.println(F(", switchPower")); updateLEDs(); //LEDs following switch relay } } @@ -939,7 +854,7 @@ void updateDisplay(){ } else if(fn >= fnOpts){ //options menu, but not setting a value displayDim = 2; - editDisplay(fn-fnOpts+1,0,1,false,false); //display option number (1-index) on hour tubes + editDisplay(optsNum[fn-fnOpts],0,1,false,false); //display option number on hour tubes blankDisplay(2,5,false); } else { //fn running @@ -1030,7 +945,7 @@ void updateDisplay(){ }//end switch } //end if fn running - if(true) { //DEBUG MODE: when display's not working, just write it to the console, with time + if(false) { //DEBUG MODE: when display's not working, just write it to the console, with time if(tod.hour()<10) Serial.print(F("0")); Serial.print(tod.hour(),DEC); Serial.print(F(":")); @@ -1099,8 +1014,8 @@ void initOutputs() { for(byte i=0; i<4; i++) { pinMode(binOutA[i],OUTPUT); pinMode(binOutB[i],OUTPUT); } for(byte i=0; i<3; i++) { pinMode(anodes[i],OUTPUT); } if(piezoPin>=0) pinMode(piezoPin, OUTPUT); - if(relayPin>=0 && relayPin!=127) pinMode(relayPin, OUTPUT); - if(ledPin>=0 && ledPin!=127) pinMode(ledPin, OUTPUT); + if(relayPin>=0) pinMode(relayPin, OUTPUT); + if(ledPin>=0) pinMode(ledPin, OUTPUT); updateLEDs(); //set to initial value } @@ -1211,8 +1126,8 @@ void signalStart(byte sigFn, byte sigDur, word pulseDur){ //make some noise! or else signalRemain = sigDur; //piezo or pulsed relay: checkRTC will handle it if(getSignalOutput()==1 && relayPin>=0 && relayMode==0) { //switched relay: turn it on now - if(relayPin!=127) digitalWrite(relayPin,HIGH); - else { Serial.print(millis(),DEC); Serial.println(F(" Relay on, signalStart")); } + digitalWrite(relayPin,HIGH); + Serial.print(millis(),DEC); Serial.println(F(" Relay on, signalStart")); } } updateLEDs(); //LEDs following signal or relay @@ -1221,8 +1136,8 @@ void signalStop(){ //stop current signal and clear out signal timer if applicabl signalRemain = 0; snoozeRemain = 0; signalPulseStop(); //piezo or pulsed relay: stop now if(getSignalOutput()==1 && relayPin>=0 && relayMode==0) { //switched relay: turn it off now - if(relayPin!=127) digitalWrite(relayPin,LOW); - else { Serial.print(millis(),DEC); Serial.println(F(" Relay off, signalStop")); } + digitalWrite(relayPin,LOW); + Serial.print(millis(),DEC); Serial.println(F(" Relay off, signalStop")); } updateLEDs(); //LEDs following relay } @@ -1233,8 +1148,8 @@ void signalPulseStart(word pulseDur){ tone(piezoPin, getSignalPitch(), (pulseDur==0?piezoPulse:pulseDur)); } else if(getSignalOutput()==1 && relayPin>=0 && relayMode==1) { //pulsed relay - if(relayPin!=127) digitalWrite(relayPin,HIGH); - else { Serial.print(millis(),DEC); Serial.println(F(" Relay on, signalPulseStart")); } + digitalWrite(relayPin,HIGH); + Serial.print(millis(),DEC); Serial.println(F(" Relay on, signalPulseStart")); signalPulseStopTime = millis()+relayPulse; //always use relayPulse in case timing is important for connected device } } @@ -1243,8 +1158,8 @@ void signalPulseStop(){ noTone(piezoPin); } else if(getSignalOutput()==1 && relayPin>=0 && relayMode==1) { //pulsed relay - if(relayPin!=127) digitalWrite(relayPin,LOW); - else { Serial.print(millis(),DEC); Serial.println(F(" Relay off, signalPulseStop")); } + digitalWrite(relayPin,LOW); + Serial.print(millis(),DEC); Serial.println(F(" Relay off, signalPulseStop")); signalPulseStopTime = 0; } } @@ -1267,25 +1182,27 @@ void updateLEDs(){ if(ledPin>=0) { switch(readEEPROM(26,false)){ case 0: //always off - if(ledPin!=127) digitalWrite(ledPin,LOW); - else { Serial.println(F("LEDs off always")); } + digitalWrite(ledPin,LOW); + Serial.println(F("LEDs off always")); break; case 1: //always on - if(ledPin!=127) digitalWrite(ledPin,HIGH); - else { Serial.println(F("LEDs on always")); } + digitalWrite(ledPin,HIGH); + Serial.println(F("LEDs on always")); break; case 2: //on, but follow day-off and night-off - if(ledPin!=127) digitalWrite(ledPin,(displayDim<2?LOW:HIGH)); - else { Serial.print(displayDim<2?F("LEDs off"):F("LEDs on")); Serial.println(F(" per dim state")); } + digitalWrite(ledPin,(displayDim<2?LOW:HIGH)); + Serial.print(displayDim<2?F("LEDs off"):F("LEDs on")); Serial.println(F(" per dim state")); break; case 3: //off, but on when alarm/timer sounds - if(ledPin!=127) digitalWrite(ledPin,(signalRemain && (signalSource==fnIsAlarm || signalSource==fnIsTimer)?HIGH:LOW)); - else { Serial.print(signalRemain && (signalSource==fnIsAlarm || signalSource==fnIsTimer)?F("LEDs on"):F("LEDs off")); Serial.println(F(" per alarm/timer")); } + digitalWrite(ledPin,(signalRemain && (signalSource==fnIsAlarm || signalSource==fnIsTimer)?HIGH:LOW)); + Serial.print(signalRemain && (signalSource==fnIsAlarm || signalSource==fnIsTimer)?F("LEDs on"):F("LEDs off")); Serial.println(F(" per alarm/timer")); break; case 4: //off, but on with switched relay if(relayPin>=0 && relayMode==0) { - if(ledPin!=127) digitalWrite(ledPin,digitalRead(relayPin)); - else { Serial.println(F("LEDs follow switched relay")); } //can't actually say in case relayPin is 127 + digitalWrite(ledPin,digitalRead(relayPin)); + Serial.print(F("LEDs ")); + if(digitalRead(ledPin)==HIGH) Serial.print(F("on")); else Serial.print(F("off")); + Serial.println(F(" per switched relay")); } break; default: break; From f652c2e2ed402a0c738b6dda2a2b15433f081f8e Mon Sep 17 00:00:00 2001 From: Luke McKenzie Date: Wed, 11 Jul 2018 21:53:50 -0500 Subject: [PATCH 04/13] three v8 configs, initInputs bugfix, remove unimplemented options --- README.md | 6 +++--- sixtube_lm/sixtube_lm.ino | 22 ++++++++++++---------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 0c4e1c9..d8d812a 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,13 @@ _Note: Many variations are possible, depending on your clock's hardware; but thi | | **General** | | | 1 | Time format | 1 = 12-hour
2 = 24-hour
(time-of-day display only; setting times is always done in 24h) | | 2 | Date format | 1 = month/date/weekday
2 = date/month/weekday
3 = month/date/year
4 = date/month/year
5 = year/month/date
Note that four-tube clocks will display only the first two values in each of these options. | -| 3 | Display date during time? | 0 = never
1 = date instead of seconds
2 = full date (as above) every minute at :30 seconds
3 = same as 2, but scrolls in and out
4 = full date every 5 minutes at :30 seconds
3 = same as 4, but scrolls in and out | +| 3 | Display date during time? | 0 = never
1 = date instead of seconds
2 = full date (as above) every minute at :30 seconds
3 = same as 2, but scrolls in and out
TODO: implement options for full date every 5 minutes | | 4 | Leading zero in hour, date, and month? | 0 = no
1 = yes | | 5 | Digit fade | 0–20 (in hundredths of a second) | | 6 | Auto DST | Add 1h for daylight saving time between these dates (at 2am):
0 = off
1 = second Sunday in March to first Sunday in November (US/CA)
2 = last Sunday in March to last Sunday in October (UK/EU)
3 = first Sunday in April to last Sunday in October (MX)
4 = last Sunday in September to first Sunday in April (NZ)
5 = first Sunday in October to first Sunday in April (AU)
6 = third Sunday in October to third Sunday in February (BZ) | | 7 | LED behavior | 0 = always off
1 = always on
2 = on, but follow day-off and night-off if enabled
3 = off, but on when alarm/timer sounds
4 = off, but on with switched relay (if equipped – great for radios!)
(Clocks with LED control only, UNDB v8+) | | 8 | Temperature format | 0 = Celsius
1 = Fahrenheit
(Clocks with temperature function enabled only) | -| 9 | Anti-cathode poisoning | Briefly cycles all digits to prevent [cathode poisoning](http://www.tube-tester.com/sites/nixie/different/cathode%20poisoning/cathode-poisoning.htm):
0 = once a day, at "Night ends at" time
1 = once a day, at midnight
2 = every hour, at minute :01
3 = every minute, at second :01 | +| 9 | Anti-cathode poisoning | Briefly cycles all digits to prevent [cathode poisoning](http://www.tube-tester.com/sites/nixie/different/cathode%20poisoning/cathode-poisoning.htm)
TODO: implement this | | | **Alarm** | | | 10 | Alarm days | 0 = every day
1 = work week only (per settings below)
2 = weekend only | | 11 | Alarm signal | 0 = beeper
1 = relay (if in switch mode, will stay on for 2 hours)
(Clocks with both beeper and relay only) | @@ -59,7 +59,7 @@ _Note: Many variations are possible, depending on your clock's hardware; but thi | | **Night-off and day-off** | | | 40 | Night-off | To save tube life and/or preserve your sleep, dim or shut off tubes nightly when you're not around or sleeping.
0 = none (tubes fully on at night)
1 = dim tubes at night
2 = shut off tubes at night
When off, you can press **Select** to illuminate the tubes briefly. | | 41 | Night starts at | Time of day. | -| 42 | Night ends at | Time of day. Set to 0:00 to use the alarm time. At this time (whether night-off/alarm is enabled or not), all tubes will briefly cycle through all digits at full brightness to help prevent . | +| 42 | Night ends at | Time of day. Set to 0:00 to use the alarm time. | | 43 | Day-off | To further save tube life, shut off tubes during the day when you're not around.
0 = none (tubes fully on during the day)
1 = clock at work (shut off all day on weekends)
2 = clock at home (shut off during work hours)
When off, you can press **Select** to illuminuate the tubes briefly. | | 44 | First day of work week | 0–6 (Sunday–Saturday) | | 45 | Last day of work week | 0–6 (Sunday–Saturday) | diff --git a/sixtube_lm/sixtube_lm.ino b/sixtube_lm/sixtube_lm.ino index a697a8e..e170a03 100644 --- a/sixtube_lm/sixtube_lm.ino +++ b/sixtube_lm/sixtube_lm.ino @@ -11,7 +11,9 @@ //#include "configs/v5-6tube.h" //UNDB v5, 6 tubes //#include "configs/v5-6tube-rotary.h" //UNDB v5, 6 tubes, rotary encoder instead of buttons //#include "configs/v5-4tube.h" //UNDB v5, 4 tubes -#include "configs/v8-6tube-relayswitch.h" //UNDB v8, 6 tubes, switching relay +#include "configs/v8-6tube.h" //UNDB v8 before modification, no LED or relay support +//#include "configs/v8a-6tube-relayswitch.h" //UNDB v8 after modification A (Select=A1, Adj=A6/A7, Alt=A0, LED=A2, Relay=A3) +//#include "configs/v8b-6tube-relayswitch.h" //UNDB v8 after modification A (Select=A6, Adj=A0/A1, Alt=A7, LED=A2, Relay=A3) ////////// Other includes, global consts, and vars ////////// @@ -68,11 +70,11 @@ Some are skipped when they wouldn't apply to a given clock's hardware config, se //Option numbers/order can be changed (though try to avoid for user convenience); //but option locs should be maintained so EEPROM doesn't have to be reset after an upgrade. // General Alarm Timer Strike Night-off and day-off -const byte optsNum[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11,12,13, 20,21,22, 30,31,32, 40, 41, 42,43,44,45, 46, 47}; -const byte optsLoc[] = {16,17,18,19,20,22,26,45,46, 23,42,39,24, 25,43,40, 21,44,41, 27, 28, 30,32,33,34, 35, 37}; -const word optsDef[] = { 2, 1, 0, 0, 5, 0, 1, 0, 0, 0, 0,61, 9, 0, 0,61, 0, 0,61, 0,1320, 360, 0, 1, 5, 480,1020}; -const word optsMin[] = { 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,49, 0, 0, 0,49, 0, 0,49, 0, 0, 0, 0, 0, 0, 0, 0}; -const word optsMax[] = { 2, 5, 3, 1,20, 6, 4, 1, 3, 2, 1,88,60, 1, 1,88, 4, 1,88, 2,1439,1439, 2, 6, 6,1439,1439}; +const byte optsNum[] = { 1, 2, 3, 4, 5, 6, 7, 8, 10,11,12,13, 20,21,22, 30,31,32, 40, 41, 42,43,44,45, 46, 47}; // 9, +const byte optsLoc[] = {16,17,18,19,20,22,26,45, 23,42,39,24, 25,43,40, 21,44,41, 27, 28, 30,32,33,34, 35, 37}; //46, +const word optsDef[] = { 2, 1, 0, 0, 5, 0, 1, 0, 0, 0,61, 9, 0, 0,61, 0, 0,61, 0,1320, 360, 0, 1, 5, 480,1020}; // 0, +const word optsMin[] = { 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,49, 0, 0, 0,49, 0, 0,49, 0, 0, 0, 0, 0, 0, 0, 0}; // 0, +const word optsMax[] = { 2, 5, 3, 1,20, 6, 4, 1, 2, 1,88,60, 1, 1,88, 4, 1,88, 2,1439,1439, 2, 6, 6,1439,1439}; // 3, //RTC objects DS3231 ds3231; //an object to access the ds3231 specifically (temp, etc) @@ -169,10 +171,10 @@ void loop(){ void initInputs(){ //TODO are there no "loose" pins left floating after this? per https://electronics.stackexchange.com/q/37696/151805 - pinMode(A0, INPUT_PULLUP); - pinMode(A1, INPUT_PULLUP); - //pinMode(A2, INPUT_PULLUP); - //pinMode(A3, INPUT_PULLUP); + pinMode(mainSel, INPUT_PULLUP); + pinMode(mainAdjUp, INPUT_PULLUP); + pinMode(mainAdjDn, INPUT_PULLUP); + pinMode(altSel, INPUT_PULLUP); //rotary encoder init if(mainAdjType==2) AdaEncoder mainRot = AdaEncoder('a',mainAdjUp,mainAdjDn); } From ddc1d047234d0d782a326bda5eb2a45626f4d0bc Mon Sep 17 00:00:00 2001 From: Luke McKenzie Date: Wed, 11 Jul 2018 21:55:20 -0500 Subject: [PATCH 05/13] ibid --- sixtube_lm/configs/v8-6tube.h | 82 +++++++++++++++++++ ...-relayswitch.h => v8a-6tube-relayswitch.h} | 1 - sixtube_lm/configs/v8b-6tube-relayswitch.h | 82 +++++++++++++++++++ 3 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 sixtube_lm/configs/v8-6tube.h rename sixtube_lm/configs/{v8-6tube-relayswitch.h => v8a-6tube-relayswitch.h} (97%) create mode 100644 sixtube_lm/configs/v8b-6tube-relayswitch.h diff --git a/sixtube_lm/configs/v8-6tube.h b/sixtube_lm/configs/v8-6tube.h new file mode 100644 index 0000000..4ae9986 --- /dev/null +++ b/sixtube_lm/configs/v8-6tube.h @@ -0,0 +1,82 @@ +const byte displaySize = 6; //number of tubes in display module. Small display adjustments are made for 4-tube clocks + +// available clock functions, and unique IDs (between 0 and 200) +const byte fnIsTime = 0; +const byte fnIsDate = 1; +const byte fnIsAlarm = 2; +const byte fnIsTimer = 3; +const byte fnIsDayCount = 4; +const byte fnIsTemp = 5; +const byte fnIsTubeTester = 6; //cycles all digits on all tubes 1/second, similar to anti-cathode-poisoning cleaner +// functions enabled in this clock, in their display order. Only fnIsTime is required +const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsAlarm, fnIsTimer, fnIsDayCount}; //, fnIsTemp, fnIsTubeTester +// To control which of these display persistently vs. switch back to Time after a few seconds, search "Temporary-display mode timeout" + +// These are the RLB board connections to Arduino analog input pins. +// S1/PL13 = Reset +// S2/PL5 = A1 +// S3/PL6 = A0 +// S4/PL7 = A6 +// S5/PL8 = A3 +// S6/PL9 = A2 +// S7/PL14 = A7 +// A6-A7 are analog-only pins that aren't quite as responsive and require a physical pullup resistor (1K to +5V), and can't be used with rotary encoders because they don't support pin change interrupts. + +// What input is associated with each control? +const byte mainSel = A1; //main select button - must be equipped +const byte mainAdjUp = A2; //main up/down buttons or rotary encoder - must be equipped +const byte mainAdjDn = A3; +const byte altSel = A0; //alt select button - if unequipped, set to 0 + +// What type of adj controls are equipped? +// 1 = momentary buttons. 2 = quadrature rotary encoder. +const byte mainAdjType = 1; + +// In normal running mode, what do the controls do? +// -1 = nothing/switch, -2 = cycle through functions, fn in fnsEnabled[] = go to that function +// If using soft alarm/power switch per below, the control(s) set to -1 will do the switching. +const char mainSelFn = -2; +const char mainAdjFn = -1; +const byte altSelFn = fnIsAlarm; //go straight to alarm + +//What are the signal pin(s) connected to? +const char piezoPin = 10; +const char relayPin = -1; +// -1 to disable feature (no relay item equipped); A3 if equipped (UNDB v8) +const byte relayMode = 0; //If relay is equipped, what does it do? +// 0 = switched mode: the relay will be switched to control an appliance like a radio or light fixture. If used with timer, it will switch on while timer is running (like a "sleep" function). If used with alarm, it will switch on when alarm trips; specify duration of this in switchDur. +// 1 = pulsed mode: the relay will be pulsed, like the beeper is, to control an intermittent signaling device like a solenoid or indicator lamp. Specify pulse duration in relayPulse. +const word signalDur = 180; //sec - when pulsed signal is going, pulses are sent once/sec for this period (e.g. 180 = 3min) +const word switchDur = 7200; //sec - when alarm triggers switched relay, it's switched on for this period (e.g. 7200 = 2hr) +const word piezoPulse = 500; //ms - used with piezo via tone() +const word relayPulse = 200; //ms - used with pulsed relay + +//Soft power switches +const byte enableSoftAlarmSwitch = 1; +// 1 = yes. Alarm can be switched on and off when clock is displaying the alarm time (fnIsAlarm). +// 0 = no. Alarm will be permanently on. Use with switched relay if the appliance has its own switch on this relay circuit. +const byte enableSoftPowerSwitch = 1; //works with switched relay only +// 1 = yes. Relay can be switched on and off directly when clock is displaying time of day (fnIsTime). This is useful if connecting an appliance (e.g. radio) that doesn't have its own switch, or if replacing the clock unit in a clock radio where the clock does all the switching (e.g. Telechron). +// 0 = no. Use if the connected appliance has its own power switch (independent of this relay circuit) or does not need to be manually switched. + +//LED circuit control +const char ledPin = -1; +// -1 to disable feature; A2 if equipped (UNDB v8) + +//When display is dim/off, a press will light the tubes for how long? +const byte unoffDur = 10; //sec + +// How long (in ms) are the button hold durations? +const word btnShortHold = 1000; //for setting the displayed feataure +const word btnLongHold = 3000; //for for entering options menu +const byte velThreshold = 150; //ms +// When an adj up/down input (btn or rot) follows another in less than this time, value will change more (10 vs 1). +// Recommend ~150 for rotaries. If you want to use this feature with buttons, extend to ~400. + +// What is the "frame rate" of the tube cleaning and display scrolling? up to 65535 ms +const word cleanSpeed = 200; //ms +const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to give the illusion of a slow scroll that doesn't pause, use (timeoutTempFn*1000)/(displaySize+1) - e.g. 714 for displaySize=6 and timeoutTempFn=5 + +// What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec +const unsigned long timeoutSet = 120; //sec +const unsigned long timeoutTempFn = 5; //sec \ No newline at end of file diff --git a/sixtube_lm/configs/v8-6tube-relayswitch.h b/sixtube_lm/configs/v8a-6tube-relayswitch.h similarity index 97% rename from sixtube_lm/configs/v8-6tube-relayswitch.h rename to sixtube_lm/configs/v8a-6tube-relayswitch.h index 9fdf211..c5750c1 100644 --- a/sixtube_lm/configs/v8-6tube-relayswitch.h +++ b/sixtube_lm/configs/v8a-6tube-relayswitch.h @@ -30,7 +30,6 @@ const byte altSel = A0; //alt select button - if unequipped, set to 0 // What type of adj controls are equipped? // 1 = momentary buttons. 2 = quadrature rotary encoder. -// If using rotary encoder, make sure it is connected to A0/A1 which can support digital input, not A6/A7. const byte mainAdjType = 1; // In normal running mode, what do the controls do? diff --git a/sixtube_lm/configs/v8b-6tube-relayswitch.h b/sixtube_lm/configs/v8b-6tube-relayswitch.h new file mode 100644 index 0000000..213bbe5 --- /dev/null +++ b/sixtube_lm/configs/v8b-6tube-relayswitch.h @@ -0,0 +1,82 @@ +const byte displaySize = 6; //number of tubes in display module. Small display adjustments are made for 4-tube clocks + +// available clock functions, and unique IDs (between 0 and 200) +const byte fnIsTime = 0; +const byte fnIsDate = 1; +const byte fnIsAlarm = 2; +const byte fnIsTimer = 3; +const byte fnIsDayCount = 4; +const byte fnIsTemp = 5; +const byte fnIsTubeTester = 6; //cycles all digits on all tubes 1/second, similar to anti-cathode-poisoning cleaner +// functions enabled in this clock, in their display order. Only fnIsTime is required +const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsAlarm, fnIsTimer, fnIsDayCount}; //, fnIsTemp, fnIsTubeTester +// To control which of these display persistently vs. switch back to Time after a few seconds, search "Temporary-display mode timeout" + +// These are the RLB board connections to Arduino analog input pins. +// S1/PL13 = Reset +// S2/PL5 = A1 +// S3/PL6 = A0 +// S4/PL7 = A6 +// S5/PL8 = A3 +// S6/PL9 = A2 +// S7/PL14 = A7 +// A6-A7 are analog-only pins that aren't quite as responsive and require a physical pullup resistor (1K to +5V), and can't be used with rotary encoders because they don't support pin change interrupts. + +// What input is associated with each control? +const byte mainSel = A6; //main select button - must be equipped +const byte mainAdjUp = A0; //main up/down buttons or rotary encoder - must be equipped +const byte mainAdjDn = A1; +const byte altSel = A7; //alt select button - if unequipped, set to 0 + +// What type of adj controls are equipped? +// 1 = momentary buttons. 2 = quadrature rotary encoder. +const byte mainAdjType = 1; + +// In normal running mode, what do the controls do? +// -1 = nothing/switch, -2 = cycle through functions, fn in fnsEnabled[] = go to that function +// If using soft alarm/power switch per below, the control(s) set to -1 will do the switching. +const char mainSelFn = -2; +const char mainAdjFn = -1; +const byte altSelFn = fnIsAlarm; //go straight to alarm + +//What are the signal pin(s) connected to? +const char piezoPin = 10; +const char relayPin = A3; +// -1 to disable feature (no relay item equipped); A3 if equipped (UNDB v8) +const byte relayMode = 0; //If relay is equipped, what does it do? +// 0 = switched mode: the relay will be switched to control an appliance like a radio or light fixture. If used with timer, it will switch on while timer is running (like a "sleep" function). If used with alarm, it will switch on when alarm trips; specify duration of this in switchDur. +// 1 = pulsed mode: the relay will be pulsed, like the beeper is, to control an intermittent signaling device like a solenoid or indicator lamp. Specify pulse duration in relayPulse. +const word signalDur = 180; //sec - when pulsed signal is going, pulses are sent once/sec for this period (e.g. 180 = 3min) +const word switchDur = 7200; //sec - when alarm triggers switched relay, it's switched on for this period (e.g. 7200 = 2hr) +const word piezoPulse = 500; //ms - used with piezo via tone() +const word relayPulse = 200; //ms - used with pulsed relay + +//Soft power switches +const byte enableSoftAlarmSwitch = 1; +// 1 = yes. Alarm can be switched on and off when clock is displaying the alarm time (fnIsAlarm). +// 0 = no. Alarm will be permanently on. Use with switched relay if the appliance has its own switch on this relay circuit. +const byte enableSoftPowerSwitch = 1; //works with switched relay only +// 1 = yes. Relay can be switched on and off directly when clock is displaying time of day (fnIsTime). This is useful if connecting an appliance (e.g. radio) that doesn't have its own switch, or if replacing the clock unit in a clock radio where the clock does all the switching (e.g. Telechron). +// 0 = no. Use if the connected appliance has its own power switch (independent of this relay circuit) or does not need to be manually switched. + +//LED circuit control +const char ledPin = A2; +// -1 to disable feature; A2 if equipped (UNDB v8) + +//When display is dim/off, a press will light the tubes for how long? +const byte unoffDur = 10; //sec + +// How long (in ms) are the button hold durations? +const word btnShortHold = 1000; //for setting the displayed feataure +const word btnLongHold = 3000; //for for entering options menu +const byte velThreshold = 150; //ms +// When an adj up/down input (btn or rot) follows another in less than this time, value will change more (10 vs 1). +// Recommend ~150 for rotaries. If you want to use this feature with buttons, extend to ~400. + +// What is the "frame rate" of the tube cleaning and display scrolling? up to 65535 ms +const word cleanSpeed = 200; //ms +const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to give the illusion of a slow scroll that doesn't pause, use (timeoutTempFn*1000)/(displaySize+1) - e.g. 714 for displaySize=6 and timeoutTempFn=5 + +// What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec +const unsigned long timeoutSet = 120; //sec +const unsigned long timeoutTempFn = 5; //sec \ No newline at end of file From cc5ff2e087cdd142ab5bcb5e5dff530954be11dc Mon Sep 17 00:00:00 2001 From: Luke McKenzie Date: Sun, 23 Sep 2018 18:27:31 -0500 Subject: [PATCH 06/13] Test sketch for modified v8.0 boards --- sixtube_lm/configs/v5-6tube-red.h | 76 ++++++++++++++++++++++++++ sixtube_lm/configs/v5-6tube-rotary.h | 2 +- sixtube_lm/configs/v5-6tube.h | 2 +- sixtube_lm/configs/v8-4tube.h | 82 ++++++++++++++++++++++++++++ sixtube_lm/sixtube_lm.ino | 10 +++- v8mod_test/v8mod_test.ino | 80 +++++++++++++++++++++++++++ 6 files changed, 247 insertions(+), 5 deletions(-) create mode 100644 sixtube_lm/configs/v5-6tube-red.h create mode 100644 sixtube_lm/configs/v8-4tube.h create mode 100644 v8mod_test/v8mod_test.ino diff --git a/sixtube_lm/configs/v5-6tube-red.h b/sixtube_lm/configs/v5-6tube-red.h new file mode 100644 index 0000000..de28bb3 --- /dev/null +++ b/sixtube_lm/configs/v5-6tube-red.h @@ -0,0 +1,76 @@ +const byte displaySize = 6; //number of tubes in display module. Small display adjustments are made for 4-tube clocks + +// available clock functions, and unique IDs (between 0 and 200) +const byte fnIsTime = 0; +const byte fnIsDate = 1; +const byte fnIsAlarm = 2; +const byte fnIsTimer = 3; +const byte fnIsDayCount = 4; +const byte fnIsTemp = 5; +const byte fnIsTubeTester = 6; //cycles all digits on all tubes 1/second, similar to anti-cathode-poisoning cleaner +// functions enabled in this clock, in their display order. Only fnIsTime is required +const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsAlarm, fnIsTimer, fnIsDayCount}; //, fnIsTemp, fnIsTubeTester +// To control which of these display persistently vs. switch back to Time after a few seconds, search "Temporary-display mode timeout" + +// These are the UNDB v5 board connections to Arduino analog input pins. +// S1/PL13 = Reset +// S2/PL5 = A1 +// S3/PL6 = A0 +// S4/PL7 = A6 +// S5/PL8 = A3 +// S6/PL9 = A2 +// S7/PL14 = A7 +// A6-A7 are analog-only pins that aren't quite as responsive and require a physical pullup resistor (1K to +5V), and can't be used with rotary encoders because they don't support pin change interrupts. + +// What input is associated with each control? +const byte mainSel = A1; //main select button - must be equipped +const byte mainAdjUp = A6; //main up/down buttons or rotary encoder - must be equipped +const byte mainAdjDn = A2; +const byte altSel = 0; //alt select button - if unequipped, set to 0 + +// What type of adj controls are equipped? +// 1 = momentary buttons. 2 = quadrature rotary encoder. +const byte mainAdjType = 1; + +// In normal running mode, what do the controls do? +// -1 = nothing/switch, -2 = cycle through functions, fn in fnsEnabled[] = go to that function +// If using soft alarm/power switch per below, the control(s) set to -1 will do the switching. +const char mainSelFn = -2; +const char mainAdjFn = -1; +const byte altSelFn = -1; + +//What are the signal pin(s) connected to? +const char piezoPin = 10; +const char relayPin = -1; //don't change - not available until UNDB v8 +const byte relayMode = 0; //don't change - not available until UNDB v8 +const word signalDur = 180; //sec - when pulsed signal is going, pulses are sent once/sec for this period (e.g. 180 = 3min) +const word switchDur = 7200; //sec - when alarm triggers switched relay, it's switched on for this period (e.g. 7200 = 2hr) +const word piezoPulse = 500; //ms - used with piezo via tone() +const word relayPulse = 200; //ms - used with pulsed relay + +//Soft power switches +const byte enableSoftAlarmSwitch = 1; +// 1 = yes. Alarm can be switched on and off when clock is displaying the alarm time (fnIsAlarm). +// 0 = no. Alarm will be permanently on. Use with switched relay if the appliance has its own switch on this relay circuit. +const byte enableSoftPowerSwitch = 0; //don't change - not available until UNDB v8 + +//LED circuit control +const char ledPin = -1; //don't change - not available until UNDB v8 + +//When display is dim/off, a press will light the tubes for how long? +const byte unoffDur = 10; //sec + +// How long (in ms) are the button hold durations? +const word btnShortHold = 1000; //for setting the displayed feataure +const word btnLongHold = 3000; //for for entering options menu +const byte velThreshold = 150; //ms +// When an adj up/down input (btn or rot) follows another in less than this time, value will change more (10 vs 1). +// Recommend ~150 for rotaries. If you want to use this feature with buttons, extend to ~400. + +// What is the "frame rate" of the tube cleaning and display scrolling? up to 65535 ms +const word cleanSpeed = 200; //ms +const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to give the illusion of a slow scroll that doesn't pause, use (timeoutTempFn*1000)/(displaySize+1) - e.g. 714 for displaySize=6 and timeoutTempFn=5 + +// What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec +const unsigned long timeoutSet = 120; //sec +const unsigned long timeoutTempFn = 5; //sec \ No newline at end of file diff --git a/sixtube_lm/configs/v5-6tube-rotary.h b/sixtube_lm/configs/v5-6tube-rotary.h index 9f5b83e..4bc6bfc 100644 --- a/sixtube_lm/configs/v5-6tube-rotary.h +++ b/sixtube_lm/configs/v5-6tube-rotary.h @@ -30,7 +30,7 @@ const byte altSel = 0; //alt select button - if unequipped, set to 0 // What type of adj controls are equipped? // 1 = momentary buttons. 2 = quadrature rotary encoder. -const byte mainAdjType = 1; +const byte mainAdjType = 2; // In normal running mode, what do the controls do? // -1 = nothing/switch, -2 = cycle through functions, fn in fnsEnabled[] = go to that function diff --git a/sixtube_lm/configs/v5-6tube.h b/sixtube_lm/configs/v5-6tube.h index 22135b7..15908fc 100644 --- a/sixtube_lm/configs/v5-6tube.h +++ b/sixtube_lm/configs/v5-6tube.h @@ -30,7 +30,7 @@ const byte altSel = 0; //alt select button - if unequipped, set to 0 // What type of adj controls are equipped? // 1 = momentary buttons. 2 = quadrature rotary encoder. -const byte mainAdjType = 2; +const byte mainAdjType = 1; // In normal running mode, what do the controls do? // -1 = nothing/switch, -2 = cycle through functions, fn in fnsEnabled[] = go to that function diff --git a/sixtube_lm/configs/v8-4tube.h b/sixtube_lm/configs/v8-4tube.h new file mode 100644 index 0000000..f63a391 --- /dev/null +++ b/sixtube_lm/configs/v8-4tube.h @@ -0,0 +1,82 @@ +const byte displaySize = 4; //number of tubes in display module. Small display adjustments are made for 4-tube clocks + +// available clock functions, and unique IDs (between 0 and 200) +const byte fnIsTime = 0; +const byte fnIsDate = 1; +const byte fnIsAlarm = 2; +const byte fnIsTimer = 3; +const byte fnIsDayCount = 4; +const byte fnIsTemp = 5; +const byte fnIsTubeTester = 6; //cycles all digits on all tubes 1/second, similar to anti-cathode-poisoning cleaner +// functions enabled in this clock, in their display order. Only fnIsTime is required +const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsAlarm, fnIsTimer, fnIsDayCount}; //, fnIsTemp, fnIsTubeTester +// To control which of these display persistently vs. switch back to Time after a few seconds, search "Temporary-display mode timeout" + +// These are the RLB board connections to Arduino analog input pins. +// S1/PL13 = Reset +// S2/PL5 = A1 +// S3/PL6 = A0 +// S4/PL7 = A6 +// S5/PL8 = A3 +// S6/PL9 = A2 +// S7/PL14 = A7 +// A6-A7 are analog-only pins that aren't quite as responsive and require a physical pullup resistor (1K to +5V), and can't be used with rotary encoders because they don't support pin change interrupts. + +// What input is associated with each control? +const byte mainSel = A1; //main select button - must be equipped +const byte mainAdjUp = A2; //main up/down buttons or rotary encoder - must be equipped +const byte mainAdjDn = A3; +const byte altSel = A0; //alt select button - if unequipped, set to 0 + +// What type of adj controls are equipped? +// 1 = momentary buttons. 2 = quadrature rotary encoder. +const byte mainAdjType = 1; + +// In normal running mode, what do the controls do? +// -1 = nothing/switch, -2 = cycle through functions, fn in fnsEnabled[] = go to that function +// If using soft alarm/power switch per below, the control(s) set to -1 will do the switching. +const char mainSelFn = -2; +const char mainAdjFn = -1; +const byte altSelFn = fnIsAlarm; //go straight to alarm + +//What are the signal pin(s) connected to? +const char piezoPin = 10; +const char relayPin = -1; +// -1 to disable feature (no relay item equipped); A3 if equipped (UNDB v8) +const byte relayMode = 0; //If relay is equipped, what does it do? +// 0 = switched mode: the relay will be switched to control an appliance like a radio or light fixture. If used with timer, it will switch on while timer is running (like a "sleep" function). If used with alarm, it will switch on when alarm trips; specify duration of this in switchDur. +// 1 = pulsed mode: the relay will be pulsed, like the beeper is, to control an intermittent signaling device like a solenoid or indicator lamp. Specify pulse duration in relayPulse. +const word signalDur = 180; //sec - when pulsed signal is going, pulses are sent once/sec for this period (e.g. 180 = 3min) +const word switchDur = 7200; //sec - when alarm triggers switched relay, it's switched on for this period (e.g. 7200 = 2hr) +const word piezoPulse = 500; //ms - used with piezo via tone() +const word relayPulse = 200; //ms - used with pulsed relay + +//Soft power switches +const byte enableSoftAlarmSwitch = 1; +// 1 = yes. Alarm can be switched on and off when clock is displaying the alarm time (fnIsAlarm). +// 0 = no. Alarm will be permanently on. Use with switched relay if the appliance has its own switch on this relay circuit. +const byte enableSoftPowerSwitch = 1; //works with switched relay only +// 1 = yes. Relay can be switched on and off directly when clock is displaying time of day (fnIsTime). This is useful if connecting an appliance (e.g. radio) that doesn't have its own switch, or if replacing the clock unit in a clock radio where the clock does all the switching (e.g. Telechron). +// 0 = no. Use if the connected appliance has its own power switch (independent of this relay circuit) or does not need to be manually switched. + +//LED circuit control +const char ledPin = -1; +// -1 to disable feature; A2 if equipped (UNDB v8) + +//When display is dim/off, a press will light the tubes for how long? +const byte unoffDur = 10; //sec + +// How long (in ms) are the button hold durations? +const word btnShortHold = 1000; //for setting the displayed feataure +const word btnLongHold = 3000; //for for entering options menu +const byte velThreshold = 150; //ms +// When an adj up/down input (btn or rot) follows another in less than this time, value will change more (10 vs 1). +// Recommend ~150 for rotaries. If you want to use this feature with buttons, extend to ~400. + +// What is the "frame rate" of the tube cleaning and display scrolling? up to 65535 ms +const word cleanSpeed = 200; //ms +const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to give the illusion of a slow scroll that doesn't pause, use (timeoutTempFn*1000)/(displaySize+1) - e.g. 714 for displaySize=6 and timeoutTempFn=5 + +// What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec +const unsigned long timeoutSet = 120; //sec +const unsigned long timeoutTempFn = 5; //sec \ No newline at end of file diff --git a/sixtube_lm/sixtube_lm.ino b/sixtube_lm/sixtube_lm.ino index e170a03..2dfdeef 100644 --- a/sixtube_lm/sixtube_lm.ino +++ b/sixtube_lm/sixtube_lm.ino @@ -9,11 +9,13 @@ //Include the config file that matches your hardware setup. If needed, duplicate an existing one. //#include "configs/v5-6tube.h" //UNDB v5, 6 tubes +//#include "configs/v5-6tube-red.h" //UNDB v5, 6 tubes, red case (weird buttons) //#include "configs/v5-6tube-rotary.h" //UNDB v5, 6 tubes, rotary encoder instead of buttons //#include "configs/v5-4tube.h" //UNDB v5, 4 tubes -#include "configs/v8-6tube.h" //UNDB v8 before modification, no LED or relay support -//#include "configs/v8a-6tube-relayswitch.h" //UNDB v8 after modification A (Select=A1, Adj=A6/A7, Alt=A0, LED=A2, Relay=A3) -//#include "configs/v8b-6tube-relayswitch.h" //UNDB v8 after modification A (Select=A6, Adj=A0/A1, Alt=A7, LED=A2, Relay=A3) +//#include "configs/v8-6tube.h" //UNDB v8 before modification, no LED or relay support +//#include "configs/v8-4tube.h" //UNDB v8 before modification, no LED or relay support +#include "configs/v8a-6tube-relayswitch.h" //UNDB v8 after modification A (Select=A1, Adj=A6/A7, Alt=A0, LED=A2, Relay=A3) +//#include "configs/v8b-6tube-relayswitch.h" //UNDB v8 after modification B (Select=A6, Adj=A0/A1, Alt=A7, LED=A2, Relay=A3) ////////// Other includes, global consts, and vars ////////// @@ -928,6 +930,8 @@ void updateDisplay(){ } else blankDisplay(mspos,mspos+1,true); //display second (in relative position). Leading zero if t>=1m. editDisplay(timerRemain%60,mspos+2,mspos+3,(timerRemain>=60?true:false),true); + //just in case 4-digit code is run on a 6-digit clock + if(displaySize<6) blankDisplay(4,5,false); break; case fnIsTemp: //thermometer int temp; temp = ds3231.getTemperature()*100; diff --git a/v8mod_test/v8mod_test.ino b/v8mod_test/v8mod_test.ino new file mode 100644 index 0000000..25c4227 --- /dev/null +++ b/v8mod_test/v8mod_test.ino @@ -0,0 +1,80 @@ +//For testing mods to the v8.0 board + +const byte btnSel = A1; +const byte btnAlt = A0; +const byte btnUp = A6; +const byte btnDn = A7; +const byte pinLED = A2; +const byte pinRelay = A3; + +byte btnCur = 0; //Momentary button currently in use - only one allowed at a time + +void setup(){ + Serial.begin(9600); + //0 and 1: set as digital input + pinMode(A0, INPUT_PULLUP); + pinMode(A1, INPUT_PULLUP); + //2 and 3: set as digital output + pinMode(A2, OUTPUT); + pinMode(A3, OUTPUT); + //4 and 5: for I2C + //6 and 7: input, but analog pins with hardware pullup resistors, so nothing to do + digitalWrite(pinLED,0); //0 = LEDs off + digitalWrite(pinRelay,1); //1 = connected device off + Serial.println(F("Ready for input")); +} + +void loop(){ + checkInputs(); + delay(10); //in case of switch bounce? +} + +void checkInputs(){ + checkBtn(btnSel); + checkBtn(btnAlt); + checkBtn(btnUp); + checkBtn(btnDn); +} +bool readInput(byte pin){ + if(pin==A6 || pin==A7) return analogRead(pin)<100?0:1; //analog-only pins + else return digitalRead(pin); +} +void checkBtn(byte btn){ + //Changes in momentary buttons, LOW = pressed. + //When a button event has occurred, will call ctrlEvt + bool bnow = readInput(btn); + //If the button has just been pressed, and no other buttons are in use... + if(btnCur==0 && bnow==LOW) { + btnCur = btn; + Serial.println(); + switch(btn) { + case btnSel: Serial.print(F("btnSel")); break; + case btnAlt: Serial.print(F("btnAlt")); break; + case btnUp: Serial.print(F("btnUp")); break; + case btnDn: Serial.print(F("btnDn")); break; + default: break; + } //end button printing switch + Serial.println(F(" pressed")); + if(btn==btnUp || btn==btnDn) { + byte curOutput = (btn==btnUp? pinLED: pinRelay); + bool newState = !digitalRead(curOutput); + digitalWrite(curOutput, newState); + Serial.println(); + switch(curOutput) { + case pinLED: + if(newState) Serial.println(F("pinLED switched on. LED pins should measure a voltage now.")); + else Serial.println(F("pinLED switched off. LED pins should measure NO voltage now.")); + break; + case pinRelay: + if(newState) Serial.println(F("pinRelay switched on. Relay pins should measure open circuit now (connected device off).")); + else Serial.println(F("pinRelay switched off. Relay pins should measure closed circuit now (connected device on).")); + break; + default: break; + } //end output printing switch + } //end if btn up/down + } //end if button presed + //If the button has just been released... + if(btnCur==btn && bnow==HIGH) { + btnCur = 0; + } +} \ No newline at end of file From 6ba149d0e492831031736f5a7d816bb717f7b812 Mon Sep 17 00:00:00 2001 From: Luke McKenzie Date: Thu, 4 Oct 2018 01:13:39 -0500 Subject: [PATCH 07/13] v8a and v8b isolated tests; mods to code for v8a and v8b (untested) --- sixtube_lm/configs/v5-4tube.h | 19 +++- sixtube_lm/configs/v5-6tube-red.h | 19 +++- sixtube_lm/configs/v5-6tube-rotary.h | 19 +++- sixtube_lm/configs/v5-6tube.h | 19 +++- sixtube_lm/configs/v8-4tube.h | 19 +++- sixtube_lm/configs/v8-6tube.h | 19 +++- sixtube_lm/configs/v8a-6tube-relayswitch.h | 25 ++++- sixtube_lm/configs/v8b-6tube-relayswitch.h | 25 ++++- sixtube_lm/sixtube_lm.ino | 6 +- v8a_mod_test/v8a_mod_test.ino | 113 +++++++++++++++++++++ v8b_mod_test/v8b_mod_test.ino | 113 +++++++++++++++++++++ v8mod_test/v8mod_test.ino | 80 --------------- 12 files changed, 379 insertions(+), 97 deletions(-) create mode 100644 v8a_mod_test/v8a_mod_test.ino create mode 100644 v8b_mod_test/v8b_mod_test.ino delete mode 100644 v8mod_test/v8mod_test.ino diff --git a/sixtube_lm/configs/v5-4tube.h b/sixtube_lm/configs/v5-4tube.h index 67a9775..9a57818 100644 --- a/sixtube_lm/configs/v5-4tube.h +++ b/sixtube_lm/configs/v5-4tube.h @@ -73,4 +73,21 @@ const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to giv // What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec const unsigned long timeoutSet = 120; //sec -const unsigned long timeoutTempFn = 5; //sec \ No newline at end of file +const unsigned long timeoutTempFn = 5; //sec + +//This clock is 2x3 multiplexed: two tubes powered at a time. +//The anode channel determines which two tubes are powered, +//and the two SN74141 cathode driver chips determine which digits are lit. +//4 pins out to each SN74141, representing a binary number with values [1,2,4,8] +const char outA1 = 2; +const char outA2 = 3; +const char outA3 = 4; +const char outA4 = 5; +const char outB1 = 6; +const char outB2 = 7; +const char outB3 = 8; +const char outB4 = 9; +//3 pins out to anode channel switches +const char anode1 = 11; +const char anode2 = 12; +const char anode3 = 13; \ No newline at end of file diff --git a/sixtube_lm/configs/v5-6tube-red.h b/sixtube_lm/configs/v5-6tube-red.h index de28bb3..3d0f4f2 100644 --- a/sixtube_lm/configs/v5-6tube-red.h +++ b/sixtube_lm/configs/v5-6tube-red.h @@ -73,4 +73,21 @@ const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to giv // What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec const unsigned long timeoutSet = 120; //sec -const unsigned long timeoutTempFn = 5; //sec \ No newline at end of file +const unsigned long timeoutTempFn = 5; //sec + +//This clock is 2x3 multiplexed: two tubes powered at a time. +//The anode channel determines which two tubes are powered, +//and the two SN74141 cathode driver chips determine which digits are lit. +//4 pins out to each SN74141, representing a binary number with values [1,2,4,8] +const char outA1 = 2; +const char outA2 = 3; +const char outA3 = 4; +const char outA4 = 5; +const char outB1 = 6; +const char outB2 = 7; +const char outB3 = 8; +const char outB4 = 9; +//3 pins out to anode channel switches +const char anode1 = 11; +const char anode2 = 12; +const char anode3 = 13; \ No newline at end of file diff --git a/sixtube_lm/configs/v5-6tube-rotary.h b/sixtube_lm/configs/v5-6tube-rotary.h index 4bc6bfc..a59cb2e 100644 --- a/sixtube_lm/configs/v5-6tube-rotary.h +++ b/sixtube_lm/configs/v5-6tube-rotary.h @@ -73,4 +73,21 @@ const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to giv // What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec const unsigned long timeoutSet = 120; //sec -const unsigned long timeoutTempFn = 5; //sec \ No newline at end of file +const unsigned long timeoutTempFn = 5; //sec + +//This clock is 2x3 multiplexed: two tubes powered at a time. +//The anode channel determines which two tubes are powered, +//and the two SN74141 cathode driver chips determine which digits are lit. +//4 pins out to each SN74141, representing a binary number with values [1,2,4,8] +const char outA1 = 2; +const char outA2 = 3; +const char outA3 = 4; +const char outA4 = 5; +const char outB1 = 6; +const char outB2 = 7; +const char outB3 = 8; +const char outB4 = 9; +//3 pins out to anode channel switches +const char anode1 = 11; +const char anode2 = 12; +const char anode3 = 13; \ No newline at end of file diff --git a/sixtube_lm/configs/v5-6tube.h b/sixtube_lm/configs/v5-6tube.h index 15908fc..0b34ff6 100644 --- a/sixtube_lm/configs/v5-6tube.h +++ b/sixtube_lm/configs/v5-6tube.h @@ -73,4 +73,21 @@ const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to giv // What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec const unsigned long timeoutSet = 120; //sec -const unsigned long timeoutTempFn = 5; //sec \ No newline at end of file +const unsigned long timeoutTempFn = 5; //sec + +//This clock is 2x3 multiplexed: two tubes powered at a time. +//The anode channel determines which two tubes are powered, +//and the two SN74141 cathode driver chips determine which digits are lit. +//4 pins out to each SN74141, representing a binary number with values [1,2,4,8] +const char outA1 = 2; +const char outA2 = 3; +const char outA3 = 4; +const char outA4 = 5; +const char outB1 = 6; +const char outB2 = 7; +const char outB3 = 8; +const char outB4 = 9; +//3 pins out to anode channel switches +const char anode1 = 11; +const char anode2 = 12; +const char anode3 = 13; \ No newline at end of file diff --git a/sixtube_lm/configs/v8-4tube.h b/sixtube_lm/configs/v8-4tube.h index f63a391..af0fd3a 100644 --- a/sixtube_lm/configs/v8-4tube.h +++ b/sixtube_lm/configs/v8-4tube.h @@ -79,4 +79,21 @@ const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to giv // What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec const unsigned long timeoutSet = 120; //sec -const unsigned long timeoutTempFn = 5; //sec \ No newline at end of file +const unsigned long timeoutTempFn = 5; //sec + +//This clock is 2x3 multiplexed: two tubes powered at a time. +//The anode channel determines which two tubes are powered, +//and the two SN74141 cathode driver chips determine which digits are lit. +//4 pins out to each SN74141, representing a binary number with values [1,2,4,8] +const char outA1 = 2; +const char outA2 = 3; +const char outA3 = 4; +const char outA4 = 5; +const char outB1 = 6; +const char outB2 = 7; +const char outB3 = 8; +const char outB4 = 9; +//3 pins out to anode channel switches +const char anode1 = 11; +const char anode2 = 12; +const char anode3 = 13; \ No newline at end of file diff --git a/sixtube_lm/configs/v8-6tube.h b/sixtube_lm/configs/v8-6tube.h index 4ae9986..fcca2a3 100644 --- a/sixtube_lm/configs/v8-6tube.h +++ b/sixtube_lm/configs/v8-6tube.h @@ -79,4 +79,21 @@ const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to giv // What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec const unsigned long timeoutSet = 120; //sec -const unsigned long timeoutTempFn = 5; //sec \ No newline at end of file +const unsigned long timeoutTempFn = 5; //sec + +//This clock is 2x3 multiplexed: two tubes powered at a time. +//The anode channel determines which two tubes are powered, +//and the two SN74141 cathode driver chips determine which digits are lit. +//4 pins out to each SN74141, representing a binary number with values [1,2,4,8] +const char outA1 = 2; +const char outA2 = 3; +const char outA3 = 4; +const char outA4 = 5; +const char outB1 = 6; +const char outB2 = 7; +const char outB3 = 8; +const char outB4 = 9; +//3 pins out to anode channel switches +const char anode1 = 11; +const char anode2 = 12; +const char anode3 = 13; \ No newline at end of file diff --git a/sixtube_lm/configs/v8a-6tube-relayswitch.h b/sixtube_lm/configs/v8a-6tube-relayswitch.h index c5750c1..67dfa36 100644 --- a/sixtube_lm/configs/v8a-6tube-relayswitch.h +++ b/sixtube_lm/configs/v8a-6tube-relayswitch.h @@ -59,9 +59,9 @@ const byte enableSoftPowerSwitch = 1; //works with switched relay only // 1 = yes. Relay can be switched on and off directly when clock is displaying time of day (fnIsTime). This is useful if connecting an appliance (e.g. radio) that doesn't have its own switch, or if replacing the clock unit in a clock radio where the clock does all the switching (e.g. Telechron). // 0 = no. Use if the connected appliance has its own power switch (independent of this relay circuit) or does not need to be manually switched. -//LED circuit control -const char ledPin = A2; -// -1 to disable feature; A2 if equipped (UNDB v8) +//LED circuit control with PWM +const char ledPin = 11; +// -1 to disable feature; 11 if equipped (UNDB v8 modded) //When display is dim/off, a press will light the tubes for how long? const byte unoffDur = 10; //sec @@ -79,4 +79,21 @@ const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to giv // What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec const unsigned long timeoutSet = 120; //sec -const unsigned long timeoutTempFn = 5; //sec \ No newline at end of file +const unsigned long timeoutTempFn = 5; //sec + +//This clock is 2x3 multiplexed: two tubes powered at a time. +//The anode channel determines which two tubes are powered, +//and the two SN74141 cathode driver chips determine which digits are lit. +//4 pins out to each SN74141, representing a binary number with values [1,2,4,8] +const char outA1 = 2; +const char outA2 = 3; +const char outA3 = 4; +const char outA4 = 5; +const char outB1 = 6; +const char outB2 = 7; +const char outB3 = 8; +const char outB4 = 9; +//3 pins out to anode channel switches +const char anode1 = 16; //A2 +const char anode2 = 12; +const char anode3 = 13; \ No newline at end of file diff --git a/sixtube_lm/configs/v8b-6tube-relayswitch.h b/sixtube_lm/configs/v8b-6tube-relayswitch.h index 213bbe5..c7d1087 100644 --- a/sixtube_lm/configs/v8b-6tube-relayswitch.h +++ b/sixtube_lm/configs/v8b-6tube-relayswitch.h @@ -59,9 +59,9 @@ const byte enableSoftPowerSwitch = 1; //works with switched relay only // 1 = yes. Relay can be switched on and off directly when clock is displaying time of day (fnIsTime). This is useful if connecting an appliance (e.g. radio) that doesn't have its own switch, or if replacing the clock unit in a clock radio where the clock does all the switching (e.g. Telechron). // 0 = no. Use if the connected appliance has its own power switch (independent of this relay circuit) or does not need to be manually switched. -//LED circuit control -const char ledPin = A2; -// -1 to disable feature; A2 if equipped (UNDB v8) +//LED circuit control with PWM +const char ledPin = 11; +// -1 to disable feature; 11 if equipped (UNDB v8 modded) //When display is dim/off, a press will light the tubes for how long? const byte unoffDur = 10; //sec @@ -79,4 +79,21 @@ const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to giv // What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec const unsigned long timeoutSet = 120; //sec -const unsigned long timeoutTempFn = 5; //sec \ No newline at end of file +const unsigned long timeoutTempFn = 5; //sec + +//This clock is 2x3 multiplexed: two tubes powered at a time. +//The anode channel determines which two tubes are powered, +//and the two SN74141 cathode driver chips determine which digits are lit. +//4 pins out to each SN74141, representing a binary number with values [1,2,4,8] +const char outA1 = 2; +const char outA2 = 3; +const char outA3 = 4; +const char outA4 = 5; +const char outB1 = 6; +const char outB2 = 7; +const char outB3 = 8; +const char outB4 = 9; +//3 pins out to anode channel switches +const char anode1 = 16; //A2 +const char anode2 = 12; +const char anode3 = 13; \ No newline at end of file diff --git a/sixtube_lm/sixtube_lm.ino b/sixtube_lm/sixtube_lm.ino index 2dfdeef..593b071 100644 --- a/sixtube_lm/sixtube_lm.ino +++ b/sixtube_lm/sixtube_lm.ino @@ -1004,10 +1004,10 @@ void startScroll() { //To scroll a value in, call this after calling editDisplay //The anode channel determines which two tubes are powered, //and the two SN74141 cathode driver chips determine which digits are lit. //4 pins out to each SN74141, representing a binary number with values [1,2,4,8] -byte binOutA[4] = {2,3,4,5}; -byte binOutB[4] = {6,7,8,9}; +byte binOutA[4] = {outA1,outA2,outA3,outA4}; +byte binOutB[4] = {outB1,outB2,outB3,outB4}; //3 pins out to anode channel switches -byte anodes[3] = {11,12,13}; +byte anodes[3] = {anode1,anode2,anode3}; const int fadeDur = 5; //ms - each multiplexed pair of digits appears for this amount of time per cycle const int dimDur = 4; //ms - portion of fadeDur that is left dark during dim times diff --git a/v8a_mod_test/v8a_mod_test.ino b/v8a_mod_test/v8a_mod_test.ino new file mode 100644 index 0000000..30c42d8 --- /dev/null +++ b/v8a_mod_test/v8a_mod_test.ino @@ -0,0 +1,113 @@ +//For testing mods to the v8.0 board, style A, with LED PWM + +const byte btnSel = A1; //was A1 +const byte btnAlt = A0; //was A0 +const byte btnUp = A6; //was A2 +const byte btnDn = A7; //was A3 +const byte pinLED = 11; //was A6 +const byte pinRelay = A3; //was A7 +byte binOutA[4] = {2,3,4,5}; +byte binOutB[4] = {6,7,8,9}; +byte anodes[3] = {16,12,13}; //first was 11 + +byte btnCur = 0; //Momentary button currently in use - only one allowed at a time + +byte ledStateNow = 0; +byte ledStateTarget = 0; + +void setup(){ + Serial.begin(9600); + //0 and 1: set as digital input + pinMode(A0, INPUT_PULLUP); + pinMode(A1, INPUT_PULLUP); + //2 and 3: set as digital output + pinMode(A2, OUTPUT); + pinMode(A3, OUTPUT); + //4 and 5: for I2C + //6 and 7: input, but analog pins with hardware pullup resistors, so nothing to do + analogWrite(pinLED,0); //0 = LEDs off + digitalWrite(pinRelay,1); //1 = connected device off + + //Set up just enough tube output to confirm the changed first anode is working + for(byte i=0; i<4; i++) { pinMode(binOutA[i],OUTPUT); pinMode(binOutB[i],OUTPUT); digitalWrite(binOutA[i],LOW); digitalWrite(binOutB[i],LOW); } + for(byte i=0; i<3; i++) { pinMode(anodes[i],OUTPUT); digitalWrite(anodes[i],LOW); } + digitalWrite(anodes[0],HIGH); //the one we want to test + + Serial.println(); + Serial.println(F("Ready for input.")); + Serial.println(F("Display should now read 0 on 3rd and 6th tubes.")); + Serial.println(F("SEL should change them to a 1; ALT should change them to 2.")); + Serial.println(F("UP should fade the LEDs in and out.")); + Serial.println(F("DOWN should toggle the relay.")); +} + +void loop(){ + checkInputs(); + if(ledStateNow != ledStateTarget) { + if(ledStateNow > ledStateTarget) { ledStateNow -= 5; } + else if(ledStateNow < ledStateTarget) { ledStateNow += 5; } + // Serial.print(ledStateNow,DEC); + // Serial.print(F(" => ")); + // Serial.println(ledStateTarget,DEC); + analogWrite(pinLED,ledStateNow); + } + delay(5); //in case of switch bounce? +} + +void checkInputs(){ + checkBtn(btnSel); + checkBtn(btnAlt); + checkBtn(btnUp); + checkBtn(btnDn); +} +bool readInput(byte pin){ + if(pin==A6 || pin==A7) return analogRead(pin)<100?0:1; //analog-only pins + else return digitalRead(pin); +} +void checkBtn(byte btn){ + //Changes in momentary buttons, LOW = pressed. + //When a button event has occurred, will call ctrlEvt + bool bnow = readInput(btn); + //If the button has just been pressed, and no other buttons are in use... + if(btnCur==0 && bnow==LOW) { + btnCur = btn; + Serial.println(); + bool newState = LOW; + switch(btn) { + case btnSel: + Serial.println(F("btnSel pressed")); + digitalWrite(binOutA[0],HIGH); + digitalWrite(binOutA[1],LOW); + digitalWrite(binOutB[0],HIGH); + digitalWrite(binOutB[1],LOW); + break; + case btnAlt: + Serial.println(F("btnAlt pressed")); + digitalWrite(binOutA[0],LOW); + digitalWrite(binOutA[1],HIGH); + digitalWrite(binOutB[0],LOW); + digitalWrite(binOutB[1],HIGH); + break; + case btnUp: + Serial.println(F("btnUp pressed")); + ledStateTarget = (ledStateTarget==0?255:0); + Serial.println(); + if(ledStateTarget==0) Serial.println(F("LED switched off. LED PWM pin should fade to open circuit.")); + else Serial.println(F("LED switched on. LED PWM pin should fade to closed circuit.")); + break; + case btnDn: + Serial.println(F("btnDn pressed")); + newState = !digitalRead(pinRelay); + digitalWrite(pinRelay, newState); + Serial.println(); + if(newState) Serial.println(F("Relay switched on. Relay pins should measure open circuit now (connected device off).")); + else Serial.println(F("Relay switched off. Relay pins should measure closed circuit now (connected device on).")); + break; + default: break; + } //end button printing switch + } //end if button presed + //If the button has just been released... + if(btnCur==btn && bnow==HIGH) { + btnCur = 0; + } +} \ No newline at end of file diff --git a/v8b_mod_test/v8b_mod_test.ino b/v8b_mod_test/v8b_mod_test.ino new file mode 100644 index 0000000..332af9e --- /dev/null +++ b/v8b_mod_test/v8b_mod_test.ino @@ -0,0 +1,113 @@ +//For testing mods to the v8.0 board, style B, with LED PWM + +const byte btnSel = A6; //was A1 +const byte btnAlt = A7; //was A0 +const byte btnUp = A0; //was A2 +const byte btnDn = A1; //was A3 +const byte pinLED = 11; //was A6 +const byte pinRelay = A3; //was A7 +byte binOutA[4] = {2,3,4,5}; +byte binOutB[4] = {6,7,8,9}; +byte anodes[3] = {16,12,13}; //first was 11 + +byte btnCur = 0; //Momentary button currently in use - only one allowed at a time + +byte ledStateNow = 0; +byte ledStateTarget = 0; + +void setup(){ + Serial.begin(9600); + //0 and 1: set as digital input + pinMode(A0, INPUT_PULLUP); + pinMode(A1, INPUT_PULLUP); + //2 and 3: set as digital output + pinMode(A2, OUTPUT); + pinMode(A3, OUTPUT); + //4 and 5: for I2C + //6 and 7: input, but analog pins with hardware pullup resistors, so nothing to do + analogWrite(pinLED,0); //0 = LEDs off + digitalWrite(pinRelay,1); //1 = connected device off + + //Set up just enough tube output to confirm the changed first anode is working + for(byte i=0; i<4; i++) { pinMode(binOutA[i],OUTPUT); pinMode(binOutB[i],OUTPUT); digitalWrite(binOutA[i],LOW); digitalWrite(binOutB[i],LOW); } + for(byte i=0; i<3; i++) { pinMode(anodes[i],OUTPUT); digitalWrite(anodes[i],LOW); } + digitalWrite(anodes[0],HIGH); //the one we want to test + + Serial.println(); + Serial.println(F("Ready for input.")); + Serial.println(F("Display should now read 0 on 3rd and 6th tubes.")); + Serial.println(F("SEL should change them to a 1; ALT should change them to 2.")); + Serial.println(F("UP should fade the LEDs in and out.")); + Serial.println(F("DOWN should toggle the relay.")); +} + +void loop(){ + checkInputs(); + if(ledStateNow != ledStateTarget) { + if(ledStateNow > ledStateTarget) { ledStateNow -= 5; } + else if(ledStateNow < ledStateTarget) { ledStateNow += 5; } + // Serial.print(ledStateNow,DEC); + // Serial.print(F(" => ")); + // Serial.println(ledStateTarget,DEC); + analogWrite(pinLED,ledStateNow); + } + delay(5); //in case of switch bounce? +} + +void checkInputs(){ + checkBtn(btnSel); + checkBtn(btnAlt); + checkBtn(btnUp); + checkBtn(btnDn); +} +bool readInput(byte pin){ + if(pin==A6 || pin==A7) return analogRead(pin)<100?0:1; //analog-only pins + else return digitalRead(pin); +} +void checkBtn(byte btn){ + //Changes in momentary buttons, LOW = pressed. + //When a button event has occurred, will call ctrlEvt + bool bnow = readInput(btn); + //If the button has just been pressed, and no other buttons are in use... + if(btnCur==0 && bnow==LOW) { + btnCur = btn; + Serial.println(); + bool newState = LOW; + switch(btn) { + case btnSel: + Serial.println(F("btnSel pressed")); + digitalWrite(binOutA[0],HIGH); + digitalWrite(binOutA[1],LOW); + digitalWrite(binOutB[0],HIGH); + digitalWrite(binOutB[1],LOW); + break; + case btnAlt: + Serial.println(F("btnAlt pressed")); + digitalWrite(binOutA[0],LOW); + digitalWrite(binOutA[1],HIGH); + digitalWrite(binOutB[0],LOW); + digitalWrite(binOutB[1],HIGH); + break; + case btnUp: + Serial.println(F("btnUp pressed")); + ledStateTarget = (ledStateTarget==0?255:0); + Serial.println(); + if(ledStateTarget==0) Serial.println(F("LED switched off. LED PWM pin should fade to open circuit.")); + else Serial.println(F("LED switched on. LED PWM pin should fade to closed circuit.")); + break; + case btnDn: + Serial.println(F("btnDn pressed")); + newState = !digitalRead(pinRelay); + digitalWrite(pinRelay, newState); + Serial.println(); + if(newState) Serial.println(F("Relay switched on. Relay pins should measure open circuit now (connected device off).")); + else Serial.println(F("Relay switched off. Relay pins should measure closed circuit now (connected device on).")); + break; + default: break; + } //end button printing switch + } //end if button presed + //If the button has just been released... + if(btnCur==btn && bnow==HIGH) { + btnCur = 0; + } +} \ No newline at end of file diff --git a/v8mod_test/v8mod_test.ino b/v8mod_test/v8mod_test.ino deleted file mode 100644 index 25c4227..0000000 --- a/v8mod_test/v8mod_test.ino +++ /dev/null @@ -1,80 +0,0 @@ -//For testing mods to the v8.0 board - -const byte btnSel = A1; -const byte btnAlt = A0; -const byte btnUp = A6; -const byte btnDn = A7; -const byte pinLED = A2; -const byte pinRelay = A3; - -byte btnCur = 0; //Momentary button currently in use - only one allowed at a time - -void setup(){ - Serial.begin(9600); - //0 and 1: set as digital input - pinMode(A0, INPUT_PULLUP); - pinMode(A1, INPUT_PULLUP); - //2 and 3: set as digital output - pinMode(A2, OUTPUT); - pinMode(A3, OUTPUT); - //4 and 5: for I2C - //6 and 7: input, but analog pins with hardware pullup resistors, so nothing to do - digitalWrite(pinLED,0); //0 = LEDs off - digitalWrite(pinRelay,1); //1 = connected device off - Serial.println(F("Ready for input")); -} - -void loop(){ - checkInputs(); - delay(10); //in case of switch bounce? -} - -void checkInputs(){ - checkBtn(btnSel); - checkBtn(btnAlt); - checkBtn(btnUp); - checkBtn(btnDn); -} -bool readInput(byte pin){ - if(pin==A6 || pin==A7) return analogRead(pin)<100?0:1; //analog-only pins - else return digitalRead(pin); -} -void checkBtn(byte btn){ - //Changes in momentary buttons, LOW = pressed. - //When a button event has occurred, will call ctrlEvt - bool bnow = readInput(btn); - //If the button has just been pressed, and no other buttons are in use... - if(btnCur==0 && bnow==LOW) { - btnCur = btn; - Serial.println(); - switch(btn) { - case btnSel: Serial.print(F("btnSel")); break; - case btnAlt: Serial.print(F("btnAlt")); break; - case btnUp: Serial.print(F("btnUp")); break; - case btnDn: Serial.print(F("btnDn")); break; - default: break; - } //end button printing switch - Serial.println(F(" pressed")); - if(btn==btnUp || btn==btnDn) { - byte curOutput = (btn==btnUp? pinLED: pinRelay); - bool newState = !digitalRead(curOutput); - digitalWrite(curOutput, newState); - Serial.println(); - switch(curOutput) { - case pinLED: - if(newState) Serial.println(F("pinLED switched on. LED pins should measure a voltage now.")); - else Serial.println(F("pinLED switched off. LED pins should measure NO voltage now.")); - break; - case pinRelay: - if(newState) Serial.println(F("pinRelay switched on. Relay pins should measure open circuit now (connected device off).")); - else Serial.println(F("pinRelay switched off. Relay pins should measure closed circuit now (connected device on).")); - break; - default: break; - } //end output printing switch - } //end if btn up/down - } //end if button presed - //If the button has just been released... - if(btnCur==btn && bnow==HIGH) { - btnCur = 0; - } -} \ No newline at end of file From 03c7f19b15654db0b58465669fc56721846d7202 Mon Sep 17 00:00:00 2001 From: Luke McKenzie Date: Sun, 14 Oct 2018 12:15:18 -0500 Subject: [PATCH 08/13] v8 mod test: tell which button was released --- v8a_mod_test/v8a_mod_test.ino | 16 ++++++++++++++++ v8b_mod_test/v8b_mod_test.ino | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/v8a_mod_test/v8a_mod_test.ino b/v8a_mod_test/v8a_mod_test.ino index 30c42d8..ec73001 100644 --- a/v8a_mod_test/v8a_mod_test.ino +++ b/v8a_mod_test/v8a_mod_test.ino @@ -108,6 +108,22 @@ void checkBtn(byte btn){ } //end if button presed //If the button has just been released... if(btnCur==btn && bnow==HIGH) { + Serial.println(); + switch(btn){ + case btnSel: + Serial.println(F("btnSel released")); + break; + case btnAlt: + Serial.println(F("btnAlt released")); + break; + case btnUp: + Serial.println(F("btnUp released")); + break; + case btnDn: + Serial.println(F("btnDn released")); + break; + default: break; + } btnCur = 0; } } \ No newline at end of file diff --git a/v8b_mod_test/v8b_mod_test.ino b/v8b_mod_test/v8b_mod_test.ino index 332af9e..2dbed76 100644 --- a/v8b_mod_test/v8b_mod_test.ino +++ b/v8b_mod_test/v8b_mod_test.ino @@ -108,6 +108,22 @@ void checkBtn(byte btn){ } //end if button presed //If the button has just been released... if(btnCur==btn && bnow==HIGH) { + Serial.println(); + switch(btn){ + case btnSel: + Serial.println(F("btnSel released")); + break; + case btnAlt: + Serial.println(F("btnAlt released")); + break; + case btnUp: + Serial.println(F("btnUp released")); + break; + case btnDn: + Serial.println(F("btnDn released")); + break; + default: break; + } btnCur = 0; } } \ No newline at end of file From 737d4573a39573d69b93bfe7fd0825570a700b02 Mon Sep 17 00:00:00 2001 From: Luke McKenzie Date: Sun, 14 Oct 2018 12:16:08 -0500 Subject: [PATCH 09/13] v8a/b configs: rename to reflect PWM change; include version with sel/alt flipped --- ...ayswitch.h => v8a-6tube-relayswitch-pwm.h} | 0 .../configs/v8b-6tube-relayswitch-pwm-top.h | 101 ++++++++++++++++++ ...ayswitch.h => v8b-6tube-relayswitch-pwm.h} | 0 3 files changed, 101 insertions(+) rename sixtube_lm/configs/{v8a-6tube-relayswitch.h => v8a-6tube-relayswitch-pwm.h} (100%) create mode 100644 sixtube_lm/configs/v8b-6tube-relayswitch-pwm-top.h rename sixtube_lm/configs/{v8b-6tube-relayswitch.h => v8b-6tube-relayswitch-pwm.h} (100%) diff --git a/sixtube_lm/configs/v8a-6tube-relayswitch.h b/sixtube_lm/configs/v8a-6tube-relayswitch-pwm.h similarity index 100% rename from sixtube_lm/configs/v8a-6tube-relayswitch.h rename to sixtube_lm/configs/v8a-6tube-relayswitch-pwm.h diff --git a/sixtube_lm/configs/v8b-6tube-relayswitch-pwm-top.h b/sixtube_lm/configs/v8b-6tube-relayswitch-pwm-top.h new file mode 100644 index 0000000..fb44b4d --- /dev/null +++ b/sixtube_lm/configs/v8b-6tube-relayswitch-pwm-top.h @@ -0,0 +1,101 @@ +//v8 with B style modification, and flipped Sel and Alt buttons so Sel is in the front when buttons are mounted display-side with IN-8-A display + +const byte displaySize = 6; //number of tubes in display module. Small display adjustments are made for 4-tube clocks + +// available clock functions, and unique IDs (between 0 and 200) +const byte fnIsTime = 0; +const byte fnIsDate = 1; +const byte fnIsAlarm = 2; +const byte fnIsTimer = 3; +const byte fnIsDayCount = 4; +const byte fnIsTemp = 5; +const byte fnIsTubeTester = 6; //cycles all digits on all tubes 1/second, similar to anti-cathode-poisoning cleaner +// functions enabled in this clock, in their display order. Only fnIsTime is required +const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsAlarm, fnIsTimer, fnIsDayCount}; //, fnIsTemp, fnIsTubeTester +// To control which of these display persistently vs. switch back to Time after a few seconds, search "Temporary-display mode timeout" + +// These are the RLB board connections to Arduino analog input pins. +// S1/PL13 = Reset +// S2/PL5 = A1 +// S3/PL6 = A0 +// S4/PL7 = A6 +// S5/PL8 = A3 +// S6/PL9 = A2 +// S7/PL14 = A7 +// A6-A7 are analog-only pins that aren't quite as responsive and require a physical pullup resistor (1K to +5V), and can't be used with rotary encoders because they don't support pin change interrupts. + +// What input is associated with each control? +const byte mainSel = A7; //main select button - must be equipped +const byte mainAdjUp = A0; //main up/down buttons or rotary encoder - must be equipped +const byte mainAdjDn = A1; +const byte altSel = A6; //alt select button - if unequipped, set to 0 + +// What type of adj controls are equipped? +// 1 = momentary buttons. 2 = quadrature rotary encoder. +const byte mainAdjType = 1; + +// In normal running mode, what do the controls do? +// -1 = nothing/switch, -2 = cycle through functions, fn in fnsEnabled[] = go to that function +// If using soft alarm/power switch per below, the control(s) set to -1 will do the switching. +const char mainSelFn = -2; +const char mainAdjFn = -1; +const byte altSelFn = fnIsAlarm; //go straight to alarm + +//What are the signal pin(s) connected to? +const char piezoPin = 10; +const char relayPin = A3; +// -1 to disable feature (no relay item equipped); A3 if equipped (UNDB v8) +const byte relayMode = 0; //If relay is equipped, what does it do? +// 0 = switched mode: the relay will be switched to control an appliance like a radio or light fixture. If used with timer, it will switch on while timer is running (like a "sleep" function). If used with alarm, it will switch on when alarm trips; specify duration of this in switchDur. +// 1 = pulsed mode: the relay will be pulsed, like the beeper is, to control an intermittent signaling device like a solenoid or indicator lamp. Specify pulse duration in relayPulse. +const word signalDur = 180; //sec - when pulsed signal is going, pulses are sent once/sec for this period (e.g. 180 = 3min) +const word switchDur = 7200; //sec - when alarm triggers switched relay, it's switched on for this period (e.g. 7200 = 2hr) +const word piezoPulse = 500; //ms - used with piezo via tone() +const word relayPulse = 200; //ms - used with pulsed relay + +//Soft power switches +const byte enableSoftAlarmSwitch = 1; +// 1 = yes. Alarm can be switched on and off when clock is displaying the alarm time (fnIsAlarm). +// 0 = no. Alarm will be permanently on. Use with switched relay if the appliance has its own switch on this relay circuit. +const byte enableSoftPowerSwitch = 1; //works with switched relay only +// 1 = yes. Relay can be switched on and off directly when clock is displaying time of day (fnIsTime). This is useful if connecting an appliance (e.g. radio) that doesn't have its own switch, or if replacing the clock unit in a clock radio where the clock does all the switching (e.g. Telechron). +// 0 = no. Use if the connected appliance has its own power switch (independent of this relay circuit) or does not need to be manually switched. + +//LED circuit control with PWM +const char ledPin = 11; +// -1 to disable feature; 11 if equipped (UNDB v8 modded) + +//When display is dim/off, a press will light the tubes for how long? +const byte unoffDur = 10; //sec + +// How long (in ms) are the button hold durations? +const word btnShortHold = 1000; //for setting the displayed feataure +const word btnLongHold = 3000; //for for entering options menu +const byte velThreshold = 150; //ms +// When an adj up/down input (btn or rot) follows another in less than this time, value will change more (10 vs 1). +// Recommend ~150 for rotaries. If you want to use this feature with buttons, extend to ~400. + +// What is the "frame rate" of the tube cleaning and display scrolling? up to 65535 ms +const word cleanSpeed = 200; //ms +const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to give the illusion of a slow scroll that doesn't pause, use (timeoutTempFn*1000)/(displaySize+1) - e.g. 714 for displaySize=6 and timeoutTempFn=5 + +// What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec +const unsigned long timeoutSet = 120; //sec +const unsigned long timeoutTempFn = 5; //sec + +//This clock is 2x3 multiplexed: two tubes powered at a time. +//The anode channel determines which two tubes are powered, +//and the two SN74141 cathode driver chips determine which digits are lit. +//4 pins out to each SN74141, representing a binary number with values [1,2,4,8] +const char outA1 = 2; +const char outA2 = 3; +const char outA3 = 4; +const char outA4 = 5; +const char outB1 = 6; +const char outB2 = 7; +const char outB3 = 8; +const char outB4 = 9; +//3 pins out to anode channel switches +const char anode1 = 16; //A2 - was 11 before PWM fix +const char anode2 = 12; +const char anode3 = 13; \ No newline at end of file diff --git a/sixtube_lm/configs/v8b-6tube-relayswitch.h b/sixtube_lm/configs/v8b-6tube-relayswitch-pwm.h similarity index 100% rename from sixtube_lm/configs/v8b-6tube-relayswitch.h rename to sixtube_lm/configs/v8b-6tube-relayswitch-pwm.h From f9037e6859e064da19f331f9905677f98c723977 Mon Sep 17 00:00:00 2001 From: Luke McKenzie Date: Sun, 14 Oct 2018 12:35:55 -0500 Subject: [PATCH 10/13] Snooze cancel signal should use beeper when relay is switched; remove unused config lines --- sixtube_lm/sixtube_lm.ino | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/sixtube_lm/sixtube_lm.ino b/sixtube_lm/sixtube_lm.ino index 593b071..ed2f5ca 100644 --- a/sixtube_lm/sixtube_lm.ino +++ b/sixtube_lm/sixtube_lm.ino @@ -8,14 +8,7 @@ ////////// Hardware configuration ////////// //Include the config file that matches your hardware setup. If needed, duplicate an existing one. -//#include "configs/v5-6tube.h" //UNDB v5, 6 tubes -//#include "configs/v5-6tube-red.h" //UNDB v5, 6 tubes, red case (weird buttons) -//#include "configs/v5-6tube-rotary.h" //UNDB v5, 6 tubes, rotary encoder instead of buttons -//#include "configs/v5-4tube.h" //UNDB v5, 4 tubes -//#include "configs/v8-6tube.h" //UNDB v8 before modification, no LED or relay support -//#include "configs/v8-4tube.h" //UNDB v8 before modification, no LED or relay support -#include "configs/v8a-6tube-relayswitch.h" //UNDB v8 after modification A (Select=A1, Adj=A6/A7, Alt=A0, LED=A2, Relay=A3) -//#include "configs/v8b-6tube-relayswitch.h" //UNDB v8 after modification B (Select=A6, Adj=A0/A1, Alt=A7, LED=A2, Relay=A3) +#include "configs/v8b-6tube-relayswitch-pwm-top.h" ////////// Other includes, global consts, and vars ////////// @@ -271,7 +264,12 @@ void ctrlEvt(byte ctrl, byte evt){ stoppingSignal = false; if(evt==2 && snoozeRemain>0) { snoozeRemain = 0; - signalStart(fnIsAlarm,0,100); //Short beep at alarm pitch + //Short signal to indicate the snooze has cancelled //TODO write these edge cases into the signal generating stuff + if(getSignalOutput()==1 && relayMode==0) { //if the output is relay, but switched + if(piezoPin>=0) { tone(piezoPin, getSignalPitch(), 100); } //use the beeper anyway + } else { + signalStart(fnIsAlarm,0,100); + } } btnStop(); return; From 2805586e4bba760fa9b13874356581c3a5a00523 Mon Sep 17 00:00:00 2001 From: Luke McKenzie Date: Sun, 14 Oct 2018 15:36:35 -0500 Subject: [PATCH 11/13] Retire mainSelFn, mainAdjFn, altSelFn. Their functions are baked-in now. --- sixtube_lm/configs/v8b-6tube-relayswitch-pwm-top.h | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/sixtube_lm/configs/v8b-6tube-relayswitch-pwm-top.h b/sixtube_lm/configs/v8b-6tube-relayswitch-pwm-top.h index fb44b4d..5730ac5 100644 --- a/sixtube_lm/configs/v8b-6tube-relayswitch-pwm-top.h +++ b/sixtube_lm/configs/v8b-6tube-relayswitch-pwm-top.h @@ -25,22 +25,15 @@ const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsAlarm, fnIsTimer, fnIsDayCoun // A6-A7 are analog-only pins that aren't quite as responsive and require a physical pullup resistor (1K to +5V), and can't be used with rotary encoders because they don't support pin change interrupts. // What input is associated with each control? -const byte mainSel = A7; //main select button - must be equipped -const byte mainAdjUp = A0; //main up/down buttons or rotary encoder - must be equipped +const byte mainSel = A7; +const byte mainAdjUp = A0; const byte mainAdjDn = A1; -const byte altSel = A6; //alt select button - if unequipped, set to 0 +const byte altSel = A6; //if not equipped, set to 0 // What type of adj controls are equipped? // 1 = momentary buttons. 2 = quadrature rotary encoder. const byte mainAdjType = 1; -// In normal running mode, what do the controls do? -// -1 = nothing/switch, -2 = cycle through functions, fn in fnsEnabled[] = go to that function -// If using soft alarm/power switch per below, the control(s) set to -1 will do the switching. -const char mainSelFn = -2; -const char mainAdjFn = -1; -const byte altSelFn = fnIsAlarm; //go straight to alarm - //What are the signal pin(s) connected to? const char piezoPin = 10; const char relayPin = A3; From 6d78cfbec6b89277414269729d4846c966c09f12 Mon Sep 17 00:00:00 2001 From: Luke McKenzie Date: Sun, 14 Oct 2018 21:37:06 -0500 Subject: [PATCH 12/13] Fading LEDs via PWM; v8c mod (LED = pin 9) to fix tone() conflict with PWM on pin 11; reverse relay control polarity (since HIGH = open circuit = device off); switchPower via Alt rather than Adj; retire SelFn/AdjFn variables; rename night-off and day-off to night and away modes --- README.md | 31 +++- .../configs/v8c-6tube-relayswitch-pwm-top.h | 94 ++++++++++ sixtube_lm/sixtube_lm.ino | 175 ++++++++++-------- v8c_mod_test/v8c_mod_test.ino | 129 +++++++++++++ 4 files changed, 346 insertions(+), 83 deletions(-) create mode 100644 sixtube_lm/configs/v8c-6tube-relayswitch-pwm-top.h create mode 100644 v8c_mod_test/v8c_mod_test.ino diff --git a/README.md b/README.md index d8d812a..fa946f5 100644 --- a/README.md +++ b/README.md @@ -17,14 +17,25 @@ _Note: Many variations are possible, depending on your clock's hardware; but thi | Function | Looks like | Notes | | --- | --- | --- | -| **Time** | `12 34 56` | The time of day. You can choose 12h or 24h format in the options menu (1). When setting, it's in 24h format (so you can tell AM from PM) and the seconds will reset to :00 when you save. The clock keeps time during power outages and compensates for temperature effects.

If your clock is controlling an appliance ([relay in switched mode](#hardware-configuration)), use **Adjust** here to switch it on and off manually. | +| **Time** | `12 34 56` | The time of day. You can choose 12h or 24h format in the options menu (1). When setting, it's in 24h format (so you can tell AM from PM) and the seconds will reset to :00 when you save. The clock keeps time during power outages and compensates for temperature effects. | | **Date** | `_2 _4 _0`
(for Sun 2/4) | You can choose the date format in the options menu (2). Setting is done in three stages: first year, then month, then date.
Weekdays are: 0=Sun, 1=Mon, 2=Tue, 3=Wed, 4=Thu, 5=Fri, 6=Sat | -| **Alarm** | `_7 00 1_` | Shows alarm time (always in 24hr format) and on/off status on 5th tube (1=on, 0=off) and by display brightness (bright=on, dim=off). Use **Adjust** to switch on/off. Hold **Select** to set time (same way as **Time**). When alarm sounds, press **Select** to snooze, or hold for 1sec (followed by a short beep) to silence the alarm for the day. Options menu lets you restrict the alarm to your workweek or weekend only. In a power outage, the alarm will remain set, but it will not sound if power is disconnected at alarm time. | -| **Timer** | `__ __ _0` | A countdown timer, in hours, minutes, and seconds; or `0` when stopped. Can be set to the minute, up to 18 hours. Begins running as soon as you set it, and will continue to run in the background if you change to a different function. To cancel while running, hold **Select**. When timer runs out, press **Select** to silence. If power is lost, the timer will reset to `0`. Can be configured to work as an interval timer in the options menu (10), or as an appliance timer instead ([relay in switched mode](#hardware-configuration)). | +| **Alarm** | `_7 00 1_` | Shows alarm time (always in 24hr format) and on/off status on 5th tube (1=on, 0=off) and by display brightness (bright=on, dim=off). Use **Adjust** to switch on/off. Hold **Select** to set time (same way as **Time**). When alarm sounds, press any button to snooze, or hold for 1sec (followed by a short beep) to silence the alarm for the day. Options menu lets you restrict the alarm to your workweek or weekend only. In a power outage, the alarm will remain set, but it will not sound if power is disconnected at alarm time. | +| **Timer** | `__ __ _0` | A countdown timer, in hours, minutes, and seconds; or `0` when stopped. Can be set to the minute, up to 18 hours. Begins running as soon as you set it, and will continue to run in the background if you change to a different function. To cancel while running, hold **Select**. When timer runs out, press **Select** to silence. If power is lost, the timer will reset to `0`. Can be configured to work as an interval timer in the options menu (10). | | **Day counter** | `_1 23 __` | Shows the number of days until/since a date you specify. Set the same way as **Date.** | | **Temperature** | `__ 38 25` | Shows the temperature of the onboard DS3231 chip (e.g. 38.25°C – I think). May not be very useful as it tends to read higher than ambient temperature and its tolerance is low. Negative temperatures indicated with leading zeroes. | | **Tube tester** | `88 88 88` | (Disabled by default.) Cycles through all the digits on all the tubes. | +### LEDs and Relay + +Later UNDBs (v8 with mods, or v9+) are equipped with controllable LEDs, as well as a relay, which can be enabled in the [hardware configuration](#hardware-configuration) in **switch mode** (such as for a radio) or **pulse mode** (such as for a bell striker). If you have one of these UNDBs: + +* The LED behavior is configurable in option 7, and can be set to switch on and off with the relay if enabled (great for a radio!) +* The alarm, timer, and strike signals can be configured to use either the beeper or the relay if enabled (options 11, 21, and 31 – strike can only use the relay in pulse mode). +* With the relay in switch mode: + * **Alt** will switch it on and off at any time (except in options menu). (Otherwise, **Alt** does nothing.) + * If the alarm is set to use the relay, it will switch on the relay at alarm time, and switch off two hours later. If **Alt** is used to switch it off, the alarm will be silenced for the day (skipping snooze). + * If the timer is set to use the relay, it will switch on while the timer is running, and switch off when it runs out, like a clock radio's sleep function. If **Alt** is used to switch it off, the timer will be cancelled. (The interval timer option cannot be used in this case.) + ### Options Menu * To access this, hold **Select** for 3 seconds until you see a single `1` on the hour tubes. This indicates option number 1. @@ -36,13 +47,13 @@ _Note: Many variations are possible, depending on your clock's hardware; but thi | | **General** | | | 1 | Time format | 1 = 12-hour
2 = 24-hour
(time-of-day display only; setting times is always done in 24h) | | 2 | Date format | 1 = month/date/weekday
2 = date/month/weekday
3 = month/date/year
4 = date/month/year
5 = year/month/date
Note that four-tube clocks will display only the first two values in each of these options. | -| 3 | Display date during time? | 0 = never
1 = date instead of seconds
2 = full date (as above) every minute at :30 seconds
3 = same as 2, but scrolls in and out
TODO: implement options for full date every 5 minutes | +| 3 | Display date during time? | 0 = never
1 = date instead of seconds
2 = full date (as above) every minute at :30 seconds
3 = same as 2, but scrolls in and out | | 4 | Leading zero in hour, date, and month? | 0 = no
1 = yes | | 5 | Digit fade | 0–20 (in hundredths of a second) | | 6 | Auto DST | Add 1h for daylight saving time between these dates (at 2am):
0 = off
1 = second Sunday in March to first Sunday in November (US/CA)
2 = last Sunday in March to last Sunday in October (UK/EU)
3 = first Sunday in April to last Sunday in October (MX)
4 = last Sunday in September to first Sunday in April (NZ)
5 = first Sunday in October to first Sunday in April (AU)
6 = third Sunday in October to third Sunday in February (BZ) | -| 7 | LED behavior | 0 = always off
1 = always on
2 = on, but follow day-off and night-off if enabled
3 = off, but on when alarm/timer sounds
4 = off, but on with switched relay (if equipped – great for radios!)
(Clocks with LED control only, UNDB v8+) | +| 7 | LED behavior | 0 = always off
1 = always on
2 = on, but follow night/away modes if enabled
3 = off, but on when alarm/timer sounds
4 = off, but on with switched relay (if equipped)
(Clocks with LED control only, UNDB v8+) | | 8 | Temperature format | 0 = Celsius
1 = Fahrenheit
(Clocks with temperature function enabled only) | -| 9 | Anti-cathode poisoning | Briefly cycles all digits to prevent [cathode poisoning](http://www.tube-tester.com/sites/nixie/different/cathode%20poisoning/cathode-poisoning.htm)
TODO: implement this | +| 9 | Anti-cathode poisoning | Briefly cycles all digits to prevent [cathode poisoning](http://www.tube-tester.com/sites/nixie/different/cathode%20poisoning/cathode-poisoning.htm) | | | **Alarm** | | | 10 | Alarm days | 0 = every day
1 = work week only (per settings below)
2 = weekend only | | 11 | Alarm signal | 0 = beeper
1 = relay (if in switch mode, will stay on for 2 hours)
(Clocks with both beeper and relay only) | @@ -53,14 +64,14 @@ _Note: Many variations are possible, depending on your clock's hardware; but thi | 21 | Timer signal | 0 = beeper
1 = relay (if in switch mode, will stay on until timer runs down)
(Clocks with both beeper and relay only) | | 22 | Timer beeper pitch | Set the same way as the alarm pitch, above
(Clocks with beeper only) | | | **Strike** | | -| 30 | Strike | Make noise on the hour:
0 = off
1 = single beep
2 = pips
3 = strike the hour (1 to 12)
4 = ship's bell (hour and half hour)
Will not sound during day-off/night-off (except when off starts at top of hour)
(Clocks with beeper or pulse relay only) | +| 30 | Strike | Make noise on the hour:
0 = off
1 = single beep
2 = pips
3 = strike the hour (1 to 12)
4 = ship's bell (hour and half hour)
Will not sound during night/away modes (except when off starts at top of hour)
(Clocks with beeper or pulse relay only) | | 31 | Strike signal | 0 = beeper
1 = relay
(Clocks with both beeper and pulse relay only) | | 32 | Strike beeper pitch | Set the same way as the alarm signal pitch, above. If using the pips, 63 (987 Hz) is closest to the real BBC pips frequency (1000 Hz).
(Clocks with beeper only) | -| | **Night-off and day-off** | | -| 40 | Night-off | To save tube life and/or preserve your sleep, dim or shut off tubes nightly when you're not around or sleeping.
0 = none (tubes fully on at night)
1 = dim tubes at night
2 = shut off tubes at night
When off, you can press **Select** to illuminate the tubes briefly. | +| | **Night mode and away mode** | | +| 40 | Night mode | To save tube life and/or preserve your sleep, dim or shut off tubes nightly when you're not around or sleeping.
0 = none (tubes fully on at night)
1 = dim tubes at night
2 = shut off tubes at night
When off, you can press **Select** to illuminate the tubes briefly. | | 41 | Night starts at | Time of day. | | 42 | Night ends at | Time of day. Set to 0:00 to use the alarm time. | -| 43 | Day-off | To further save tube life, shut off tubes during the day when you're not around.
0 = none (tubes fully on during the day)
1 = clock at work (shut off all day on weekends)
2 = clock at home (shut off during work hours)
When off, you can press **Select** to illuminuate the tubes briefly. | +| 43 | Away mode | To further save tube life, shut off tubes during the day when you're not around.
0 = none (tubes fully on during the day)
1 = clock at work (shut off all day on weekends)
2 = clock at home (shut off during work hours)
When off, you can press **Select** to illuminuate the tubes briefly. | | 44 | First day of work week | 0–6 (Sunday–Saturday) | | 45 | Last day of work week | 0–6 (Sunday–Saturday) | | 46 | Work starts at | Time of day. | diff --git a/sixtube_lm/configs/v8c-6tube-relayswitch-pwm-top.h b/sixtube_lm/configs/v8c-6tube-relayswitch-pwm-top.h new file mode 100644 index 0000000..7912b31 --- /dev/null +++ b/sixtube_lm/configs/v8c-6tube-relayswitch-pwm-top.h @@ -0,0 +1,94 @@ +//v8 with B style modification, and flipped Sel and Alt buttons so Sel is in the front when buttons are mounted display-side with IN-8-A display + +const byte displaySize = 6; //number of tubes in display module. Small display adjustments are made for 4-tube clocks + +// available clock functions, and unique IDs (between 0 and 200) +const byte fnIsTime = 0; +const byte fnIsDate = 1; +const byte fnIsAlarm = 2; +const byte fnIsTimer = 3; +const byte fnIsDayCount = 4; +const byte fnIsTemp = 5; +const byte fnIsTubeTester = 6; //cycles all digits on all tubes 1/second, similar to anti-cathode-poisoning cleaner +// functions enabled in this clock, in their display order. Only fnIsTime is required +const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsAlarm, fnIsTimer, fnIsDayCount}; //, fnIsTemp, fnIsTubeTester +// To control which of these display persistently vs. switch back to Time after a few seconds, search "Temporary-display mode timeout" + +// These are the RLB board connections to Arduino analog input pins. +// S1/PL13 = Reset +// S2/PL5 = A1 +// S3/PL6 = A0 +// S4/PL7 = A6 +// S5/PL8 = A3 +// S6/PL9 = A2 +// S7/PL14 = A7 +// A6-A7 are analog-only pins that aren't quite as responsive and require a physical pullup resistor (1K to +5V), and can't be used with rotary encoders because they don't support pin change interrupts. + +// What input is associated with each control? +const byte mainSel = A7; +const byte mainAdjUp = A0; +const byte mainAdjDn = A1; +const byte altSel = A6; //if not equipped, set to 0 + +// What type of adj controls are equipped? +// 1 = momentary buttons. 2 = quadrature rotary encoder. +const byte mainAdjType = 1; + +//What are the signal pin(s) connected to? +const char piezoPin = 10; +const char relayPin = A3; +// -1 to disable feature (no relay item equipped); A3 if equipped (UNDB v8) +const byte relayMode = 0; //If relay is equipped, what does it do? +// 0 = switched mode: the relay will be switched to control an appliance like a radio or light fixture. If used with timer, it will switch on while timer is running (like a "sleep" function). If used with alarm, it will switch on when alarm trips; specify duration of this in switchDur. +// 1 = pulsed mode: the relay will be pulsed, like the beeper is, to control an intermittent signaling device like a solenoid or indicator lamp. Specify pulse duration in relayPulse. +const word signalDur = 180; //sec - when pulsed signal is going, pulses are sent once/sec for this period (e.g. 180 = 3min) +const word switchDur = 7200; //sec - when alarm triggers switched relay, it's switched on for this period (e.g. 7200 = 2hr) +const word piezoPulse = 500; //ms - used with piezo via tone() +const word relayPulse = 200; //ms - used with pulsed relay + +//Soft power switches +const byte enableSoftAlarmSwitch = 1; +// 1 = yes. Alarm can be switched on and off when clock is displaying the alarm time (fnIsAlarm). +// 0 = no. Alarm will be permanently on. Use with switched relay if the appliance has its own switch on this relay circuit. +const byte enableSoftPowerSwitch = 1; //works with switched relay only +// 1 = yes. Relay can be switched on and off directly when clock is displaying time of day (fnIsTime). This is useful if connecting an appliance (e.g. radio) that doesn't have its own switch, or if replacing the clock unit in a clock radio where the clock does all the switching (e.g. Telechron). +// 0 = no. Use if the connected appliance has its own power switch (independent of this relay circuit) or does not need to be manually switched. + +//LED circuit control with PWM +const char ledPin = 9; +// -1 to disable feature; 11 if equipped (UNDB v8 modded) + +//When display is dim/off, a press will light the tubes for how long? +const byte unoffDur = 10; //sec + +// How long (in ms) are the button hold durations? +const word btnShortHold = 1000; //for setting the displayed feataure +const word btnLongHold = 3000; //for for entering options menu +const byte velThreshold = 150; //ms +// When an adj up/down input (btn or rot) follows another in less than this time, value will change more (10 vs 1). +// Recommend ~150 for rotaries. If you want to use this feature with buttons, extend to ~400. + +// What is the "frame rate" of the tube cleaning and display scrolling? up to 65535 ms +const word cleanSpeed = 200; //ms +const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to give the illusion of a slow scroll that doesn't pause, use (timeoutTempFn*1000)/(displaySize+1) - e.g. 714 for displaySize=6 and timeoutTempFn=5 + +// What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec +const unsigned long timeoutSet = 120; //sec +const unsigned long timeoutTempFn = 5; //sec + +//This clock is 2x3 multiplexed: two tubes powered at a time. +//The anode channel determines which two tubes are powered, +//and the two SN74141 cathode driver chips determine which digits are lit. +//4 pins out to each SN74141, representing a binary number with values [1,2,4,8] +const char outA1 = 2; +const char outA2 = 3; +const char outA3 = 4; +const char outA4 = 5; +const char outB1 = 6; +const char outB2 = 7; +const char outB3 = 8; +const char outB4 = 16; //A2 - was 9 before PWM fix pt2 +//3 pins out to anode channel switches +const char anode1 = 11; +const char anode2 = 12; +const char anode3 = 13; \ No newline at end of file diff --git a/sixtube_lm/sixtube_lm.ino b/sixtube_lm/sixtube_lm.ino index ed2f5ca..754a1fb 100644 --- a/sixtube_lm/sixtube_lm.ino +++ b/sixtube_lm/sixtube_lm.ino @@ -4,11 +4,15 @@ // based on original sketch by Robin Birtles (rlb-designs.com) and Chris Gerekos // based on http://arduinix.com/Main/Code/ANX-6Tube-Clock-Crossfade.txt +//TODO: implement options for full date every 5 minutes +//TODO: see other TODOs throughout +//TODO: cathode anti-poisoning +//TODO: is it possible to trip the chime *after* determining if we're in night mode or not ////////// Hardware configuration ////////// //Include the config file that matches your hardware setup. If needed, duplicate an existing one. -#include "configs/v8b-6tube-relayswitch-pwm-top.h" +#include "configs/v8c-6tube-relayswitch-pwm-top.h" ////////// Other includes, global consts, and vars ////////// @@ -43,10 +47,10 @@ Some are skipped when they wouldn't apply to a given clock's hardware config, se 24 Alarm snooze 25 Timer interval mode - skipped when no piezo and relay is switch (start=0) 26 LED circuit behavior - skipped when no led pin - 27 Night-off + 27 Night mode 28-29 Night start, mins 30-31 Night end, mins - 32 Day-off + 32 Away mode 33 First day of workweek 34 Last day of workweek 35-36 Work starts at, mins @@ -64,7 +68,7 @@ Some are skipped when they wouldn't apply to a given clock's hardware config, se //Options menu numbers (displayed in UI and readme), EEPROM locations, and default/min/max values. //Option numbers/order can be changed (though try to avoid for user convenience); //but option locs should be maintained so EEPROM doesn't have to be reset after an upgrade. -// General Alarm Timer Strike Night-off and day-off +// General Alarm Timer Strike Night and away mode const byte optsNum[] = { 1, 2, 3, 4, 5, 6, 7, 8, 10,11,12,13, 20,21,22, 30,31,32, 40, 41, 42,43,44,45, 46, 47}; // 9, const byte optsLoc[] = {16,17,18,19,20,22,26,45, 23,42,39,24, 25,43,40, 21,44,41, 27, 28, 30,32,33,34, 35, 37}; //46, const word optsDef[] = { 2, 1, 0, 0, 5, 0, 1, 0, 0, 0,61, 9, 0, 0,61, 0, 0,61, 0,1320, 360, 0, 1, 5, 480,1020}; // 0, @@ -99,7 +103,7 @@ word snoozeRemain = 0; //snooze timeout counter, seconds word timerInitial = 0; //timer original setting, seconds - up to 18 hours (64,800 seconds - fits just inside a word) word timerRemain = 0; //timer actual counter unsigned long signalPulseStopTime = 0; //to stop beeps after a time -word unoffRemain = 0; //un-off (briefly turn on tubes during full night-off or day-off) timeout counter, seconds +word unoffRemain = 0; //un-off (briefly turn on tubes during full night or away modes) timeout counter, seconds byte displayDim = 2; //dim per display or function: 2=normal, 1=dim, 0=off byte cleanRemain = 11; //anti-cathode-poisoning clean timeout counter, increments at cleanSpeed ms (see loop()). Start at 11 to run at clock startup char scrollRemain = 0; //"frames" of scroll – 0=not scrolling, >0=coming in, <0=going out, -128=scroll out at next change @@ -159,6 +163,7 @@ void loop(){ checkInputs(); //if inputs have changed, this will do things + updateDisplay as needed doSetHold(); //if inputs have been held, this will do more things + updateDisplay as needed cycleDisplay(); //keeps the display hardware multiplexing cycle going + cycleLEDs(); } @@ -249,14 +254,21 @@ void ctrlEvt(byte ctrl, byte evt){ //evt: 1=press, 2=short hold, 3=long hold, 0=release. //We only handle press evts for adj ctrls, as that's the only evt encoders generate. //But we can handle short and long holds and releases for the sel ctrls (always buttons). - //TODO needs altSel //Before all else, is it a press to stop the signal? Silence it if(signalRemain>0 && evt==1){ stoppingSignal = true; signalStop(); - //If source is alarm, start snooze. 0 will have no effect. - if(signalSource==fnIsAlarm) snoozeRemain = readEEPROM(24,false)*60; //snoozeRemain is seconds, but snooze duration is minutes + if(signalSource==fnIsAlarm) { //If this was the alarm + //If the alarm is using the switched relay and this is the Alt button, don't set the snooze + if(relayMode==0 && readEEPROM(42,false)==1 && altSel!=0 && ctrl==altSel) { + //Short signal to indicate the alarm has been silenced until tomorrow - on beeper if relay is switched + if(getSignalOutput()==1 && relayMode==0) { if(piezoPin>=0) { tone(piezoPin, getSignalPitch(), 100); }} + else signalStart(fnIsAlarm,0,100); + } else { //start snooze + snoozeRemain = readEEPROM(24,false)*60; //snoozeRemain is seconds, but snooze duration is minutes + } + } return; } //After pressing to silence, short hold cancels a snooze; ignore other btn evts @@ -264,20 +276,17 @@ void ctrlEvt(byte ctrl, byte evt){ stoppingSignal = false; if(evt==2 && snoozeRemain>0) { snoozeRemain = 0; - //Short signal to indicate the snooze has cancelled //TODO write these edge cases into the signal generating stuff - if(getSignalOutput()==1 && relayMode==0) { //if the output is relay, but switched - if(piezoPin>=0) { tone(piezoPin, getSignalPitch(), 100); } //use the beeper anyway - } else { - signalStart(fnIsAlarm,0,100); - } + //Short signal to indicate the alarm has been silenced until tomorrow - on beeper if relay is switched + if(getSignalOutput()==1 && relayMode==0) { if(piezoPin>=0) { tone(piezoPin, getSignalPitch(), 100); }} + else signalStart(fnIsAlarm,0,100); } btnStop(); return; } //Is it a press for an un-off? + unoffRemain = unoffDur; //always do this so continued button presses during an unoff keep it alive if(displayDim==0 && evt==1) { - unoffRemain = unoffDur; updateDisplay(); btnStop(); return; @@ -312,28 +321,22 @@ void ctrlEvt(byte ctrl, byte evt){ } return; } - else if((ctrl==mainSel && evt==0) || ((ctrl==mainAdjUp || ctrl==mainAdjDn) && evt==1)) { //sel release or adj press - switch fn, depending on config - //-1 = nothing, -2 = cycle through functions, other = go to specific function (see fn) + else if((ctrl==mainSel && evt==0) || ((ctrl==mainAdjUp || ctrl==mainAdjDn) && evt==1)) { //sel release or adj press //we can't handle sel press here because, if attempting to enter setting mode, it would switch the fn first if(ctrl==mainSel){ - if(mainSelFn!=-1) { //do a function switch - if(mainSelFn==-2) fnScroll(1); //Go to next fn in the cycle - else fn = mainSelFn; - checkRTC(true); //updates display - } - else if(fn==fnIsAlarm) switchAlarm(0); //switch alarm - else if(fn==fnIsTime) switchPower(0); //switch power + fnScroll(1); //Go to next fn in the cycle + checkRTC(true); //updates display } else if(ctrl==mainAdjUp || ctrl==mainAdjDn) { - if(mainAdjFn!=-1) { //do a function switch - if(mainAdjFn==-2) fnScroll(ctrl==mainAdjUp?1:-1); //Go to next or previous fn in the cycle - else fn = mainAdjFn; - checkRTC(true); //updates display - } - else if(fn==fnIsAlarm) switchAlarm(ctrl==mainAdjUp?1:-1); //switch alarm - else if(fn==fnIsTime) switchPower(ctrl==mainAdjUp?1:-1); //switch power + if(fn==fnIsAlarm) switchAlarm(ctrl==mainAdjUp?1:-1); //switch alarm + //if(fn==fnIsTime) TODO volume in I2C radio } } + else if(altSel!=0 && ctrl==altSel) { //alt sel press + //TODO switch I2C radio + switchPower(0); + btnStop(); + } } //end fn running else { //fn setting @@ -377,8 +380,8 @@ void ctrlEvt(byte ctrl, byte evt){ timerInitial = fnSetVal*60; //timerRemain is seconds, but timer is set by minute timerRemain = timerInitial; //set actual timer going if(relayPin>=0 && relayMode==0 && readEEPROM(43,false)==1) { //if switched relay, and timer signal is set to it - digitalWrite(relayPin,HIGH); - Serial.print(millis(),DEC); Serial.println(F(" Relay on, timer set")); + digitalWrite(relayPin,LOW); //LOW = device on + //Serial.print(millis(),DEC); Serial.println(F(" Relay on, timer set")); updateLEDs(); //LEDs following switch relay //TODO will this cancel properly? especially if alarm interrupts? } @@ -643,7 +646,7 @@ void checkRTC(bool force){ if(readEEPROM(18,false)==3) { startScroll(); } } - //Strikes - only if fn=clock, not setting, not night-off/day-off. Setting 21 will be off if signal type is no good + //Strikes - only if fn=clock, not setting, not night/away. Setting 21 will be off if signal type is no good //Short pips before the top of the hour if(tod.minute()==59 && tod.second()>=55 && readEEPROM(21,false)==2 && signalRemain==0 && snoozeRemain==0 && fn==fnIsTime && fnSetPg==0 && displayDim==2) { signalStart(fnIsTime,0,100); @@ -671,7 +674,7 @@ void checkRTC(bool force){ timerRemain--; if(timerRemain<=0) { //timer has elasped if(relayPin>=0 && relayMode==0 && readEEPROM(43,false)==1) { //if switched relay, and timer signal is set to it - signalStop(); + signalStop(); //interval timer setting is ignored (unlike below) } else { //not switched relay: turn on signal if(readEEPROM(25,false)) { //interval timer: a short signal and restart; don't change to timer fn signalStart(fnIsTimer,0,0); timerRemain = timerInitial; @@ -703,7 +706,7 @@ void checkRTC(bool force){ } //end natural second //Finally, update the display, whether natural tick or not, as long as we're not setting or on a scrolled display (unless forced eg. fn change) - //This also determines night-off/day-off, which is why strikes will happen if we go into off at top of hour TODO find a way to fix this + //This also determines night/away mode, which is why strikes will happen if we go into off at top of hour, and not when we come into on at the top of the hour TODO find a way to fix this if(fnSetPg==0 && (scrollRemain==0 || force)) updateDisplay(); rtcSecLast = tod.second(); @@ -763,14 +766,23 @@ void switchAlarm(char dir){ } void switchPower(char dir){ if(enableSoftPowerSwitch && relayPin>=0 && relayMode==0) { //if switched relay, and soft switch enabled - //signalStop(); could use this instead of the below to turn the radio off - if(dir==0) dir = !digitalRead(relayPin); - digitalWrite(relayPin,(dir==1?HIGH:LOW)); - Serial.print(millis(),DEC); - Serial.print(F(" Relay ")); - if(dir==0) { Serial.print(F("toggled")); Serial.print(digitalRead(relayPin)==HIGH?F(" on"):F(" off")); } - else Serial.print(dir==1?F("switched on"):F("switched off")); - Serial.println(F(", switchPower")); + signalRemain = 0; snoozeRemain = 0; //in case alarm is going now - alternatively use signalStop()? + //If the timer is running and is using the switched relay, this instruction conflicts with it, so cancel it + if(timerRemain>0 && readEEPROM(43,false)==1) { + timerRemain=0; + updateDisplay(); + } + //relayPin state is the reverse of the appliance state: LOW = device on, HIGH = device off + //Serial.print(millis(),DEC); + //Serial.print(F(" Relay requested to ")); + if(dir==0) { //toggle + dir = (digitalRead(relayPin)?1:-1); //LOW = device on, so this effectively does our dir reversion for us + //Serial.print(dir==1?F("toggle on"):F("toggle off")); + } else { + //Serial.print(dir==1?F("switch on"):F("switch off")); + } + digitalWrite(relayPin,(dir==1?0:1)); //LOW = device on + //Serial.println(F(", switchPower")); updateLEDs(); //LEDs following switch relay } } @@ -861,20 +873,21 @@ void updateDisplay(){ } else { //fn running - //Set displayDim per night-off and day-off settings - fnIsAlarm may override this + //Set displayDim per night/away settings - fnIsAlarm may override this //issue: moving from off alarm to next fn briefly shows alarm in full brightness. I think because of the display delays. TODO word todmins = tod.hour()*60+tod.minute(); //In order of precedence: //temporary unoff - if(unoffRemain > 0) displayDim = 2; - //day-off on weekends, all day + if(unoffRemain > 0) displayDim = 2; //TODO can we fade between dim states? + //clock at work: away on weekends, all day else if( readEEPROM(32,false)==1 && !isDayInRange(readEEPROM(33,false),readEEPROM(34,false),toddow) ) displayDim = 0; - //day-off on weekdays, during office hours only + //clock at home: away on weekdays, during office hours only else if( readEEPROM(32,false)==2 && isDayInRange(readEEPROM(33,false),readEEPROM(34,false),toddow) && isTimeInRange(readEEPROM(35,true), readEEPROM(37,true), todmins) ) displayDim = 0; - //night-off - if night end is 0:00, use alarm time instead + //night mode - if night end is 0:00, use alarm time instead else if( readEEPROM(27,false) && isTimeInRange(readEEPROM(28,true), (readEEPROM(30,true)==0?readEEPROM(0,true):readEEPROM(30,true)), todmins) ) displayDim = (readEEPROM(27,false)==1?1:0); //dim or off //normal else displayDim = 2; + updateLEDs(); switch(fn){ case fnIsTime: @@ -1018,7 +1031,7 @@ void initOutputs() { for(byte i=0; i<4; i++) { pinMode(binOutA[i],OUTPUT); pinMode(binOutB[i],OUTPUT); } for(byte i=0; i<3; i++) { pinMode(anodes[i],OUTPUT); } if(piezoPin>=0) pinMode(piezoPin, OUTPUT); - if(relayPin>=0) pinMode(relayPin, OUTPUT); + if(relayPin>=0) pinMode(relayPin, OUTPUT); digitalWrite(relayPin, HIGH); //LOW = device on if(ledPin>=0) pinMode(ledPin, OUTPUT); updateLEDs(); //set to initial value } @@ -1122,16 +1135,16 @@ void signalStart(byte sigFn, byte sigDur, word pulseDur){ //make some noise! or //sigDur is the number of seconds to put on signalRemain, or 0 for a single immediate beep (skipped in radio mode). //Special case: if sigFn==fnIsAlarm, and sigDur>0, we'll use signalDur or switchDur as appropriate. //If doing a single beep, pulseDur is the number of ms it should last, or 0 for signal source's chosen output's pulse length (which will be used anyway if pulsed relay) + signalSource = sigFn; //Set this first so signalStop won't inadvertently turn off a switched relay started by something else signalStop(); - signalSource = sigFn; if(sigDur==0) signalPulseStart(pulseDur); //single immediate beep else { //long-duration signal (alarm, sleep, etc) if(sigFn==fnIsAlarm) signalRemain = (readEEPROM(42,false)==1 && relayPin>=0 && relayMode==0 ? switchDur : signalDur); else signalRemain = sigDur; //piezo or pulsed relay: checkRTC will handle it if(getSignalOutput()==1 && relayPin>=0 && relayMode==0) { //switched relay: turn it on now - digitalWrite(relayPin,HIGH); - Serial.print(millis(),DEC); Serial.println(F(" Relay on, signalStart")); + digitalWrite(relayPin,LOW); //LOW = device on + //Serial.print(millis(),DEC); Serial.println(F(" Relay on, signalStart")); } } updateLEDs(); //LEDs following signal or relay @@ -1140,8 +1153,8 @@ void signalStop(){ //stop current signal and clear out signal timer if applicabl signalRemain = 0; snoozeRemain = 0; signalPulseStop(); //piezo or pulsed relay: stop now if(getSignalOutput()==1 && relayPin>=0 && relayMode==0) { //switched relay: turn it off now - digitalWrite(relayPin,LOW); - Serial.print(millis(),DEC); Serial.println(F(" Relay off, signalStop")); + digitalWrite(relayPin,HIGH); //LOW = device on + //Serial.print(millis(),DEC); Serial.println(F(" Relay off, signalStop")); } updateLEDs(); //LEDs following relay } @@ -1152,8 +1165,8 @@ void signalPulseStart(word pulseDur){ tone(piezoPin, getSignalPitch(), (pulseDur==0?piezoPulse:pulseDur)); } else if(getSignalOutput()==1 && relayPin>=0 && relayMode==1) { //pulsed relay - digitalWrite(relayPin,HIGH); - Serial.print(millis(),DEC); Serial.println(F(" Relay on, signalPulseStart")); + digitalWrite(relayPin,LOW); //LOW = device on + //Serial.print(millis(),DEC); Serial.println(F(" Relay on, signalPulseStart")); signalPulseStopTime = millis()+relayPulse; //always use relayPulse in case timing is important for connected device } } @@ -1162,8 +1175,8 @@ void signalPulseStop(){ noTone(piezoPin); } else if(getSignalOutput()==1 && relayPin>=0 && relayMode==1) { //pulsed relay - digitalWrite(relayPin,LOW); - Serial.print(millis(),DEC); Serial.println(F(" Relay off, signalPulseStop")); + digitalWrite(relayPin,HIGH); //LOW = device on + //Serial.print(millis(),DEC); Serial.println(F(" Relay off, signalPulseStop")); signalPulseStopTime = 0; } } @@ -1181,35 +1194,51 @@ char getSignalOutput(){ //for current signal: time, timer, or (default) alarm: 0 return readEEPROM((signalSource==fnIsTime?44:(signalSource==fnIsTimer?43:42)),false); } +const byte ledFadeStep = 10; //fade speed – with every loop() we'll increment/decrement the LED brightness (between 0-255) by this amount +byte ledStateNow = 0; +byte ledStateTarget = 0; void updateLEDs(){ //Run whenever something is changed that might affect the LED state: initial (initOutputs), signal start/stop, relay on/off, setting change if(ledPin>=0) { switch(readEEPROM(26,false)){ case 0: //always off - digitalWrite(ledPin,LOW); - Serial.println(F("LEDs off always")); + ledStateTarget = 0; + //Serial.println(F("LEDs off always")); break; case 1: //always on - digitalWrite(ledPin,HIGH); - Serial.println(F("LEDs on always")); + ledStateTarget = 255; + //Serial.println(F("LEDs on always")); break; - case 2: //on, but follow day-off and night-off - digitalWrite(ledPin,(displayDim<2?LOW:HIGH)); - Serial.print(displayDim<2?F("LEDs off"):F("LEDs on")); Serial.println(F(" per dim state")); + case 2: //on, but follow night/away modes + ledStateTarget = (displayDim==2? 255: (displayDim==1? 127: 0)); + //Serial.print(displayDim==2? F("LEDs on"): (displayDim==1? F("LEDs dim"): F("LEDs off"))); Serial.println(F(" per dim state")); break; case 3: //off, but on when alarm/timer sounds - digitalWrite(ledPin,(signalRemain && (signalSource==fnIsAlarm || signalSource==fnIsTimer)?HIGH:LOW)); - Serial.print(signalRemain && (signalSource==fnIsAlarm || signalSource==fnIsTimer)?F("LEDs on"):F("LEDs off")); Serial.println(F(" per alarm/timer")); + ledStateTarget = (signalRemain && (signalSource==fnIsAlarm || signalSource==fnIsTimer)? 255: 0); + //Serial.print(signalRemain && (signalSource==fnIsAlarm || signalSource==fnIsTimer)?F("LEDs on"):F("LEDs off")); Serial.println(F(" per alarm/timer")); break; case 4: //off, but on with switched relay if(relayPin>=0 && relayMode==0) { - digitalWrite(ledPin,digitalRead(relayPin)); - Serial.print(F("LEDs ")); - if(digitalRead(ledPin)==HIGH) Serial.print(F("on")); else Serial.print(F("off")); - Serial.println(F(" per switched relay")); + ledStateTarget = (!digitalRead(relayPin)? 255: 0); //LOW = device on + //Serial.print(!digitalRead(relayPin)? F("LEDs on"): F("LEDs off")); Serial.println(F(" per switched relay")); } break; default: break; } //end switch } //if ledPin -} //end updateLEDs \ No newline at end of file +} //end updateLEDs +void cycleLEDs() { + //Allows us to fade the LEDs to ledStateTarget by stepping via ledFadeStep + //TODO: it appears setting analogWrite(pin,0) does not completely turn the LEDs off. Anything else we could do? + if(ledStateNow != ledStateTarget) { + if(ledStateNow < ledStateTarget) { + ledStateNow = (ledStateTarget-ledStateNow <= ledFadeStep? ledStateTarget: ledStateNow+ledFadeStep); + } else { + ledStateNow = (ledStateNow-ledStateTarget <= ledFadeStep? ledStateTarget: ledStateNow-ledFadeStep); + } + // Serial.print(ledStateNow,DEC); + // Serial.print(F(" => ")); + // Serial.println(ledStateTarget,DEC); + analogWrite(ledPin,ledStateNow); + } +} \ No newline at end of file diff --git a/v8c_mod_test/v8c_mod_test.ino b/v8c_mod_test/v8c_mod_test.ino new file mode 100644 index 0000000..3cbd09a --- /dev/null +++ b/v8c_mod_test/v8c_mod_test.ino @@ -0,0 +1,129 @@ +//For testing mods to the v8.0 board, style B, with LED PWM + +const byte btnSel = A6; //was A1 +const byte btnAlt = A7; //was A0 +const byte btnUp = A0; //was A2 +const byte btnDn = A1; //was A3 +const byte pinLED = 9; //was A6 +const byte pinRelay = A3; //was A7 +byte binOutA[4] = {2,3,4,5}; +byte binOutB[4] = {6,7,8,16}; //last was 9 +byte anodes[3] = {11,12,13}; + +byte btnCur = 0; //Momentary button currently in use - only one allowed at a time + +byte ledStateNow = 0; +byte ledStateTarget = 0; + +void setup(){ + Serial.begin(9600); + //0 and 1: set as digital input + pinMode(A0, INPUT_PULLUP); + pinMode(A1, INPUT_PULLUP); + //2 and 3: set as digital output + pinMode(A2, OUTPUT); + pinMode(A3, OUTPUT); + //4 and 5: for I2C + //6 and 7: input, but analog pins with hardware pullup resistors, so nothing to do + analogWrite(pinLED,0); //0 = LEDs off + digitalWrite(pinRelay,1); //1 = connected device off + + //Set up just enough tube output to confirm the changed first anode is working + for(byte i=0; i<4; i++) { pinMode(binOutA[i],OUTPUT); pinMode(binOutB[i],OUTPUT); digitalWrite(binOutA[i],LOW); digitalWrite(binOutB[i],LOW); } + for(byte i=0; i<3; i++) { pinMode(anodes[i],OUTPUT); digitalWrite(anodes[i],LOW); } + digitalWrite(anodes[0],HIGH); //the one we want to test + + Serial.println(); + Serial.println(F("Ready for input.")); + Serial.println(F("Display should now read 0 on 3rd and 6th tubes.")); + Serial.println(F("SEL should change them to a 1; ALT should change them to 2.")); + Serial.println(F("UP should fade the LEDs in and out.")); + Serial.println(F("DOWN should toggle the relay.")); +} + +void loop(){ + checkInputs(); + if(ledStateNow != ledStateTarget) { + if(ledStateNow > ledStateTarget) { ledStateNow -= 5; } + else if(ledStateNow < ledStateTarget) { ledStateNow += 5; } + // Serial.print(ledStateNow,DEC); + // Serial.print(F(" => ")); + // Serial.println(ledStateTarget,DEC); + analogWrite(pinLED,ledStateNow); + } + delay(5); //in case of switch bounce? +} + +void checkInputs(){ + checkBtn(btnSel); + checkBtn(btnAlt); + checkBtn(btnUp); + checkBtn(btnDn); +} +bool readInput(byte pin){ + if(pin==A6 || pin==A7) return analogRead(pin)<100?0:1; //analog-only pins + else return digitalRead(pin); +} +void checkBtn(byte btn){ + //Changes in momentary buttons, LOW = pressed. + //When a button event has occurred, will call ctrlEvt + bool bnow = readInput(btn); + //If the button has just been pressed, and no other buttons are in use... + if(btnCur==0 && bnow==LOW) { + btnCur = btn; + Serial.println(); + bool newState = LOW; + switch(btn) { + case btnSel: + Serial.println(F("btnSel pressed")); + digitalWrite(binOutA[0],HIGH); + digitalWrite(binOutA[1],LOW); + digitalWrite(binOutB[0],HIGH); + digitalWrite(binOutB[1],LOW); + break; + case btnAlt: + Serial.println(F("btnAlt pressed")); + digitalWrite(binOutA[0],LOW); + digitalWrite(binOutA[1],HIGH); + digitalWrite(binOutB[0],LOW); + digitalWrite(binOutB[1],HIGH); + break; + case btnUp: + Serial.println(F("btnUp pressed")); + ledStateTarget = (ledStateTarget==0?255:0); + Serial.println(); + if(ledStateTarget==0) Serial.println(F("LED switched off. LED PWM pin should fade to open circuit.")); + else Serial.println(F("LED switched on. LED PWM pin should fade to closed circuit.")); + break; + case btnDn: + Serial.println(F("btnDn pressed")); + newState = !digitalRead(pinRelay); + digitalWrite(pinRelay, newState); + Serial.println(); + if(newState) Serial.println(F("Relay switched on. Relay pins should measure open circuit now (connected device off).")); + else Serial.println(F("Relay switched off. Relay pins should measure closed circuit now (connected device on).")); + break; + default: break; + } //end button printing switch + } //end if button presed + //If the button has just been released... + if(btnCur==btn && bnow==HIGH) { + Serial.println(); + switch(btn){ + case btnSel: + Serial.println(F("btnSel released")); + break; + case btnAlt: + Serial.println(F("btnAlt released")); + break; + case btnUp: + Serial.println(F("btnUp released")); + break; + case btnDn: + Serial.println(F("btnDn released")); + break; + default: break; + } + btnCur = 0; + } +} \ No newline at end of file From 34b1cb2646542be491c213fb8d7ddbb05c7682ad Mon Sep 17 00:00:00 2001 From: Luke McKenzie Date: Sun, 14 Oct 2018 23:21:12 -0500 Subject: [PATCH 13/13] millis() rollover fixes; reintroduce anti-poisoning options --- README.md | 2 +- .../configs/v8c-6tube-relayswitch-pwm-top.h | 2 +- .../configs/v8c-6tube-relayswitch-pwm.h | 94 +++++++++++++++++++ sixtube_lm/sixtube_lm.ino | 94 +++++++++++-------- 4 files changed, 152 insertions(+), 40 deletions(-) create mode 100644 sixtube_lm/configs/v8c-6tube-relayswitch-pwm.h diff --git a/README.md b/README.md index fa946f5..e20bb31 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Later UNDBs (v8 with mods, or v9+) are equipped with controllable LEDs, as well | 6 | Auto DST | Add 1h for daylight saving time between these dates (at 2am):
0 = off
1 = second Sunday in March to first Sunday in November (US/CA)
2 = last Sunday in March to last Sunday in October (UK/EU)
3 = first Sunday in April to last Sunday in October (MX)
4 = last Sunday in September to first Sunday in April (NZ)
5 = first Sunday in October to first Sunday in April (AU)
6 = third Sunday in October to third Sunday in February (BZ) | | 7 | LED behavior | 0 = always off
1 = always on
2 = on, but follow night/away modes if enabled
3 = off, but on when alarm/timer sounds
4 = off, but on with switched relay (if equipped)
(Clocks with LED control only, UNDB v8+) | | 8 | Temperature format | 0 = Celsius
1 = Fahrenheit
(Clocks with temperature function enabled only) | -| 9 | Anti-cathode poisoning | Briefly cycles all digits to prevent [cathode poisoning](http://www.tube-tester.com/sites/nixie/different/cathode%20poisoning/cathode-poisoning.htm) | +| 9 | Anti-cathode poisoning | Briefly cycles all digits to prevent [cathode poisoning](http://www.tube-tester.com/sites/nixie/different/cathode%20poisoning/cathode-poisoning.htm)
0 = once a day, at "Night ends at" time (or alarm time if applicable)
1 = at midnight
2 = at the top of every hour
3 = at the top of every minute | | | **Alarm** | | | 10 | Alarm days | 0 = every day
1 = work week only (per settings below)
2 = weekend only | | 11 | Alarm signal | 0 = beeper
1 = relay (if in switch mode, will stay on for 2 hours)
(Clocks with both beeper and relay only) | diff --git a/sixtube_lm/configs/v8c-6tube-relayswitch-pwm-top.h b/sixtube_lm/configs/v8c-6tube-relayswitch-pwm-top.h index 7912b31..4be84d9 100644 --- a/sixtube_lm/configs/v8c-6tube-relayswitch-pwm-top.h +++ b/sixtube_lm/configs/v8c-6tube-relayswitch-pwm-top.h @@ -1,4 +1,4 @@ -//v8 with B style modification, and flipped Sel and Alt buttons so Sel is in the front when buttons are mounted display-side with IN-8-A display +//v8 with C style modification and flipped Sel and Alt buttons so Sel is in the front when buttons are mounted display-side with IN-8-A display const byte displaySize = 6; //number of tubes in display module. Small display adjustments are made for 4-tube clocks diff --git a/sixtube_lm/configs/v8c-6tube-relayswitch-pwm.h b/sixtube_lm/configs/v8c-6tube-relayswitch-pwm.h new file mode 100644 index 0000000..8e044a8 --- /dev/null +++ b/sixtube_lm/configs/v8c-6tube-relayswitch-pwm.h @@ -0,0 +1,94 @@ +//v8 with C style modification + +const byte displaySize = 6; //number of tubes in display module. Small display adjustments are made for 4-tube clocks + +// available clock functions, and unique IDs (between 0 and 200) +const byte fnIsTime = 0; +const byte fnIsDate = 1; +const byte fnIsAlarm = 2; +const byte fnIsTimer = 3; +const byte fnIsDayCount = 4; +const byte fnIsTemp = 5; +const byte fnIsTubeTester = 6; //cycles all digits on all tubes 1/second, similar to anti-cathode-poisoning cleaner +// functions enabled in this clock, in their display order. Only fnIsTime is required +const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsAlarm, fnIsTimer, fnIsDayCount}; //, fnIsTemp, fnIsTubeTester +// To control which of these display persistently vs. switch back to Time after a few seconds, search "Temporary-display mode timeout" + +// These are the RLB board connections to Arduino analog input pins. +// S1/PL13 = Reset +// S2/PL5 = A1 +// S3/PL6 = A0 +// S4/PL7 = A6 +// S5/PL8 = A3 +// S6/PL9 = A2 +// S7/PL14 = A7 +// A6-A7 are analog-only pins that aren't quite as responsive and require a physical pullup resistor (1K to +5V), and can't be used with rotary encoders because they don't support pin change interrupts. + +// What input is associated with each control? +const byte mainSel = A6; +const byte mainAdjUp = A0; +const byte mainAdjDn = A1; +const byte altSel = A7; //if not equipped, set to 0 + +// What type of adj controls are equipped? +// 1 = momentary buttons. 2 = quadrature rotary encoder. +const byte mainAdjType = 1; + +//What are the signal pin(s) connected to? +const char piezoPin = 10; +const char relayPin = A3; +// -1 to disable feature (no relay item equipped); A3 if equipped (UNDB v8) +const byte relayMode = 0; //If relay is equipped, what does it do? +// 0 = switched mode: the relay will be switched to control an appliance like a radio or light fixture. If used with timer, it will switch on while timer is running (like a "sleep" function). If used with alarm, it will switch on when alarm trips; specify duration of this in switchDur. +// 1 = pulsed mode: the relay will be pulsed, like the beeper is, to control an intermittent signaling device like a solenoid or indicator lamp. Specify pulse duration in relayPulse. +const word signalDur = 180; //sec - when pulsed signal is going, pulses are sent once/sec for this period (e.g. 180 = 3min) +const word switchDur = 7200; //sec - when alarm triggers switched relay, it's switched on for this period (e.g. 7200 = 2hr) +const word piezoPulse = 500; //ms - used with piezo via tone() +const word relayPulse = 200; //ms - used with pulsed relay + +//Soft power switches +const byte enableSoftAlarmSwitch = 1; +// 1 = yes. Alarm can be switched on and off when clock is displaying the alarm time (fnIsAlarm). +// 0 = no. Alarm will be permanently on. Use with switched relay if the appliance has its own switch on this relay circuit. +const byte enableSoftPowerSwitch = 1; //works with switched relay only +// 1 = yes. Relay can be switched on and off directly when clock is displaying time of day (fnIsTime). This is useful if connecting an appliance (e.g. radio) that doesn't have its own switch, or if replacing the clock unit in a clock radio where the clock does all the switching (e.g. Telechron). +// 0 = no. Use if the connected appliance has its own power switch (independent of this relay circuit) or does not need to be manually switched. + +//LED circuit control with PWM +const char ledPin = 9; +// -1 to disable feature; 11 if equipped (UNDB v8 modded) + +//When display is dim/off, a press will light the tubes for how long? +const byte unoffDur = 10; //sec + +// How long (in ms) are the button hold durations? +const word btnShortHold = 1000; //for setting the displayed feataure +const word btnLongHold = 3000; //for for entering options menu +const byte velThreshold = 150; //ms +// When an adj up/down input (btn or rot) follows another in less than this time, value will change more (10 vs 1). +// Recommend ~150 for rotaries. If you want to use this feature with buttons, extend to ~400. + +// What is the "frame rate" of the tube cleaning and display scrolling? up to 65535 ms +const word cleanSpeed = 200; //ms +const word scrollSpeed = 100; //ms - e.g. scroll-in-and-out date at :30 - to give the illusion of a slow scroll that doesn't pause, use (timeoutTempFn*1000)/(displaySize+1) - e.g. 714 for displaySize=6 and timeoutTempFn=5 + +// What are the timeouts for setting and temporarily-displayed functions? up to 65535 sec +const unsigned long timeoutSet = 120; //sec +const unsigned long timeoutTempFn = 5; //sec + +//This clock is 2x3 multiplexed: two tubes powered at a time. +//The anode channel determines which two tubes are powered, +//and the two SN74141 cathode driver chips determine which digits are lit. +//4 pins out to each SN74141, representing a binary number with values [1,2,4,8] +const char outA1 = 2; +const char outA2 = 3; +const char outA3 = 4; +const char outA4 = 5; +const char outB1 = 6; +const char outB2 = 7; +const char outB3 = 8; +const char outB4 = 16; //A2 - was 9 before PWM fix pt2 +//3 pins out to anode channel switches +const char anode1 = 11; +const char anode2 = 12; +const char anode3 = 13; \ No newline at end of file diff --git a/sixtube_lm/sixtube_lm.ino b/sixtube_lm/sixtube_lm.ino index 754a1fb..c3ff7ba 100644 --- a/sixtube_lm/sixtube_lm.ino +++ b/sixtube_lm/sixtube_lm.ino @@ -69,11 +69,11 @@ Some are skipped when they wouldn't apply to a given clock's hardware config, se //Option numbers/order can be changed (though try to avoid for user convenience); //but option locs should be maintained so EEPROM doesn't have to be reset after an upgrade. // General Alarm Timer Strike Night and away mode -const byte optsNum[] = { 1, 2, 3, 4, 5, 6, 7, 8, 10,11,12,13, 20,21,22, 30,31,32, 40, 41, 42,43,44,45, 46, 47}; // 9, -const byte optsLoc[] = {16,17,18,19,20,22,26,45, 23,42,39,24, 25,43,40, 21,44,41, 27, 28, 30,32,33,34, 35, 37}; //46, -const word optsDef[] = { 2, 1, 0, 0, 5, 0, 1, 0, 0, 0,61, 9, 0, 0,61, 0, 0,61, 0,1320, 360, 0, 1, 5, 480,1020}; // 0, -const word optsMin[] = { 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,49, 0, 0, 0,49, 0, 0,49, 0, 0, 0, 0, 0, 0, 0, 0}; // 0, -const word optsMax[] = { 2, 5, 3, 1,20, 6, 4, 1, 2, 1,88,60, 1, 1,88, 4, 1,88, 2,1439,1439, 2, 6, 6,1439,1439}; // 3, +const byte optsNum[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11,12,13, 20,21,22, 30,31,32, 40, 41, 42,43,44,45, 46, 47}; +const byte optsLoc[] = {16,17,18,19,20,22,26,45,46, 23,42,39,24, 25,43,40, 21,44,41, 27, 28, 30,32,33,34, 35, 37}; +const word optsDef[] = { 2, 1, 0, 0, 5, 0, 1, 0, 0, 0, 0,61, 9, 0, 0,61, 0, 0,61, 0,1320, 360, 0, 1, 5, 480,1020}; +const word optsMin[] = { 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,49, 0, 0, 0,49, 0, 0,49, 0, 0, 0, 0, 0, 0, 0, 0}; +const word optsMax[] = { 2, 5, 3, 1,20, 6, 4, 1, 3, 2, 1,88,60, 1, 1,88, 4, 1,88, 2,1439,1439, 2, 6, 6,1439,1439}; //RTC objects DS3231 ds3231; //an object to access the ds3231 specifically (temp, etc) @@ -86,6 +86,7 @@ byte btnCur = 0; //Momentary button currently in use - only one allowed at a tim byte btnCurHeld = 0; //Button hold thresholds: 0=none, 1=unused, 2=short, 3=long, 4=set by btnStop() unsigned long inputLast = 0; //When a button was last pressed unsigned long inputLast2 = 0; //Second-to-last of above +//TODO the math between these two may fail very rarely due to millis() rolling over while setting. Need to find a fix. I think it only applies to the rotary encoder though. const byte fnOpts = 201; //fn values from here to 255 correspond to options in the options menu byte fn = fnIsTime; //currently displayed fn (per fnsEnabled) @@ -102,7 +103,7 @@ word signalRemain = 0; //alarm/timer signal timeout counter, seconds word snoozeRemain = 0; //snooze timeout counter, seconds word timerInitial = 0; //timer original setting, seconds - up to 18 hours (64,800 seconds - fits just inside a word) word timerRemain = 0; //timer actual counter -unsigned long signalPulseStopTime = 0; //to stop beeps after a time +unsigned long signalPulseStartTime = 0; //to keep track of individual pulses so we can stop them word unoffRemain = 0; //un-off (briefly turn on tubes during full night or away modes) timeout counter, seconds byte displayDim = 2; //dim per display or function: 2=normal, 1=dim, 0=off byte cleanRemain = 11; //anti-cathode-poisoning clean timeout counter, increments at cleanSpeed ms (see loop()). Start at 11 to run at clock startup @@ -136,19 +137,18 @@ void setup(){ initOutputs(); //depends on some EEPROM settings } -unsigned long pollLast = 0; //every 50ms unsigned long pollCleanLast = 0; //every cleanSpeed ms unsigned long pollScrollLast = 0; //every scrollSpeed ms void loop(){ unsigned long now = millis(); //If we're running a tube cleaning, advance it every cleanSpeed ms. - if(cleanRemain && pollCleanLast+cleanSpeed=cleanSpeed) { //account for rollover pollCleanLast=now; cleanRemain--; updateDisplay(); } //If we're scrolling an animation, advance it every scrollSpeed ms. - else if(scrollRemain!=0 && scrollRemain!=-128 && pollScrollLast+scrollSpeed=scrollSpeed) { pollScrollLast=now; if(scrollRemain<0) { scrollRemain++; updateDisplay(); @@ -158,7 +158,6 @@ void loop(){ } } //Every loop cycle, check the RTC and inputs (previously polled, but works fine without and less flicker) - pollLast=now; checkRTC(false); //if clock has ticked, decrement timer if running, and updateDisplay checkInputs(); //if inputs have changed, this will do things + updateDisplay as needed doSetHold(); //if inputs have been held, this will do more things + updateDisplay as needed @@ -197,6 +196,7 @@ void checkBtn(byte btn){ //Changes in momentary buttons, LOW = pressed. //When a button event has occurred, will call ctrlEvt bool bnow = readInput(btn); + unsigned long now = millis(); //If the button has just been pressed, and no other buttons are in use... if(btnCur==0 && bnow==LOW) { btnCur = btn; btnCurHeld = 0; inputLast2 = inputLast; inputLast = millis(); @@ -204,11 +204,11 @@ void checkBtn(byte btn){ } //If the button is being held... if(btnCur==btn && bnow==LOW) { - if(millis() >= inputLast+btnLongHold && btnCurHeld < 3) { + if((unsigned long)(now-inputLast)>=btnLongHold && btnCurHeld < 3) { //account for rollover btnCurHeld = 3; ctrlEvt(btn,3); //hey, the button has been long-held } - else if(millis() >= inputLast+btnShortHold && btnCurHeld < 2) { + else if((unsigned long)(now-inputLast)>=btnShortHold && btnCurHeld < 2) { btnCurHeld = 2; ctrlEvt(btn,2); //hey, the button has been short-held } @@ -232,15 +232,15 @@ void checkRot(){ AdaEncoder *thisEncoder=NULL; thisEncoder = AdaEncoder::genie(); if(thisEncoder!=NULL) { - unsigned long inputThis = millis(); - if(inputThis-inputLast < 70) return; //ignore inputs that come faster than a human could rotate + unsigned long now = millis(); + if((unsigned long)(now-inputLast)<70) return; //ignore inputs that come faster than a human could rotate int8_t clicks = thisEncoder->query(); //signed number of clicks it has moved byte dir = (clicks<0?0:1); clicks = abs(clicks); for(byte i=0; i=250) { + doSetHoldLast = now; if(fnSetPg!=0 && (mainAdjType==1 && (btnCur==mainAdjUp || btnCur==mainAdjDn)) ){ //if we're setting, and this is an adj btn bool dir = (btnCur==mainAdjUp ? 1 : 0); //If short hold, or long hold but high velocity isn't supported, use low velocity (delta=1) @@ -603,20 +604,21 @@ void checkRTC(bool force){ //Checks display timeouts; //checks for new time-of-day second -> decrements timers and checks for timed events; //updates display for "running" functions. + unsigned long now = millis(); //Things to do every time this is called: timeouts to reset display. These may force a tick. - if(pollLast > inputLast){ //don't bother if the last input (which may have called checkRTC) was more recent than poll - //Option/setting timeout: if we're in the options menu, or we're setting a value - if(fnSetPg || fn>=fnOpts){ - if(pollLast>inputLast+(timeoutSet*1000)) { fnSetPg = 0; fn = fnIsTime; force=true; } //Time out after 2 mins - } - //Temporary-display mode timeout: if we're *not* in a permanent one (time, day counter, temp, tester, or running/signaling timer) - else if(fn!=fnIsTime && fn!=fnIsTubeTester && fn!=fnIsDayCount && fn!=fnIsTemp && !(fn==fnIsTimer && (timerRemain>0 || signalRemain>0))){ - if(pollLast>inputLast+(timeoutTempFn*1000)) { fnSetPg = 0; fn = fnIsTime; force=true; } - } - //Stop a signal beep if it's time to - if(signalPulseStopTime && signalPulseStopTime=fnOpts){ + if((unsigned long)(now-inputLast)>=timeoutSet*1000) { fnSetPg = 0; fn = fnIsTime; force=true; } //Time out after 2 mins } + //Temporary-display mode timeout: if we're *not* in a permanent one (time, day counter, temp, tester, or running/signaling timer) + else if(fn!=fnIsTime && fn!=fnIsTubeTester && fn!=fnIsDayCount && fn!=fnIsTemp && !(fn==fnIsTimer && (timerRemain>0 || signalRemain>0))){ + if((unsigned long)(now-inputLast)>=timeoutTempFn*1000) { fnSetPg = 0; fn = fnIsTime; force=true; } + } + //Stop a signal pulse if it's time to + //This is only used for relay pulses, since beeper beep durations are done via tone() + //So we can safely assume the length of the pulse should be relayPulse + if(signalPulseStartTime && (unsigned long)(now-signalPulseStartTime)>=relayPulse) { signalPulseStop(); signalPulseStartTime = 0; } //Update things based on RTC tod = rtc.now(); @@ -636,15 +638,29 @@ void checkRTC(bool force){ fnSetPg = 0; fn = fnIsTime; signalStart(fnIsAlarm,1,0); } //end toddow check } //end alarm trigger - //check if we should trigger the cleaner (at night end time, or alarm time if night end is 0:00) - if(tod.hour()*60+tod.minute()==(readEEPROM(30,true)==0?readEEPROM(0,true):readEEPROM(30,true))) { + //If cleaner is set to option value 0 (at night end time, or alarm time if night end is 0:00), run it at that time + if(readEEPROM(46,false)==0 && tod.hour()*60+tod.minute()==(readEEPROM(30,true)==0?readEEPROM(0,true):readEEPROM(30,true))) { cleanRemain = 11; //loop() will pick this up - } //end cleaner check + } } if(tod.second()==30 && fn==fnIsTime && fnSetPg==0 && unoffRemain==0) { //At bottom of minute, maybe show date - not when unoffing - if(readEEPROM(18,false)>=2) { fn = fnIsDate; inputLast = pollLast; updateDisplay(); } + if(readEEPROM(18,false)>=2) { fn = fnIsDate; inputLast = now; updateDisplay(); } if(readEEPROM(18,false)==3) { startScroll(); } } + if(tod.second()==1) { //If cleaner is set to option value >0, run the cleaner at second :01 as applicable + switch(readEEPROM(46,false)) { + case 1: //at 00:00:01 + if(tod.hour()==0 && tod.minute()==0) cleanRemain = 11; + break; + case 2: //at :00:01 + if(tod.minute()==0) cleanRemain = 11; + break; + case 3: //at :01 + cleanRemain = 11; + break; + default: break; //case 0 is handled at top of minute + } + } //Strikes - only if fn=clock, not setting, not night/away. Setting 21 will be off if signal type is no good //Short pips before the top of the hour @@ -679,7 +695,7 @@ void checkRTC(bool force){ if(readEEPROM(25,false)) { //interval timer: a short signal and restart; don't change to timer fn signalStart(fnIsTimer,0,0); timerRemain = timerInitial; } else { - fnSetPg = 0; fn = fnIsTimer; inputLast = pollLast; signalStart(fnIsTimer,signalDur,0); + fnSetPg = 0; fn = fnIsTimer; inputLast = now; signalStart(fnIsTimer,signalDur,0); } } //end not switched relay } //end timer elapsed @@ -1044,7 +1060,7 @@ void cycleDisplay(){ //But if we're setting, decide here to dim for every other 500ms since we started setting if(fnSetPg>0) { if(setStartLast==0) setStartLast = mils; - dim = 1-(((mils-setStartLast)/500)%2); + dim = 1-(((unsigned long)(mils-setStartLast)/500)%2); } else { if(setStartLast>0) setStartLast=0; } @@ -1065,8 +1081,9 @@ void cycleDisplay(){ // at 0ms, next = (( 0*(6-1))/20)+1 = 1; last = (6-nextDur) = 5; // at 10ms, next = ((10*(6-1))/20)+1 = 3; last = (6-nextDur) = 3; ... // at 20ms, next = ((20*(6-1))/20)+1 = 6; next = total, so fade is over! - //TODO facilitate longer fades by writing a tweening function that smooths the frames, i.e. 111121222 - fadeNextDur = (((mils-fadeStartLast)*(fadeDur-1))/(readEEPROM(20,false)*10))+1; + //TODO facilitate longer fades by writing a tweening function that smooths the frames, i.e. 111121222 - or use delayMicroseconds as below + //TODO does this have more problems with the mils rollover issue? + fadeNextDur = (((unsigned long)(mils-fadeStartLast)*(fadeDur-1))/(readEEPROM(20,false)*10))+1; if(fadeNextDur >= fadeLastDur) { //fade is over fadeStartLast = 0; fadeNextDur = 0; @@ -1079,6 +1096,7 @@ void cycleDisplay(){ } //end curently fading } //end fading enabled + //TODO consider using delayMicroseconds() which, with its tighter resolution, may give better control over fades and dim levels if(displayDim>0) { //if other display code says to shut off entirely, skip this part //Anode channel 0: tubes #2 (min x10) and #5 (sec x1) setCathodes(displayLast[2],displayLast[5]); //Via d2b decoder chip, set cathodes to old digits @@ -1167,7 +1185,7 @@ void signalPulseStart(word pulseDur){ else if(getSignalOutput()==1 && relayPin>=0 && relayMode==1) { //pulsed relay digitalWrite(relayPin,LOW); //LOW = device on //Serial.print(millis(),DEC); Serial.println(F(" Relay on, signalPulseStart")); - signalPulseStopTime = millis()+relayPulse; //always use relayPulse in case timing is important for connected device + signalPulseStartTime = millis(); } } void signalPulseStop(){ @@ -1177,7 +1195,7 @@ void signalPulseStop(){ else if(getSignalOutput()==1 && relayPin>=0 && relayMode==1) { //pulsed relay digitalWrite(relayPin,HIGH); //LOW = device on //Serial.print(millis(),DEC); Serial.println(F(" Relay off, signalPulseStop")); - signalPulseStopTime = 0; + signalPulseStartTime = 0; } } word getSignalPitch(){ //for current signal: time, timer, or (default) alarm