| 
					
				 | 
			
			
				@@ -13,92 +13,92 @@ I2C i2c(PA_10, PA_9); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 bool RtcTick, RefreshTick, RngTick;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 typedef enum {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    Idle = 0,
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    Decrementing = 0,
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     Incrementing = 1,
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    Decrementing = 2,
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } DigitState;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 typedef struct {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     DigitState CurrentState;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     int Value;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    int Brightness;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    int Step;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     bool Updated;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } Digit;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 typedef struct {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     Digit Digits[10];
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     bool Refresh;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    int UpdateRate;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    int FadeDuration;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } Tube;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 Tube Tubes[4];  // Active per-tube configuration
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 Digit Dot;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-Ticker RuntimeUpdateTicker, RngUpdateTicker;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-int RuntimeTickerIter, RngTickerIter;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-void RtcTickCallback(void) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    RtcTick = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-}
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-void RefreshTickCallback(void) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    RefreshTick = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-}
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-void RngTickCallback(void) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    RngTick = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-}
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-Timeout DotUpdateTick;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+Timeout DotUpdateTimeout;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 void DotUpdateCallback(void) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if (Dot.CurrentState == Incrementing && Dot.Brightness < PCA9685_Max_Brightness) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        Dot.Brightness += Dot.Step;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    } else if (Dot.CurrentState == Decrementing && Dot.Brightness > PCA9685_Min_Brightness) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        Dot.Brightness -= Dot.Step;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (Dot.CurrentState == Incrementing && Dot.Value < DOT_MAX) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Dot.Value = (Dot.Value + DOT_FADE_STEP >= DOT_MAX) ? DOT_MAX : Dot.Value + DOT_FADE_STEP;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } else if (Dot.CurrentState == Decrementing && Dot.Value > DOT_MIN) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Dot.Value = (Dot.Value - DOT_FADE_STEP <= DOT_MIN) ? DOT_MIN : Dot.Value - DOT_FADE_STEP;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     Dot.Updated = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if (Dot.Brightness == PCA9685_Max_Brightness ||
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        Dot.Brightness == PCA9685_Min_Brightness) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        DotUpdateTick.detach();
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    } else {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        // DotUpdateTick.attach(DotUpdateCallback, (float)1/DOT_FADE_TICKS);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        DotUpdateTick.attach_us(DotUpdateCallback, 700);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (Dot.Value != DOT_MAX && Dot.Value != DOT_MIN) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        DotUpdateTimeout.attach_us(DotUpdateCallback, DOT_FADE_DURATION_US / REFRESH_RATE_US);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-void Tube0UpdateCallback(void) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+// Macro the tube callback function as each callback needs to be defined as
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+// a unique function (callbacks cannot have arguments and are not reentrant)
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+#define TUBE_CALLBACK(x) \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+void Tube##x##UpdateCallback(void) { \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    int step = (DOT_MAX + (Tubes[x].FadeDuration / REFRESH_RATE_US) - 1) / (Tubes[x].FadeDuration / REFRESH_RATE_US); \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    bool activeTube = false; \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for (int i = 0; i < 10; i++) { \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Digit *digit = &Tubes[x].Digits[i]; \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if (digit->CurrentState == Incrementing && digit->Value < DIGIT_MAX) { \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            digit->Value = (digit->Value + step >= DIGIT_MAX) ? DIGIT_MAX : digit->Value + step; \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            digit->Updated = true; \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } else if (digit->CurrentState == Decrementing && digit->Value > DOT_MIN) { \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            digit->Value = (digit->Value - step <= DIGIT_MIN) ? DIGIT_MIN : digit->Value - step; \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            digit->Updated = true; \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        activeTube |= (digit->Value != DIGIT_MAX && digit->Value != DIGIT_MIN); \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (activeTube) { \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Tube##x##UpdateTimeout.attach_us(Tube##x##UpdateCallback, DIGIT_FADE_DURATION_US / REFRESH_RATE_US); \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } \
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+Timeout Tube0UpdateTimeout;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+TUBE_CALLBACK(0)
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+Timeout Tube1UpdateTimeout;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+TUBE_CALLBACK(1)
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+Timeout Tube2UpdateTimeout;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+TUBE_CALLBACK(2)
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+Timeout Tube3UpdateTimeout;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+TUBE_CALLBACK(3)
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+void FadeInOutDigit(int T, int D, int Duration) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for (int i = 0; i < 10; i++) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Tubes[T].Digits[i].CurrentState = Decrementing;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (D != -1) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Tubes[T].Digits[D].CurrentState = Incrementing;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    Tubes[T].FadeDuration = Duration;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (T == 0) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Tube0UpdateTimeout.attach_us(Tube0UpdateCallback, DIGIT_FADE_DURATION_US / REFRESH_RATE_US);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } else if (T == 1) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Tube1UpdateTimeout.attach_us(Tube1UpdateCallback, DIGIT_FADE_DURATION_US / REFRESH_RATE_US);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } else if (T == 2) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Tube2UpdateTimeout.attach_us(Tube2UpdateCallback, DIGIT_FADE_DURATION_US / REFRESH_RATE_US);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } else if (T == 3) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Tube3UpdateTimeout.attach_us(Tube3UpdateCallback, DIGIT_FADE_DURATION_US / REFRESH_RATE_US);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-// void RuntimeTickerUpdate(void) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//     for (int i = 0; i < NUM_TUBES; i++) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//         if(!Tube[i].RefreshActive) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//             for (int j = 0; j < MAX_FADE_DIGITS; j++) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//                 DIGIT *digit = &Tube[i].Digits[j];
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//                 if (digit->Active) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//                     if (digit->Increment && digit->Reg < PCA9685_Max_Brightness) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//                         digit->Reg += FADE_TICK_STEP;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//                     } else if (digit->Reg > PCA9685_Min_Brightness) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//                         digit->Reg -= FADE_TICK_STEP;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//                     }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//                     Tube[i].Updated = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//                 }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//             }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//         }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//     }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//     DotValue = DotIncrement ? DotValue + FADE_TICK_STEP : DotValue - FADE_TICK_STEP;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//     DotUpdated = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//     if (--RuntimeTickerIter == 0) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//         RuntimeUpdateTicker.detach();
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//         DotIncrement = !DotIncrement;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//     }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-// }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+Ticker RefreshTicker, RngUpdateTicker;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+int RngTickerIter;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 void RngTickerUpdate(void) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -107,12 +107,36 @@ void RngTickerUpdate(void) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+// Callback from DS3231 interrupt (1Hz)
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+void RtcTickCallback(void) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    RtcTick = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+}
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+// Callback from RefreshTicker (REFRESH_RATE_US)
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+void RefreshTickCallback(void) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    RefreshTick = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+}
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+void RngTickCallback(void) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    RngTick = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+}
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 int main() {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     // Initialize pointers in global data structure
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    // for (int i = 0; i < NUM_TUBES; i++) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    //     Tube[i].NextDigit = &Tube[i].Digits[0];
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    // }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for (int i = 0; i < NUM_TUBES; i++) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for (int j = 0; j < 10; j++) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Tubes[i].Digits[j].CurrentState = Decrementing;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Tubes[i].Digits[j].Value = 0;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Tubes[i].Digits[j].Updated = false;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Tubes[i].Refresh = false;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Tubes[i].FadeDuration = 0;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    Dot.CurrentState = Decrementing;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    Dot.Value = 0;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    Dot.Updated = false;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     RtcTick = false;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     RefreshTick = false;
 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -144,8 +168,7 @@ int main() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     // i2c.frequency(1000000);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     // Setup a ticker to refresh the display at 1kHz
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    Ticker refreshTicker;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    refreshTicker.attach_us(RefreshTickCallback, DIGIT_REFRESH_RATE_US);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    RefreshTicker.attach_us(RefreshTickCallback, REFRESH_RATE_US);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     // Kick off the RNG ticker at 0.1Hz
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     // Ticker rngTicker;
 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -154,10 +177,6 @@ int main() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     // DS3231_SetTime(0, 55, 6, true);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     // DS3231_SetDate(0, 2, 12, 18, 0);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    Dot.CurrentState = Decrementing;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    Dot.Step = DOT_TICK_STEP;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    Dot.Brightness = 0;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     while(1) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         // Animate_Cycle_Basic();
 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -173,24 +192,17 @@ int main() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         if (RefreshTick) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             RefreshTick = false;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            // for (int i = 0; i < NUM_TUBES; i++) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //     if (Tube[i].Updated) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         Tube[i].Updated = false;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         for (int j = 0; j < MAX_FADE_DIGITS; j++) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //             DIGIT *digit = &Tube[i].Digits[j];
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //             if (digit->Active) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //                 PCA9685_SetDigit(i, digit->Value, digit->Reg);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //                 if (digit->Reg == 0 && !digit->Increment) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //                     digit->Active = false;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //                 }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //             }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //     }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            // }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            for (int i = 0; i < 4; i++) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                for (int j = 0; j < 10; j++) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (Tubes[i].Digits[j].Updated) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        PCA9685_SetDigit(i, j, Tubes[i].Digits[j].Value);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        Tubes[i].Digits[j].Updated = false;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             if (Dot.Updated) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                PCA9685_SetDot(Dot.Brightness);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                PCA9685_SetDot(Dot.Value);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 Dot.Updated = false;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         }
 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -206,54 +218,24 @@ int main() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             DS3231_GetTime(&nextSecond, &nextMinute, &nextHour);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             // Update the display configuration based on the new/previous time
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            // for (int i = 0; i < NUM_TUBES; i++) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //     bool Update = false;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //     if (i == 0 && (startup || prevHour / 10 != nextHour / 10)) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         if (nextHour / 10 != 0) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //             Tube[i].NextDigit->Value = nextHour / 10;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //             Tube[i].NextDigit->Increment = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //             Tube[i].NextDigit->Active = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         Update = true; // ?
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //     } else if (i == 1 && (startup || prevHour % 10 != nextHour % 10)) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         Tube[i].NextDigit->Value = nextHour % 10;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         Tube[i].NextDigit->Increment = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         Tube[i].NextDigit->Active = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         Update = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //     } else if (i == 2 && (startup || prevMinute / 10 != nextMinute / 10)) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         Tube[i].NextDigit->Value = nextMinute / 10;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         Tube[i].NextDigit->Increment = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         Tube[i].NextDigit->Active = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         Update = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //     } else if (i == 3 && (startup || prevMinute % 10 != nextMinute % 10)) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         Tube[i].NextDigit->Value = nextMinute % 10;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         Tube[i].NextDigit->Increment = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         Tube[i].NextDigit->Active = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         Update = true;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //     }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                // Handle display configuration changes for the given tube
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //     if (Update) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         if (!startup) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //             Tube[i].PrevDigit->Increment = false;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         Tube[i].PrevDigit = Tube[i].NextDigit;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //         Tube[i].NextDigit = Tube[i].NextDigit == &Tube[i].Digits[0] ? 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //             &Tube[i].Digits[1] : &Tube[i].Digits[0];
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            //     }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            // }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (startup || prevHour / 10 != nextHour / 10) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                FadeInOutDigit(0, (nextHour / 10 != 0) ? nextHour / 10 : -1 , DIGIT_FADE_DURATION_US);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (startup || prevHour % 10 != nextHour % 10) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                FadeInOutDigit(1, nextHour % 10, DIGIT_FADE_DURATION_US);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (startup || prevMinute / 10 != nextMinute / 10) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                FadeInOutDigit(2, nextMinute / 10, DIGIT_FADE_DURATION_US);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (startup || prevMinute % 10 != nextMinute % 10) {
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                FadeInOutDigit(3, nextMinute % 10, DIGIT_FADE_DURATION_US);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             Dot.CurrentState = (Dot.CurrentState == Decrementing) ? Incrementing : Decrementing;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            // DotUpdateTick.attach(DotUpdateCallback, (float)1/DOT_FADE_TICKS);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            DotUpdateTick.attach_us(DotUpdateCallback, 700);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            DotUpdateTimeout.attach_us(DotUpdateCallback, DOT_FADE_DURATION_US / REFRESH_RATE_US);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             // Clear the startup run-once flag after the first iteration
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            // startup = false;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            // Fade in the new time
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            // RuntimeTickerIter = FADE_TICKS;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            // RuntimeUpdateTicker.attach(RuntimeTickerUpdate, (float)(DIGIT_REFRESH_RATE_US / 1000) / RuntimeTickerIter);
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            startup = false;
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         }
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 
 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         if (RngTick) {
 
			 |