Pinscape Controller version 1 fork. This is a fork to allow for ongoing bug fixes to the original controller version, from before the major changes for the expansion board project.

Dependencies:   FastIO FastPWM SimpleDMA mbed

Fork of Pinscape_Controller by Mike R

Files at this revision

API Documentation at this revision

Comitter:
mjr
Date:
Fri Sep 25 18:49:53 2015 +0000
Parent:
28:2097c6f8f2db
Child:
30:6e9902f06f48
Commit message:
Test of direct bit writes instead of SPI.

Changed in this revision

FastIO.lib Show annotated file Show diff for this revision Revisions of this file
TLC5940/TLC5940.h Show annotated file Show diff for this revision Revisions of this file
USBJoystick/USBJoystick.cpp Show annotated file Show diff for this revision Revisions of this file
USBJoystick/USBJoystick.h Show annotated file Show diff for this revision Revisions of this file
config.h Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed.bld Show annotated file Show diff for this revision Revisions of this file
--- a/FastIO.lib	Wed Sep 23 05:38:27 2015 +0000
+++ b/FastIO.lib	Fri Sep 25 18:49:53 2015 +0000
@@ -1,1 +1,1 @@
-http://developer.mbed.org/users/Sissors/code/FastIO/#327ae1d5fecb
+http://developer.mbed.org/users/Sissors/code/FastIO/#199aca52ac42
--- a/TLC5940/TLC5940.h	Wed Sep 23 05:38:27 2015 +0000
+++ b/TLC5940/TLC5940.h	Fri Sep 25 18:49:53 2015 +0000
@@ -48,6 +48,7 @@
   * isn't a factor.  E.g., at SPI=30MHz and GSCLK=500kHz, 
   * t(blank) is 8192us and t(refresh) is 25us.
   */
+#define USE_SPI 1
 #define SPI_SPEED 3000000
 
 /**
@@ -111,7 +112,11 @@
       *  @param nchips - The number of TLC5940s (if you are daisy chaining)
       */
     TLC5940(PinName SCLK, PinName MOSI, PinName GSCLK, PinName BLANK, PinName XLAT, int nchips)
+#if USE_SPI
         : spi(MOSI, NC, SCLK),
+#else
+        : sin(MOSI), sclk(SCLK),
+#endif
           gsclk(GSCLK),
           blank(BLANK),
           xlat(XLAT),
@@ -122,6 +127,7 @@
         gs = new unsigned short[nchips*16];
         memset(gs, 0, nchips*16*sizeof(gs[0]));
         
+#if USE_SPI
         // Configure SPI format and speed.  Note that KL25Z ONLY supports 8-bit
         // mode.  The TLC5940 nominally requires 12-bit data blocks for the
         // grayscale levels, but SPI is ultimately just a bit-level serial format,
@@ -131,7 +137,10 @@
         // format 0.
         spi.format(8, 0);
         spi.frequency(SPI_SPEED);
-        
+#else
+        sclk = 1;
+#endif
+
         // Set output pin states
         xlat = 0;
         blank = 1;
@@ -140,7 +149,11 @@
         gsclk.period(1.0/GSCLK_SPEED);
         gsclk.write(.5);
         blank = 0;
