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:
Mon Jan 11 21:08:36 2016 +0000
Parent:
38:091e511ce8a0
Child:
40:cc0d9814522b
Commit message:
USB fixes; accelerometer auto un-sticking with watchdog timer.

Changed in this revision

TLC5940/TLC5940.h Show annotated file Show diff for this revision Revisions of this file
USBDevice.lib 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
USBProtocol.h Show annotated file Show diff for this revision Revisions of this file
Updates.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
--- a/TLC5940/TLC5940.h	Tue Jan 05 05:23:07 2016 +0000
+++ b/TLC5940/TLC5940.h	Mon Jan 11 21:08:36 2016 +0000
@@ -173,10 +173,6 @@
         // power-on, for example.)
         blank = 1;
         
-        // allocate the grayscale buffer, and set all outputs to fully off
-        gs = new unsigned short[nchips*16];
-        memset(gs, 0, nchips*16*sizeof(gs[0]));
-        
         // 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,
@@ -202,8 +198,8 @@
         xlat = 1;
         xlat = 0;
 
-        // Allocate a DMA buffer.  The transfer on each cycle is 192 bits per
-        // chip = 24 bytes per chip.
+        // Allocate our DMA buffers.  The transfer on each cycle is 192 bits per
+        // chip = 24 bytes per chip. 
         dmabuf = new char[nchips*24];
         memset(dmabuf, 0, nchips*24);
         
@@ -257,20 +253,55 @@
     
     ~TLC5940()
     {
-        delete [] gs;
         delete [] dmabuf;
     }
 
-    /**
-      *  Set the next chunk of grayscale data to be sent
-      *  @param data - Array of 16 bit shorts containing 16 12 bit grayscale data chunks per TLC5940
-      *  @note These must be in intervals of at least (1/GSCLK_SPEED) * 4096 to be sent
+     /*
+      *  Set an output
       */
     void set(int idx, unsigned short data) 
     {
-        // store the data, and flag the pending update for the interrupt handler to carry out
-        gs[idx] = data; 
-        newGSData = true;
+        // validate the index
+        if (idx >= 0 && idx < nchips*16)
+        {
+            // Figure the DMA buffer location of the data.  The DMA buffer has the
+            // packed bit format that we send across the wire, with 12 bits per output,
+            // arranged from last output to first output (N = number of outputs = nchips*16):
+            //
+            //       byte 0  =  high 8 bits of output N-1
+            //            1  =  low 4 bits of output N-1 | high 4 bits of output N-2
+            //            2  =  low 8 bits of N-2
+            //            3  =  high 8 bits of N-3
+            //            4  =  low 4 bits of N-3 | high 4 bits of N-2
+            //            5  =  low 8bits of N-4
+            //           ...
+            //  24*nchips-3  =  high 8 bits of output 1
+            //  24*nchips-2  =  low 4 bits of output 1 | high 4 bits of output 0
+            //  24*nchips-1  =  low 8 bits of output 0
+            //
+            // So this update will affect two bytes.  If the output number if even, we're
+            // in the high 4 + low 8 pair; if odd, we're in the high 8 + low 4 pair.
+            int di = nchips*24 - 3 - (3*(idx/2));
+            if (idx & 1)
+            {
+                //printf("out %d = %d -> updating dma[%d] odd (xx x. ..)\r\n", idx, data, di);
+                // ODD = high 8 | low 4
+                dmabuf[di]    = uint8_t((data >> 4) & 0xff);
+                dmabuf[di+1] &= 0x0F;
+                dmabuf[di+1] |= uint8_t((data << 4) & 0xf0);
+            }
+            else
+            {
+                // EVEN = high 4 | low 8
+                //printf("out %d = %d -> updating dma[%d] even (.. .x xx)\r\n", idx, data, di);
+                dmabuf[di+1] &= 0xF0;
+                dmabuf[di+1] |= uint8_t((data >> 8) & 0x0f);
+                dmabuf[di+2]  = uint8_t(data & 0xff);
+            }
+            
+            // note the update
+            newGSData = true;
+        }
     }
 
 private:
