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

Revision:
9:fd65b0a94720
Parent:
8:c732e279ee29
Child:
10:976666ffa4ef
--- 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)