Browse Source

Fix PWM calculation and add unittests

Kevin Lee 2 years ago
parent
commit
a282ae7015
3 changed files with 343 additions and 294 deletions
  1. 13 2
      Nixie_Firmware_Rust/src/main.rs
  2. 324 288
      Nixie_Firmware_Rust/src/nixie.rs
  3. 6 4
      Nixie_Firmware_Rust/src/pca9685.rs

+ 13 - 2
Nixie_Firmware_Rust/src/main.rs

@@ -103,6 +103,17 @@ fn main() -> ! {
             .pa2
             .into_push_pull_output_with_state(&mut gpioa.moder, &mut gpioa.otyper, State::Low);
 
+    // Configure serial port
+    // let tx = gpiob.pb6.into_af7(&mut gpiob.moder, &mut gpiob.afrl);
+    // let rx = gpiob.pb7.into_af7(&mut gpiob.moder, &mut gpiob.afrl);
+    // let _serial = Serial::usart1(
+    //     dp.USART1,
+    //     (tx, rx),
+    //     Config::default().baudrate(115_200.bps()),
+    //     clocks,
+    //     &mut rcc.apb2,
+    // );
+
     // Configure fault LED output on PC15
     let fault_led = gpioc.pc15.into_push_pull_output_with_state(
         &mut gpioc.moder,
@@ -259,7 +270,7 @@ fn EXTI9_5() {
         if let Some(ref mut rtc_int) = rtc_int_ref.deref_mut() {
             if let Some(ref mut i2c) = i2c_int_ref.deref_mut() {
                 if rtc_int.check_interrupt() {
-                    nixie::rtc_tick(i2c);
+                    nixie::rtc_interrupt(i2c);
                     rtc_int.clear_interrupt_pending_bit();
                 }
             }
@@ -285,7 +296,7 @@ fn TIM2() {
     free(|cs| {
         let mut i2c_int_ref = I2C.borrow(cs).borrow_mut();
         if let Some(ref mut i2c) = i2c_int_ref.deref_mut() {
-            nixie::refresh_tick(i2c);
+            nixie::refresh_interrupt(i2c);
         }
     });
 }

+ 324 - 288
Nixie_Firmware_Rust/src/nixie.rs

@@ -1,6 +1,6 @@
-use core::ops::DerefMut;
+use core::{cell::RefCell, ops::DerefMut};
 
-use cortex_m::interrupt::free;
+use cortex_m::interrupt::{free, Mutex};
 use stm32l4xx_hal::{
     prelude::{
         _embedded_hal_blocking_i2c_Read, _embedded_hal_blocking_i2c_Write,
@@ -246,7 +246,7 @@ static TUBE_MAPPING: PwmOutputMap = {
     }
 };
 
-#[derive(PartialEq)]
+#[derive(Debug, PartialEq)]
 enum State {
     Idle,
     Incrementing,
@@ -292,10 +292,14 @@ impl Tube {
     }
 }
 
+static CLOCK: Mutex<RefCell<Clock>> = Mutex::new(RefCell::new(Clock::default()));
+
 struct Clock {
     tubes: [Tube; NUM_TUBES],
     dot: Digit,
     fade_duration: u32,
+    minute: Option<u32>,
+    hour: Option<u32>,
 }
 
 impl Clock {
@@ -305,112 +309,86 @@ impl Clock {
             tubes: [TUBE_INIT; NUM_TUBES],
             dot: Digit::default(),
             fade_duration: 0,
+            minute: None,
+            hour: None,
         }
     }
-}
-
-static mut CLOCK: Clock = Clock::default();
-
-pub fn fade_in_out_digit(tube: usize, digit: Option<usize>, fade_duration: u32, refresh_cmd: bool) {
-    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 {
-                if CLOCK.tubes[tube].digits[digit].value != DIGIT_MIN_BRIGHTNESS {
-                    CLOCK.tubes[tube].digits[digit].state = State::Decrementing;
+    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);
                 }
             }
-
-            // Fade in the specified digit
-            if let Some(digit) = digit {
-                if CLOCK.tubes[tube].digits[digit].value != DIGIT_MAX_BRIGHTNESS {
-                    CLOCK.tubes[tube].digits[digit].state = State::Incrementing;
-                }
+            _ => {
+                self.fade_in_out_digit(
+                    0,
+                    Some((hour / 10) as usize),
+                    DIGIT_FADE_DURATION_US,
+                    false,
+                );
             }
-
-            CLOCK.tubes[tube].last_active_digit = digit;
-            CLOCK.fade_duration = fade_duration;
         }
-    }
-}
 
-pub fn rtc_tick<T>(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
-            );
+        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,
+                );
+            }
         }
