33// Originally written by Robin Birtles and Chris Gerekos based on http://arduinix.com/Main/Code/ANX-6Tube-Clock-Crossfade.txt
44// Refactored and expanded by Luke McKenzie (luke@theclockspot.com)
55
6- // TODO: Rotary encoders with velocity - test
6+ // Requires ooPinChangeInt.h
7+ // Requires AdaEncoder.h
8+
79// TODO: Alarm - display, set, sound, snooze, 24h silence
810// TODO: Timer - display, set, run, sound, silence
911// TODO: Cathode anti-poisoning
@@ -26,7 +28,7 @@ RTC_DS1307 rtc;
2628// S5/PL8 = A3
2729// S6/PL9 = A2
2830// S7/PL14 = A7
29- // A6-A7 are analog-only pins that aren't as responsive and require a physical pullup resistor (1K to +5V).
31+ // 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 .
3032
3133// What input is associated with each control?
3234const byte mainSel = A2; // main select button - must be equipped
@@ -38,7 +40,6 @@ const byte altAdjDn = 0; //A3;
3840
3941// What type of adj controls are equipped?
4042// 1 = momentary buttons. 2 = quadrature rotary encoder.
41- // Currently using AdaEncoder library that uses pin change interrupts, not useful on A6/A7!
4243const byte mainAdjType = 2 ;
4344AdaEncoder mainRot = AdaEncoder(' a' ,mainAdjUp,mainAdjDn);
4445const byte altAdjType = 0 ; // if unquipped, set to 0
@@ -70,26 +71,25 @@ const byte velThreshold = 100; //ms
7071// When an adj up/down input (btn or rot) follows another in less than this time, value will change more (10 vs 1).
7172// Recommend ~100 for rotaries. If you want to use this feature with buttons, extend to ~400.
7273
73-
7474// //////// Global consts and vars used in multiple sections //////////
7575
7676// Hardware inputs
7777byte btnCur = 0 ; // Momentary button currently in use - only one allowed at a time
7878byte btnCurHeld = 0 ; // Button hold thresholds: 0=none, 1=unused, 2=short, 3=long, 4=set by btnStop()
79- unsigned long inputLast = 0 ; // When a button was last pressed / knob was last turned
79+ unsigned long inputLast = 0 ; // When a button was last pressed
8080unsigned long inputLast2 = 0 ; // Second-to-last of above
8181
8282// Input handling and value setting
83- const byte fnCt = 2 ; // number of functions in the clock
84- byte fn = 0 ; // currently displayed function : 0=time, 1=date, 2=alarm, 3=timer, 255=SETUP menu
83+ const byte fnCt = 5 ; // number of functions in the clock
84+ byte fn = 0 ; // currently displayed fn : 0=time, 1=date, 2=alarm, 3=timer, 4=temp , 255=SETUP menu
8585byte fnSet = 0 ; // whether this function is currently being set, and which option/page it's on
8686word fnSetVal; // the value currently being set, if any - unsigned int 0-65535
8787word fnSetValMin; // min possible - unsigned int
8888word fnSetValMax; // max possible - unsigned int
8989bool fnSetValVel; // whether it supports velocity setting (if max-min > 30)
9090word fnSetValDate[3 ]; // holder for newly set date, so we can set it in 3 stages but set the RTC only once
91- const byte alarmTimeLoc = 0 ; // EEPROM locs 0-1 (2 bytes) in minutes past midnight.
92- const byte alarmOnLoc = 2 ; // EEPROM loc 2
91+ // const byte alarmTimeLoc = 0; //EEPROM locs 0-1 (2 bytes) in minutes past midnight.
92+ // const byte alarmOnLoc = 2; //EEPROM loc 2
9393unsigned long alarmSoundStart = 0 ; // also used for timer expiry TODO what happens if they are concurrent?
9494word snoozeTime = 0 ; // seconds
9595word timerTime = 0 ; // seconds - up to just under 18 hours
@@ -101,6 +101,9 @@ const byte optsLoc[19] = {0, 3, 4, 5, 6, 7, 8, 9,10,11, 12,14, 15, 17,19,20,21
101101const word optsDef[19 ] = {0 , 2 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,500 , 0 ,1320 , 360 , 0 , 1 , 5 , 480 ,1020 };
102102const word optsMin[19 ] = {0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 };
103103const word optsMax[19 ] = {0 , 2 , 2 , 2 , 1 ,50 , 1 , 6 , 4 ,60 ,999 , 2 ,1439 ,1439 , 2 , 6 , 6 ,1439 ,1439 };
104+ // Buffer for reading extra data from ds3231
105+ #define BUFF_MAX 128
106+ char buff[BUFF_MAX];
104107
105108// Display formatting
106109byte displayNext[6 ] = {15 ,15 ,15 ,15 ,15 ,15 }; // Internal representation of display. Blank to start. Change this to change tubes.
@@ -111,8 +114,13 @@ byte displayNext[6] = {15,15,15,15,15,15}; //Internal representation of display.
111114void setup (){
112115 Serial.begin (57600 );
113116 Wire.begin ();
114- rtc.begin ();
115- if (!rtc.isrunning ()) rtc.adjust (DateTime (2017 ,1 ,1 ,0 ,0 ,0 )); // TODO test
117+ // rtc.begin();
118+ // if(!rtc.isrunning()) rtc.adjust(DateTime(2017,1,1,0,0,0)); //TODO test TODO ds3231 version?
119+
120+ // ds3231
121+ DS3231_init (DS3231_INTCN);
122+ memset (recv,0 ,BUFF_MAX); // TODO what does this do
123+
116124 initOutputs ();
117125 initInputs ();
118126 initEEPROM (readInput (mainSel)==LOW);
@@ -121,10 +129,12 @@ void setup(){
121129}
122130
123131unsigned long pollLast = 0 ;
132+ struct ts t; // ds3231
124133void loop (){
134+ unsigned long now = millis ();
125135 // Things done every 50ms - avoids overpolling(?) and switch bounce(?)
126- if (pollLast<millis () +50 ) {
127- pollLast=millis () ;
136+ if (pollLast<now +50 ) {
137+ pollLast=now ;
128138 checkRTC (false ); // if clock has ticked, decrement timer if running, and updateDisplay
129139 checkInputs (); // if inputs have changed, this will do things + updateDisplay as needed
130140 doSetHold (); // if inputs have been held, this will do more things + updateDisplay as needed
@@ -237,15 +247,23 @@ void ctrlEvt(byte ctrl, byte evt){
237247 btnStop (); fn = 255 ; startOpt (1 ); return ;
238248 }
239249
240- DateTime now = rtc.now ();
250+ // DateTime now = rtc.now();
251+ // ds3231 - checkRTC should already have run?? TODO
241252
242253 if (!fnSet) { // fn running
243254 if (evt==2 && ctrl==mainSel) { // sel hold: enter fnSet
244255 switch (fn){
245- case 0 : startSet ((now.hour ()*60 )+now.minute (),0 ,1439 ,1 ); break ; // time: set mins
246- case 1 : fnSetValDate[1 ]=now.month (), fnSetValDate[2 ]=now.day (); startSet (now.year (),0 ,32767 ,1 ); break ; // date: set year
247- case 2 : // alarm: set mins
256+ case 0 : startSet ((t.hour *60 )+t.min ,0 ,1439 ,1 ); break ; // time: set mins
257+ case 1 : fnSetValDate[1 ]=t.mon , fnSetValDate[2 ]=t.mday ; startSet (t.year ,0 ,32767 ,1 ); break ; // date: set year
258+ case 2 :
259+ DS3231_get_a1 (&buff[0 ], 59 ); // TODO write a wrapper function for this
260+ startSet (buff,0 ,1439 ,1 ); // alarm: set mins
261+ break ;
248262 case 3 : // timer: set mins
263+ startSet (timerTime/60 ,0 ,719 ,1 ); // 12 hours
264+ break ;
265+ case 4 : // temperature
266+ // nothing - or is this where we do the calibration? TODO
249267 default : break ;
250268 }
251269 return ;
@@ -266,9 +284,8 @@ void ctrlEvt(byte ctrl, byte evt){
266284 }
267285 if (fnChgd){
268286 switch (fn){
269- case 0 : case 1 : checkRTC (true ); break ;
270- case 2 : // alarm: show
271- case 3 : // timer: show
287+ case 0 : case 1 : checkRTC (true ); break ; // time or date
288+ case 2 : case 3 : updateDisplay (); break ; // alarm or timer
272289 default : break ;
273290 }
274291 }
@@ -279,9 +296,12 @@ void ctrlEvt(byte ctrl, byte evt){
279296 if (evt==1 ) { // we respond only to press evts during fn setting
280297 if (ctrl==mainSel) { // mainSel push: go to next option or save and exit fnSet
281298 btnStop (); // not waiting for mainSelHold, so can stop listening here
299+ // In case we are setting the time or date, in an effort to not lose synchronization too badly, should we update t here?
282300 switch (fn){
283301 case 0 : // time of day: save in RTC
284- rtc.adjust (DateTime (now.year (),now.month (),now.day (),fnSetVal/60 ,fnSetVal%60 ,0 ));
302+ // rtc.adjust(DateTime(t.year,t.mon,t.mday,fnSetVal/60,fnSetVal%60,0));
303+ t.hour = fnSetVal/60 ; t.min = fnSetVal%60 , t.sec = 0 ;
304+ DS3231_set (t);
285305 clearSet (); break ;
286306 case 1 : switch (fnSet){ // date: save in RTC - year can be 2-byte int
287307 case 1 : // date: save year, set month
@@ -291,15 +311,19 @@ void ctrlEvt(byte ctrl, byte evt){
291311 fnSetValDate[1 ]=fnSetVal;
292312 startSet (fnSetValDate[2 ],1 ,daysInMonth (fnSetValDate[0 ],fnSetValDate[1 ]),3 ); break ;
293313 case 3 : // date: save in RTC
294- rtc.adjust (DateTime (fnSetValDate[0 ],fnSetValDate[1 ],fnSetVal,now.hour (),now.minute (),now.second ()));
295- // TODO this rounds down the seconds and loses synchronization! find a way to set the date only
314+ // rtc.adjust(DateTime( fnSetValDate[0],fnSetValDate[1],fnSetVal,t.hour,t.min,t.sec));
315+ // TODO this rounds down the seconds and loses synchronization! find a way to set the date only.
316+ t.year = fnSetValDate[0 ]; t.mon = fnSetValDate[1 ]; t.mday = fnSetVal;
317+ DS3231_set (t);
296318 clearSet (); break ;
297319 default : break ;
298320 } break ;
299321 case 2 : // alarm
300- // EEPROM set TODO
322+ do (
323+ uint8_t flags[5 ] = {0 ,0 ,0 ,1 ,1 }; // what calendar component triggers the alarm, see datasheet
324+ DS3231_set_a1 (0 ,fnSetVal%60 ,fnSetVal/60 ,0 ,flags);
301325 case 3 : // timer
302- // TODO
326+ timerTime = fnSetVal;
303327 default : break ;
304328 } // end switch fn
305329 } // end mainSel push
@@ -439,26 +463,31 @@ void checkRTC(bool force){
439463 if (fnSet && pollLast-inputLast>120000 ) { fnSet = 0 ; fn = 0 ; force=true ; } // abandon set
440464 else if (!fnSet && fn!=0 && !(fn==3 && (timerTime>0 || alarmSoundStart!=0 )) && pollLast>inputLast+5000 ) { fnSet = 0 ; fn = 0 ; force=true ; } // abandon fn
441465 // Update things based on RTC
442- DateTime now = rtc.now ();
443- if (rtcSecLast != now.second () || force) {
444- rtcSecLast = now.second (); // this was missing! TODO reintroduce
466+ DS3231_get (&t); // ds3231 //DateTime now = rtc.now();
467+ // replace now.year(), month, day, dayOfTheWeek, hour, minute, second
468+ // with t.year, mon, mday, wday, hour, min, sec
469+ // hoping t.mon continues to be 1-index and wday 0-index starting with Sunday
470+ // TODO what is inp2toi?
471+
472+ if (rtcSecLast != t.sec || force) {
473+ rtcSecLast = t.sec ; // this was missing! TODO reintroduce
445474 // trip alarm TODO
446475 // decrement timer TODO
447476 // trip minutely date at :30 TODO
448477 // trip digit cycle TODO
449478 // finally display live time of day / date
450479 if (fnSet==0 && fn==0 ){ // time of day
451- byte hr = now .hour () ;
480+ byte hr = t .hour ;
452481 if (readEEPROM (optsLoc[1 ],false )==1 ) hr = (hr==0 ?12 :(hr>12 ?hr-12 :hr));
453482 editDisplay (hr, 0 , 1 , readEEPROM (optsLoc[4 ],false ));
454- editDisplay (now. minute () , 2 , 3 , true );
455- if (EEPROM.read (optsLoc[3 ])==1 ) editDisplay (now. day () , 4 , 5 , EEPROM.read (optsLoc[4 ])); // date
456- else editDisplay (now. second () , 4 , 5 , true ); // seconds
483+ editDisplay (t. min , 2 , 3 , true );
484+ if (EEPROM.read (optsLoc[3 ])==1 ) editDisplay (t. mday , 4 , 5 , EEPROM.read (optsLoc[4 ])); // date
485+ else editDisplay (t. sec , 4 , 5 , true ); // seconds
457486 } else if (fnSet==0 && fn==1 ){ // date
458- editDisplay (EEPROM.read (optsLoc[2 ])==1 ?now. month ():now. day () , 0 , 1 , EEPROM.read (optsLoc[4 ]));
459- editDisplay (EEPROM.read (optsLoc[2 ])==1 ?now. day ():now. month () , 2 , 3 , EEPROM.read (optsLoc[4 ]));
487+ editDisplay (EEPROM.read (optsLoc[2 ])==1 ?t. mon :t. mday , 0 , 1 , EEPROM.read (optsLoc[4 ]));
488+ editDisplay (EEPROM.read (optsLoc[2 ])==1 ?t. mday :t. mon , 2 , 3 , EEPROM.read (optsLoc[4 ]));
460489 blankDisplay (4 , 4 );
461- editDisplay (now. dayOfTheWeek () , 5 , 5 , false );
490+ editDisplay (t. wday , 5 , 5 , false ); // TODO is this 0=Sunday, 6=Saturday?
462491 }
463492 }
464493}
@@ -481,11 +510,26 @@ void updateDisplay(){
481510 }
482511 else { // fn running
483512 switch (fn){
484- // case 0: //time taken care of by checkRTC()
485- // case 1: //date taken care of by checkRTC()
513+ // case 0 and 1: time/date display taken care of by checkRTC()
486514 case 2 : // alarm
487- case 3 : // timer
488- break ;
515+ editDisplay (0 ,0 ,1 ,EEPROM.read (optsLoc[4 ])); // hrs
516+ editDisplay (0 ,2 ,3 ,true ); // mins
517+ editDisplay (0 ,4 ,5 ,false ); // status
518+ break ;
519+ case 3 : // timer - display time duration, not time of day with leading zeros
520+ // todo does checkRTC need to do this one too?
521+ if (false /* hrs > 0*/ ) editDisplay (1 ,0 ,1 ,false ); else blankDisplay (0 ,1 ); // hrs if present
522+ editDisplay (5 ,2 ,3 ,(false /* hrs>0*/ ?true :false )); // mins - leading zero only if hrs present
523+ editDisplay (
524+ break ;
525+ case 4 : // thermometer
526+ float temp = DS3231_get_treg (); // TODO signed? decimal? what?
527+ if (temp>0 ) blankDisplay (0 ,0 ); else editDisplay (0 ,0 ,1 ,true ); // 0 in left tube if negative
528+ editDisplay (temp,1 ,3 ,false ); // whole degrees
529+ editDisplay (temp%10 ,4 ,4 ,true ); // tenths?
530+ blankDisplay (5 ,5 );
531+ break ;
532+ default : break ;
489533 }
490534 }
491535} // end updateDisplay()
0 commit comments