Mirror with some correction

Dependencies:   mbed FastIO FastPWM USBDevice

Revision:
29:582472d0bc57
Parent:
26:cb71c4af2912
Child:
30:6e9902f06f48
--- a/main.cpp	Wed Sep 23 05:38:27 2015 +0000
+++ b/main.cpp	Fri Sep 25 18:49:53 2015 +0000
@@ -337,7 +337,7 @@
 
 // ---------------------------------------------------------------------------
 //
-// LedWiz emulation
+// LedWiz emulation, and enhanced TLC5940 output controller
 //
 // There are two modes for this feature.  The default mode uses the on-board
 // GPIO ports to implement device outputs - each LedWiz software port is
@@ -357,6 +357,17 @@
 // for 32 outputs).  Every port in this mode has full PWM support.
 //
 
+// Figure the number of outputs.  If we're in the default LedWiz mode,
+// we have a fixed set of 32 outputs.  If we're in TLC5940 enhanced mode,
+// we have 16 outputs per chip.  To simplify the LedWiz compatibility code,
+// always use a minimum of 32 outputs even if we have fewer than two of the
+// TLC5940 chips.
+#if !defined(ENABLE_TLC5940) || (TLC_NCHIPS) < 2
+# define NUM_OUTPUTS   32
+#else
+# define NUM_OUTPUTS   ((TLC5940_NCHIPS)*16)
+#endif
+
 // Current starting output index for "PBA" messages from the PC (using
 // the LedWiz USB protocol).  Each PBA message implicitly uses the
 // current index as the starting point for the ports referenced in
@@ -393,7 +404,7 @@
     virtual void set(float val)
     {
         if (val != prv)
-            tlc5940.set(idx, (int)(val * 4095));
+           tlc5940.set(idx, (int)(val * 4095));
     }
     int idx;
     float prv;
@@ -454,12 +465,14 @@
     virtual void set(float val) { }
 };
 
-// Array of output assignments.  This array is indexed by the LedWiz
-// output port number; that protocol is hardwired for 32 ports, so we
-// need 32 elements in the array.  Each element is an LwOut object
-// that provides the mapping to the physical output corresponding to
-// the software port.
-static LwOut *lwPin[32];
+// Array of output physical pin assignments.  This array is indexed
+// by LedWiz logical port number - lwPin[n] is the maping for LedWiz
+// port n (0-based).  If we're using GPIO ports to implement outputs,
+// we initialize the array at start-up to map each logical port to the 
+// physical GPIO pin for the port specified in the ledWizPortMap[] 
+// array in config.h.  If we're using TLC5940 chips for the outputs,
+// we map each logical port to the corresponding TLC5940 output.
+static LwOut *lwPin[NUM_OUTPUTS];
 
 // initialize the output pin array
 void initLwOut()
@@ -470,14 +483,16 @@
         // Set up a TLC5940 output.  If the output is within range of
         // the connected number of chips (16 outputs per chip), assign it
         // to the current index, otherwise leave it unattached.
-        if (i < TLC5940_NCHIPS*16)
+        if (i < (TLC5940_NCHIPS)*16)
             lwPin[i] = new Lw5940Out(i);
         else
             lwPin[i] = new LwUnusedOut();
 
 #else // ENABLE_TLC5940
-        // Set up the GPIO pin, according to whether it's PWM-capable or
-        // digital-only, and whether or not it's assigned at all.
+        // Set up the GPIO pin.  If the pin is not connected ("NC" in the
+        // pin map), set up a dummy "unused" output for it.  If it's a
+        // real pin, set up a PWM-capable or Digital-Only output handler
+        // object, according to the pin type in the map.
         PinName p = (i < countof(ledWizPortMap) ? ledWizPortMap[i].pin : NC);
         if (p == NC)
             lwPin[i] = new LwUnusedOut();
@@ -491,10 +506,44 @@
     }
 }
 