-        if STARTUP || PREV_MINUTE / 10 != minute / 10 {
-            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(
+                    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,
-            );
+
+        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,
+                );
+            }
         }
 
-        CLOCK.dot.state = match second % 2 {
+        #[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,
         };
 
-        PREV_MINUTE = minute;
-        PREV_HOUR = hour;
+        #[cfg(test)]
+        println!("RTC tick: dot state is {:?}", self.dot.state);
 
-        STARTUP = false;
+        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() {
@@ -418,236 +396,294 @@ where
             }
         })
     }
-}
 
-pub fn refresh_tick<T>(i2c: &mut T)
-where
-    T: _embedded_hal_blocking_i2c_WriteRead
-        + _embedded_hal_blocking_i2c_Read
-        + _embedded_hal_blocking_i2c_Write,
-{
-    let mut pending_refresh: bool = false;
-    unsafe {
-        let ticks = CLOCK.fade_duration / REFRESH_RATE_HZ;
+    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;
 
-        CLOCK.tubes.iter_mut().for_each(|tube| {
+        #[cfg(not(test))]
+        self.tubes.iter_mut().for_each(|tube| {
             tube.digits.iter_mut().for_each(|digit| {
-                match digit.state {
-                    State::Incrementing => {
-                        if digit.value >= DIGIT_MAX_BRIGHTNESS {
-                            digit.value = DIGIT_MAX_BRIGHTNESS;
-                            digit.state = State::Idle;
-                        } else {
-                            digit.value = digit
-                                .value
-                                .saturating_add(steps)
-                                .clamp(DIGIT_MIN_BRIGHTNESS, DIGIT_MAX_BRIGHTNESS);
-                            digit.updated = true;
-                            pending_refresh = true;
-                        }
-                    }
-                    State::Decrementing => {
-                        if digit.value <= DIGIT_MIN_BRIGHTNESS {
-                            digit.value = DIGIT_MIN_BRIGHTNESS;
-                            digit.state = State::Idle;
-                        } else {
-                            digit.value = digit
-                                .value
-                                .saturating_sub(steps)
-                                .clamp(DIGIT_MIN_BRIGHTNESS, DIGIT_MAX_BRIGHTNESS);
-                            digit.updated = true;
-                            pending_refresh = true;
-                        }
-                    }
-                    State::Idle => (),
-                };
+                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;
-        match CLOCK.dot.state {
-            State::Incrementing => {
-                CLOCK.dot.value = CLOCK
-                    .dot
-                    .value
-                    .saturating_add(steps)
-                    .clamp(DOT_MIN_BRIGHTNESS, DOT_MAX_BRIGHTNESS);
-                if CLOCK.dot.value >= DOT_MAX_BRIGHTNESS {
-                    CLOCK.dot.value = DOT_MAX_BRIGHTNESS;
-                    CLOCK.dot.state = State::Idle;
+        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<T>(&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<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().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,
+                    );
                 }
-                CLOCK.dot.updated = true;
-                pending_refresh = true;
             }
-            State::Decrementing => {
-                CLOCK.dot.value = CLOCK
-                    .dot
-                    .value
-                    .saturating_sub(steps)
-                    .clamp(DOT_MIN_BRIGHTNESS, DOT_MAX_BRIGHTNESS);
-                if CLOCK.dot.value <= DOT_MIN_BRIGHTNESS {
-                    CLOCK.dot.value = DOT_MIN_BRIGHTNESS;
-                    CLOCK.dot.state = State::Idle;
+        }
+
+        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<usize>,
+        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;
                 }
-                CLOCK.dot.updated = true;
-                pending_refresh = true;
             }
-            State::Idle => (),
+
+            self.tubes[tube].last_active_digit = digit;
+            self.fade_duration = fade_duration;
         }
     }
 
-    if !pending_refresh {
-        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);
-            }
-        })
-    } else {
-        compute_pwm_offset();
-
-        unsafe {
-            for (t, tube) in CLOCK.tubes.iter().enumerate() {
-                for (d, digit) in tube.digits.iter().enumerate() {
-                    if digit.updated {
-                        pca9685::set_digit(
-                            TUBE_MAPPING.driver[t].digit[d].address,
-                            i2c,
-                            TUBE_MAPPING.driver[t].digit[d].pin,
-                            digit.pwm_start,
-                            digit.pwm_end,
-                        );
+    // 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;
             }
-            if CLOCK.dot.updated {
-                pca9685::set_digit(
-                    TUBE_MAPPING.dot_address,
-                    i2c,
-                    TUBE_MAPPING.dot_pin,
-                    CLOCK.dot.pwm_start,
-                    CLOCK.dot.pwm_end,
-                );
+        };
+
+        #[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
+                    );
+                }
             }
         }
 
-        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);
-            }
-        })
+        update_digit(&mut self.dot);
+
+        #[cfg(test)]
+        println!(
+            "Distribute PWM: dot start {} end {}",
+            self.dot.pwm_start, self.dot.pwm_end
+        );
     }
 }
 