@@ -330,9 +361,7 @@
         // did updates on some cycles and not others.  By doing an
         // update on every cycle, we make the brightness reduction
         // uniform across time, which makes it less perceptible.
-        update();
         sdma.start(nchips*24);
-
         
 #else // DATA_UPDATE_INSIDE_BLANKING
         
@@ -340,12 +369,14 @@
         endBlank();
         
         // if we have pending grayscale data, update the DMA data
-        if (newGSData)
-            update();
-                    
-        // send out the DMA contents
-        sdma.start(nchips*24);
-
+        // if (newGSData) 
+        {
+            // send out the DMA contents
+            sdma.start(nchips*24);
+            
+            // we don't have to send again until the next gs data cahnge
+            newGSData = false;
+        }
 
 #endif // DATA_UPDATE_INSIDE_BLANKING
     }
@@ -377,47 +408,6 @@
         resetTimer.attach(this, &TLC5940::reset, (1.0/GSCLK_SPEED)*4096.0);
     }
     
-    void update()
-    {
-        // Send new grayscale data to the TLC5940 chips.
-        //
-        // To do this, we set up our DMA buffer with the bytes formatted exactly
-        // as they will go across the wire, then kick off the transfer request with 
-        // the DMA controller.  We can then return from the interrupt and continue
-        // with other tasks while the DMA hardware handles the transfer for us.
-        // When the transfer is completed, the DMA controller will fire an
-        // interrupt, which will call our interrupt handler, which will finish
-        // the blanking cycle.
-        //
-        // 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 level (0 = fully 
-        // off, 0xFFF = fully on).  Bit order is most significant bit first.  
-        // 
-        // The KL25Z SPI can only send in 8-bit increments, so we need to divvy up 
-        // the 12-bit outputs into 8-bit bytes.  Each pair of 12-bit outputs adds up 
-        // to 24 bits, which divides evenly into 3 bytes, so send each pairs of 
-        // outputs as three bytes:
-        //
-        //   [    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, dst = 0 ; i >= 0 ; i -= 2)
-        {
-            // first byte - element i+1 bits 4-11
-            dmabuf[dst++] = (((gs[i+1] & 0xFF0) >> 4) & 0xff);
-            
-            // second byte - element i+1 bits 0-3, then element i bits 8-11
-            dmabuf[dst++] = ((((gs[i+1] & 0x00F) << 4) | ((gs[i] & 0xF00) >> 8)) & 0xFF);
-            
-            // third byte - element i bits 0-7
-            dmabuf[dst++] = (gs[i] & 0x0FF);
-        }
-        
-        // we've now cleared the new GS data
-        newGSData = false;
-    }
-
     // Interrupt handler for DMA completion.  The DMA controller calls this
     // when it finishes with the transfer request we set up above.  When the
     // transfer is done, we simply end the blanking cycle and start a new
--- a/USBDevice.lib	Tue Jan 05 05:23:07 2016 +0000
+++ b/USBDevice.lib	Mon Jan 11 21:08:36 2016 +0000
@@ -1,1 +1,1 @@
-http://mbed.org/users/mjr/code/USBDevice/#20bb47609697
+http://mbed.org/users/mjr/code/USBDevice/#c5ac4ccf6597
--- a/USBJoystick/USBJoystick.cpp	Tue Jan 05 05:23:07 2016 +0000
+++ b/USBJoystick/USBJoystick.cpp	Mon Jan 11 21:08:36 2016 +0000
@@ -421,7 +421,7 @@
             C_RESERVED | C_SELF_POWERED,    // bmAttributes
             C_POWER(0),                     // bMaxPowerHello World from Mbed
         
-            // INTERFACE 0 - JOYSTICK/LEDWIZ
+            // ***** INTERFACE 0 - JOYSTICK/LEDWIZ ******
             INTERFACE_DESCRIPTOR_LENGTH,    // bLength
             INTERFACE_DESCRIPTOR,           // bDescriptorType
             0x00,                           // bInterfaceNumber - first interface = 0