+// Current absolute brightness level for an output.  This is a float
+// value from 0.0 for fully off to 1.0 for fully on.  This is the final
+// derived value for the port.  For outputs set by LedWiz messages, 
+// this is derived from te LedWiz state, and is updated on each pulse 
+// timer interrupt for lights in flashing states.  For outputs set by 
+// extended protocol messages, this is simply the brightness last set.
+static float outLevel[NUM_OUTPUTS];
+
+// LedWiz output states.
+//
+// The LedWiz protocol has two separate control axes for each output.
+// One axis is its on/off state; the other is its "profile" state, which
+// is either a fixed brightness or a blinking pattern for the light.
+// The two axes are independent.
+//
+// Note that the LedWiz protocol can only address 32 outputs, so the
+// wizOn and wizVal arrays have fixed sizes of 32 elements no matter
+// how many physical outputs we're using.
+
 // on/off state for each LedWiz output
 static uint8_t wizOn[32];
 
-// profile (brightness/blink) state for each LedWiz output
+// Profile (brightness/blink) state for each LedWiz output.  If the
+// output was last updated through an LedWiz protocol message, it
+// will have one of these values:
+//
+//   0-48 = fixed brightness 0% to 100%
+//   129 = ramp up / ramp down
+//   130 = flash on / off
+//   131 = on / ramp down
+//   132 = ramp up / on
+//
+// Special value 255:  If the output was updated through the 
+// extended protocol, we'll set the wizVal entry to 255, which has 
+// no meaning in the LedWiz protocol.  This tells us that the value 
+// in outLevel[] was set directly from the extended protocol, so it 
+// shouldn't be derived from wizVal[].
+//
 static uint8_t wizVal[32] = {
     48, 48, 48, 48, 48, 48, 48, 48,
     48, 48, 48, 48, 48, 48, 48, 48,
@@ -502,87 +551,156 @@
     48, 48, 48, 48, 48, 48, 48, 48
 };
 
