3
3
// Originally written by Robin Birtles and Chris Gerekos based on http://arduinix.com/Main/Code/ANX-6Tube-Clock-Crossfade.txt
4
4
// Refactored and expanded by Luke McKenzie (luke@theclockspot.com)
5
5
6
- // TODO: Rotary encoders with velocity - test
6
+ // Requires ooPinChangeInt.h
7
+ // Requires AdaEncoder.h
8
+
7
9
// TODO: Alarm - display, set, sound, snooze, 24h silence
8
10
// TODO: Timer - display, set, run, sound, silence
9
11
// TODO: Cathode anti-poisoning
@@ -26,7 +28,7 @@ RTC_DS1307 rtc;
26
28
// S5/PL8 = A3
27
29
// S6/PL9 = A2
28
30
// 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 .
30
32
31
33
// What input is associated with each control?
32
34
const byte mainSel = A2; // main select button - must be equipped
@@ -38,7 +40,6 @@ const byte altAdjDn = 0; //A3;
38
40
39
41
// What type of adj controls are equipped?
40
42
// 1 = momentary buttons. 2 = quadrature rotary encoder.
41
- // Currently using AdaEncoder library that uses pin change interrupts, not useful on A6/A7!
42
43
const byte mainAdjType = 2 ;
43
44
AdaEncoder mainRot = AdaEncoder(' a' ,mainAdjUp,mainAdjDn);
44
45
const byte altAdjType = 0 ; // if unquipped, set to 0
@@ -70,26 +71,25 @@ const byte velThreshold = 100; //ms
70
71
// When an adj up/down input (btn or rot) follows another in less than this time, value will change more (10 vs 1).
71
72
// Recommend ~100 for rotaries. If you want to use this feature with buttons, extend to ~400.
72
73
73
-
74
74
// //////// Global consts and vars used in multiple sections //////////
75
75
76
76
// Hardware inputs
77
77
byte btnCur = 0 ; // Momentary button currently in use - only one allowed at a time
78
78
byte 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
80
80
unsigned long inputLast2 = 0 ; // Second-to-last of above
81
81
82
82
// 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
85
85
byte fnSet = 0 ; // whether this function is currently being set, and which option/page it's on
86
86
word fnSetVal; // the value currently being set, if any - unsigned int 0-65535
87
87
word fnSetValMin; // min possible - unsigned int
88
88
word fnSetValMax; // max possible - unsigned int
89
89
bool fnSetValVel; // whether it supports velocity setting (if max-min > 30)
90
90
word 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
93
93
unsigned long alarmSoundStart = 0 ; // also used for timer expiry TODO what happens if they are concurrent?
94
94
word snoozeTime = 0 ; // seconds
95
95
word 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
101
101
const word optsDef[19 ] = {0 , 2 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,500 , 0 ,1320 , 360 , 0 , 1 , 5 , 480 ,1020 };
102
102
const word optsMin[19 ] = {0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 };
103
103
const 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];
104
107
105
108
// Display formatting
106
109
byte 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.
111
114
void setup (){
112
115
Serial.begin (57600 );
113
116
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
+
116
124
initOutputs ();
117
125
initInputs ();
118
126
initEEPROM (readInput (mainSel)==LOW);
@@ -121,10 +129,12 @@ void setup(){
121
129
}
122
130
123
131
unsigned long pollLast = 0 ;
132
+ struct ts t; // ds3231
124
133
void loop (){
134
+ unsigned long now = millis ();
125
135
// 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 ;
128
138
checkRTC (false ); // if clock has ticked, decrement timer if running, and updateDisplay
129
139
checkInputs (); // if inputs have changed, this will do things + updateDisplay as needed
130
140
doSetHold (); // if inputs have been held, this will do more things + updateDisplay as needed
@@ -237,15 +247,23 @@ void ctrlEvt(byte ctrl, byte evt){
237
247
btnStop (); fn = 255 ; startOpt (1 ); return ;
238
248
}
239
249
240
- DateTime now = rtc.now ();
250
+ // DateTime now = rtc.now();
251
+ // ds3231 - checkRTC should already have run?? TODO
241
252
242
253
if (!fnSet) { // fn running
243
254
if (evt==2 && ctrl==mainSel) { // sel hold: enter fnSet
244
255
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 ;
248
262
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
249
267
default : break ;
250
268
}
251
269
return ;
@@ -266,9 +284,8 @@ void ctrlEvt(byte ctrl, byte evt){
266
284
}
267
285
if (fnChgd){
268
286
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
272
289
default : break ;
273
290
}
274
291
}
@@ -279,9 +296,12 @@ void ctrlEvt(byte ctrl, byte evt){
279
296
if (evt==1 ) { // we respond only to press evts during fn setting
280
297
if (ctrl==mainSel) { // mainSel push: go to next option or save and exit fnSet
281
298
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?
282
300
switch (fn){
283
301
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);
285
305
clearSet (); break ;
286
306
case 1 : switch (fnSet){ // date: save in RTC - year can be 2-byte int
287
307
case 1 : // date: save year, set month
@@ -291,15 +311,19 @@ void ctrlEvt(byte ctrl, byte evt){
291
311
fnSetValDate[1 ]=fnSetVal;
292
312
startSet (fnSetValDate[2 ],1 ,daysInMonth (fnSetValDate[0 ],fnSetValDate[1 ]),3 ); break ;
293
313
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);
296
318
clearSet (); break ;
297
319
default : break ;
298
320
} break ;
299
321
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);
301
325
case 3 : // timer
302
- // TODO
326
+ timerTime = fnSetVal;
303
327
default : break ;
304
328
} // end switch fn
305
329
} // end mainSel push
@@ -439,26 +463,31 @@ void checkRTC(bool force){
439
463
if (fnSet && pollLast-inputLast>120000 ) { fnSet = 0 ; fn = 0 ; force=true ; } // abandon set
440
464
else if (!fnSet && fn!=0 && !(fn==3 && (timerTime>0 || alarmSoundStart!=0 )) && pollLast>inputLast+5000 ) { fnSet = 0 ; fn = 0 ; force=true ; } // abandon fn
441
465
// 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
445
474
// trip alarm TODO
446
475
// decrement timer TODO
447
476
// trip minutely date at :30 TODO
448
477
// trip digit cycle TODO
449
478
// finally display live time of day / date
450
479
if (fnSet==0 && fn==0 ){ // time of day
451
- byte hr = now .hour () ;
480
+ byte hr = t .hour ;
452
481
if (readEEPROM (optsLoc[1 ],false )==1 ) hr = (hr==0 ?12 :(hr>12 ?hr-12 :hr));
453
482
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
457
486
} 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 ]));
460
489
blankDisplay (4 , 4 );
461
- editDisplay (now. dayOfTheWeek () , 5 , 5 , false );
490
+ editDisplay (t. wday , 5 , 5 , false ); // TODO is this 0=Sunday, 6=Saturday?
462
491
}
463
492
}
464
493
}
@@ -481,11 +510,26 @@ void updateDisplay(){
481
510
}
482
511
else { // fn running
483
512
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()
486
514
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 ;
489
533
}
490
534
}
491
535
} // end updateDisplay()
0 commit comments