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
Revision 18:5e890ebd0023, committed 2015-02-27
- Comitter:
- mjr
- Date:
- Fri Feb 27 07:41:29 2015 +0000
- Parent:
- 17:ab3cec0c8bf4
- Child:
- 19:054f8af32fce
- Commit message:
- Old debounce about to be removed
Changed in this revision
--- a/TSL1410R/tsl1410r.h Fri Feb 27 04:14:04 2015 +0000 +++ b/TSL1410R/tsl1410r.h Fri Feb 27 07:41:29 2015 +0000 @@ -63,11 +63,7 @@ // If the caller has other work to tend to that takes longer than the // desired maximum integration time, it can call clear() to clock out // the current pixels and start a fresh integration cycle. - void read(uint16_t *pix, int n) { read(pix, n, 0, 0, 0); } - - // Read with interval callback. We'll call the callback the given - // number of times per read cycle. - void read(uint16_t *pix, int n, void (*cb)(void *ctx), void *cbctx, int cbcnt) + void read(uint16_t *pix, int n) { // start the next integration cycle by pulsing SI and one clock si = 1; @@ -78,42 +74,24 @@ // figure how many pixels to skip on each read int skip = nPix/n - 1; - // figure the callback interval - int cbInterval = nPix; - if (cb != 0) - cbInterval = nPix/(cbcnt+1); - // read all of the pixels - for (int src = 0, dst = 0 ; src < nPix ; ) + for (int src = 0, dst = 0 ; src < nPix ; ++src) { - // figure the end of this callback interval - int srcEnd = src + cbInterval; - if (srcEnd > nPix) - srcEnd = nPix; - - // read one callback chunk of pixels - for ( ; src < srcEnd ; ++src) + // clock in and read the next pixel + clock = 1; + ao.enable(); + wait_us(1); + clock = 0; + wait_us(11); + pix[dst++] = ao.read_u16(); + ao.disable(); + + // clock skipped pixels + for (int i = 0 ; i < skip ; ++i, ++src) { - // clock in and read the next pixel clock = 1; - ao.enable(); - wait_us(1); clock = 0; - wait_us(11); - pix[dst++] = ao.read_u16(); - ao.disable(); - - // clock skipped pixels - for (int i = 0 ; i < skip ; ++i, ++src) - { - clock = 1; - clock = 0; - } } - - // call the callback, if we're not at the last pixel - if (cb != 0 && src < nPix) - (*cb)(cbctx); } // clock out one extra pixel to leave A1 in the high-Z state
--- a/ccdSensor.h Fri Feb 27 04:14:04 2015 +0000 +++ b/ccdSensor.h Fri Feb 27 07:41:29 2015 +0000 @@ -92,21 +92,21 @@ bool highResScan(int &pos) { // read the array - ccd.read(pix, npix, ccdReadCB, 0, 3); + ccd.read(pix, npix); - // get the average brightness at each end of the sensor - long avg1 = (long(pix[0]) + long(pix[1]) + long(pix[2]) + long(pix[3]) + long(pix[4]))/5; - long avg2 = (long(pix[npix-1]) + long(pix[npix-2]) + long(pix[npix-3]) + long(pix[npix-4]) + long(pix[npix-5]))/5; + // get the brightness at each end of the sensor + long b1 = pix[0]; + long b2 = pix[npix-1]; // Work from the bright end to the dark end. VP interprets the // Z axis value as the amount the plunger is pulled: zero is the // rest position, and the axis maximum is fully pulled. So we // essentially want to report how much of the sensor is lit, // since this increases as the plunger is pulled back. - int si = 1, di = 1; - long avgHi = avg1; - if (avg1 < avg2) - si = npix - 2, di = -1, avgHi = avg2; + int si = 0, di = 1; + long hi = b1; + if (b1 < b2) + si = npix - 1, di = -1, hi = b2; // Figure the shadow threshold. In practice, the portion of the // sensor that's not in shadow has all pixels consistently near @@ -122,17 +122,9 @@ // = lo*1/3 + hi*2/3 // = (lo + hi*2)/3 // - // Then multiply the whole thing by 3 to factor out the averaging - // of each three adjacent pixels that we do in the loop (to save a - // little time on a mulitply on each loop): - // - // threshold' = lo + 2*hi - // - // Now, 'lo' is always one of avg1 or avg2, and 'hi' is the other - // one, so we can rewrite this as hi + avg1 + avg2. We also already - // pulled out 'hi' as avgHi, so we finally come to the final - // simplified expression: - long midpt = avg1 + avg2 + avgHi; + // Now, 'lo' is always one of b1 or b2, and 'hi' is the other + // one, so we can rewrite this as: + long midpt = (b1 + b2 + hi)/3; // If we have enough contrast, proceed with the scan. // @@ -141,13 +133,13 @@ // or the sensor is misaligned and is either fully in or out of shadow // (it's supposed to be mounted such that the edge of the shadow always // falls within the sensor, for any possible plunger position). - if (labs(avg1 - avg2) > 0x1000) + if (labs(b1 - b2) > 0x1000) { uint16_t *pixp = pix + si; - for (int n = 1 ; n < npix - 1 ; ++n, pixp += di) + for (int n = 0 ; n < npix ; ++n, pixp += di) { // if we've crossed the midpoint, report this position - if (long(pixp[-1]) + long(pixp[0]) + long(pixp[1]) < midpt) + if (long(*pixp) < midpt) { // note the new position pos = n; @@ -166,7 +158,10 @@ // send reports for all pixels int idx = 0; while (idx < npix) + { js.updateExposure(idx, npix, pix); + wait_ms(1); + } // The pixel dump requires many USB reports, since each report // can only send a few pixel values. An integration cycle has
--- a/config.h Fri Feb 27 04:14:04 2015 +0000 +++ b/config.h Fri Feb 27 07:41:29 2015 +0000 @@ -165,6 +165,26 @@ const int ZBLaunchBallPort = 32; const int LaunchBallButton = 24; +// Distance necessary to push the plunger to activate the simulated +// launch ball button, in inches. A standard pinball plunger can be +// pushed forward about 1/2". However, the barrel spring is very +// stiff, and anything more than about 1/8" requires quite a bit +// of force. Ideally the force required should be about the same as +// for any ordinary pushbutton. +// +// On my cabinet, empirically, a distance around 2mm (.08") seems +// to work pretty well. It's far enough that it doesn't trigger +// spuriously, but short enough that it responds to a reasonably +// light push. +// +// You might need to adjust this up or down to get the right feel. +// Alternatively, if you don't like the "push" gesture at all and +// would prefer to only make the plunger respond to a pull-and-release +// motion, simply set this to, say, 2.0 - it's impossible to push a +// plunger forward that far, so that will effectively turn off the +// push mode. +const float LaunchBallPushDistance = .08; + // -------------------------------------------------------------------------- //
--- a/main.cpp Fri Feb 27 04:14:04 2015 +0000 +++ b/main.cpp Fri Feb 27 07:41:29 2015 +0000 @@ -153,9 +153,10 @@ // long yellow/green = everything's working, but the plunger hasn't // been calibrated; follow the calibration procedure described above. // This flash mode won't appear if the CCD has been disabled. Note -// that the device can't tell whether a CCD is physically attached, -// so you should use the config command to disable the CCD software -// features if you won't be attaching a CCD. +// that the device can't tell whether a CCD is physically attached; +// if you don't have a CCD attached, you can set the appropriate option +// in config.h or use the Windows config tool to disable the CCD +// software features. // // alternating blue/green = everything's working // @@ -442,6 +443,19 @@ // button input map array DigitalIn *buttonDigIn[32]; +// button state +struct ButtonState +{ + // current on/off state + int pressed; + + // Sticky time remaining for current state. When a + // state transition occurs, we set this to a debounce + // period. Future state transitions will be ignored + // until the debounce time elapses. + int t; +} buttonState[32]; + // timer for button reports static Timer buttonTimer; @@ -462,24 +476,67 @@ } -// read the raw button input state -uint32_t readButtonsRaw() +// read the button input state +uint32_t readButtons() { // start with all buttons off uint32_t buttons = 0; + // figure the time elapsed since the last scan + int dt = buttonTimer.read_ms(); + + // reset the timef for the next scan + buttonTimer.reset(); + // scan the button list uint32_t bit = 1; - for (int i = 0 ; i < countof(buttonDigIn) ; ++i, bit <<= 1) + DigitalIn **di = buttonDigIn; + ButtonState *bs = buttonState; + for (int i = 0 ; i < countof(buttonDigIn) ; ++i, ++di, ++bs, bit <<= 1) { - if (buttonDigIn[i] != 0 && !buttonDigIn[i]->read()) - buttons |= bit; + // read this button + if (*di != 0) + { + // deduct the elapsed time since the last update + // from the button's remaining sticky time + bs->t -= dt; + if (bs->t < 0) + bs->t = 0; + + // If the sticky time has elapsed, note the new physical + // state of the button. If we still have sticky time + // remaining, ignore the physical state; the last state + // change persists until the sticky time elapses so that + // we smooth out any "bounce" (electrical transients that + // occur when the switch contact is opened or closed). + if (bs->t == 0) + { + // get the new physical state + int pressed = !(*di)->read(); + + // update the button's logical state if this is a change + if (pressed != bs->pressed) + { + // store the new state + bs->pressed = pressed; + + // start a new sticky period for debouncing this + // state change + bs->t = 1000; + } + } + + // if it's pressed, OR its bit into the state + if (bs->pressed) + buttons |= bit; + } } - // return the button list + // return the new button list return buttons; } +#if 0 // Read buttons with debouncing. // // Debouncing is the process of filtering out transients from button @@ -505,7 +562,7 @@ uint32_t b; // Change mask at this report. This is a bit mask of the buttons - // that *didn't* change on this report. AND this mask with a + // that changed on this report. AND the NOT of this mask with a // new reading to filter buttons out of the new reading that // changed on this report. uint32_t m; @@ -524,19 +581,20 @@ // start timing the next interval buttonTimer.reset(); - // mask out changes for any buttons that changed state within the - // past 50ms - for (int i = 1 ; i < countof(readings) && ms < 50 ; ++i) + // Mask out changes for any buttons that changed state within the + // past 50ms. This ensures that each state change sticks for at + // least 50ms, which should be long enough to be sure that a change + // that reverses a prior change isn't just a transient. + for (int i = 1, j = ri - 1 ; i < countof(readings) && ms < 50 ; ++i, --j) { // find the next prior reading, wrapping in the circular buffer - int j = ri - i; if (j < 0) j = countof(readings) - 1; reading *rj = &readings[j]; // For any button that changed state in the prior reading 'rj', // remove any new change and restore it to its 'rj' state. - b &= rj->m; + b &= ~rj->m; b |= rj->b; // add in the time to the next prior report @@ -547,7 +605,7 @@ uint32_t m = b ^ bPrv; // save the change mask and changed button vector in our history entry - r->m = ~m; + r->m = m; r->b = b & m; // save this as the prior report @@ -561,6 +619,7 @@ // return the debounced result return b; } +#endif // --------------------------------------------------------------------------- // @@ -917,21 +976,6 @@ // --------------------------------------------------------------------------- // -// CCD read interval callback. When reading the CCD, we'll call this -// several times over the course of the read loop to refresh the button -// states. This allows us to debounce the buttons while the long CCD -// read cycle is taking place, so that we can reliably report button -// states after each CCD read cycle. (The read cycle takes about 30ms, -// which should be enough time to reliably debounce the buttons.) -// -void ccdReadCB(void *) -{ - // read the keyboard - readButtonsDebounced(); -} - -// --------------------------------------------------------------------------- -// // Include the appropriate plunger sensor definition. This will define a // class called PlungerSensor, with a standard interface that we use in // the main loop below. This is *kind of* like a virtual class interface, @@ -1185,6 +1229,11 @@ Timer lbTimer; lbTimer.start(); + // Launch Ball simulated push timer. We start this when we simulate + // the button push, and turn off the simulated button when enough time + // has elapsed. + Timer lbBtnTimer; + // Simulated button states. This is a vector of button states // for the simulated buttons. We combine this with the physical // button states on each USB joystick report, so we will report @@ -1245,12 +1294,11 @@ // host requests for (;;) { - // Look for an incoming report. Continue processing input as - // long as there's anything pending - this ensures that we - // handle input in as timely a fashion as possible by deferring - // output tasks as long as there's input to process. + // Look for an incoming report. Process a few input reports in + // a row, but stop after a few so that a barrage of inputs won't + // starve our output event processing. HID_REPORT report; - while (js.readNB(&report)) + for (int rr = 0 ; rr < 4 && js.readNB(&report) ; ++rr) { // all Led-Wiz reports are 8 bytes exactly if (report.length == 8) @@ -1559,8 +1607,10 @@ } // Check for a simulated Launch Ball button press, if enabled - if (ZBLaunchBallPort != 0 && wizOn[ZBLaunchBallPort-1]) + if (ZBLaunchBallPort != 0) { + const int cockThreshold = JOYMAX/3; + const int pushThreshold = int(-JOYMAX/3 * LaunchBallPushDistance); int newState = lbState; switch (lbState) { @@ -1568,9 +1618,9 @@ // Base state. If the plunger is pulled back by an inch // or more, go to "cocked" state. If the plunger is pushed // forward by 1/4" or more, go to "launch" state. - if (znew >= JOYMAX/3) + if (znew >= cockThreshold) newState = 1; - else if (znew < -JOYMAX/12) + else if (znew <= pushThreshold) newState = 3; break; @@ -1582,7 +1632,7 @@ // to trigger a launch. if (firing || znew <= 0) newState = 3; - else if (znew < JOYMAX/3) + else if (znew < cockThreshold) newState = 2; break; @@ -1590,8 +1640,11 @@ // Uncocked state. If the plunger is more than an inch // retracted, return to cocked state. If we've been in // the uncocked state for more than half a second, return - // to the base state. - if (znew >= JOYMAX/3) + // to the base state. This allows the user to return the + // plunger to rest without triggering a launch, by moving + // it at manual speed to the rest position rather than + // releasing it. + if (znew >= cockThreshold) newState = 1; else if (lbTimer.read_ms() > 500) newState = 0; @@ -1600,7 +1653,7 @@ case 3: // Launch state. If the plunger is no longer pushed // forward, switch to launch rest state. - if (znew > -JOYMAX/24) + if (znew >= 0) newState = 4; break; @@ -1609,7 +1662,7 @@ // again, switch back to launch state. If not, and we've // been in this state for at least 200ms, return to the // default state. - if (znew < -JOYMAX/12) + if (znew <= pushThreshold) newState = 3; else if (lbTimer.read_ms() > 200) newState = 0; @@ -1617,11 +1670,18 @@ } // change states if desired + const uint32_t lbButtonBit = (1 << (LaunchBallButton - 1)); if (newState != lbState) { - // if we're entering Launch state, press the Launch Ball button - if (newState == 3 && lbState != 4) - simButtons |= (1 << (LaunchBallButton - 1)); + // if we're entering Launch state, and the ZB Launch Ball + // LedWiz signal is turned on, simulate a Launch Ball button + // press + if (newState == 3 && lbState != 4 && wizOn[ZBLaunchBallPort-1]) + { + lbBtnTimer.reset(); + lbBtnTimer.start(); + simButtons |= lbButtonBit; + } // if we're switching to state 0, release the button if (newState == 0) @@ -1633,6 +1693,17 @@ // start timing in the new state lbTimer.reset(); } + + // if the simulated Launch Ball button press is in effect, + // and either it's been in effect too long or the ZB Launch + // Ball signal is no longer active, turn off the button + if ((simButtons & lbButtonBit) != 0 + && (!wizOn[ZBLaunchBallPort-1] || lbBtnTimer.read_ms() > 250)) + { + lbBtnTimer.stop(); + simButtons &= ~lbButtonBit; + } + } // If a firing event is in progress, generate synthetic reports to @@ -1719,7 +1790,7 @@ } // update the buttons - uint32_t buttons = readButtonsDebounced(); + uint32_t buttons = readButtons(); // If it's been long enough since our last USB status report, // send the new report. We throttle the report rate because