-        
+    }
+    
+    // start the clock running
+    void start()
+    {        
         // Set up the first call to the reset function, which asserts BLANK to
         // end the PWM cycle and handles new grayscale data output and latching.
         // The original version of this library uses a timer to call reset
@@ -176,15 +189,20 @@
     {
         // store the data, and flag the pending update for the interrupt handler to carry out
         gs[idx] = data; 
-        newGSData = true;
+//        newGSData = true;
     }
 
 private:
     // current level for each output
     unsigned short *gs;
     
+#if USE_SPI
     // SPI port - only MOSI and SCK are used
     SPI spi;
+#else
+    DigitalOut sin;
+    DigitalOut sclk;
+#endif
 
     // use a PWM out for the grayscale clock - this provides a stable
     // square wave signal without consuming CPU
@@ -212,7 +230,7 @@
         blank = 1;        
 
         // If we have new GS data, send it now
-        if (newGSData)
+        if (true) // (newGSData)
         {
             // Send the new grayscale data.
             //
@@ -267,6 +285,7 @@
     
     void update()
     {
+#if USE_SPI
         // Send GS data.  The serial format orders the outputs from last to first
         // (output #15 on the last chip in the daisy-chain to output #0 on the
         // first chip).  For each output, we send 12 bits containing the grayscale
@@ -281,7 +300,7 @@
         //   [    element i+1 bits   ]  [ element i bits        ]
         //   11 10 9 8 7 6 5 4 3 2 1 0  11 10 9 8 7 6 5 4 3 2 1 0
         //   [  first byte   ] [   second byte  ] [  third byte ]
-        for (int i = (16 * nchips) - 2 ; i >= 0 ; i -= 2)
+        for (int i = 61 /* (16 * nchips) - 2 */ ; i >= 0 ; i -= 2)
         {
             // first byte - element i+1 bits 4-11
             spi.write(((gs[i+1] & 0xFF0) >> 4) & 0xff);
@@ -292,6 +311,20 @@
             // third byte - element i bits 0-7
             spi.write(gs[i] & 0x0FF);
         }
+#else
+        // Send GS data, from last output to first output, 12 bits per output,
+        // most significant bit first.
+        for (int i = 16*3 - 1 ; i >= 0 ; --i)
+        {
+            unsigned data = gs[i];
+            for (unsigned int mask = 1 << 11, bit = 0 ; bit < 12 ; ++bit, mask >>= 1)
+            {
+                sclk = 0;                    
+                sin = (data & mask) ? 1 : 0;
+                sclk = 1;
+            }
+        }
+#endif
     }
 };
 
--- a/USBJoystick/USBJoystick.cpp	Wed Sep 23 05:38:27 2015 +0000
+++ b/USBJoystick/USBJoystick.cpp	Fri Sep 25 18:49:53 2015 +0000
@@ -150,30 +150,8 @@
     {         
          USAGE_PAGE(1), 0x01,            // Generic desktop
          USAGE(1), 0x04,                 // Joystick
-
          COLLECTION(1), 0x01,            // Application
          
-         // NB - the canonical joystick has a nested collection at this
-         // point.  We remove the inner collection to enable the LedWiz 
-         // emulation.  The LedWiz API implementation on the PC side
-         // appears to use the collection structure as part of the
-         // device signature, and the real LedWiz descriptor has just
-         // one top-level collection.  The built-in Windows HID drivers 
-         // don't appear to care whether this collection is present or
-         // not for the purposes of recognizing a joystick, so it seems
-         // to make everyone happy to leave it out.  
-         //
-         // All of the reference material for USB joystick device builders 
-         // does use the inner collection, so it's possible that omitting 
-         // it will create an incompatibility with some non-Windows hosts.  
-         // But that seems largely moot in that VP only runs on Windows. 
-         //  If you're you're trying to adapt this code for a different 
-         // device and run into problems connecting to a non-Windows host, 
-         // try restoring the inner collection.  You probably won't 
-         // care about LedWiz compatibility in such a situation so there
-         // should be no reason not to return to the standard structure.
-       //  COLLECTION(1), 0x00,          // Physical
-           
              // input report (device to host)
 
              USAGE_PAGE(1), 0x06,        // generic device controls - for config status
@@ -207,12 +185,12 @@
              
              // output report (host to device)
              REPORT_SIZE(1), 0x08,       // 8 bits per report
-             REPORT_COUNT(1), 0x08,      // output report count (LEDWiz messages)
+             REPORT_COUNT(1), 0x08,      // output report count - 8-byte LedWiz format
              0x09, 0x01,                 // usage
              0x91, 0x01,                 // Output (array)
 
-     //    END_COLLECTION(0),
          END_COLLECTION(0)
+
       };
 #else /* defined(ENABLE_JOYSTICK) */
 
--- a/USBJoystick/USBJoystick.h	Wed Sep 23 05:38:27 2015 +0000
+++ b/USBJoystick/USBJoystick.h	Fri Sep 25 18:49:53 2015 +0000
@@ -91,7 +91,7 @@
          * @param product_release Your product_release (default: 0x0001)
          */
          USBJoystick(uint16_t vendor_id = 0x1234, uint16_t product_id = 0x0100, uint16_t product_release = 0x0001, int waitForConnect = true): 
-             USBHID(16, 8, vendor_id, product_id, product_release, false)
+             USBHID(16, 64, vendor_id, product_id, product_release, false)
              { 
                  _init();
                  connect(waitForConnect);
--- a/config.h	Wed Sep 23 05:38:27 2015 +0000
+++ b/config.h	Fri Sep 25 18:49:53 2015 +0000
@@ -74,13 +74,15 @@
 //
 // The reason we start at unit #8 is that we want to avoid conflicting with
 // any real LedWiz devices you have in your system.  If you have a real
-// LedWiz, it's probably unit #1, since that's the standard factor setting.
-// If you have two real LedWiz's, they're probably units #1 and #2.  If you 
-// have three... well, I don't think anyone actually has three, but if you 
-// did it would probably be unit #3.  And so on.  That's why we start at #8 - 
-// it seems really unlikely that this will conflict with anybody's existing 
-// setup.  On the off chance it does, simply change the setting here to a 
-// different unit number that's not already used in your system.
+// LedWiz, it's probably unit #1, since that's the default factory setting
+// that they'll give you if you didn't specifically ask for something else
+// when you ordered it.  If you have two real LedWiz's, they're probably 
+// units #1 and #2.  If you have three... well, I don't think anyone 
+// actually has three, but if you did it would probably be unit #3.  And so 
+// on.  That's why we start at #8: it seems really unlikely that anyone
+// with a pin cab has a real LedWiz unit #8.  On the off chance that you
+// do, simply change the setting here to a different unit number that's not 
+// already used in your system.
 //
 // Note 1:  the unit number here is the *user visible* unit number that
 // you use on the PC side.  It's the number you specify in your DOF
@@ -90,67 +92,22 @@
 // are all off by one from the unit number you select here, that's why.
 //
 // Note 2:  the DOF Configtool (google it) knows about the Pinscape 
-// controller (it's known there as just a "KL25Z" rather than Pinscape).
-// And the DOF tool knows that it uses #8 as its default unit number, so
-// it names the .ini file for this controller xxx8.ini.  If you change the 
-// unit number here, remember to rename the DOF-generated .ini file to 
-// match, by changing the "8" at the end of the filename to the new number
-// you set here.
+// controller.  There it's referred to as simply "KL25Z" rather than 
+// Pinscape Controller, but that's what they're talking about.  The DOF 
+// tool knows that it uses #8 as its default unit number, so it names the 
+// .ini file for this controller xxx8.ini.  If you change the unit number 
+// here, remember to rename the DOF-generated .ini file to match, by 
+// changing the "8" at the end of the filename to the new number you set 
+// here.
 const uint8_t DEFAULT_LEDWIZ_UNIT_NUMBER = 
 #ifdef ENABLE_JOYSTICK
-   0x08;   // joystick enabled - assume we're the primary KL25Z, so use unit #8
+   0x01;   // joystick enabled - assume we're the primary KL25Z, so use unit #8
 #else
    0x09;   // joystick disabled - assume we're a secondary, output-only KL25Z, so use #9
 #endif
 
 // --------------------------------------------------------------------------
 //
-// TLC5940 PWM controller chip setup - Enhanced LedWiz emulation
-//
-// By default, the Pinscape Controller software can provide limited LedWiz
-// emulation through the KL25Z's on-board GPIO ports.  This lets you hook
-// up external devices, such as LED flashers or solenoids, to the KL25Z
-// outputs (using external circuitry to boost power - KL25Z GPIO ports
-// are limited to a meager 4mA per port).  This capability is limited by
-// the number of available GPIO ports on the KL25Z, and even smaller limit
-// of 10 PWM-capable GPIO ports.
-//
-// As an alternative, the controller software lets you use external PWM
-// controller chips to control essentially unlimited channels with full
-// PWM control on all channels.  This requires building external circuitry
-// using TLC5940 chips.  Each TLC5940 chip provides 16 full PWM channels,
-// and you can daisy-chain multiple TLC5940 chips together to set up 32, 
-// 48, 64, or more channels.
-//
-// If you do add TLC5940 circuits to your controller hardware, use this
-// section to configure the connection to the KL25Z.
-//
-// Note that if you're using TLC5940 outputs, ALL of the outputs must go
-// through the TLC5940s - you can't mix TLC5940s and the default GPIO
-// device outputs.  This lets us take GPIO ports that we'd normally use
-// for device outputs and reassign them to control the TLC5940 hardware.
-
-// Uncomment this line if using TLC5940 chips
-//#define ENABLE_TLC5940
-
-// Number of TLC5940 chips you're using.  For a full LedWiz-compatible
-// setup, you need two of these chips, for 32 outputs.
-#define TLC5940_NCHIPS   2
-
-// If you're using TLC5940s, change any of these as needed to match the
-// GPIO pins that you connected to the TLC5940 control pins.  Note that
-// SIN and SCLK *must* be connected to the KL25Z SPI0 MOSI and SCLK
-// outputs, respectively, which effectively limits them to the default
-// selections, and that the GSCLK pin must be PWM-capable.
-#define TLC5940_SIN    PTC6    // Must connect to SPI0 MOSI -> PTC6 or PTD2
-#define TLC5940_SCLK   PTC5    // Must connect to SPI0 SCLK -> PTC5 or PTD1; however, PTD1 isn't
-                               //   recommended because it's hard-wired to the on-board blue LED
-#define TLC5940_XLAT   PTC10   // Any GPIO pin can be used
-#define TLC5940_BLANK  PTC0    // Any GPIO pin can be used
-#define TLC5940_GSCLK  PTD4    // Must be a PWM-capable pin
-
-// --------------------------------------------------------------------------
-//
 // Plunger CCD sensor.
 //
 // If you're NOT using the CCD sensor, comment out the next line (by adding
@@ -344,10 +301,59 @@
 // push mode.
 const float LaunchBallPushDistance = .08;
 
-#endif // CONFIG_H
+
+// --------------------------------------------------------------------------
+//
+// TLC5940 PWM controller chip setup - Enhanced LedWiz emulation
+//
+// By default, the Pinscape Controller software can provide limited LedWiz
+// emulation through the KL25Z's on-board GPIO ports.  This lets you hook
+// up external devices, such as LED flashers or solenoids, to the KL25Z
+// outputs (using external circuitry to boost power - KL25Z GPIO ports
+// are limited to a meager 4mA per port).  This capability is limited by
+// the number of available GPIO ports on the KL25Z, and even smaller limit
+// of 10 PWM-capable GPIO ports.
+//
+// As an alternative, the controller software lets you use external PWM
+// controller chips to control essentially unlimited channels with full
+// PWM control on all channels.  This requires building external circuitry
+// using TLC5940 chips.  Each TLC5940 chip provides 16 full PWM channels,
+// and you can daisy-chain multiple TLC5940 chips together to set up 32, 
+// 48, 64, or more channels.
+//
+// If you do add TLC5940 circuits to your controller hardware, use this
+// section to configure the connection to the KL25Z.
+//
+// Note that if you're using TLC5940 outputs, ALL of the outputs must go
+// through the TLC5940s - you can't mix TLC5940s and the default GPIO
+// device outputs.  This lets us take GPIO ports that we'd normally use
+// for device outputs and reassign them to control the TLC5940 hardware.
+
+// Uncomment this line if using TLC5940 chips
+#define ENABLE_TLC5940
+
+// Number of TLC5940 chips you're using.  For a full LedWiz-compatible
+// setup, you need two of these chips, for 32 outputs.
+#define TLC5940_NCHIPS   4
+
+// If you're using TLC5940s, change any of these as needed to match the
+// GPIO pins that you connected to the TLC5940 control pins.  Note that
+// SIN and SCLK *must* be connected to the KL25Z SPI0 MOSI and SCLK
+// outputs, respectively, which effectively limits them to the default
+// selections, and that the GSCLK pin must be PWM-capable.
+#define TLC5940_SIN    PTC6    // Must connect to SPI0 MOSI -> PTC6 or PTD2
+#define TLC5940_SCLK   PTC5    // Must connect to SPI0 SCLK -> PTC5 or PTD1; however, PTD1 isn't
+                               //   recommended because it's hard-wired to the on-board blue LED
+#define TLC5940_XLAT   PTC10   // Any GPIO pin can be used
+#define TLC5940_BLANK  PTC0    // Any GPIO pin can be used
+#define TLC5940_GSCLK  PTD4    // Must be a PWM-capable pin
 
 
-#ifdef DECL_EXTERNS
+#endif // CONFIG_H - end of include-once section (code below this point can be multiply included)
+
+
+#ifdef DECL_EXTERNS  // this section defines global variables, only if this macro is set
+
 // --------------------------------------------------------------------------
 //
 
@@ -421,7 +427,7 @@
 
 // --------------------------------------------------------------------------
 //
-// LED-Wiz emulation output pin assignments.  
+// LED-Wiz emulation output pin assignments - GPIO mode
 //
 //   NOTE!  This section isn't used if you have TLC5940 outputs - ALL
 //   device outputs will be through the 5940s if you're using them.
--- 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();
--- a/mbed.bld	Wed Sep 23 05:38:27 2015 +0000
+++ b/mbed.bld	Fri Sep 25 18:49:53 2015 +0000
@@ -1,1 +1,1 @@
-http://mbed.org/users/mbed_official/code/mbed/builds/6213f644d804
\ No newline at end of file
+http://mbed.org/users/mbed_official/code/mbed/builds/4f6c30876dfa
\ No newline at end of file