Browse Source

Implement PWM offset computation and refresh tick

Kevin Lee 2 years ago
parent
commit
bceed9aa49
2 changed files with 142 additions and 37 deletions
  1. 140 35
      Nixie_Firmware_Rust/src/nixie.rs
  2. 2 2
      Nixie_Firmware_Rust/src/pca9685.rs

+ 140 - 35
Nixie_Firmware_Rust/src/nixie.rs

@@ -1,9 +1,11 @@
-use stm32l4xx_hal::prelude::{
-    _embedded_hal_blocking_i2c_Read, _embedded_hal_blocking_i2c_Write,
-    _embedded_hal_blocking_i2c_WriteRead,
+use stm32l4xx_hal::{
+    prelude::{
+        _embedded_hal_blocking_i2c_Read, _embedded_hal_blocking_i2c_Write,
+        _embedded_hal_blocking_i2c_WriteRead,
+    },
 };
 
-use crate::ds3231;
+use crate::{ds3231, pca9685};
 
 pub const DS3231_ADDR: u8 = 0x68;
 pub const TUSB322_ADDR: u8 = 0x47;
@@ -305,19 +307,6 @@ impl Clock {
 
 static mut CLOCK: Clock = Clock::default();
 
-// pub fn set_digit(tube: usize, digit: usize, pwm_start: u32, pwm_end: u32) {
-//     assert!(pwm_end >= pwm_start);
-//     assert!(tube < NUM_TUBES);
-//     assert!(digit < NUM_DIGITS);
-//     unsafe {
-//         CLOCK.tubes[tube].digits[digit].current_state = DigitState::Incrementing;
-//         CLOCK.tubes[tube].digits[digit].pwm_start = pwm_start;
-//         CLOCK.tubes[tube].digits[digit].pwm_end = pwm_end;
-//         CLOCK.tubes[tube].digits[digit].value = pwm_end - pwm_start;
-//         CLOCK.tubes[tube].digits[digit].updated = true;
-//     }
-// }
-
 pub fn fade_in_out_digit(tube: usize, digit: Option<usize>, fade_duration: u32, refresh_cmd: bool) {
     // assert!(tube < NUM_TUBES);
     // assert!(Some(digit) < NUM_DIGITS);
@@ -354,33 +343,33 @@ pub fn refresh_frame() {
     let mut pending_refresh: bool = false;
     unsafe {
         let ticks = CLOCK.fade_duration / REFRESH_RATE_US;
-        for tube in 0..NUM_TUBES {
-            let steps = ((DIGIT_MAX_BRIGHTNESS - DIGIT_MIN_BRIGHTNESS) + ticks - 1) / ticks;
-            for digit in 0..NUM_DIGITS {
-                let mut d = &mut CLOCK.tubes[tube].digits[digit];
-                match d.current_state {
+        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 => {
-                        d.value = d.value.saturating_add(steps);
-                        if d.value >= DIGIT_MAX_BRIGHTNESS {
-                            d.value = DIGIT_MAX_BRIGHTNESS;
-                            d.current_state = DigitState::Idle;
+                        digit.value = digit.value.saturating_add(steps);
+                        if digit.value >= DIGIT_MAX_BRIGHTNESS {
+                            digit.value = DIGIT_MAX_BRIGHTNESS;
+                            digit.current_state = DigitState::Idle;
                         }
-                        d.updated = true;
+                        digit.updated = true;
                         pending_refresh = true;
                     }
                     DigitState::Decrementing => {
-                        d.value = d.value.saturating_sub(steps);
-                        if d.value <= DIGIT_MIN_BRIGHTNESS {
-                            d.value = DIGIT_MIN_BRIGHTNESS;
-                            d.current_state = DigitState::Idle;
+                        digit.value = digit.value.saturating_sub(steps);
+                        if digit.value <= DIGIT_MIN_BRIGHTNESS {
+                            digit.value = DIGIT_MIN_BRIGHTNESS;
+                            digit.current_state = DigitState::Idle;
                         }
-                        d.updated = true;
+                        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 {
@@ -483,6 +472,30 @@ where
         + _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<T>(i2c: &mut T)
@@ -492,3 +505,95 @@ where
         + _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;
+            }
+        }
+    }
+}

+ 2 - 2
Nixie_Firmware_Rust/src/pca9685.rs

@@ -120,7 +120,7 @@ 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,
-    digit: usize,
+    pin: usize,
     pwm_start: u32,
     pwm_end: u32,
 ) {
@@ -136,7 +136,7 @@ pub fn set_digit(
     }
 
     let buffer: [u8; 5] = [
-        (offset_of!(Pca9685Registers => led).get_byte_offset() + size_of::<u32>() * digit) as u8,
+        (offset_of!(Pca9685Registers => led).get_byte_offset() + size_of::<u32>() * pin) as u8,
         led_reg.get().to_le_bytes()[0],
         led_reg.get().to_le_bytes()[1],
         led_reg.get().to_le_bytes()[2],