From 6d78cfbec6b89277414269729d4846c966c09f12 Mon Sep 17 00:00:00 2001 From: Luke McKenzie Date: Sun, 14 Oct 2018 21:37:06 -0500 Subject: [PATCH] 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