@@ -458,7 +458,7 @@
             MSB(MAX_PACKET_SIZE_EPINT),     // wMaxPacketSize (MSB)
             1,                              // bInterval (milliseconds)
             
-            // INTERFACE 1 - KEYBOARD
+            // ****** INTERFACE 1 - KEYBOARD ******
             INTERFACE_DESCRIPTOR_LENGTH,    // bLength
             INTERFACE_DESCRIPTOR,           // bDescriptorType
             0x01,                           // bInterfaceNumber - second interface = 1
@@ -604,12 +604,16 @@
     uint32_t bytesRead = 0;
     USBDevice::readEP(EP1OUT, buf.buf, &bytesRead, MAX_HID_REPORT_SIZE);
     
+//    printf("joy.read len=%d [%2x %2x %2x %2x %2x %2x %2x %2x], msg=[%2x %2x %2x %2x %2x %2x %2x %2x]\r\n", bytesRead, 
+//        buf.buf[0], buf.buf[1], buf.buf[2], buf.buf[3], buf.buf[4], buf.buf[5], buf.buf[6], buf.buf[7],
+//        buf.msg.data[0], buf.msg.data[1], buf.msg.data[2], buf.msg.data[3], buf.msg.data[4], buf.msg.data[5], buf.msg.data[6], buf.msg.data[7]);
+    
     // if it's the right length, queue it to our circular buffer
     if (bytesRead == 8)
         lwbuf.write(buf.msg);
 
     // start the next read
-    return readStart(EP1OUT, 9);
+    return readStart(EP1OUT, MAX_HID_REPORT_SIZE);
 }
 
 // Handle incoming messages on the keyboard interface = endpoint 4.
@@ -626,3 +630,4 @@
     // start the next read
     return readStart(EP4OUT, MAX_HID_REPORT_SIZE);
 }
+
--- a/USBJoystick/USBJoystick.h	Tue Jan 05 05:23:07 2016 +0000
+++ b/USBJoystick/USBJoystick.h	Mon Jan 11 21:08:36 2016 +0000
@@ -8,12 +8,20 @@
  
 #include "USBHID.h"
 
+// Bufferd incoming LedWiz message structure
 struct LedWizMsg
 {
     uint8_t data[8];
 };
 
-// circular buffer for incoming reports
+// Circular buffer for incoming reports.  We write reports in the IRQ
+// handler, and we read reports in the main loop in normal application
+// (non-IRQ) context.  
+// 
+// The design is organically safe for IRQ threading; there are no critical 
+// sections.  The IRQ context has exclusive access to the write pointer, 
+// and the application context has exclusive access to the read pointer, 
+// so there are no test-and-set or read-and-modify race conditions.
 template<class T, int cnt> class CircBuf
 {
 public:
@@ -23,11 +31,13 @@
     }
 
     // Read an item from the buffer.  Returns true if an item was available,
-    // false if the buffer was empty.
+    // false if the buffer was empty.  (Called in the main loop, in application
+    // context.)
     bool read(T &result) 
     {
         if (iRead != iWrite)
         {
+            //{uint8_t *d = buf[iRead].data; printf("circ read [%02x %02x %02x %02x %02x %02x %02x %02x]\r\n", d[0],d[1],d[2],d[3],d[4],d[5],d[6],d[7]);}
             memcpy(&result, &buf[iRead], sizeof(T));
             iRead = advance(iRead);
             return true;
@@ -36,12 +46,14 @@
             return false;
     }
     
+    // Write an item to the buffer.  (Called in the IRQ handler, in interrupt
+    // context.)
     bool write(const T &item)
     {
         int nxt = advance(iWrite);
         if (nxt != iRead)
         {
-            memcpy(&buf[nxt], &item, sizeof(T));
+            memcpy(&buf[iWrite], &item, sizeof(T));
             iWrite = nxt;
             return true;
         }
@@ -52,7 +64,8 @@
 private:
     int advance(int i)
     {
-        return i + 1 >= cnt ? 0 : i + 1;
+        ++i;
+        return i < cnt ? i : 0;
     } 
     
     int iRead;
@@ -60,6 +73,10 @@
     T buf[cnt];
 };
 
+// interface IDs
+const uint8_t IFC_ID_JS = 0;        // joystick + LedWiz interface
+const uint8_t IFC_ID_KB = 1;        // keyboard interface
+
 // keyboard interface report IDs 
 const uint8_t REPORT_ID_KB = 1;
 const uint8_t REPORT_ID_MEDIA = 2;
@@ -151,7 +168,6 @@
              _init();
              this->useKB = useKB;
              this->enableJoystick = enableJoystick;
-             reqTimer.start();
              connect(waitForConnect);
          };
 
