Adjusts the great pinscape controller to work with a cheap linear potentiometer instead of the expensive CCD array

Dependencies:   USBDevice mbed

Fork of Pinscape_Controller by Mike R

Files at this revision

API Documentation at this revision

Comitter:
mjr
Date:
Mon Aug 18 21:46:10 2014 +0000
Parent:
8:c732e279ee29
Child:
10:976666ffa4ef
Commit message:
Tweaks to plunger firing detection

Changed in this revision

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
main.cpp Show annotated file Show diff for this revision Revisions of this file
--- a/USBJoystick/USBJoystick.cpp	Fri Aug 08 20:59:39 2014 +0000
+++ b/USBJoystick/USBJoystick.cpp	Mon Aug 18 21:46:10 2014 +0000
@@ -20,14 +20,13 @@
 #include "stdint.h"
 #include "USBJoystick.h"
  
-bool USBJoystick::update(int16_t x, int16_t y, int16_t z, int16_t rx, int16_t ry, uint16_t buttons) 
+bool USBJoystick::update(int16_t x, int16_t y, int16_t z, uint16_t buttons, uint16_t status) 
 {
    _x = x;
    _y = y;
    _z = z;
-   _rx = rx;
-   _ry = ry;
    _buttons = buttons;     
+   _status = (uint8_t)status;
  
    // send the report
    return update();
@@ -37,29 +36,18 @@
    HID_REPORT report;
  
    // Fill the report according to the Joystick Descriptor
-   report.data[0] = _buttons & 0xff;
-   report.data[1] = (_buttons >> 8) & 0xff;
-#if 0 // 8-bit coordinate reports
-   report.data[2] = _x & 0xff;            
-   report.data[3] = _y & 0xff;            
-   report.data[4] = _z & 0xff;
-   report.data[5] = _rx & 0xff;
-   report.data[6] = _ry & 0xff;
-   report.length = 7; 
-#else // 16-bit reports
 #define put(idx, val) (report.data[idx] = (val) & 0xff, report.data[(idx)+1] = ((val) >> 8) & 0xff)
+   put(0, _buttons);
    put(2, _x);
    put(4, _y);
    put(6, _z);
-   put(8, _rx);
-   put(10, _ry);
-   report.length = 12;
-#endif
+   report.data[8] = _status;
+   report.length = 9;
  
    // send the report
    return sendNB(&report);
 }
- 
+
 bool USBJoystick::move(int16_t x, int16_t y) {
      _x = x;
      _y = y;
@@ -83,6 +71,7 @@
    _y = 0;     
    _z = 0;
    _buttons = 0x0000;
+   _status = 0;
 }
  
  
@@ -94,8 +83,29 @@
          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), 0x09,        // Buttons
              USAGE_MINIMUM(1), 0x01,     // { buttons }
              USAGE_MAXIMUM(1), 0x10,     // {  1-16   }
@@ -108,18 +118,26 @@
              INPUT(1), 0x02,             // Data, Variable, Absolute
            
              USAGE_PAGE(1), 0x01,        // Generic desktop
-             USAGE(1), 0x30,             // X
-             USAGE(1), 0x31,             // Y
-             USAGE(1), 0x32,             // Z
-             USAGE(1), 0x33,             // Rx
-             USAGE(1), 0x34,             // Ry
+             USAGE(1), 0x30,             // X axis
+             USAGE(1), 0x31,             // Y axis
+             USAGE(1), 0x32,             // Z axis
              LOGICAL_MINIMUM(2), 0x00,0xF0,   // each value ranges -4096
              LOGICAL_MAXIMUM(2), 0x00,0x10,   // ...to +4096
              REPORT_SIZE(1), 0x10,       // 16 bits per report
-             REPORT_COUNT(1), 0x05,      // 5 reports (X, Y, Z, Rx, Ry)
+             REPORT_COUNT(1), 0x03,      // 3 reports (X, Y, Z)
+             INPUT(1), 0x02,             // Data, Variable, Absolute
+             
+             USAGE_PAGE(1), 0x06,        // generic device controls - for config status
+             USAGE(1), 0x00,             // undefined device control
+             LOGICAL_MINIMUM(1), 0x00,   // 1-bit flags
+             LOGICAL_MAXIMUM(1), 0x01,
+             REPORT_SIZE(1), 0x01,       // 1 bit per report
+             REPORT_COUNT(1), 0x08,      // 8 reports (8 bits)
              INPUT(1), 0x02,             // Data, Variable, Absolute
 
