123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593 |
- use stm32l4xx_hal::{
- prelude::{
- _embedded_hal_blocking_i2c_Read, _embedded_hal_blocking_i2c_Write,
- _embedded_hal_blocking_i2c_WriteRead,
- },
- };
- use crate::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<u32>,
- 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<u32>,
- next_digit: u32,
- iteration: usize,
- last_fade_duration: u32,
- }
- struct Tube {
- digits: [Digit; NUM_DIGITS],
- last_digit: Option<u32>,
- cycle: Option<CycleSettings>,
- }
- 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<u32>, 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 {
- self.digits[digit].state = State::Decrementing;
- self.digits[digit].fade_duration = Some(fade_duration);
- }
- // Fade in the specified digit
- if let Some(digit) = digit {
- self.digits[digit as usize].state = State::Incrementing;
- self.digits[digit as usize].fade_duration = Some(fade_duration);
- }
- self.last_digit = digit;
- }
- }
- }
- pub struct Clock {
- tubes: [Tube; NUM_TUBES],
- dot: Digit,
- minute: Option<u32>,
- hour: Option<u32>,
- }
- impl Clock {
- pub const fn default() -> Self {
- const TUBE_INIT: Tube = Tube::default();
- Self {
- tubes: [TUBE_INIT; NUM_TUBES],
- dot: Digit::default(),
- minute: None,
- hour: None,
- }
- }
- // Sets a new time to be displayed
- pub fn rtc_tick(&mut self, second: u32, minute: u32, hour: u32) {
- // Update digit for each tube if value has changed
- 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
- );
- // Set fade direction for dot
- 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);
- // Store the last set value for the next update
- self.hour = Some(hour);
- self.minute = Some(minute);
- }
- // Updates the display with values due to fade in/out
- // Returns true if values have changed
- 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
- );
- }
- }
- }
- // Update dot values
- 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);
- }
- // Compute actual PWM values if display values have changed
- if pending_refresh {
- self.distribute_pwm();
- }
- pending_refresh
- }
- // Updates the digit displayed during a cycle sequence
- // Returns true if the cycle sequence has completed
- 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);
- }
- }
- });
- cycle_ended
- }
- // Start cycling sequence for the given tube to prevent long term damage from cathode poisoning
- pub fn cycle_start(&mut self, tube: usize) {
- self.tubes[tube].cycle = Some(CycleSettings {
- last_digit: self.tubes[tube].last_digit,
- next_digit: 0,
- iteration: CYCLE_ITERATIONS,
- last_fade_duration: DIGIT_FADE_DURATION_MS,
- });
- }
- // Writes updated PWM values to each PCA9685
- pub fn write_i2c<T>(&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
- );
- }
- }
- }
- // The dot is somewhat sensitive to changes to PWM signal, so for the sake
- // of consistency, keep the signal on time at the end of the PWM period.
- // Otherwise the distribution algorithm will sometimes place the on time at
- // the end of the PWM period in one cycle and at the start of the period for
- // the next, which results in visual flickers.
- // update_digit(&mut self.dot);
- self.dot.pwm_start = DIGIT_MAX_BRIGHTNESS - self.dot.value;
- self.dot.pwm_end = DIGIT_MAX_BRIGHTNESS;
- #[cfg(test)]
- println!(
- "Distribute PWM: dot start {} end {}",
- self.dot.pwm_start, self.dot.pwm_end
- );
- }
- }
- #[cfg(test)]
- mod test {
- use super::*;
- use std::println;
- #[test]
- fn pwm_calc_test() {
- let mut clock: Clock = Clock::default();
- // Initialize clock to arbitrary time
- clock.rtc_tick(10, 8, 12);
- // Iterate and print output values for each display refresh tick
- for tick in 0..1000 {
- println!("\nRefresh tick: {}", tick);
- if !clock.fps_tick() {
- println!("Refresh halted");
- break;
- }
- // Reset the updated field for each digit
- clock.tubes.iter_mut().for_each(|tube| {
- tube.digits.iter_mut().for_each(|digit| {
- digit.updated = false;
- });
- });
- clock.dot.updated = false;
- }
- }
- #[test]
- fn cycle_test() {
- let mut clock: Clock = Clock::default();
- // Initialize clock to arbitrary time
- clock.rtc_tick(00, 27, 8);
- // Simulate a cycle refresh sequence on tube 0
- clock.tubes[0].cycle = Some(CycleSettings {
- last_digit: clock.tubes[0].last_digit,
- next_digit: 0,
- iteration: CYCLE_ITERATIONS,
- last_fade_duration: DIGIT_FADE_DURATION_MS,
- });
- // Iterate and print debug values for each cycle
- for cycle in 0..1000 {
- println!("\nCycle tick: {}", cycle);
- if clock.cycle_tick() {
- println!("Cycle halted");
- break;
- }
- // Iterate and print output values for each display refresh tick
- for tick in 0..1000 {
- println!("\nRefresh tick: {}", tick);
- if !clock.fps_tick() {
- println!("Refresh halted");
- break;
- }
- // Reset the updated field for each digit
- clock.tubes.iter_mut().for_each(|tube| {
- tube.digits.iter_mut().for_each(|digit| {
- digit.updated = false;
- });
- });
- clock.dot.updated = false;
- }
- }
- }
- }
|