+// LedWiz flash speed.  This is a value from 1 to 7 giving the pulse
+// rate for lights in blinking states.
+static uint8_t wizSpeed = 2;
+
+// Current LedWiz flash cycle counter.
+static uint8_t wizFlashCounter = 0;
+
+// Get the current brightness level for an LedWiz output.
 static float wizState(int idx)
 {
-    if (wizOn[idx]) 
+    // if the output was last set with an extended protocol message,
+    // use the value set there, ignoring the output's LedWiz state
+    if (wizVal[idx] == 255)
+        return outLevel[idx];
+    
+    // if it's off, show at zero intensity
+    if (!wizOn[idx])
+        return 0;
+
+    // check the state
+    uint8_t val = wizVal[idx];
+    if (val <= 48)
+    {
+        // PWM brightness/intensity level.  Rescale from the LedWiz
+        // 0..48 integer range to our internal PwmOut 0..1 float range.
+        // Note that on the actual LedWiz, level 48 is actually about
+        // 98% on - contrary to the LedWiz documentation, level 49 is 
+        // the true 100% level.  (In the documentation, level 49 is
+        // simply not a valid setting.)  Even so, we treat level 48 as
+        // 100% on to match the documentation.  This won't be perfectly
+        // ocmpatible with the actual LedWiz, but it makes for such a
+        // small difference in brightness (if the output device is an
+        // LED, say) that no one should notice.  It seems better to
+        // err in this direction, because while the difference in
+        // brightness when attached to an LED won't be noticeable, the
+        // difference in duty cycle when attached to something like a
+        // contactor *can* be noticeable - anything less than 100%
+        // can cause a contactor or relay to chatter.  There's almost
+        // never a situation where you'd want values other than 0% and
+        // 100% for a contactor or relay, so treating level 48 as 100%
+        // makes us work properly with software that's expecting the
+        // documented LedWiz behavior and therefore uses level 48 to
+        // turn a contactor or relay fully on.
+        return val/48.0;
+    }
+    else if (val == 49)
     {
-        // on - map profile brightness state to PWM level
-        uint8_t val = wizVal[idx];
-        if (val <= 48)
-        {
-            // PWM brightness/intensity level.  Rescale from the LedWiz
-            // 0..48 integer range to our internal PwmOut 0..1 float range.
-            // Note that on the actual LedWiz, level 48 is actually about
-            // 98% on - contrary to the LedWiz documentation, level 49 is 
-            // the true 100% level.  (In the documentation, level 49 is
-            // simply not a valid setting.)  Even so, we treat level 48 as
-            // 100% on to match the documentation.  This won't be perfectly
-            // ocmpatible with the actual LedWiz, but it makes for such a
-            // small difference in brightness (if the output device is an
-            // LED, say) that no one should notice.  It seems better to
-            // err in this direction, because while the difference in
-            // brightness when attached to an LED won't be noticeable, the
-            // difference in duty cycle when attached to something like a
-            // contactor *can* be noticeable - anything less than 100%
-            // can cause a contactor or relay to chatter.  There's almost
-            // never a situation where you'd want values other than 0% and
-            // 100% for a contactor or relay, so treating level 48 as 100%
-            // makes us work properly with software that's expecting the
-            // documented LedWiz behavior and therefore uses level 48 to
-            // turn a contactor or relay fully on.
-            return val/48.0;
-        }
-        else if (val == 49)
-        {
-            // 49 is undefined in the LedWiz documentation, but actually
-            // means 100% on.  The documentation says that levels 1-48 are
-            // the full PWM range, but empirically it appears that the real
-            // range implemented in the firmware is 1-49.  Some software on
-            // the PC side (notably DOF) is aware of this and uses level 49
-            // to mean "100% on".  To ensure compatibility with existing 
-            // PC-side software, we need to recognize level 49.
-            return 1.0;
-        }
-        else if (val >= 129 && val <= 132)
-        {
-            // Values of 129-132 select different flashing modes.  We don't
-            // support any of these.  Instead, simply treat them as fully on.  
-            // Note that DOF doesn't ever use modes 129-132, as it implements 
-            // all flashing modes itself on the host side, so this limitation 
-            // won't have any effect on DOF users.  You can observe it using 
-            // LedBlinky, though.
-            return 1.0;
-        }
+        // 49 is undefined in the LedWiz documentation, but actually
+        // means 100% on.  The documentation says that levels 1-48 are
+        // the full PWM range, but empirically it appears that the real
+        // range implemented in the firmware is 1-49.  Some software on
+        // the PC side (notably DOF) is aware of this and uses level 49
+        // to mean "100% on".  To ensure compatibility with existing 
+        // PC-side software, we need to recognize level 49.
+        return 1.0;
+    }
+    else if (val == 129)
+    {
+        //   129 = ramp up / ramp down
+        if (wizFlashCounter < 128)
+            return wizFlashCounter/127.0;
         else
-        {
-            // Other values are undefined in the LedWiz documentation.  Hosts
-            // *should* never send undefined values, since whatever behavior an
-            // LedWiz unit exhibits in response is accidental and could change
-            // in a future version.  We'll treat all undefined values as equivalent 
-            // to 48 (fully on).
-            // 
-            // NB: the 49 and 129-132 cases are broken out above for the sake
-            // of documentation.  We end up using 1.0 as the return value for
-            // everything outside of the defined 0-48 range, so we could collapse
-            // this whole thing to a single 'else' branch, but I wanted to call 
-            // out the specific reasons for handling the settings above as we do.
-            return 1.0;
-        }
+            return (255 - wizFlashCounter)/127.0;
+    }
+    else if (val == 130)
+    {
+        //   130 = flash on / off
+        return (wizFlashCounter < 128 ? 1.0 : 0.0);
+    }
+    else if (val == 131)
+    {
+        //   131 = on / ramp down
+        return (255 - wizFlashCounter)/255.0;
     }
-    else 
+    else if (val == 132)
+    {
+        //   132 = ramp up / on
+        return wizFlashCounter/255.0;
+    }
+    else
     {
-        // off - show at 0 intensity
-        return 0.0;
+        // Other values are undefined in the LedWiz documentation.  Hosts
+        // *should* never send undefined values, since whatever behavior an
+        // LedWiz unit exhibits in response is accidental and could change
+        // in a future version.  We'll treat all undefined values as equivalent 
+        // to 48 (fully on).
+        return 1.0;
     }
 }
 