@@ -161,6 +177,11 @@
              return lwbuf.read(msg);
          }
          
+         /* get the idle time settings, in milliseconds */
+         uint32_t getKbIdle() const { return kbIdleTime * 4UL; }
+         uint32_t getMediaIdle() const { return mediaIdleTime * 4UL; }
+         
+
          /**
           * Send a keyboard report.  The argument gives the key state, in the standard
           * 6KRO USB keyboard report format: byte 0 is the modifier key bit mask, byte 1
@@ -251,6 +272,35 @@
          virtual uint8_t *stringIserialDesc();
          virtual uint8_t *stringIproductDesc();
          
+         /* set/get idle time */
+         virtual void setIdleTime(int ifc, int rptid, int t)
+         {
+             // Remember the new value if operating on the keyboard.  Remember
+             // separate keyboard and media control idle times, in case the
+             // host wants separate report rates.
+             if (ifc == IFC_ID_KB)
+             {
+                if (rptid == REPORT_ID_KB)
+                    kbIdleTime = t;
+                else if (rptid == REPORT_ID_MEDIA)
+                    mediaIdleTime = t;
+            }
+         }
+         virtual uint8_t getIdleTime(int ifc, int rptid)
+         {
+             // Return the kb idle time if the kb interface is the one requested.
+             if (ifc == IFC_ID_KB)
+             {
+                 if (rptid == REPORT_ID_KB)
+                    return kbIdleTime;
+                 if (rptid == REPORT_ID_MEDIA)
+                    return mediaIdleTime;
+             }
+             
+             // we don't use idle times for other interfaces or report types
+             return 0;
+         }
+         
          /* callback overrides */
          virtual bool USBCallback_setConfiguration(uint8_t configuration);
          virtual bool USBCallback_setInterface(uint16_t interface, uint8_t alternate)
@@ -260,9 +310,14 @@
          virtual bool EP4_OUT_callback();
          
      private:
-         Timer reqTimer;
+
+         // Incoming LedWiz message buffer.  Each LedWiz message is exactly 8 bytes.
+         CircBuf<LedWizMsg, 64> lwbuf;
+         
          bool enableJoystick;
          bool useKB;
+         uint8_t kbIdleTime;
+         uint8_t mediaIdleTime;
          int16_t _x;                       
          int16_t _y;     
          int16_t _z;
@@ -270,9 +325,6 @@
          uint16_t _buttonsHi;
          uint16_t _status;
 
-         // Incoming LedWiz message buffer.  Each LedWiz message is exactly 8 bytes.
-         CircBuf<LedWizMsg, 64> lwbuf;
-         
          void _init();                 
 };
  
--- a/USBProtocol.h	Tue Jan 05 05:23:07 2016 +0000
+++ b/USBProtocol.h	Mon Jan 11 21:08:36 2016 +0000
@@ -7,12 +7,34 @@
 
 // ------ OUTGOING MESSAGES (DEVICE TO HOST) ------
 //
+// 1. Joystick reports
 // In most cases, our outgoing messages are HID joystick reports, using the
 // format defined in USBJoystick.cpp.  This allows us to be installed on
 // Windows as a standard USB joystick, which all versions of Windows support
 // using in-the-box drivers.  This allows a completely transparent, driverless,
