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 DISPLAY_REFRESH_FPS: u32 = 500; pub const DIGIT_FADE_DURATION_MS: u32 = 1000; pub const CYCLE_FADE_DURATION_MS: u32 = 200; pub const CYCLE_ITERATIONS: usize = 20; pub const CYCLE_REFRESH_INTERVAL: u32 = 60; pub const CYCLE_REFRESH_VARIANCE: u32 = 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, fade_duration: Option, updated: bool, } impl Digit { const fn default() -> Self { Self { state: State::Idle, value: 0, pwm_start: 0, pwm_end: 0, fade_duration: None, updated: false, } } } struct CycleSettings { last_digit: Option, next_digit: u32, iteration: usize, last_fade_duration: u32, } struct Tube { digits: [Digit; NUM_DIGITS], last_digit: Option, cycle: Option, } impl Tube { const fn default() -> Self { const DIGIT_INIT: Digit = Digit::default(); Self { digits: [DIGIT_INIT; 10], last_digit: None, cycle: None, } } fn fade_in_out_digit( &mut self, digit: Option, fade_duration: u32, cycle_cmd: bool, ) { // If the tube is in the middle of a cycle sequence and a call comes // in to update the tube digit (for time), override the last value of // the cycle sequence with the new digit. if let Some(ref mut cycle) = self.cycle { if !cycle_cmd { cycle.last_digit = digit; cycle.last_fade_duration = fade_duration; } } // Dont update if actively cycling tube unless cycle_cmd is set if (self.cycle.is_none() && !cycle_cmd) || cycle_cmd { // Fade out all digits for digit in 0..NUM_DIGITS { if self.digits[digit].value != DIGIT_MIN_BRIGHTNESS { self.digits[digit].state = State::Decrementing; self.digits[digit].fade_duration = Some(fade_duration); } } // Fade in the specified digit if let Some(digit) = digit { if self.digits[digit as usize].value != DIGIT_MAX_BRIGHTNESS { self.digits[digit as usize].state = State::Incrementing; self.digits[digit as usize].fade_duration = Some(fade_duration); } } self.last_digit = digit; } } } static CLOCK: Mutex> = Mutex::new(RefCell::new(Clock::default())); struct Clock { tubes: [Tube; NUM_TUBES], dot: Digit, 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(), 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.tubes[0].fade_in_out_digit(None, DIGIT_FADE_DURATION_MS, false); } } _ => { self.tubes[0].fade_in_out_digit(Some(hour / 10), DIGIT_FADE_DURATION_MS, false); } } match self.hour { Some(prev_hour) if prev_hour % 10 == hour % 10 => {} _ => { self.tubes[1].fade_in_out_digit(Some(hour % 10), DIGIT_FADE_DURATION_MS, false); } } match self.minute { Some(prev_minute) if prev_minute / 10 == minute / 10 => {} _ => { self.tubes[2].fade_in_out_digit(Some(minute / 10), DIGIT_FADE_DURATION_MS, false); } } match self.minute { Some(prev_minute) if prev_minute % 10 == minute % 10 => {} _ => { self.tubes[3].fade_in_out_digit(Some(minute % 10), DIGIT_FADE_DURATION_MS, 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, }; self.dot.fade_duration = Some(DIGIT_FADE_DURATION_MS); #[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::FPS_TIMER.borrow(cs).borrow_mut(); if let Some(ref mut timer) = timer_ref.deref_mut() { timer.listen(Event::TimeOut); } }); } pub fn fps_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 => { digit.fade_duration = None; }, }; }; #[cfg(not(test))] self.tubes.iter_mut().for_each(|tube| { tube.digits.iter_mut().for_each(|digit| { if let Some(fade_duration) = digit.fade_duration { let ticks = fade_duration * 1000 / (1000 / DISPLAY_REFRESH_FPS * 1000); let steps = ((DIGIT_MAX_BRIGHTNESS - DIGIT_MIN_BRIGHTNESS) + ticks - 1) / ticks; 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() { if let Some(fade_duration) = digit.fade_duration { let ticks = fade_duration * 1000 / (1000 / DISPLAY_REFRESH_FPS * 1000); let steps = ((DIGIT_MAX_BRIGHTNESS - DIGIT_MIN_BRIGHTNESS) + ticks - 1) / ticks; 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 if let Some(fade_duration) = self.dot.fade_duration { let ticks = fade_duration * 1000 / (1000 / DISPLAY_REFRESH_FPS * 1000); 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 cycle_tick(&mut self) -> bool { let mut cycle_ended = true; self.tubes.iter_mut().for_each(|tube| { if let Some(cycle) = tube.cycle.as_mut() { #[cfg(test)] println!("Cycle tick: iteration {}", cycle.iteration); if cycle.iteration > 0 { let next_digit = cycle.next_digit; cycle.next_digit = if cycle.next_digit == 9 { 0 } else { cycle.next_digit + 1 }; cycle.iteration = cycle.iteration - 1; tube.fade_in_out_digit(Some(next_digit), CYCLE_FADE_DURATION_MS, true); cycle_ended = false; } else { let last_digit = cycle.last_digit; let last_fade = cycle.last_fade_duration; tube.cycle = None; tube.fade_in_out_digit(last_digit, last_fade, false); } } }); #[cfg(not(test))] free(|cs| { let mut timer_ref = super::FPS_TIMER.borrow(cs).borrow_mut(); if let Some(ref mut timer) = timer_ref.deref_mut() { timer.listen(Event::TimeOut); } }); cycle_ended } 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_mut().enumerate() { for (d, digit) in tube.digits.iter_mut().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, ); digit.updated = false; } } } if self.dot.updated { pca9685::set_digit( i2c, TUBE_MAPPING.dot_address, TUBE_MAPPING.dot_pin, self.dot.pwm_start, self.dot.pwm_end, ); self.dot.updated = false; } } // 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); }); } // This function is called by an interrupt that is triggered every // DISPLAY_REFRESH_FPS to update the display with a new brightness value. pub fn fps_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 updated = clock.fps_tick(); if updated { clock.write_i2c(i2c); free(|cs| { let mut timer_ref = super::FPS_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::FPS_TIMER.borrow(cs).borrow_mut(); if let Some(ref mut timer) = timer_ref.deref_mut() { timer.unlisten(Event::TimeOut); } }) } }); } // This function is called by an interrupt that is triggered every // DIGIT_CYCLE_FADE_DURATION_HZ to update the digit being refreshed. pub fn cycle_interrupt() { free(|cs| { let mut clock_ref = CLOCK.borrow(cs).borrow_mut(); let clock = clock_ref.deref_mut(); let cycle_ended = clock.cycle_tick(); free(|cs| { let mut cycle_timer_ref = super::CYCLE_TIMER.borrow(cs).borrow_mut(); if let Some(ref mut cycle_timer) = cycle_timer_ref.deref_mut() { if cycle_ended { cycle_timer.unlisten(Event::TimeOut); } else { cycle_timer.clear_interrupt(Event::TimeOut); } } }); }); } // This function is called to start cycling through all digits for a // tube to prevent damage to the nixie tube due to cathode poisoning. pub fn cycle_start(tube: usize) { free(|cs| { let mut clock_ref = CLOCK.borrow(cs).borrow_mut(); let clock = clock_ref.deref_mut(); clock.tubes[tube].cycle = Some(CycleSettings { last_digit: clock.tubes[tube].last_digit, next_digit: 0, iteration: CYCLE_ITERATIONS, last_fade_duration: DIGIT_FADE_DURATION_MS, }); }); // Start the timer to cycle through all digits free(|cs| { let mut cycle_timer_ref = super::CYCLE_TIMER.borrow(cs).borrow_mut(); if let Some(ref mut cycle_timer) = cycle_timer_ref.deref_mut() { cycle_timer.listen(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.fps_tick() { println!("Refresh halted"); break; } } } }