use stm32l4xx_hal::{ prelude::{ _embedded_hal_blocking_i2c_Read, _embedded_hal_blocking_i2c_Write, _embedded_hal_blocking_i2c_WriteRead, }, }; use crate::{ds3231, pca9685}; pub const DS3231_ADDR: u8 = 0x68; pub const TUSB322_ADDR: u8 = 0x47; pub const PCA9685_ADDR_1: u8 = 0x41; pub const PCA9685_ADDR_2: u8 = 0x42; pub const PCA9685_ADDR_3: u8 = 0x43; pub const PCA9685_ALL_CALL: u8 = 0x70; // Default enabled pub const PCA9685_SUB_CALL_1: u8 = 0x71; // Default disabled pub const PCA9685_SUB_CALL_2: u8 = 0x72; // Default disabled pub const PCA9685_SUB_CALL_3: u8 = 0x73; // Default disabled const MAP_DOT_ADDR: u8 = PCA9685_ADDR_2; 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, } struct PwmDriver { digit: [DigitToPin; 10], } struct PwmOutputMap { driver: [PwmDriver; 4], dot_address: u8, dot_pin: usize, } static TUBE_MAPPING: PwmOutputMap = { PwmOutputMap { driver: [ PwmDriver { digit: [ DigitToPin { address: PCA9685_ADDR_1, pin: 8, }, // Tube 0 Digit 0 DigitToPin { address: PCA9685_ADDR_1, pin: 9, }, // Tube 0 Digit 1 DigitToPin { address: PCA9685_ADDR_1, pin: 10, }, // Tube 0 Digit 2 DigitToPin { address: PCA9685_ADDR_1, pin: 12, }, // Tube 0 Digit 3 DigitToPin { address: PCA9685_ADDR_1, pin: 15, }, // Tube 0 Digit 4 DigitToPin { address: PCA9685_ADDR_1, pin: 14, }, // Tube 0 Digit 5 DigitToPin { address: PCA9685_ADDR_1, pin: 11, }, // Tube 0 Digit 6 DigitToPin { address: PCA9685_ADDR_1, pin: 0, }, // Tube 0 Digit 7 DigitToPin { address: PCA9685_ADDR_1, pin: 1, }, // Tube 0 Digit 8 DigitToPin { address: PCA9685_ADDR_1, pin: 13, }, // Tube 0 Digit 9 ], }, PwmDriver { digit: [ DigitToPin { address: PCA9685_ADDR_1, pin: 5, }, // Tube 1 Digit 0 DigitToPin { address: PCA9685_ADDR_1, pin: 6, }, // Tube 1 Digit 1 DigitToPin { address: PCA9685_ADDR_1, pin: 7, }, // Tube 1 Digit 2 DigitToPin { address: PCA9685_ADDR_1, pin: 2, }, // Tube 1 Digit 3 DigitToPin { address: PCA9685_ADDR_2, pin: 4, }, // Tube 1 Digit 4 DigitToPin { address: PCA9685_ADDR_2, pin: 1, }, // Tube 1 Digit 5 DigitToPin { address: PCA9685_ADDR_1, pin: 4, }, // Tube 1 Digit 6 DigitToPin { address: PCA9685_ADDR_2, pin: 2, }, // Tube 1 Digit 7 DigitToPin { address: PCA9685_ADDR_2, pin: 3, }, // Tube 1 Digit 8 DigitToPin { address: PCA9685_ADDR_1, pin: 3, }, // Tube 1 Digit 9 ], }, PwmDriver { digit: [ DigitToPin { address: PCA9685_ADDR_3, pin: 8, }, // Tube 2 Digit 0 DigitToPin { address: PCA9685_ADDR_3, pin: 9, }, // Tube 2 Digit 1 DigitToPin { address: PCA9685_ADDR_3, pin: 10, }, // Tube 2 Digit 2 DigitToPin { address: PCA9685_ADDR_3, pin: 12, }, // Tube 2 Digit 3 DigitToPin { address: PCA9685_ADDR_2, pin: 12, }, // Tube 2 Digit 4 DigitToPin { address: PCA9685_ADDR_2, pin: 13, }, // Tube 2 Digit 5 DigitToPin { address: PCA9685_ADDR_3, pin: 11, }, // Tube 2 Digit 6 DigitToPin { address: PCA9685_ADDR_2, pin: 14, }, // Tube 2 Digit 7 DigitToPin { address: PCA9685_ADDR_2, pin: 11, }, // Tube 2 Digit 8 DigitToPin { address: PCA9685_ADDR_3, pin: 13, }, // Tube 2 Digit 9 ], }, PwmDriver { digit: [ DigitToPin { address: PCA9685_ADDR_3, pin: 5, }, // Tube 3 Digit 0 DigitToPin { address: PCA9685_ADDR_3, pin: 6, }, // Tube 3 Digit 1 DigitToPin { address: PCA9685_ADDR_3, pin: 7, }, // Tube 3 Digit 2 DigitToPin { address: PCA9685_ADDR_3, pin: 2, }, // Tube 3 Digit 3 DigitToPin { address: PCA9685_ADDR_3, pin: 14, }, // Tube 3 Digit 4 DigitToPin { address: PCA9685_ADDR_3, pin: 15, }, // Tube 3 Digit 5 DigitToPin { address: PCA9685_ADDR_3, pin: 4, }, // Tube 3 Digit 6 DigitToPin { address: PCA9685_ADDR_3, pin: 1, }, // Tube 3 Digit 7 DigitToPin { address: PCA9685_ADDR_3, pin: 0, }, // Tube 3 Digit 8 DigitToPin { address: PCA9685_ADDR_3, pin: 3, }, // Tube 3 Digit 9 ], }, ], dot_address: PCA9685_ADDR_2, dot_pin: 15, } }; #[derive(PartialEq)] enum DigitState { Idle, Incrementing, Decrementing, } struct Digit { current_state: DigitState, value: u32, pwm_start: u32, pwm_end: u32, 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; NUM_DIGITS], last_active_digit: Option, refresh_last_digit: Option, 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; 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 fade_in_out_digit(tube: usize, digit: Option, 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; let steps = ((DIGIT_MAX_BRIGHTNESS - DIGIT_MIN_BRIGHTNESS) + ticks - 1) / ticks; CLOCK.tubes.iter_mut().for_each(|tube| { tube.digits.iter_mut().for_each(|digit| { match digit.current_state { DigitState::Incrementing => { digit.value = digit.value.saturating_add(steps); if digit.value >= DIGIT_MAX_BRIGHTNESS { digit.value = DIGIT_MAX_BRIGHTNESS; digit.current_state = DigitState::Idle; } digit.updated = true; pending_refresh = true; } DigitState::Decrementing => { digit.value = digit.value.saturating_sub(steps); if digit.value <= DIGIT_MIN_BRIGHTNESS { digit.value = DIGIT_MIN_BRIGHTNESS; digit.current_state = DigitState::Idle; } digit.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 } } pub fn rtc_tick(i2c: &mut T) where T: _embedded_hal_blocking_i2c_WriteRead + _embedded_hal_blocking_i2c_Read + _embedded_hal_blocking_i2c_Write, { static mut STARTUP: bool = true; static mut PREV_MINUTE: u32 = 0; static mut PREV_HOUR: u32 = 0; let (second, minute, hour) = ds3231::get_time(DS3231_ADDR, i2c); let (weekday, day, month, _, _) = ds3231::get_date(DS3231_ADDR, i2c); let hour = if ds3231::in_dst(weekday, day, month, hour) { (hour + 1) % 12 } else { hour % 12 }; let hour = if hour == 0 { 12 } else { hour }; unsafe { if STARTUP || PREV_HOUR / 10 != hour / 10 { fade_in_out_digit( 0, if hour / 10 != 0 { Some((hour / 10) as usize) } else { None }, DIGIT_FADE_DURATION_US, false, ) } if STARTUP || PREV_HOUR % 10 != hour % 10 { fade_in_out_digit(1, Some((hour % 10) as usize), DIGIT_FADE_DURATION_US, false); } if STARTUP || PREV_MINUTE / 10 != minute / 10 { fade_in_out_digit( 2, Some((minute / 10) as usize), DIGIT_FADE_DURATION_US, false, ); } if STARTUP || PREV_MINUTE % 10 != minute % 10 { fade_in_out_digit( 3, Some((minute % 10) as usize), DIGIT_FADE_DURATION_US, false, ); } CLOCK.dot.current_state = match second % 2 { 0 => DigitState::Incrementing, 1 => DigitState::Decrementing, _ => DigitState::Idle, }; PREV_MINUTE = minute; PREV_HOUR = hour; STARTUP = false; } } pub fn refresh_tick(i2c: &mut T) where T: _embedded_hal_blocking_i2c_WriteRead + _embedded_hal_blocking_i2c_Read + _embedded_hal_blocking_i2c_Write, { compute_pwm_offset(); unsafe { for (t, tube) in CLOCK.tubes.iter().enumerate() { for (d, digit) in tube.digits.iter().filter(|d| d.updated).enumerate() { pca9685::set_digit( TUBE_MAPPING.driver[t].digit[d].address, i2c, TUBE_MAPPING.driver[t].digit[d].pin, digit.pwm_start, digit.pwm_end, ); } } if CLOCK.dot.updated { pca9685::set_digit( TUBE_MAPPING.dot_address, i2c, TUBE_MAPPING.dot_pin, CLOCK.dot.pwm_start, CLOCK.dot.pwm_end, ); } } } pub fn rng_tick(i2c: &mut T) where T: _embedded_hal_blocking_i2c_WriteRead + _embedded_hal_blocking_i2c_Read + _embedded_hal_blocking_i2c_Write, { } // In the event that there are multiple PWM outputs at less than 100% duty cycle, // stagger the start time of each PWM to reduce the switch on surge current. fn compute_pwm_offset() { let mut active_digits: u32 = 0; let mut total_on_time: u32 = 0; let mut last_pwm_end: u32 = 0; // Determine the number of active outputs as well as the total on-time across all outputs. // Ignore outputs that are off (min) or fully on (max) as they have no surge impact. unsafe { CLOCK.tubes.iter().for_each(|tube| { tube.digits.iter().for_each(|digit| { if digit.value != DIGIT_MAX_BRIGHTNESS && digit.value != DIGIT_MIN_BRIGHTNESS { active_digits = active_digits + 1; total_on_time = total_on_time + digit.value; } }); }); if CLOCK.dot.value != DIGIT_MAX_BRIGHTNESS && CLOCK.dot.value != DIGIT_MIN_BRIGHTNESS { active_digits = active_digits + 1; total_on_time = total_on_time + CLOCK.dot.value; } // If the total on-time across all outputs is less than one PWM period, stagger each // output such that the rise of one pulse begins at the end of the previous pulse. if total_on_time <= DIGIT_MAX_BRIGHTNESS { CLOCK.tubes.iter_mut().for_each(|tube| { tube.digits.iter_mut().for_each(|digit| { if digit.value == DIGIT_MIN_BRIGHTNESS { digit.pwm_start = 0; digit.pwm_end = 0; } else if digit.value == DIGIT_MAX_BRIGHTNESS { digit.pwm_start = 0; digit.pwm_end = DIGIT_MAX_BRIGHTNESS; } else { digit.pwm_start = last_pwm_end; digit.pwm_end = last_pwm_end + digit.value; last_pwm_end = digit.pwm_end; digit.updated = true; } }); }); if CLOCK.dot.value == DIGIT_MIN_BRIGHTNESS { CLOCK.dot.pwm_start = 0; CLOCK.dot.pwm_end = 0; } else if CLOCK.dot.value == DIGIT_MAX_BRIGHTNESS { CLOCK.dot.pwm_start = 0; CLOCK.dot.pwm_end = DIGIT_MAX_BRIGHTNESS; } else { CLOCK.dot.pwm_start = last_pwm_end; CLOCK.dot.pwm_end = last_pwm_end + CLOCK.dot.value; CLOCK.dot.updated = true; } } else { // Compute the amount of overlap between all outputs // int overlap = (totalOnTime - PCA9685_Max_Brightness) / (validOutputs - 1); let overlap = (total_on_time - DIGIT_MAX_BRIGHTNESS) / (active_digits - 1); // Compute the staggered output period for each output CLOCK.tubes.iter_mut().for_each(|tube| { tube.digits.iter_mut().for_each(|digit| { if digit.value == DIGIT_MIN_BRIGHTNESS { digit.pwm_start = 0; digit.pwm_end = 0; } else if digit.value == DIGIT_MAX_BRIGHTNESS { digit.pwm_start = 0; digit.pwm_end = DIGIT_MAX_BRIGHTNESS; } else { digit.pwm_start = last_pwm_end.saturating_sub(overlap); digit.pwm_end = digit.pwm_start + digit.value; last_pwm_end = digit.pwm_end; digit.updated = true; } }); }); if CLOCK.dot.value == DIGIT_MIN_BRIGHTNESS { CLOCK.dot.pwm_start = 0; CLOCK.dot.pwm_end = 0; } else if CLOCK.dot.value == DIGIT_MAX_BRIGHTNESS { CLOCK.dot.pwm_start = 0; CLOCK.dot.pwm_end = DIGIT_MAX_BRIGHTNESS; } else { CLOCK.dot.pwm_start = last_pwm_end.saturating_sub(overlap); CLOCK.dot.pwm_end = CLOCK.dot.pwm_start + CLOCK.dot.value; CLOCK.dot.updated = true; } } } }