+// LedWiz flash timer pulse.  This fires periodically to update 
+// LedWiz flashing outputs.  At the slowest pulse speed set via
+// the SBA command, each waveform cycle has 256 steps, so we
+// choose the pulse time base so that the slowest cycle completes
+// in 2 seconds.  This seems to roughly match the real LedWiz
+// behavior.  We run the pulse timer at the same rate regardless
+// of the pulse speed; at higher pulse speeds, we simply use
+// larger steps through the cycle on each interrupt.  Running
+// every 1/127 of a second = 8ms seems to be a pretty light load.
+Timeout wizPulseTimer;
+#define WIZ_PULSE_TIME_BASE  (1.0/127.0)
+static void wizPulse()
+{
+    // increase the counter by the speed increment, and wrap at 256
+    wizFlashCounter += wizSpeed;
+    wizFlashCounter &= 0xff;
+    
+    // if we have any flashing lights, update them
+    int ena = false;
+    for (int i = 0 ; i < 32 ; ++i)
+    {
+        if (wizOn[i])
+        {
+            uint8_t s = wizVal[i];
+            if (s >= 129 && s <= 132)
+            {
+                lwPin[i]->set(wizState(i));
+                ena = true;
+            }
+        }
+    }    
+
+    // Set up the next timer pulse only if we found anything flashing.
+    // To minimize overhead from this feature, we only enable the interrupt
+    // when we need it.  This eliminates any performance penalty to other
+    // features when the host software doesn't care about the flashing 
+    // modes.  For example, DOF never uses these modes, so there's no 
+    // need for them when running Visual Pinball.
+    if (ena)
+        wizPulseTimer.attach(wizPulse, WIZ_PULSE_TIME_BASE);
+}
+
+// Update the physical outputs connected to the LedWiz ports.  This is 
+// called after any update from an LedWiz protocol message.
 static void updateWizOuts()
 {
+    // update each output
+    int pulse = false;
     for (int i = 0 ; i < 32 ; ++i)
+    {
+        pulse |= (wizVal[i] >= 129 && wizVal[i] <= 132);
         lwPin[i]->set(wizState(i));
+    }
+    
+    // if any outputs are set to flashing mode, and the pulse timer
+    // isn't running, turn it on
+    if (pulse)
+        wizPulseTimer.attach(wizPulse, WIZ_PULSE_TIME_BASE);
 }
 
-
 // ---------------------------------------------------------------------------
 //
 // Button input
@@ -907,7 +1025,7 @@
              printf("%f %f %d %d %f\r\n", vx, vy, x, y, dt);
 #endif
      }    
-    
+         
 private:
     // adjust a raw acceleration figure to a usb report value
     int rawToReport(float v)
@@ -1243,6 +1361,11 @@
     bool reportPix = false;
 #endif
 
+#ifdef ENABLE_TLC5940
+    // start the TLC5940 clock
+    tlc5940.start();
+#endif
+
     // create our plunger sensor object
     PlungerSensor plungerSensor;
 
@@ -1368,7 +1491,7 @@
                 if (data[0] == 64) 
                 {
                     // LWZ-SBA - first four bytes are bit-packed on/off flags
-                    // for the outputs; 5th byte is the pulse speed (0-7)
+                    // for the outputs; 5th byte is the pulse speed (1-7)
                     //printf("LWZ-SBA %02x %02x %02x %02x ; %02x\r\n",
                     //       data[1], data[2], data[3], data[4], data[5]);
     
@@ -1381,6 +1504,13 @@
                         }
                         wizOn[i] = ((data[ri] & bit) != 0);
                     }
+                    
+                    // set the flash speed - enforce the value range 1-7
+                    wizSpeed = data[5];
+                    if (wizSpeed < 1)
+                        wizSpeed = 1;
+                    else if (wizSpeed > 7)
+                        wizSpeed = 7;
         
                     // update the physical outputs
                     updateWizOuts();