-pub fn rng_tick<T>(_i2c: &mut T)
+pub fn rtc_interrupt<T>(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);
+    });
 }
 
-// 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;
+pub fn refresh_interrupt<T>(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);
+                }
+            })
+        }
+    });
+}
 
-    unsafe {
-        CLOCK.tubes.iter_mut().for_each(|tube| {
-            tube.digits.iter_mut().for_each(|digit| {
-                digit.pwm_start = 0;
-                digit.pwm_end = digit.value;
-            });
-        });
-        CLOCK.dot.pwm_start = 0;
-        CLOCK.dot.pwm_end = CLOCK.dot.value;
-    }
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::println;
+
+    #[test]
+    fn pwm_calc_test() {
+        let mut clock: Clock = Clock::default();
 
-    // 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
-    //         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;
-    //         }
-    //     }
-    // }
+        clock.rtc_tick(10, 23, 12);
+
+        for tick in 0..1005 {
+            println!("\nRefresh tick: {}", tick);
+            if !clock.refresh_tick() {
+                println!("Refresh halted");
+                break;
+            }
+        }
+    }
 }

+ 6 - 4
Nixie_Firmware_Rust/src/pca9685.rs

@@ -127,8 +127,8 @@ pub fn init(address: u8, i2c: &mut impl _embedded_hal_blocking_i2c_Write) {
 }
 
 pub fn set_digit(
-    address: u8,
     i2c: &mut impl _embedded_hal_blocking_i2c_Write,
+    address: u8,
     pin: usize,
     pwm_start: u32,
     pwm_end: u32,
@@ -138,10 +138,12 @@ pub fn set_digit(
     led_reg.modify(LED_CTRL::On.val(pwm_start));
     led_reg.modify(LED_CTRL::Off.val(pwm_end));
 
-    if pwm_end == 0 {
-        led_reg.modify(LED_CTRL::Off_Full::SET)
+    if pwm_end - pwm_start >= MAX_BRIGHTNESS {
+        led_reg.write(LED_CTRL::On_Full::SET)
+    } else if pwm_end == 0 {
+        led_reg.write(LED_CTRL::Off_Full::SET)
     } else if pwm_end >= MAX_BRIGHTNESS {
-        led_reg.modify(LED_CTRL::On_Full::SET)
+        led_reg.modify(LED_CTRL::Off.val(0xFFF));
     }
 
     let buffer: [u8; 5] = [