use core::{cell::RefCell, ops::DerefMut}; use cortex_m::interrupt::{free, Mutex}; use stm32l4xx_hal::{ prelude::{ _embedded_hal_blocking_i2c_Read, _embedded_hal_blocking_i2c_Write, _embedded_hal_blocking_i2c_WriteRead, }, timer::Event, }; 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 pub const REFRESH_RATE_HZ: u32 = 1000; const DIGIT_FADE_DURATION_US: u32 = 1_000_000; 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; const MAP_DOT_ADDR: u8 = PCA9685_ADDR_2; const MAP_DOT_PIN: u8 = 15; const MAP_ADDR: usize = 0; const MAP_PIN: usize = 1; 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(Debug, PartialEq)] enum State { Idle, Incrementing, Decrementing, } struct Digit { state: State, value: u32, pwm_start: u32, pwm_end: u32, updated: bool, } impl Digit { const fn default() -> Self { Self { state: State::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, } } } static CLOCK: Mutex> = Mutex::new(RefCell::new(Clock::default())); struct Clock { tubes: [Tube; NUM_TUBES], dot: Digit, fade_duration: u32, minute: Option, hour: Option, } impl Clock { const fn default() -> Self { const TUBE_INIT: Tube = Tube::default(); Self { tubes: [TUBE_INIT; NUM_TUBES], dot: Digit::default(), fade_duration: 0, minute: None, hour: None, } } pub fn rtc_tick(&mut self, second: u32, minute: u32, hour: u32) { match self.hour { Some(prev_hour) if prev_hour / 10 == hour / 10 => { if hour / 10 == 0 { self.fade_in_out_digit(0, None, DIGIT_FADE_DURATION_US, false); } } _ => { self.fade_in_out_digit( 0, Some((hour / 10) as usize), DIGIT_FADE_DURATION_US, false, ); } } match self.hour { Some(prev_hour) if prev_hour % 10 == hour % 10 => {} _ => { self.fade_in_out_digit( 1, Some((hour % 10) as usize), DIGIT_FADE_DURATION_US, false, ); } } match self.minute { Some(prev_minute) if prev_minute / 10 == minute / 10 => {} _ => { self.fade_in_out_digit( 2, Some((minute / 10) as usize), DIGIT_FADE_DURATION_US, false, ); } } match self.minute { Some(prev_minute) if prev_minute % 10 == minute % 10 => {} _ => { self.fade_in_out_digit( 3, Some((minute % 10) as usize), DIGIT_FADE_DURATION_US, false, ); } } #[cfg(test)] println!( "RTC tick: {}{}:{}{}", hour / 10, hour % 10, minute / 10, minute % 10 ); self.dot.state = match second % 2 { 0 => State::Incrementing, 1 => State::Decrementing, _ => State::Idle, }; #[cfg(test)] println!("RTC tick: dot state is {:?}", self.dot.state); self.hour = Some(hour); self.minute = Some(minute); #[cfg(not(test))] free(|cs| { let mut timer_ref = super::REFRESH_TIMER.borrow(cs).borrow_mut(); if let Some(ref mut timer) = timer_ref.deref_mut() { timer.listen(Event::TimeOut); } }) } pub fn refresh_tick(&mut self) -> bool { let mut pending_refresh: bool = false; let mut update_fn = |digit: &mut Digit, min: u32, max: u32, steps: u32| { match digit.state { State::Incrementing => { if digit.value >= max { digit.value = max; digit.state = State::Idle; } else { digit.value = digit.value.saturating_add(steps).clamp(min, max); digit.updated = true; pending_refresh = true; } } State::Decrementing => { if digit.value <= min { digit.value = min; digit.state = State::Idle; } else { digit.value = digit.value.saturating_sub(steps).clamp(min, max); digit.updated = true; pending_refresh = true; } } State::Idle => (), }; }; let ticks = self.fade_duration / REFRESH_RATE_HZ; let steps = ((DIGIT_MAX_BRIGHTNESS - DIGIT_MIN_BRIGHTNESS) + ticks - 1) / ticks; #[cfg(not(test))] self.tubes.iter_mut().for_each(|tube| { tube.digits.iter_mut().for_each(|digit| { update_fn(digit, DIGIT_MIN_BRIGHTNESS, DIGIT_MAX_BRIGHTNESS, steps); }); }); #[cfg(test)] for (t, tube) in self.tubes.iter_mut().enumerate() { for (d, digit) in tube.digits.iter_mut().enumerate() { update_fn(digit, DIGIT_MIN_BRIGHTNESS, DIGIT_MAX_BRIGHTNESS, steps); if digit.updated { println!( "Refresh tick: updated tube {} digit {} to value {}", t, d, digit.value ); } } } // Handle dot let steps = ((DOT_MAX_BRIGHTNESS - DOT_MIN_BRIGHTNESS) + ticks - 1) / ticks; update_fn(&mut self.dot, DOT_MIN_BRIGHTNESS, DOT_MAX_BRIGHTNESS, steps); #[cfg(test)] if self.dot.updated { println!("Refresh tick: updated dot to value {}", self.dot.value); } if pending_refresh { self.distribute_pwm(); } pending_refresh } pub fn rng_tick(&mut self, _i2c: &mut T) where T: _embedded_hal_blocking_i2c_WriteRead + _embedded_hal_blocking_i2c_Read + _embedded_hal_blocking_i2c_Write, { } pub fn write_i2c(&mut self, i2c: &mut T) where T: _embedded_hal_blocking_i2c_WriteRead + _embedded_hal_blocking_i2c_Read + _embedded_hal_blocking_i2c_Write, { for (t, tube) in self.tubes.iter().enumerate() { for (d, digit) in tube.digits.iter().enumerate() { if digit.updated { pca9685::set_digit( i2c, TUBE_MAPPING.driver[t].digit[d].address, TUBE_MAPPING.driver[t].digit[d].pin, digit.pwm_start, digit.pwm_end, ); } } } if self.dot.updated { pca9685::set_digit( i2c, TUBE_MAPPING.dot_address, TUBE_MAPPING.dot_pin, self.dot.pwm_start, self.dot.pwm_end, ); } } fn fade_in_out_digit( &mut self, tube: usize, digit: Option, fade_duration: u32, refresh_cmd: bool, ) { // 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 self.tubes[tube].refresh_active && !refresh_cmd { self.tubes[tube].refresh_last_digit = digit; } // Dont update if actively refreshing tube unless RngUpdate is set if (!self.tubes[tube].refresh_active && !refresh_cmd) || refresh_cmd { // Fade out all digits for digit in 0..NUM_DIGITS { if self.tubes[tube].digits[digit].value != DIGIT_MIN_BRIGHTNESS { self.tubes[tube].digits[digit].state = State::Decrementing; } } // Fade in the specified digit if let Some(digit) = digit { if self.tubes[tube].digits[digit].value != DIGIT_MAX_BRIGHTNESS { self.tubes[tube].digits[digit].state = State::Incrementing; } } self.tubes[tube].last_active_digit = digit; self.fade_duration = fade_duration; } } // 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. If the // duty cycle is greater than 100%, distribute the PWM outputs as much as possible // to keep the current consumption at a minimum. fn distribute_pwm(&mut self) { let mut last_pwm: u32 = 0; let mut incrementing: bool = true; // Closure to avoid duplicate code let mut update_digit = |digit: &mut 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 { if incrementing { if last_pwm + digit.value > DIGIT_MAX_BRIGHTNESS { digit.pwm_start = DIGIT_MAX_BRIGHTNESS - digit.value; digit.pwm_end = DIGIT_MAX_BRIGHTNESS; last_pwm = digit.pwm_start; incrementing = false; } else { digit.pwm_start = last_pwm; digit.pwm_end = digit.pwm_start + digit.value; last_pwm = digit.pwm_end; } } else { if last_pwm - DIGIT_MIN_BRIGHTNESS < digit.value { digit.pwm_start = DIGIT_MIN_BRIGHTNESS; digit.pwm_end = digit.pwm_start + digit.value; last_pwm = digit.pwm_end; incrementing = true; } else { digit.pwm_end = last_pwm; digit.pwm_start = digit.pwm_end - digit.value; last_pwm = digit.pwm_start; } } digit.updated = true; } }; #[cfg(not(test))] self.tubes.iter_mut().for_each(|tube| { tube.digits.iter_mut().for_each(|digit| { update_digit(digit); }); }); #[cfg(test)] for (t, tube) in self.tubes.iter_mut().enumerate() { for (d, digit) in tube.digits.iter_mut().enumerate() { update_digit(digit); if digit.updated { println!( "Distribute PWM: tube {} digit {} start {} end {}", t, d, digit.pwm_start, digit.pwm_end ); } } } update_digit(&mut self.dot); #[cfg(test)] println!( "Distribute PWM: dot start {} end {}", self.dot.pwm_start, self.dot.pwm_end ); } } pub fn rtc_interrupt(i2c: &mut T) where T: _embedded_hal_blocking_i2c_WriteRead + _embedded_hal_blocking_i2c_Read + _embedded_hal_blocking_i2c_Write, { 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 }; free(|cs| { let mut clock_ref = CLOCK.borrow(cs).borrow_mut(); let clock = clock_ref.deref_mut(); clock.rtc_tick(second, minute, hour); }); } pub fn refresh_interrupt(i2c: &mut T) where T: _embedded_hal_blocking_i2c_WriteRead + _embedded_hal_blocking_i2c_Read + _embedded_hal_blocking_i2c_Write, { free(|cs| { let mut clock_ref = CLOCK.borrow(cs).borrow_mut(); let clock = clock_ref.deref_mut(); let refresh = clock.refresh_tick(); if refresh { clock.write_i2c(i2c); free(|cs| { let mut timer_ref = super::REFRESH_TIMER.borrow(cs).borrow_mut(); if let Some(ref mut timer) = timer_ref.deref_mut() { timer.clear_interrupt(Event::TimeOut); } }) } else { free(|cs| { let mut timer_ref = super::REFRESH_TIMER.borrow(cs).borrow_mut(); if let Some(ref mut timer) = timer_ref.deref_mut() { timer.unlisten(Event::TimeOut); } }) } }); } #[cfg(test)] mod test { use super::*; use std::println; #[test] fn pwm_calc_test() { let mut clock: Clock = Clock::default(); clock.rtc_tick(10, 23, 12); for tick in 0..1005 { println!("\nRefresh tick: {}", tick); if !clock.refresh_tick() { println!("Refresh halted"); break; } } } }