-// plug-and-play installation experience on Windows.
+// plug-and-play installation experience on Windows.  Our joystick report
+// looks like this (see USBJoystick.cpp for the formal HID report descriptor):
 //
+//    ss     status bits:  0x01 -> plunger enabled
+//    00     always zero for joystick reports
+//    bb     joystick buttons, low byte (buttons 1-16, 1 bit per button)
+//    bb     joystick buttons, high byte (buttons 17-32)
+//    xx     low byte of X position = nudge/accelerometer X axis
+//    xx     high byte of X position
+//    yy     low byte of Y position = nudge/accelerometer Y axis
+//    yy     high byte of Y position
+//    zz     low byte of Z position = plunger position
+//    zz     high byte of Z position
+//
+// The X, Y, and Z values are 16-bit signed integers.  The accelerometer
+// values are on an abstract scale, where 0 represents no acceleration,
+// negative maximum represents -1g on that axis, and positive maximum
+// represents +1g on that axis.  For the plunger position, 0 is the park
+// position (the rest position of the plunger) and positive values represent
+// retracted (pulled back) positions.  A negative value means that the plunger
+// is pushed forward of the park position.
+//
+// 2. Special reports
 // We subvert the joystick report format in certain cases to report other 
 // types of information, when specifically requested by the host.  This allows
 // our custom configuration UI on the Windows side to query additional 
@@ -20,15 +42,24 @@
 // define a custom vendor-specific "status" field in the reports that we
 // use to identify these special reports, as described below.
 //
-// Normal joystick reports always have 0 in the high bit of the first byte
+// Normal joystick reports always have 0 in the high bit of the 2nd byte
 // of the report.  Special non-joystick reports always have 1 in the high bit 
 // of the first byte.  (This byte is defined in the HID Report Descriptor
 // as an opaque vendor-defined value, so the joystick interface on the
 // Windows side simply ignores it.)
 //
-// Pixel dumps:  requested by custom protocol message 65 3 (see below).  
-// This sends a series of reports to the host in the following format, for 
-// as many messages as are neessary to report all pixels:
+// 2A. Plunger sensor pixel dump
+// Software on the PC can request a full read of the pixels from the plunger 
+// image sensor (if an imaging sensor type is being used) by sending custom 
+// protocol message 65 3 (see below).  Normally, the pixels from the image
+// sensor are read and processed on the controller device without being sent
+// to the PC; the PC only receives the plunger position reading obtained from
+// analyzing the image data.  For debugging and setup purposes, software on
+// the host can use this special report to obtain the full image pixel array.
+// The image sensors we use have too many pixels to fit into one report, so 
+// we have to send a series of reports to transmit the full image.  We send
+// as many reports as necessary to transmit the full image.  Each report
+// looks like this:
 //
 //    bytes 0:1 = 11-bit index, with high 5 bits set to 10000.  For 
 //                example, 0x04 0x80 indicates index 4.  This is the 
@@ -38,8 +69,9 @@
 //    bytes 4:5 = brightness of pixel at index+1
 //    etc for the rest of the packet
 //
-// Configuration query:  requested by custom protocol message 65 4 (see 
-// below).  This sends one report to the host using this format:
+// 2B. Configuration query.
+// This is requested by sending custom protocol message 65 4 (see below).
+// In reponse, the device sends one report to the host using this format:
 //
 //    bytes 0:1 = 0x8800.  This has the bit pattern 10001 in the high
 //                5 bits, which distinguishes it from regular joystick
@@ -157,6 +189,9 @@
 // 65  -> Miscellaneous control message.  The second byte specifies the specific
 //        operation:
 //
+//        0 -> No Op - does nothing.  (This can be used to send a test message on the
+//             USB endpoint.)
+//
 //        1 -> Set device unit number and plunger status, and save the changes immediately
 //             to flash.  The device will automatically reboot after the changes are saved.
 //             The additional bytes of the message give the parameters:
--- a/Updates.h	Tue Jan 05 05:23:07 2016 +0000
+++ b/Updates.h	Mon Jan 11 21:08:36 2016 +0000
@@ -29,4 +29,49 @@
 // moving at high speed, allowing for more realistic plunger action on the
 // virtual side.
 //
