Browse Source

Add support for staggered PWM output

Kevin Lee 3 years ago
parent
commit
8816d970d9
3 changed files with 130 additions and 4 deletions
  1. 96 4
      Nixie_Firmware_Mbed/main.cpp
  2. 32 0
      Nixie_Firmware_Mbed/pca9685.cpp
  3. 2 0
      Nixie_Firmware_Mbed/pca9685.h

+ 96 - 4
Nixie_Firmware_Mbed/main.cpp

@@ -21,6 +21,8 @@ typedef enum {
 typedef struct {
     DigitState CurrentState;
     int Value;
+    int PwmStart;
+    int PwmEnd;
     bool Updated;
 } Digit;
 
@@ -83,6 +85,79 @@ TUBE_CALLBACK(2)
 Timeout Tube3UpdateTimeout;
 TUBE_CALLBACK(3)
 
+// 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.
+void StaggerPwmOutput() {
+    int validOutputs = 0;
+    int totalOnTime = 0;
+    int lastPwmEnd = 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.
+    for (int i = 0; i < NUM_TUBES; i++) {
+        for (int j = 0; j < NUM_DIGITS; j++) {
+            if (Tubes[i].Digits[j].Value != PCA9685_Min_Brightness && 
+                Tubes[i].Digits[j].Value != PCA9685_Max_Brightness) {
+
+                validOutputs++;
+                totalOnTime += Tubes[i].Digits[j].Value;
+            }
+        }
+    }
+    if (Dot.Value != PCA9685_Min_Brightness && Dot.Value != PCA9685_Max_Brightness) {
+        validOutputs++;
+        totalOnTime += 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 (totalOnTime <= PCA9685_Max_Brightness) {
+
+        for (int i = 0; i < NUM_TUBES; i++) {
+            for (int j = 0; j < NUM_DIGITS; j++) {
+                if (Tubes[i].Digits[j].Value != PCA9685_Min_Brightness && 
+                    Tubes[i].Digits[j].Value != PCA9685_Max_Brightness) {
+
+                    Tubes[i].Digits[j].PwmStart = lastPwmEnd;
+                    Tubes[i].Digits[j].PwmEnd = lastPwmEnd + Tubes[i].Digits[j].Value;
+                    lastPwmEnd = Tubes[i].Digits[j].PwmEnd;
+                    Tubes[i].Digits[j].Updated = true;
+                }
+            }
+        }
+        if (Dot.Value != PCA9685_Min_Brightness && Dot.Value != PCA9685_Max_Brightness) {
+            Dot.PwmStart = lastPwmEnd;
+            Dot.PwmEnd = lastPwmEnd + Dot.Value;
+            lastPwmEnd = Dot.PwmEnd;
+            Dot.Updated = true;
+        }
+    }
+    else {
+        // Compute the amount of overlap between all outputs
+        int overlap = totalOnTime / (validOutputs - 1);
+
+        // Compute the staggered output period for each output
+        for (int i = 0; i < NUM_TUBES; i++) {
+            for (int j = 0; j < NUM_DIGITS; j++) {
+                if (Tubes[i].Digits[j].Value != PCA9685_Min_Brightness && 
+                    Tubes[i].Digits[j].Value != PCA9685_Max_Brightness) {
+
+                    Tubes[i].Digits[j].PwmStart = (lastPwmEnd == 0) ? 0 : lastPwmEnd - overlap;
+                    Tubes[i].Digits[j].PwmEnd = Tubes[i].Digits[j].PwmStart + Tubes[i].Digits[j].Value;
+                    lastPwmEnd = Tubes[i].Digits[j].PwmEnd;
+                    Tubes[i].Digits[j].Updated = true;
+                }
+            }
+        }
+        if (Dot.Value != PCA9685_Min_Brightness && Dot.Value != PCA9685_Max_Brightness) {
+            Dot.PwmStart = (lastPwmEnd == 0) ? 0 : lastPwmEnd - overlap;
+            Dot.PwmEnd = Dot.PwmStart + Dot.Value;
+            lastPwmEnd = Dot.PwmEnd;
+            Dot.Updated = true;
+        }
+    }
+}
+
 void FadeInOutDigit(int T, int D, int Duration, bool RngUpdate = false) {
 
     // If the tube is in the middle of a refresh sequence and a call comes 
@@ -203,6 +278,7 @@ int main() {
     // Start with HV PSU disabled
     HV_EnableOutput(false);
     
+    IO_Init();
     TUSB322_Init();
     PCA9685_Init();
     DS3231_Init(RtcInterruptCallback);
@@ -229,8 +305,13 @@ int main() {
 
     // wait(3);
         
-    // DS3231_SetTime(00, 05, 00);
-    // DS3231_SetDate(FRIDAY, 15, 3, 19, 0);
+    // Set RTC time through the following steps:
+    // 1. Build and flash firmware that sets time on boot
+    // 2. Reset board (non-DFU) at programmed time to set RTC time
+    // 3. Reset board in DFU mode
+    // 4. Build and flash firmware that does not set time on boot
+    // DS3231_SetTime(00, 13, 17);
+    // DS3231_SetDate(SUNDAY, 1, 11, 20, 0);
 
     // Setup a ticker to refresh the display at 1kHz
     RefreshTicker.attach_us(RefreshTickerCallback, REFRESH_RATE_US);
@@ -248,17 +329,28 @@ int main() {
         if (RefreshTick) {
             RefreshTick = false;
 
+            StaggerPwmOutput();
             for (int i = 0; i < NUM_TUBES; i++) {
                 for (int j = 0; j < NUM_DIGITS; j++) {
                     if (Tubes[i].Digits[j].Updated) {
-                        PCA9685_SetDigit(i, j, Tubes[i].Digits[j].Value);
+                        if (Tubes[i].Digits[j].Value == PCA9685_Min_Brightness ||
+                            Tubes[i].Digits[j].Value == PCA9685_Max_Brightness) {
+
+                            PCA9685_SetDigit(i, j, Tubes[i].Digits[j].Value);
+                        } else {
+                            PCA9685_SetDigitPwm(i, j, Tubes[i].Digits[j].PwmStart, Tubes[i].Digits[j].PwmEnd);
+                        }
                         Tubes[i].Digits[j].Updated = false;
                     }
                 }
             }
 
             if (Dot.Updated) {
-                PCA9685_SetDot(Dot.Value);
+                if (Dot.Value == PCA9685_Min_Brightness || Dot.Value == PCA9685_Max_Brightness) {
+                    PCA9685_SetDot(Dot.Value);
+                } else {
+                    PCA9685_SetDotPwm(Dot.PwmStart, Dot.PwmEnd);
+                }
                 Dot.Updated = false;
             }
         }

+ 32 - 0
Nixie_Firmware_Mbed/pca9685.cpp

@@ -68,6 +68,23 @@ void PCA9685_SetDigit(int Tube, int Digit, int Brightness) {
         reg.AS_BYTE, sizeof(LED_CTRL));
 }
 
+void PCA9685_SetDigitPwm(int Tube, int Digit, int StartPwm, int EndPwm) {
+    LED_CTRL reg = {0};
+
+    if (StartPwm == PCA9685_Min_Brightness && EndPwm == PCA9685_Max_Brightness) {
+        reg.ON_FULL = 1;
+    } else if (StartPwm == EndPwm) {
+        reg.OFF_FULL = 1;
+    } else {
+        reg.ON = StartPwm;
+        reg.OFF = EndPwm;
+    }
+
+    I2C_Write(Tube_Mapping[Tube][Digit][MAP_ADDR],
+        FIELD_OFFSET(PCA9685_REGS, LED0) + (Tube_Mapping[Tube][Digit][MAP_PIN] * sizeof(LED_CTRL)),
+        reg.AS_BYTE, sizeof(LED_CTRL));
+}
+
 void PCA9685_SetDot(int Brightness) {
     LED_CTRL reg = {0};
 
@@ -82,6 +99,21 @@ void PCA9685_SetDot(int Brightness) {
     I2C_Write(TUBE_DOT_ADDR, FIELD_OFFSET(PCA9685_REGS, TUBE_DOT_PIN), reg.AS_BYTE, sizeof(LED_CTRL));
 }
 
+void PCA9685_SetDotPwm(int StartPwm, int EndPwm) {
+    LED_CTRL reg = {0};
+
+    if (StartPwm == PCA9685_Min_Brightness && EndPwm == PCA9685_Max_Brightness) {
+        reg.ON_FULL = 1;
+    } else if (StartPwm == EndPwm) {
+        reg.OFF_FULL = 1;
+    } else {
+        reg.ON = StartPwm;
+        reg.OFF = EndPwm;
+    }
+
+    I2C_Write(TUBE_DOT_ADDR, FIELD_OFFSET(PCA9685_REGS, TUBE_DOT_PIN), reg.AS_BYTE, sizeof(LED_CTRL));
+}
+
 void PCA9685_EnableOutput(bool Enabled) {
     if (Enabled) {
         PCA9685_PWM_nEN.write(0);

+ 2 - 0
Nixie_Firmware_Mbed/pca9685.h

@@ -158,7 +158,9 @@ const char Tube_Mapping[4][10][2] =
 void PCA9685_Init(void);
 void PCA9685_SetVoltage(float Percent);
 void PCA9685_SetDigit(int Tube, int Digit, int Brightness);
+void PCA9685_SetDigitPwm(int Tube, int Digit, int StartPwm, int EndPwm);
 void PCA9685_SetDot(int Brightness);
+void PCA9685_SetDotPwm(int StartPwm, int EndPwm);
 void PCA9685_EnableOutput(bool Enabled);
 
 #endif