|  | @@ -1,3 +1,5 @@
 | 
	
		
			
				|  |  | +use core::future::pending;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  pub const DS3231_ADDR: u8 = 0x68;
 | 
	
		
			
				|  |  |  pub const TUSB322_ADDR: u8 = 0x47;
 | 
	
		
			
				|  |  |  pub const PCA9685_ADDR_1: u8 = 0x41;
 | 
	
	
		
			
				|  | @@ -15,6 +17,24 @@ const MAP_DOT_PIN: u8 = 15;
 | 
	
		
			
				|  |  |  const MAP_ADDR: usize = 0;
 | 
	
		
			
				|  |  |  const MAP_PIN: usize = 1;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +const DIGIT_FADE_DURATION_US: u32 = 1_000_000;
 | 
	
		
			
				|  |  | +const REFRESH_RATE_US: u32 = 1000;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const DIGIT_RNG_FADE_DURATION_US: u32 = 200_000;
 | 
	
		
			
				|  |  | +const DIGIT_RNG_FADE_ITERATIONS: usize = 20;
 | 
	
		
			
				|  |  | +const DIGIT_RNG_REFRESH_INTERVAL: usize = 60;
 | 
	
		
			
				|  |  | +const DIGIT_RNG_REFRESH_VARIANCE: usize = 30;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const DOT_MIN_BRIGHTNESS: u32 = 256;
 | 
	
		
			
				|  |  | +const DOT_MAX_BRIGHTNESS: u32 = 640;
 | 
	
		
			
				|  |  | +const DOT_FADE_DURATION_US: u32 = 1_000_000;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const DIGIT_MAX_BRIGHTNESS: u32 = 4096;
 | 
	
		
			
				|  |  | +const DIGIT_MIN_BRIGHTNESS: u32 = 0;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const NUM_TUBES: usize = 4;
 | 
	
		
			
				|  |  | +const NUM_DIGITS: usize = 10;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  struct DigitToPin {
 | 
	
		
			
				|  |  |      address: u8,
 | 
	
		
			
				|  |  |      pin: usize,
 | 
	
	
		
			
				|  | @@ -215,9 +235,11 @@ static TUBE_MAPPING: PwmOutputMap = {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +#[derive(PartialEq)]
 | 
	
		
			
				|  |  |  enum DigitState {
 | 
	
		
			
				|  |  | +    Idle,
 | 
	
		
			
				|  |  | +    Incrementing,
 | 
	
		
			
				|  |  |      Decrementing,
 | 
	
		
			
				|  |  | -    Incrementing
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  struct Digit {
 | 
	
	
		
			
				|  | @@ -228,14 +250,159 @@ struct Digit {
 | 
	
		
			
				|  |  |      updated: bool,
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +impl Digit {
 | 
	
		
			
				|  |  | +    const fn default() -> Self {
 | 
	
		
			
				|  |  | +        Self {
 | 
	
		
			
				|  |  | +            current_state: DigitState::Idle,
 | 
	
		
			
				|  |  | +            value: 0,
 | 
	
		
			
				|  |  | +            pwm_start: 0,
 | 
	
		
			
				|  |  | +            pwm_end: 0,
 | 
	
		
			
				|  |  | +            updated: false,
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  struct Tube {
 | 
	
		
			
				|  |  | -    digits: [Digit; 10],
 | 
	
		
			
				|  |  | -    last_active: usize,
 | 
	
		
			
				|  |  | -    refresh_last: usize,
 | 
	
		
			
				|  |  | -    active: bool,
 | 
	
		
			
				|  |  | -    fade_duration: u32,
 | 
	
		
			
				|  |  | +    digits: [Digit; NUM_DIGITS],
 | 
	
		
			
				|  |  | +    last_active_digit: Option<usize>,
 | 
	
		
			
				|  |  | +    refresh_last_digit: Option<usize>,
 | 
	
		
			
				|  |  | +    refresh_active: bool,
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +impl Tube {
 | 
	
		
			
				|  |  | +    const fn default() -> Self {
 | 
	
		
			
				|  |  | +        const DIGIT_INIT: Digit = Digit::default();
 | 
	
		
			
				|  |  | +        Self {
 | 
	
		
			
				|  |  | +            digits: [DIGIT_INIT; 10],
 | 
	
		
			
				|  |  | +            last_active_digit: None,
 | 
	
		
			
				|  |  | +            refresh_last_digit: None,
 | 
	
		
			
				|  |  | +            refresh_active: false,
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  struct Clock {
 | 
	
		
			
				|  |  | -    tubes: [Tube; 4],
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | +    tubes: [Tube; NUM_TUBES],
 | 
	
		
			
				|  |  | +    dot: Digit,
 | 
	
		
			
				|  |  | +    fade_duration: u32,
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +impl Clock {
 | 
	
		
			
				|  |  | +    const fn default() -> Self {
 | 
	
		
			
				|  |  | +        const TUBE_INIT: Tube = Tube::default();
 | 
	
		
			
				|  |  | +        Self {
 | 
	
		
			
				|  |  | +            tubes: [TUBE_INIT; NUM_TUBES],
 | 
	
		
			
				|  |  | +            dot: Digit::default(),
 | 
	
		
			
				|  |  | +            fade_duration: 0,
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +static mut CLOCK: Clock = Clock::default();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// pub fn set_digit(tube: usize, digit: usize, pwm_start: u32, pwm_end: u32) {
 | 
	
		
			
				|  |  | +//     assert!(pwm_end >= pwm_start);
 | 
	
		
			
				|  |  | +//     assert!(tube < NUM_TUBES);
 | 
	
		
			
				|  |  | +//     assert!(digit < NUM_DIGITS);
 | 
	
		
			
				|  |  | +//     unsafe {
 | 
	
		
			
				|  |  | +//         CLOCK.tubes[tube].digits[digit].current_state = DigitState::Incrementing;
 | 
	
		
			
				|  |  | +//         CLOCK.tubes[tube].digits[digit].pwm_start = pwm_start;
 | 
	
		
			
				|  |  | +//         CLOCK.tubes[tube].digits[digit].pwm_end = pwm_end;
 | 
	
		
			
				|  |  | +//         CLOCK.tubes[tube].digits[digit].value = pwm_end - pwm_start;
 | 
	
		
			
				|  |  | +//         CLOCK.tubes[tube].digits[digit].updated = true;
 | 
	
		
			
				|  |  | +//     }
 | 
	
		
			
				|  |  | +// }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +pub fn fade_in_out_digit(tube: usize, digit: Option<usize>, fade_duration: u32, refresh_cmd: bool) {
 | 
	
		
			
				|  |  | +    // assert!(tube < NUM_TUBES);
 | 
	
		
			
				|  |  | +    // assert!(Some(digit) < NUM_DIGITS);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    unsafe {
 | 
	
		
			
				|  |  | +        // If the tube is in the middle of a refresh sequence and a call comes
 | 
	
		
			
				|  |  | +        // in to update the tube digit (for time), override the last value of
 | 
	
		
			
				|  |  | +        // the refresh sequence with the new digit.
 | 
	
		
			
				|  |  | +        if CLOCK.tubes[tube].refresh_active && !refresh_cmd {
 | 
	
		
			
				|  |  | +            CLOCK.tubes[tube].refresh_last_digit = digit;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Dont update if actively refreshing tube unless RngUpdate is set
 | 
	
		
			
				|  |  | +        if (!CLOCK.tubes[tube].refresh_active && !refresh_cmd) || refresh_cmd {
 | 
	
		
			
				|  |  | +            // Fade out all digits
 | 
	
		
			
				|  |  | +            for digit in 0..NUM_DIGITS {
 | 
	
		
			
				|  |  | +                CLOCK.tubes[tube].digits[digit].current_state = DigitState::Decrementing;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            // Fade in the specified digit
 | 
	
		
			
				|  |  | +            if let Some(digit) = digit {
 | 
	
		
			
				|  |  | +                CLOCK.tubes[tube].digits[digit].current_state = DigitState::Incrementing;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            CLOCK.tubes[tube].last_active_digit = digit;
 | 
	
		
			
				|  |  | +            CLOCK.fade_duration = fade_duration;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // TODO! trigger refresh timer
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +pub fn refresh_frame() {
 | 
	
		
			
				|  |  | +    let mut pending_refresh: bool = false;
 | 
	
		
			
				|  |  | +    unsafe {
 | 
	
		
			
				|  |  | +        let ticks = CLOCK.fade_duration / REFRESH_RATE_US;
 | 
	
		
			
				|  |  | +        for tube in 0..NUM_TUBES {
 | 
	
		
			
				|  |  | +            let steps = ((DIGIT_MAX_BRIGHTNESS - DIGIT_MIN_BRIGHTNESS) + ticks - 1) / ticks;
 | 
	
		
			
				|  |  | +            for digit in 0..NUM_DIGITS {
 | 
	
		
			
				|  |  | +                let mut d = &mut CLOCK.tubes[tube].digits[digit];
 | 
	
		
			
				|  |  | +                match d.current_state {
 | 
	
		
			
				|  |  | +                    DigitState::Incrementing => {
 | 
	
		
			
				|  |  | +                        d.value = d.value.saturating_add(steps);
 | 
	
		
			
				|  |  | +                        if d.value >= DIGIT_MAX_BRIGHTNESS {
 | 
	
		
			
				|  |  | +                            d.value = DIGIT_MAX_BRIGHTNESS;
 | 
	
		
			
				|  |  | +                            d.current_state = DigitState::Idle;
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +                        d.updated = true;
 | 
	
		
			
				|  |  | +                        pending_refresh = true;
 | 
	
		
			
				|  |  | +                    },
 | 
	
		
			
				|  |  | +                    DigitState::Decrementing => {
 | 
	
		
			
				|  |  | +                        d.value = d.value.saturating_sub(steps);
 | 
	
		
			
				|  |  | +                        if d.value <= DIGIT_MIN_BRIGHTNESS {
 | 
	
		
			
				|  |  | +                            d.value = DIGIT_MIN_BRIGHTNESS;
 | 
	
		
			
				|  |  | +                            d.current_state = DigitState::Idle;
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +                        d.updated = true;
 | 
	
		
			
				|  |  | +                        pending_refresh = true;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    },
 | 
	
		
			
				|  |  | +                    DigitState::Idle => (),
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        // Handle dot
 | 
	
		
			
				|  |  | +        let steps = ((DOT_MAX_BRIGHTNESS - DOT_MIN_BRIGHTNESS) + ticks - 1) / ticks;
 | 
	
		
			
				|  |  | +        match CLOCK.dot.current_state {
 | 
	
		
			
				|  |  | +            DigitState::Incrementing => {
 | 
	
		
			
				|  |  | +                CLOCK.dot.value = CLOCK.dot.value.saturating_add(steps);
 | 
	
		
			
				|  |  | +                if CLOCK.dot.value >= DIGIT_MAX_BRIGHTNESS {
 | 
	
		
			
				|  |  | +                    CLOCK.dot.value = DIGIT_MAX_BRIGHTNESS;
 | 
	
		
			
				|  |  | +                    CLOCK.dot.current_state = DigitState::Idle;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                CLOCK.dot.updated = true;
 | 
	
		
			
				|  |  | +                pending_refresh = true;
 | 
	
		
			
				|  |  | +            },
 | 
	
		
			
				|  |  | +            DigitState::Decrementing => {
 | 
	
		
			
				|  |  | +                CLOCK.dot.value = CLOCK.dot.value.saturating_sub(steps);
 | 
	
		
			
				|  |  | +                if CLOCK.dot.value >= DIGIT_MIN_BRIGHTNESS {
 | 
	
		
			
				|  |  | +                    CLOCK.dot.value = DIGIT_MIN_BRIGHTNESS;
 | 
	
		
			
				|  |  | +                    CLOCK.dot.current_state = DigitState::Idle;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                CLOCK.dot.updated = true;
 | 
	
		
			
				|  |  | +                pending_refresh = true;
 | 
	
		
			
				|  |  | +            },
 | 
	
		
			
				|  |  | +            DigitState::Idle => (),
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if pending_refresh {
 | 
	
		
			
				|  |  | +        // TODO! trigger refresh timer
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 |