-// 
+// Keyboard mappings for buttons: button inputs can now be mapped to keyboard
+// keys.  Joystick buttons are of course also still supported.  Some software on
+// the PC side is easier to configure for keyboard input than for joystick
+// input, so many users might prefer to map some or all buttons to keys.  If
+// you map any buttons to keyboard input, the controller device will have
+// two entries in the Windows Device Manager list, one as a joystick and
+// the other as a keyboard.  This is automatic; the keyboard interface will
+// appear automatically if you have any keyboard keys mapped, otherwise only
+// the joystick interface will appear.
+//
+// "Pulse" buttons: you can now designate individual button inputs as pulse
+// mode buttons.  When a button is configured in pulse mode, the software
+// translates each ON/OFF or OFF/ON transition in the physical switch to a
+// short virtual key press.  This is especially designed to make it easier
+// to wire a coin door switch, but could be used for other purposes as well.
+// For the coin door, the VPinMAME software uses the End key to *toggle* the
+// open/closed state of the door in the simulation, but it's much easier
+// to wire a physical on/off switch to the door instead.  Pulse mode bridges
+// this gap by translating the on/off switch state to key presses.  When
+// you open the door, the switch will go from OFF to ON, so the controller
+// will send one short key press, causing VPinMAME to toggle the simulated
+// door to OPEN.  When you close the door, the switch will go from ON to
+// OFF, which will make the controller send another short key press, which
+// in turn will make VPinMAME toggle the simulated door state to CLOSED.
+// There are other ways to solve this problem (VP cab builders have come
+// up with various physical devices and electronic timer circuits to deal
+// with it), but the software approach implemented here is a lot simpler
+// to set up and is very reliable.
+//
+// Night mode: you can now put the device in "night mode" by configuring a 
+// physical button input to activate the mode, or by sending a command from
+// the PC config tool software.  When night mode is activated, outputs that
+// you designate as "noisemaker" devices are disabled.  You can designate
+// any outputs as noisy or not.  This feature is designed to let you use your
+// virtual pinball machine during quiet hours (e.g., late at night) without
+// disturbing housemates or neighbors with noise from flippers, knockers,
+// shaker motors, and so on.  You can designate outputs individually as
+// noisy, so you can still enjoy the rest of your feedback features during
+// night play (e.g., flashers and other lighting effects).
+//
+// USB fixes: the low-level USB device code had some serious bugs that only
+// very occasionally manifested in past versions, but became much more
+// frequently triggered due to other changes in this release (particularly
+// the USB keyboard input feature).  These should now be fixed, so the USB
+// connection should now be very reliable.
+//
--- a/config.h	Tue Jan 05 05:23:07 2016 +0000
+++ b/config.h	Mon Jan 11 21:08:36 2016 +0000
@@ -216,6 +216,7 @@
         
         button[20].typ = BtnTypeMedia;
         button[20].val = 0x01;  // vol up
+        
 #endif
         
 #if 1 // $$$
--- a/main.cpp	Tue Jan 05 05:23:07 2016 +0000
+++ b/main.cpp	Mon Jan 11 21:08:36 2016 +0000
@@ -1063,6 +1063,11 @@
                 bs->mediakey = val;
                 kbKeys = true;
                 break;
+                
+            case BtnTypeSpecial:
+                // special key
+                bs->special = val;
+                break;
             }
         }
     }
@@ -1438,6 +1443,26 @@
             
              // reset the timer
              tCenter_.reset();
+             
+             // If we haven't seen an interrupt in a while, do an explicit read to
+             // "unstick" the device.  The device can become stuck - which is to say,
+             // it will stop delivering data-ready interrupts - if we fail to service
+             // one data-ready interrupt before the next one occurs.  Reading a sample
+             // will clear up this overrun condition and allow normal interrupt
+             // generation to continue.
+             //
+             // Note that this stuck condition *shouldn't* ever occur - if it does,
+             // it means that we're spending a long period with interrupts disabled
+             // (either in a critical section or in another interrupt handler), which
+             // will likely cause other worse problems beyond the sticky accelerometer.
+             // Even so, it's easy to detect and correct, so we'll do so for the sake
+             // of making the system more fault-tolerant.
+             if (tInt_.read() > 1.0f)
+             {
+                 printf("unwedging the accelerometer\r\n");
+                float x, y, z;
+                mma_.getAccXYZ(x, y, z);
+             }
          }
          
          // report our integrated velocity reading in x,y