-             REPORT_COUNT(1), 0x08,      // input report count (LEDWiz messages)
+             // output report (host to device)
+             REPORT_SIZE(1), 0x08,       // 8 bits per report
+             REPORT_COUNT(1), 0x08,      // output report count (LEDWiz messages)
              0x09, 0x01,                 // usage
              0x91, 0x01,                 // Output (array)
 
@@ -151,7 +169,7 @@
 
 uint8_t * USBJoystick::stringIproductDesc() {
     static uint8_t stringIproductDescriptor[] = {
-        0x2E,                                                       /*bLength*/
+        0x28,                                                       /*bLength*/
         STRING_DESCRIPTOR,                                          /*bDescriptorType 0x03*/
         'P',0,'i',0,'n',0,'s',0,'c',0,'a',0,'p',0,'e',0,
         ' ',0,'C',0,'o',0,'n',0,'t',0,'r',0,'o',0,'l',0,
--- a/USBJoystick/USBJoystick.h	Fri Aug 08 20:59:39 2014 +0000
+++ b/USBJoystick/USBJoystick.h	Mon Aug 18 21:46:10 2014 +0000
@@ -106,7 +106,7 @@
          * @param buttons buttons state, as a bit mask (combination with '|' of JOY_Bn values)
          * @returns true if there is no error, false otherwise
          */
-         bool update(int16_t x, int16_t y, int16_t z, int16_t rx, int16_t ry, uint16_t buttons);
+         bool update(int16_t x, int16_t y, int16_t z, uint16_t buttons, uint16_t status);
  
          /**
          * Write a state of the mouse
@@ -114,7 +114,7 @@
          * @returns true if there is no error, false otherwise
          */
          bool update();
- 
+         
          /**
          * Move the cursor to (x, y)
          *
@@ -155,9 +155,8 @@
          int16_t _x;                       
          int16_t _y;     
          int16_t _z;
-         int16_t _rx;
-         int16_t _ry;
          uint16_t _buttons;
+         uint8_t _status;
          
          void _init();                 
 };
--- a/main.cpp	Fri Aug 08 20:59:39 2014 +0000
+++ b/main.cpp	Mon Aug 18 21:46:10 2014 +0000
@@ -171,7 +171,16 @@
 //    byte 2 = new LedWiz unit number, 0x01 to 0x0f
 //    byte 3 = feature enable bit mask:
 //             0x01 = enable CCD (default = on)
-
+//
+// Plunger calibration mode: the host can activate plunger calibration mode
+// by sending this packet.  This has the same effect as pressing and holding
+// the plunger calibration button for two seconds, to allow activating this
+// mode without attaching a physical button.
+//
+//    length = 8 bytes
+//    byte 0 = 65 (0x41)
+//    byte 1 = 2 (0x02)
+//
  
 #include "mbed.h"
 #include "math.h"
@@ -220,6 +229,22 @@
 const uint16_t USB_VERSION_NO = 0x0006;
 const uint8_t DEFAULT_LEDWIZ_UNIT_NUMBER = 0x07;
 
+// Number of pixels we read from the sensor on each frame.  This can be
+// less than the physical pixel count if desired; we'll read every nth
+// piexl if so.  E.g., with a 1280-pixel physical sensor, if npix is 320,
+// we'll read every 4th pixel.  It takes time to read each pixel, so the
+// fewer pixels we read, the higher the refresh rate we can achieve.
+// It's therefore better not to read more pixels than we have to.
+//
+// VP seems to have an internal resolution in the 8-bit range, so there's
+// no apparent benefit to reading more than 128-256 pixels when using VP.
+// Empirically, 160 pixels seems about right.  The overall travel of a
+// standard pinball plunger is about 3", so 160 pixels gives us resolution
+// of about 1/50".  This seems to take full advantage of VP's modeling
+// ability, and is probably also more precise than a human player's
+// perception of the plunger position.
+const int npix = 160;
+
 // On-board RGB LED elements - we use these for diagnostic displays.
 DigitalOut ledR(LED1), ledG(LED2), ledB(LED3);
 
@@ -302,23 +327,24 @@
     { PTD5, true },      // pin J2-4,  LW port 8  (PWM capable - TPM 0.5 = channel 6)
     { PTD0, true },      // pin J2-6,  LW port 9  (PWM capable - TPM 0.0 = channel 1)
     { PTD3, true },      // pin J2-10, LW port 10 (PWM capable - TPM 0.3 = channel 4)
-    { PTC8, false },     // pin J1-14, LW port 11
-    { PTC9, false },     // pin J1-16, LW port 12
-    { PTC7, false },     // pin J1-1,  LW port 13
-    { PTC0, false },     // pin J1-3,  LW port 14
-    { PTC3, false },     // pin J1-5,  LW port 15
-    { PTC4, false },     // pin J1-7,  LW port 16
-    { PTC5, false },     // pin J1-9,  LW port 17
-    { PTC6, false },     // pin J1-11, LW port 18
-    { PTC10, false },    // pin J1-13, LW port 19
-    { PTC11, false },    // pin J1-15, LW port 20
-    { PTC12, false },    // pin J2-1,  LW port 21
-    { PTC13, false },    // pin J2-3,  LW port 22
-    { PTC16, false },    // pin J2-5,  LW port 23
-    { PTC17, false },    // pin J2-7,  LW port 24
-    { PTA16, false },    // pin J2-9,  LW port 25
-    { PTA17, false },    // pin J2-11, LW port 26
-    { PTE31, false },    // pin J2-13, LW port 27
+    { PTD2, false },     // pin J2-8,  LW port 11
+    { PTC8, false },     // pin J1-14, LW port 12
+    { PTC9, false },     // pin J1-16, LW port 13
+    { PTC7, false },     // pin J1-1,  LW port 14
+    { PTC0, false },     // pin J1-3,  LW port 15
+    { PTC3, false },     // pin J1-5,  LW port 16
+    { PTC4, false },     // pin J1-7,  LW port 17
+    { PTC5, false },     // pin J1-9,  LW port 18
+    { PTC6, false },     // pin J1-11, LW port 19
+    { PTC10, false },    // pin J1-13, LW port 20
+    { PTC11, false },    // pin J1-15, LW port 21
+    { PTC12, false },    // pin J2-1,  LW port 22
+    { PTC13, false },    // pin J2-3,  LW port 23
+    { PTC16, false },    // pin J2-5,  LW port 24
+    { PTC17, false },    // pin J2-7,  LW port 25
+    { PTA16, false },    // pin J2-9,  LW port 26
+    { PTA17, false },    // pin J2-11, LW port 27
+    { PTE31, false },    // pin J2-13, LW port 28
     { PTD6, false },     // pin J2-17, LW port 29
     { PTD7, false },     // pin J2-19, LW port 30
     { PTE0, false },     // pin J2-18, LW port 31
@@ -343,6 +369,12 @@
 
 
 // ---------------------------------------------------------------------------
+// utilities
+
+// number of elements in an array
+#define countof(x) (sizeof(x)/sizeof((x)[0]))
+
+// ---------------------------------------------------------------------------
 //
 // LedWiz emulation
 //
@@ -381,7 +413,7 @@
 // initialize the output pin array
 void initLwOut()
 {
-    for (int i = 0 ; i < sizeof(lwPin) / sizeof(lwPin[0]) ; ++i)
+    for (int i = 0 ; i < countof(lwPin) ; ++i)
     {
         PinName p = ledWizPortMap[i].pin;
         lwPin[i] = (ledWizPortMap[i].isPWM
@@ -467,6 +499,15 @@
         iap.program_flash(addr, this, sizeof(*this));
     }
     
+    // reset calibration data for calibration mode
+    void resetPlunger()
+    {
+        // set extremes for the calibration data
+        d.plungerMax = 0;
+        d.plungerZero = npix;
+        d.plungerMin = npix;
+    }
+    
     // stored data (excluding the checksum)
     struct
     {
@@ -640,7 +681,7 @@
         tInt_.start();
     }
     
-    void get(int &x, int &y, int &rx, int &ry) 
+    void get(int &x, int &y) 
     {
          // disable interrupts while manipulating the shared data
          __disable_irq();
@@ -711,14 +752,6 @@
          x = rawToReport(vx);
          y = rawToReport(vy);
          
-         // apply a small dead zone near the center
-         // if (abs(x) < 6) x = 0;
-         // if (abs(y) < 6) y = 0;
-         
-         // report the calibrated instantaneous acceleration in rx,ry
-         rx = int(round((ax - cx_)*JOYMAX));
-         ry = int(round((ay - cy_)*JOYMAX));
-         
 #ifdef DEBUG_PRINTF
          if (x != 0 || y != 0)        
              printf("%f %f %d %d %f\r\n", vx, vy, x, y, dt);
@@ -874,22 +907,6 @@
     // check for valid flash
     bool flash_valid = flash->valid();
                       
-    // Number of pixels we read from the sensor on each frame.  This can be
-    // less than the physical pixel count if desired; we'll read every nth
-    // piexl if so.  E.g., with a 1280-pixel physical sensor, if npix is 320,
-    // we'll read every 4th pixel.  It takes time to read each pixel, so the
-    // fewer pixels we read, the higher the refresh rate we can achieve.
-    // It's therefore better not to read more pixels than we have to.
-    //
-    // VP seems to have an internal resolution in the 8-bit range, so there's
-    // no apparent benefit to reading more than 128-256 pixels when using VP.
-    // Empirically, 160 pixels seems about right.  The overall travel of a
-    // standard pinball plunger is about 3", so 160 pixels gives us resolution
-    // of about 1/50".  This seems to take full advantage of VP's modeling
-    // ability, and is probably also more precise than a human player's
-    // perception of the plunger position.
-    const int npix = 160;
-
     // if the flash is valid, load it; otherwise initialize to defaults
     if (flash_valid) {
         memcpy(&cfg, flash, sizeof(cfg));
@@ -918,7 +935,6 @@
     // plunger calibration button debounce timer
     Timer calBtnTimer;
     calBtnTimer.start();
-    int calBtnDownTime = 0;
     int calBtnLit = false;
     
     // Calibration button state:
@@ -957,10 +973,16 @@
     // so when we detect the start of this motion, we immediately tell VP
     // to return the plunger to rest, then we monitor the real plunger 
     // until it atcually stops.
-    bool firing = false;
+    int firing = 0;
 
     // start the first CCD integration cycle
     ccd.clear();
+    
+    // Device status.  We report this on each update so that the host config
+    // tool can detect our current settings.  This is a bit mask consisting
+    // of these bits:
+    //    0x01  -> plunger sensor enabled
+    uint16_t statusFlags = (cfg.d.ccdEnabled ? 0x01 : 0x00);
 
     // we're all set up - now just loop, processing sensor reports and 
     // host requests
@@ -1009,7 +1031,7 @@
                     // message type.
                     if (data[1] == 1)
                     {
-                        // Set Configuration:
+                        // 1 = Set Configuration:
                         //     data[2] = LedWiz unit number (0x00 to 0x0f)
                         //     data[3] = feature enable bit mask:
                         //               0x01 = enable CCD
@@ -1022,9 +1044,26 @@
                         cfg.d.ledWizUnitNo = newUnitNo;
                         cfg.d.ccdEnabled = data[3] & 0x01;
                         
+                        // update the status flags
+                        statusFlags = (statusFlags & ~0x01) | (data[3] & 0x01);
+                        
+                        // if the ccd is no longer enabled, use 0 for z reports
+                        if (!cfg.d.ccdEnabled)
+                            z = 0;
+                        
                         // save the configuration
                         cfg.save(iap, flash_addr);
                     }
+                    else if (data[1] == 2)
+                    {
+                        // 2 = Calibrate plunger
+                        // (No parameters)
+                        
+                        // enter calibration mode
+                        calBtnState = 3;
+                        calBtnTimer.reset();
+                        cfg.resetPlunger();
+                    }
                 }
                 else 
                 {
@@ -1057,38 +1096,32 @@
             case 0: 
                 // button not yet pushed - start debouncing
                 calBtnTimer.reset();
-                calBtnDownTime = calBtnTimer.read_ms();
                 calBtnState = 1;
                 break;
                 
             case 1:
                 // pushed, not yet debounced - if the debounce time has
                 // passed, start the hold period
-                if (calBtnTimer.read_ms() - calBtnDownTime > 50)
+                if (calBtnTimer.read_ms() > 50)
                     calBtnState = 2;
                 break;
                 
             case 2:
                 // in the hold period - if the button has been held down
                 // for the entire hold period, move to calibration mode
-                if (calBtnTimer.read_ms() - calBtnDownTime > 2050)
+                if (calBtnTimer.read_ms() > 2050)
                 {
                     // enter calibration mode
                     calBtnState = 3;
-                    
-                    // set extremes for the calibration data, so that the actual
-                    // values we read will set new high/low water marks on the fly
-                    cfg.d.plungerMax = 0;
-                    cfg.d.plungerZero = npix;
-                    cfg.d.plungerMin = npix;
+                    calBtnTimer.reset();
+                    cfg.resetPlunger();
                 }
                 break;
                 
             case 3:
-                // Already in calibration mode - pushing the button in this
-                // state doesn't change the current state, but we won't leave
-                // this state as long as it's held down.  We can simply do
-                // nothing here.
+                // Already in calibration mode - pushing the button here
+                // doesn't change the current state, but we won't leave this
+                // state as long as it's held down.  So nothing changes here.
                 break;
             }
         }
@@ -1101,8 +1134,7 @@
             // Otherwise, return to the base state without saving anything.
             // If the button is released before we make it to calibration
             // mode, it simply cancels the attempt.
-            if (calBtnState == 3
-                && calBtnTimer.read_ms() - calBtnDownTime > 17500)
+            if (calBtnState == 3 && calBtnTimer.read_ms() > 15000)
             {
                 // exit calibration mode
                 calBtnState = 0;
@@ -1127,7 +1159,7 @@
         {
         case 2:
             // in the hold period - flash the light
-            newCalBtnLit = (((calBtnTimer.read_ms() - calBtnDownTime)/250) & 1);
+            newCalBtnLit = ((calBtnTimer.read_ms()/250) & 1);
             break;
             
         case 3:
@@ -1150,13 +1182,13 @@
                 calBtnLed = 1;
                 ledR = 1;
                 ledG = 1;
-                ledB = 1;
+                ledB = 0;
             }
             else {
                 calBtnLed = 0;
                 ledR = 1;
                 ledG = 1;
-                ledB = 0;
+                ledB = 1;
             }
         }
         
@@ -1246,14 +1278,34 @@
             // is complete, allowing VP to carry out the firing motion using
             // its internal model plunger rather than trying to track the
             // intermediate positions of the mechanical plunger throughout
-            // the firing motion.  This has several benefits.  First is that 
-            // our readings aren't very accurate during rapid movement,
-            // because we get too much motion blur.  Second is that the
-            // event approach allows VP to simulate the plunger motion
-            // according to each table's particular plunger settings.
-            // Different tables have different plunger strengths and speeds,
-            // so we want to defer to the model for the physics of the firing
-            // motion within each simulation.
+            // the firing motion.  This is essential because the firing
+            // motion is too fast for us to track - in the time it takes us
+            // to read one frame, the plunger can make it all the way to the
+            // zero position and bounce back halfway.  Fortunately, the range
+            // of motions for the plunger is limited, so if we see any rapid
+            // change of position toward the rest position, it's reasonably
+            // safe to interpret it as a firing event.  
+            //
+            // This isn't foolproof.  The user can trick us by doing a 
+            // controlled rapid forward push but stopping short of the rest 
+            // position.  We'll misinterpret that as a firing event.  But 
+            // that's not a natural motion that a user would make with a
+            // plunger, so it's probably an acceptable false positive.
+            //
+            // Possible future enhancement: we could add a second physical
+            // sensor that detects when the plunger reaches the zero position
+            // and asserts an interrupt.  In the interrupt handler, set a
+            // flag indicating the zero position signal.  On each scan of
+            // the CCD, also check that flag; if it's set, enter firing
+            // event mode just as we do now.  The key here is that the
+            // secondary sensor would have to be something much faster
+            // than our CCD scan - it would have to react on, say, the
+            // millisecond time scale.  A simple mechanical switch or a
+            // proximity sensor could work.  This would let us detect
+            // with certainty when the plunger physically fires, eliminating
+            // the case where the use can fool us with motion that's fast
+            // enough to look like a release but doesn't actually reach the
+            // starting position.
             //
             // To detremine when a firing even occurs, we watch for rapid
             // motion from a retracted position towards the rest position -
@@ -1264,12 +1316,38 @@
             // position, and then suspend reports until the mechanical
             // readings indicate that the plunger has come to rest (indicated
             // by several readings in a row at roughly the same position).
-                    
-            // Check to see if plunger firing is in progress.  If not, check
-            // to see if it looks like we just started firing.
-            const int restTol = JOYMAX/npix * 4;
-            const int fireTol = JOYMAX/npix * 12;
-            if (firing)
+            //
+            // Tolerance for firing is 1/3 of the current pull distance, or
+            // about 1/2", whichever is greater.  Making this value too small
+            // makes for too many false positives.  Empirically, 1/4" is too
+            // twitchy, so set a floor at about 1/2".  But we can be less
+            // sensitive the further back the plunger is pulled, since even
+            // a long pull will snap back quickly.  Note that JOYMAX always
+            // corresponds to about 3", no matter how many pixels we're
+            // reading, since the physical sensor is about 3" long; so we
+            // factor out the pixel count calculate (approximate) physical
+            // distances based on the normalized axis range.
+            // 
+            // Firing pattern: when firing, don't simply report a solid 0,
+            // but instead report a series of pseudo-bouces.  This looks
+            // more realistic, beacause the real plunger is also bouncing
+            // around during this time.  To get maximum firing power in
+            // the simulation, though, our pseudo-bounces are tiny cmopared
+            // to the real thing.
+            const int restTol = JOYMAX/24;
+            int fireTol = z/3 > JOYMAX/6 ? z/3 : JOYMAX/6;
+            static const int firePattern[] = { 
+                -JOYMAX/12, -JOYMAX/12, -JOYMAX/12, 
+                0, 0, 0,
+                JOYMAX/16, JOYMAX/16, JOYMAX/16,
+                0, 0, 0,
+                -JOYMAX/20, -JOYMAX/20, -JOYMAX/20,
+                0, 0, 0, 
+                JOYMAX/24, JOYMAX/24, JOYMAX/24,
+                0, 0, 0,
+                -JOYMAX/30, -JOYMAX/30, -JOYMAX/30 
+            };
+            if (firing != 0)
             {
                 // Firing in progress - we've already told VP to send its
                 // model plunger all the way back to the rest position, so
@@ -1278,11 +1356,23 @@
                 if (abs(z0 - z2) < restTol && abs(znew - z2) < restTol)
                 {
                     // the plunger is back at rest - firing is done
-                    firing = false;
+                    firing = 0;
                     
                     // resume normal reporting
                     z = z2;
                 }
+                else if (firing < countof(firePattern))
+                {
+                    // firing - report the next position in the pseudo-bounce 
+                    // pattern
+                    z = firePattern[firing++];
+                }
+                else
+                {
+                    // firing, out of pseudo-bounce items - just report the
+                    // rest position
+                    z = 0;
+                }
             }
             else if (z0 < z2 && z1 < z2 && znew < z2
                      && (z0 < z2 - fireTol 
@@ -1305,8 +1395,10 @@
                 // virtual plunger, rather than imposing the actual
                 // mechanical charateristics of the physical plunger on
                 // every table.
-                firing = true;
-                z = 0;
+                firing = 1;
+                
+                // report the first firing pattern position
+                z = firePattern[0];
             }
             else
             {
@@ -1320,10 +1412,16 @@
             z1 = z0;
             z0 = znew;
         }
+        else
+        {
+            // plunger disabled - pause 10ms to throttle updates to a
+            // reasonable pace
+            wait_ms(10);
+        }
 
         // read the accelerometer
-        int xa, ya, rxa, rya;
-        accel.get(xa, ya, rxa, rya);
+        int xa, ya;
+        accel.get(xa, ya);
         
         // confine the results to our joystick axis range
         if (xa < -JOYMAX) xa = -JOYMAX;
@@ -1342,7 +1440,7 @@
         // arrangement of our nominal axes aligns with VP's standard
         // setting, so that we can configure VP with X Axis = X on the
         // joystick and Y Axis = Y on the joystick.
-        js.update(y, x, z, rxa, rya, 0);
+        js.update(y, x, z, 0, statusFlags);
         
 #ifdef DEBUG_PRINTF
         if (x != 0 || y != 0)