You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
//Temporary-display function timeout: if we're *not* in a permanent one (time, or running timer)
807
-
// Stopped timer shouldn't be permanent, but have a much longer timeout, mostly in case someone is waiting to start the chrono in sync with some event, so we'll give that an hour.
//Temporary-display function timeout: if we're *not* in a permanent one (time, or running/signaling timer)
808
+
// Stopped/non-signaling timer shouldn't be permanent, but have a much longer timeout, mostly in case someone is waiting to start the chrono in sync with some event, so we'll give that an hour.
// There are two timing sources in the UNDB – the Arduino itself (eg millis()), which gives subsecond precision but isn't very accurate, so it's only good for short-term timing and taking action in response to user activity (eg button press hold thresholds); and the DS3231, which is very accurate but only gives seconds (unless you're monitoring its square wave via a digital pin), so it's only good for long-term timing and taking action in response to time of day. The one place we need both short-term precision and long-term accuracy is in the chrono/timer – so I have based it on millis() but with an offset applied to correct for its drift, periodically adjusted per the DS3231.
//unsigned long millisAtLastCorrection (defined at top, so ctrlEvt can reset it when setting RTC). 0 when unreliable (at start and after RTC set).
1071
+
// There are two timing sources in the UNDB – the Arduino itself (eg millis()), which gives subsecond precision but isn't very accurate, so it's only good for short-term timing and taking action in response to user activity (eg button press hold thresholds); and the DS3231, which is very accurate but only gives seconds (unless you're monitoring its square wave via a digital pin), so it's only good for long-term timing and taking action in response to time of day. The one place we need both short-term precision and long-term accuracy is in the chrono/timer – so I have based it on millis() but with an offset applied to correct for its drift, periodically adjusted per the DS3231. I also use it for the signal, so the 1/sec measure cycle stays in sync with real time; but we don't need to use it for stuff like button polling.
1072
+
unsignedlong millisDriftOffset = 0; //The cumulative running offset. Since it's circular, doesn't matter whether signed or not.
1073
+
//unsigned long millisAtLastCheck (defined at top, so ctrlEvt can reset it when setting RTC). 0 when unreliable (at start and after RTC set).
1073
1074
//const byte millisCorrectionInterval (defined at top, so checkRTC can see it)
1075
+
int millisDriftBuffer = 0; // Each time we calculate millis() drift, we add it to this signed buffer, which gets applied to millisDriftOffset slowly to smooth the correction and minimize (eliminate?) the chance of a discontinuity, which prevents unsightly glitches in the chrono/timer and the signal performance, and failures in eg detecting button presses.
1074
1076
// TODO the adjustments are a little noisy in the short term, because of a rolling offset between the loop cycle (slowed down by cycleDisplay's delays) and the rtc ticks. It's kind of academic since the variance is probably only around ±.02sec per adjustment at most (largely the duration of cycleDisplay's delays), which I'd say is within the tolerance of display/button/human limitations, but no point doing it as quickly as once per second I think.
1075
-
voidmillisCorrectForDrift(){
1077
+
voidmillisCheckDrift(){
1076
1078
unsignedlong now = millis();
1077
-
if(millisAtLastCorrection){ //if this has a value, check to see how much millis has drifted since then.
if(millisAtLastCheck){ // if this has a value, check to see how much millis has drifted since then. If this is 0, it means either the RTC was recently set (or the extremely unlikely case that the last sync occurred exactly at millis rollover) so we will hold off until next drift check.
1080
+
long millisDrift = now-(millisAtLastCheck+(millisCorrectionInterval*1000)); // Converting difference to a signed long.
1081
+
if(abs((long)(millisDrift+millisDriftBuffer))>32767){} // If adding drift to buffer would make it overflow, ignore it this time
1082
+
else {
1083
+
millisDriftBuffer -= millisDrift;
1084
+
// tod = rtc.now();
1085
+
// if(tod.hour()<10) Serial.print(F("0"));
1086
+
// Serial.print(tod.hour(),DEC);
1087
+
// Serial.print(F(":"));
1088
+
// if(tod.minute()<10) Serial.print(F("0"));
1089
+
// Serial.print(tod.minute(),DEC);
1090
+
// Serial.print(F(":"));
1091
+
// if(tod.second()<10) Serial.print(F("0"));
1092
+
// Serial.print(tod.second(),DEC);
1093
+
// Serial.print(F(" millis: "));
1094
+
// Serial.print(now,DEC);
1095
+
// Serial.print(F(" drift: "));
1096
+
// Serial.print(millisDrift,DEC);
1097
+
// Serial.print(F(" new buffer: "));
1098
+
// Serial.print(millisDriftBuffer,DEC);
1099
+
// Serial.println();
1100
+
}
1101
+
}
1102
+
millisAtLastCheck = now;
1103
+
}
1104
+
voidmillisApplyDrift(){
1105
+
//Applies millisDriftBuffer to millisDriftOffset at the rate of 1ms per loop. See above for details.
// Serial.print(F(" (")); Serial.print(~millisDrift,DEC); //in case of wraparound, this is more human-readable
1094
-
// Serial.print(F(") new offset: "));
1118
+
// Serial.print(F(" new offset: "));
1095
1119
// Serial.print(millisDriftOffset,DEC);
1096
-
// Serial.print(F(" (")); Serial.print(~millisDriftOffset,DEC); //in case of wraparound, this is more human-readable
1097
-
// Serial.print(F(")"));
1120
+
// Serial.print(F(" new buffer: "));
1121
+
// Serial.print(millisDriftBuffer,DEC);
1098
1122
// Serial.println();
1099
1123
}
1100
-
millisAtLastCorrection = now;
1101
1124
}
1102
1125
unsignedlongms(){
1103
1126
// Returns millis() with the drift offset applied, for timer/chrono purposes.
1104
-
// WARNING: Since the offset is being periodically adjusted, there is the possibility of a discontinuity in ms() output – if we give out a timestamp and then effectively set the clock back, the next timestamp might possibly be earlier than the last one, which could mess up duration math. I tried to think of a way to monitor for that discontinuity – e.g. if now-then is greater than then-now, due to overflow – but it gets tricky since millis() is effectively circular, and that condition occurs naturally at rollover as well – so I think we would need a flag that millisCorrectForDrift sets when it sets the offset backward, and ms clears when the real time has caught up.... or something like that.
1127
+
// WARNING: Since the offset is being periodically adjusted, there is the possibility of a discontinuity in ms() output – if we give out a timestamp and then effectively set the clock back, the next timestamp might possibly be earlier than the last one, which could mess up duration math. I tried to think of a way to monitor for that discontinuity – e.g. if now-then is greater than then-now, due to overflow – but it gets tricky since millis() is effectively circular, and that condition occurs naturally at rollover as well – so I think we would need a flag that millisCheckDrift sets when it sets the offset backward, and ms clears when the real time has caught up.... or something like that.
1105
1128
// So for now we'll restrict use of ms() to the timer/chrono duration – the only place we really need it, and fortunately it's not a problem here, because that duration never exceeds 100 hours so we can easily detect that overflow. And the only time it might really be an issue is if it occurs immediately after the chrono starts, since it would briefly display the overflowed time – in that case we'll just reset the timer to 0 and keep going.
0 commit comments