@@ -1479,7 +1504,7 @@
         mma_.getAccXYZ(x, y, z);
         
         // calculate the time since the last interrupt
-        float dt = tInt_.read_us()/1.0e6;
+        float dt = tInt_.read();
         tInt_.reset();
 
         // integrate the time slice from the previous reading to this reading
@@ -2250,7 +2275,7 @@
 // Handle an input report from the USB host.  Input reports use our extended
 // LedWiz protocol.
 //
-void handleInputMsg(uint8_t data[8], USBJoystick &js, int &z)
+void handleInputMsg(LedWizMsg &lwm, USBJoystick &js, int &z)
 {
     // LedWiz commands come in two varieties:  SBA and PBA.  An
     // SBA is marked by the first byte having value 64 (0x40).  In
@@ -2270,6 +2295,7 @@
     //               N is (first byte - 200)*7
     //   other    -> reserved for future use
     //
+    uint8_t *data = lwm.data;
     if (data[0] == 64) 
     {
         // LWZ-SBA - first four bytes are bit-packed on/off flags
@@ -2325,40 +2351,47 @@
         // and isn't used for any other LedWiz message, so we appropriate
         // it for our own private use.  The first byte specifies the 
         // message type.
-        if (data[1] == 1)
+        switch (data[1])
         {
+        case 0:
+            // No Op
+            break;
+            
+        case 1:
             // 1 = Old Set Configuration:
             //     data[2] = LedWiz unit number (0x00 to 0x0f)
             //     data[3] = feature enable bit mask:
             //               0x01 = enable plunger sensor
-
-            // get the new LedWiz unit number - this is 0-15, whereas we
-            // we save the *nominal* unit number 1-16 in the config                
-            uint8_t newUnitNo = (data[2] & 0x0f) + 1;
-
-            // we'll need a reset if the LedWiz unit number is changing
-            bool needReset = (newUnitNo != cfg.psUnitNo);
-            
-            // set the configuration parameters from the message
-            cfg.psUnitNo = newUnitNo;
-            cfg.plunger.enabled = data[3] & 0x01;
+            {
+    
+                // get the new LedWiz unit number - this is 0-15, whereas we
+                // we save the *nominal* unit number 1-16 in the config                
+                uint8_t newUnitNo = (data[2] & 0x0f) + 1;
+    
+                // we'll need a reset if the LedWiz unit number is changing
+                bool needReset = (newUnitNo != cfg.psUnitNo);
+                
+                // set the configuration parameters from the message
+                cfg.psUnitNo = newUnitNo;
+                cfg.plunger.enabled = data[3] & 0x01;
+                
+                // update the status flags
+                statusFlags = (statusFlags & ~0x01) | (data[3] & 0x01);
+                
+                // if the plunger is no longer enabled, use 0 for z reports
+                if (!cfg.plunger.enabled)
+                    z = 0;
+                
+                // save the configuration
+                saveConfigToFlash();
+                
+                // reboot if necessary
+                if (needReset)
+                    reboot(js);
+            }
+            break;
             
-            // update the status flags
-            statusFlags = (statusFlags & ~0x01) | (data[3] & 0x01);
-            
-            // if the plunger is no longer enabled, use 0 for z reports
-            if (!cfg.plunger.enabled)
-                z = 0;
-            
-            // save the configuration
-            saveConfigToFlash();
-            
-            // reboot if necessary
-            if (needReset)
-                reboot(js);
-        }
-        else if (data[1] == 2)
-        {
+        case 2:
             // 2 = Calibrate plunger
             // (No parameters)
             
@@ -2366,33 +2399,32 @@
             calBtnState = 3;
             calBtnTimer.reset();
             cfg.plunger.cal.reset(plungerSensor->npix);
-        }
-        else if (data[1] == 3)
-        {
+            break;
+            
+        case 3:
             // 3 = pixel dump
             // (No parameters)
             reportPix = true;
             
             // show purple until we finish sending the report
             diagLED(1, 0, 1);
-        }
-        else if (data[1] == 4)
-        {
+            break;
+            
+        case 4:
             // 4 = hardware configuration query
             // (No parameters)
-            wait_ms(1);
             js.reportConfig(
                 numOutputs, 
                 cfg.psUnitNo - 1,   // report 0-15 range for unit number (we store 1-16 internally)
                 cfg.plunger.cal.zero, cfg.plunger.cal.max);
-        }
-        else if (data[1] == 5)
-        {
+            break;
+            
+        case 5:
             // 5 = all outputs off, reset to LedWiz defaults
             allOutputsOff();
-        }
-        else if (data[1] == 6)
-        {
+            break;
+            
+        case 6:
             // 6 = Save configuration to flash.
             saveConfigToFlash();
             
@@ -2401,6 +2433,7 @@
             // so we don't bother tracking whether or not a reboot is
             // really needed.
             reboot(js);
+            break;
         }
     }
     else if (data[0] == 66)
@@ -2518,9 +2551,10 @@
 //
 int main(void)
 {
-    printf("\r\nPinscape Controller starting\r\n"); // $$$ debug
+    printf("\r\nPinscape Controller starting\r\n");
+    // memory config debugging: {int *a = new int; printf("Stack=%lx, heap=%lx, free=%ld\r\n", (long)&a, (long)a, (long)&a - (long)a);}
     
-    // clear the I2C bus for the accelerometer
+    // clear the I2C bus (for the accelerometer)
     clear_i2c();
 
     // load the saved configuration
@@ -2715,12 +2749,12 @@
     // host requests
     for (;;)
     {
-        // Process incoming reports
-        LedWizMsg lwmsg;
-        for (int rr = 0 ; rr < 64 && js.readLedWizMsg(lwmsg) ; ++rr) 
-            handleInputMsg(lwmsg.data, js, z);
+        // Process incoming reports on the joystick interface.  This channel
+        // is used for LedWiz commands are our extended protocol commands.
+        LedWizMsg lwm;
+        while (js.readLedWizMsg(lwm))
+            handleInputMsg(lwm, js, z);
        
-
         // check for plunger calibration
         if (calBtn != 0 && !calBtn->read())
         {
@@ -3300,20 +3334,24 @@
             }
             else if (jsOKTimer.read() > 5)
             {
-                // too long without a USB report - show red/yellow
+                // USB freeze - show red/yellow.
+                //  Our outgoing joystick messages aren't going through, even though we
+                // think we're still connected.  This indicates that one or more of our
+                // USB endpoints have stopped working, which can happen as a result of
+                // bugs in the USB HAL or latency responding to a USB IRQ.  Show a
+                // distinctive diagnostic flash to signal the error.  I haven't found a 
+                // way to recover from this class of error other than rebooting the MCU, 
+                // so the goal is to fix the HAL so that this error never happens.  This
+                // flash pattern is thus for debugging purposes only; hopefully it won't
+                // ever occur in a real installation.
                 static bool dumped;
                 if (!dumped) {
+                    // If we haven't already, dump the USB HAL status to the debug console,
+                    // in case it helps identify the reason for the endpoint failure.
                     extern void USBDeviceStatusDump(void);
                     USBDeviceStatusDump();
                     dumped = true;
                 }
-                extern bool USB_DMAERR;
-                if (USB_DMAERR) {
-                    printf("USB DMAERR DETECTED!\r\n");
-                    //   js.disconnect();
-                    //   js.connect();
-                    //   USB_DMAERR = false;
-                }
                 jsOKTimer.stop();
                 hb = !hb;
                 diagLED(1, hb, 0);