Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
Diff: main.cpp
- Revision:
- 87:8d35c74403af
- Parent:
- 86:e30a1f60f783
- Child:
- 88:98bce687e6c0
--- a/main.cpp Fri Apr 21 18:50:37 2017 +0000 +++ b/main.cpp Tue May 09 05:48:37 2017 +0000 @@ -47,34 +47,48 @@ // have native support for this type of input; as with the nudge setup, you just // have to set some options in VP to activate the plunger. // -// The Pinscape software supports optical sensors (the TAOS TSL1410R and TSL1412R -// linear sensor arrays) as well as slide potentiometers. The specific equipment -// that's supported, along with physical mounting and wiring details, can be found -// in the Build Guide. +// We support several sensor types: // -// Note that VP has built-in support for plunger devices like this one, but -// some VP tables can't use it without some additional scripting work. The -// Build Guide has advice on adjusting tables to add plunger support when -// necessary. +// - AEDR-8300-1K2 optical encoders. These are quadrature encoders with +// reflective optical sensing and built-in lighting and optics. The sensor +// is attached to the plunger so that it moves with the plunger, and slides +// along a guide rail with a reflective pattern of regularly spaces bars +// for the encoder to read. We read the plunger position by counting the +// bars the sensor passes as it moves across the rail. This is the newest +// option, and it's my current favorite because it's highly accurate, +// precise, and fast, plus it's relatively inexpensive. +// +// - Slide potentiometers. There are slide potentioneters available with a +// long enough travel distance (at least 85mm) to cover the plunger travel. +// Attach the plunger to the potentiometer knob so that the moving the +// plunger moves the pot knob. We sense the position by simply reading +// the analog voltage on the pot brush. A pot with a "linear taper" (that +// is, the resistance varies linearly with the position) is required. +// This option is cheap, easy to set up, and works well. // -// For best results, the plunger sensor should be calibrated. The calibration -// is stored in non-volatile memory on board the KL25Z, so it's only necessary -// to do the calibration once, when you first install everything. (You might -// also want to re-calibrate if you physically remove and reinstall the CCD -// sensor or the mechanical plunger, since their alignment shift change slightly -// when you put everything back together.) You can optionally install a -// dedicated momentary switch or pushbutton to activate the calibration mode; -// this is describe in the project documentation. If you don't want to bother -// with the extra button, you can also trigger calibration using the Windows -// setup software, which you can find on the Pinscape project page. +// - VL6108X time-of-flight distance sensor. This is an optical distance +// sensor that measures the distance to a nearby object (within about 10cm) +// by measuring the travel time for reflected pulses of light. It's fairly +// cheap and easy to set up, but I don't recommend it because it has very +// low precision. // -// The calibration procedure is described in the project documentation. Briefly, -// when you trigger calibration mode, the software will scan the CCD for about -// 15 seconds, during which you should simply pull the physical plunger back -// all the way, hold it for a moment, and then slowly return it to the rest -// position. (DON'T just release it from the retracted position, since that -// let it shoot forward too far. We want to measure the range from the park -// position to the fully retracted position only.) +// - TSL1410R/TSL1412R linear array optical sensors. These are large optical +// sensors with the pixels arranged in a single row. The pixel arrays are +// large enough on these to cover the travel distance of the plunger, so we +// can set up the sensor near the plunger in such a way that the plunger +// casts a shadow on the sensor. We detect the plunger position by finding +// the edge of the sahdow in the image. The optics for this setup are very +// simple since we don't need any lenses. This was the first sensor we +// supported, and works very well, but unfortunately the sensor is difficult +// to find now since it's been discontinued by the manufacturer. +// +// The v2 Build Guide has details on how to build and configure all of the +// sensor options. +// +// Visual Pinball has built-in support for plunger devices like this one, but +// some older VP tables (particularly for VP 9) can't use it without some +// modifications to their scripting. The Build Guide has advice on how to +// fix up VP tables to add plunger support when necessary. // // - Button input wiring. You can assign GPIO ports as inputs for physical // pinball-style buttons, such as flipper buttons, a Start button, coin @@ -107,28 +121,34 @@ // current handing. The Build Guide has a reference circuit design for this // purpose that's simple and inexpensive to build. // -// - Enhanced LedWiz emulation with TLC5940 PWM controller chips. You can attach -// external PWM controller chips for controlling device outputs, instead of using -// the on-board GPIO ports as described above. The software can control a set of -// daisy-chained TLC5940 chips. Each chip provides 16 PWM outputs, so you just -// need two of them to get the full complement of 32 output ports of a real LedWiz. -// You can hook up even more, though. Four chips gives you 64 ports, which should -// be plenty for nearly any virtual pinball project. To accommodate the larger -// supply of ports possible with the PWM chips, the controller software provides -// a custom, extended version of the LedWiz protocol that can handle up to 128 -// ports. PC software designed only for the real LedWiz obviously won't know -// about the extended protocol and won't be able to take advantage of its extra -// capabilities, but the latest version of DOF (DirectOutput Framework) *does* -// know the new language and can take full advantage. Older software will still -// work, though - the new extensions are all backward compatible, so old software -// that only knows about the original LedWiz protocol will still work, with the -// obvious limitation that it can only access the first 32 ports. +// - Enhanced LedWiz emulation with TLC5940 and/or TLC59116 PWM controller chips. +// You can attach external PWM chips for controlling device outputs, instead of +// using (or in addition to) the on-board GPIO ports as described above. The +// software can control a set of daisy-chained TLC5940 or TLC59116 chips. Each +// chip provides 16 PWM outputs, so you just need two of them to get the full +// complement of 32 output ports of a real LedWiz. You can hook up even more, +// though. Four chips gives you 64 ports, which should be plenty for nearly any +// virtual pinball project. // // The Pinscape Expansion Board project (which appeared in early 2016) provides // a reference hardware design, with EAGLE circuit board layouts, that takes full // advantage of the TLC5940 capability. It lets you create a customized set of // outputs with full PWM control and power handling for high-current devices -// built in to the boards. +// built in to the boards. +// +// To accommodate the larger supply of ports possible with the external chips, +// the controller software provides a custom, extended version of the LedWiz +// protocol that can handle up to 128 ports. Legacy PC software designed only +// for the original LedWiz obviously can't use the extended protocol, and thus +// can't take advantage of its extra capabilities, but the latest version of +// DOF (DirectOutput Framework) *does* know the new language and can take full +// advantage. Older software will still work, though - the new extensions are +// all backwards compatible, so old software that only knows about the original +// LedWiz protocol will still work, with the limitation that it can only access +// the first 32 ports. In addition, we provide a replacement LEDWIZ.DLL that +// creates virtual LedWiz units representing additional ports beyond the first +// 32. This allows legacy LedWiz client software to address all ports by +// making them think that you have several physical LedWiz units installed. // // - Night Mode control for output devices. You can connect a switch or button // to the controller to activate "Night Mode", which disables feedback devices @@ -222,6 +242,7 @@ #include "FreescaleIAP.h" #include "crc32.h" #include "TLC5940.h" +#include "TLC59116.h" #include "74HC595.h" #include "nvm.h" #include "TinyDigitalIn.h" @@ -237,6 +258,7 @@ #include "nullSensor.h" #include "barCodeSensor.h" #include "distanceSensor.h" +#include "tsl14xxSensor.h" #define DECL_EXTERNS @@ -646,17 +668,26 @@ // about 50 GPIO pins. So if you want to do everything with GPIO ports, // you have to ration pins among features. // -// To overcome some of these limitations, we also provide two types of +// To overcome some of these limitations, we also support several external // peripheral controllers that allow adding many more outputs, using only -// a small number of GPIO pins to interface with the peripherals. First, -// we support TLC5940 PWM controller chips. Each TLC5940 provides 16 ports -// with full PWM, and multiple TLC5940 chips can be daisy-chained. The -// chip only requires 5 GPIO pins for the interface, no matter how many -// chips are in the chain, so it effectively converts 5 GPIO pins into -// almost any number of PWM outputs. Second, we support 74HC595 chips. -// These provide only digital outputs, but like the TLC5940 they can be -// daisy-chained to provide almost unlimited outputs with a few GPIO pins -// to control the whole chain. +// a small number of GPIO pins to interface with the peripherals: +// +// - TLC5940 PWM controller chips. Each TLC5940 provides 16 ports with +// 12-bit PWM, and multiple TLC5940 chips can be daisy-chained. The +// chips connect via 5 GPIO pins, and since they're daisy-chainable, +// one set of 5 pins can control any number of the chips. So this chip +// effectively converts 5 GPIO pins into almost any number of PWM outputs. +// +// - TLC59116 PWM controller chips. These are similar to the TLC5940 but +// a newer generation with an improved design. These use an I2C bus, +// allowing up to 14 chips to be connected via 3 GPIO pins. +// +// - 74HC595 shift register chips. These provide 8 digital (on/off only) +// outputs per chip. These need 4 GPIO pins, and like the other can be +// daisy chained to add more outputs without using more GPIO pins. These +// are advantageous for outputs that don't require PWM, since the data +// transfer sizes are so much smaller. The expansion boards use these +// for the chime board outputs. // // Direct GPIO output ports and peripheral controllers can be mixed and // matched in one system. The assignment of pins to ports and the @@ -735,7 +766,7 @@ // Gamma correction table for 8-bit input values -static const uint8_t gamma[] = { +static const uint8_t dof_to_gamma_8bit[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, @@ -761,7 +792,7 @@ { public: LwGammaOut(LwOut *o) : out(o) { } - virtual void set(uint8_t val) { out->set(gamma[val]); } + virtual void set(uint8_t val) { out->set(dof_to_gamma_8bit[val]); } private: LwOut *out; @@ -903,10 +934,55 @@ uint8_t prv; }; - - +// +// TLC59116 interface object +// +TLC59116 *tlc59116 = 0; +void init_tlc59116(Config &cfg) +{ + // Create the interface if any chips are enabled + if (cfg.tlc59116.chipMask != 0) + { + // set up the interface + tlc59116 = new TLC59116( + wirePinName(cfg.tlc59116.sda), + wirePinName(cfg.tlc59116.scl), + wirePinName(cfg.tlc59116.reset)); + + // initialize the chips + tlc59116->init(); + } +} + +// LwOut class for TLC59116 outputs. The 'addr' value in the constructor +// is low 4 bits of the chip's I2C address; this is the part of the address +// that's configurable per chip. 'port' is the output number on the chip +// (0-15). +// +// Note that we don't need a separate gamma-corrected subclass for this +// output type, since there's no loss of precision with the standard layered +// gamma (it emits 8-bit values, and we take 8-bit inputs). +class Lw59116Out: public LwOut +{ +public: + Lw59116Out(uint8_t addr, uint8_t port) : addr(addr), port(port) { prv = 0; } + virtual void set(uint8_t val) + { + if (val != prv) + tlc59116->set(addr, port, prv = val); + } + +protected: + uint8_t addr; + uint8_t port; + uint8_t prv; +}; + + +// // 74HC595 interface object. Set this up with the port assignments in // config.h. +// HC595 *hc595 = 0; // initialize the 74HC595 interface @@ -1276,13 +1352,22 @@ break; case PortType74HC595: - // 74HC595 port (if we don't have an HC595 controller object, or it's not a valid - // output number, create a virtual port) + // 74HC595 port (if we don't have an HC595 controller object, or it's not + // a valid output number, create a virtual port) if (hc595 != 0 && pin < cfg.hc595.nchips*8) lwp = new Lw595Out(pin); else lwp = new LwVirtualOut(); break; + + case PortTypeTLC59116: + // TLC59116 port. The pin number in the config encodes the chip address + // in the high 4 bits and the output number on the chip in the low 4 bits. + // There's no gamma-corrected version of this output handler, so we don't + // need to worry about that here; just use the layered gamma as needed. + if (tlc59116 != 0) + lwp = new Lw59116Out((pin >> 4) & 0x0F, pin & 0x0F); + break; case PortTypeVirtual: case PortTypeDisabled: @@ -3907,10 +3992,8 @@ else { // The latch didn't stick, so PSU2 was still off at - // our last check. Try pulsing it again in case PSU2 - // was turned on since the last check. - psu2_status_set->write(1); - psu2_state = 2; + // our last check. Return to idle state. + psu2_state = 1; } break; @@ -4197,6 +4280,8 @@ saveConfigSucceededFlag = 0x40; // start the followup timer + saveConfigFollowupTime = tFollowup; + saveConfigFollowupTimer.reset(); saveConfigFollowupTimer.start(); // if a reboot is pending, flag it @@ -4336,10 +4421,10 @@ // the plunger sensor interface object PlungerSensor *plungerSensor = 0; -// wait for the plunger sensor to complete any outstanding read +// wait for the plunger sensor to complete any outstanding DMA transfer static void waitPlungerIdle(void) { - while (!plungerSensor->ready()) { } + while (plungerSensor->dmaBusy()) { } } // Create the plunger sensor based on the current configuration. If @@ -4409,8 +4494,9 @@ break; } - // set the jitter filter - plungerSensor->setJitterWindow(cfg.plunger.jitterWindow); + // initialize the config variables affecting the plunger + plungerSensor->onConfigChange(19, cfg); + plungerSensor->onConfigChange(20, cfg); } // Global plunger calibration mode flag @@ -4473,9 +4559,6 @@ { // not in a firing event yet firing = 0; - - // no history yet - histIdx = 0; } // Collect a reading from the plunger sensor. The main loop calls @@ -4492,25 +4575,6 @@ PlungerReading r; if (plungerSensor->read(r)) { - // Pull the previous reading from the history - const PlungerReading &prv = nthHist(0); - - // If the new reading is within 1ms of the previous reading, - // ignore it. We require a minimum time between samples to - // ensure that we have a usable amount of precision in the - // denominator (the time interval) for calculating the plunger - // velocity. The CCD sensor hardware takes about 2.5ms to - // read, so it will never be affected by this, but other sensor - // types don't all have the same hardware cycle time, so we need - // to throttle them artificially. E.g., the potentiometer only - // needs one ADC sample per reading, which only takes about 15us; - // the quadrature sensor needs no time at all since it keeps - // track of the position continuously via interrupts. We don't - // need to check which sensor type we have here; we just ignore - // readings until the minimum interval has passed. - if (uint32_t(r.t - prv.t) < 1000UL) - return; - // check for calibration mode if (plungerCalMode) { @@ -4575,98 +4639,130 @@ r.pos = -JOYMAX; } - // Calculate the velocity from the second-to-last reading - // to here, in joystick distance units per microsecond. - // Note that we use the second-to-last reading rather than - // the very last reading to give ourselves a little longer - // time base. The time base is so short between consecutive - // readings that the error bars in the position would be too - // large. + // Look for a firing event - the user releasing the plunger and + // allowing it to shoot forward at full speed. Wait at least 5ms + // between samples for this, to help distinguish random motion + // from the rapid motion of a firing event. // - // For reference, the physical plunger velocity ranges up - // to about 100,000 joystick distance units/sec. This is - // based on empirical measurements. The typical time for - // a real plunger to travel the full distance when released - // from full retraction is about 85ms, so the average velocity - // covering this distance is about 56,000 units/sec. The - // peak is probably about twice that. In real-world units, - // this translates to an average speed of about .75 m/s and - // a peak of about 1.5 m/s. + // There's a trade-off in the choice of minimum sampling interval. + // The longer we wait, the more certain we can be of the trend. + // But if we wait too long, the user will perceive a delay. We + // also want to sample frequently enough to see the release motion + // at intermediate steps along the way, so the sampling has to be + // considerably faster than the whole travel time, which is about + // 25-50ms. + if (uint32_t(r.t - prv.t) < 5000UL) + return; + + // assume that we'll report this reading as-is + z = r.pos; + + // Firing event detection. + // + // A "firing event" is when the player releases the plunger from + // a retracted position, allowing it to shoot forward under the + // spring tension. // - // Note that we actually calculate the value here in units - // per *microsecond* - the discussion above is in terms of - // units/sec because that's more on a human scale. Our - // choice of internal units here really isn't important, - // since we only use the velocity for comparison purposes, - // to detect acceleration trends. We therefore save ourselves - // a little CPU time by using the natural units of our inputs. + // We monitor the plunger motion for these events, and when they + // occur, we report an "idealized" version of the motion to the + // PC. The idealized version consists of a series of readings + // frozen at the fully retracted position for the whole duration + // of the forward travel, followed by a series of readings at the + // fully forward position for long enough for the plunger to come + // mostly to rest. The series of frozen readings aren't meant to + // be perceptible to the player - we try to keep them short enough + // that they're not apparent as delay. Instead, they're for the + // PC client software's benefit. PC joystick clients use polling, + // so they only see an unpredictable subset of the readings we + // send. The only way to be sure that the client sees a particular + // reading is to hold it for long enough that the client is sure to + // poll within the hold interval. In the case of the plunger + // firing motion, it's important that the client sees the *ends* + // of the travel - the fully retracted starting position in + // particular. If the PC client only polls for a sample while the + // plunger is somewhere in the middle of the travel, the PC will + // think that the firing motion *started* in that middle position, + // so it won't be able to model the right amount of momentum when + // the plunger hits the ball. We try to ensure that the PC sees + // the right starting point by reporting the starting point for + // extra time during the forward motion. By the same token, we + // want the PC to know that the plunger has moved all the way + // forward, rather than mistakenly thinking that it stopped + // somewhere in the middle of the travel, so we freeze at the + // forward position for a short time. // - // We don't care about the absolute velocity; this is a purely - // relative calculation. So to speed things up, calculate it - // in the integer domain, using a fixed-point representation - // with a 64K scale. In other words, with the stored values - // shifted left 16 bits from the actual values: the value 1 - // is stored as 1<<16. The position readings are in the range - // -JOYMAX..JOYMAX, which fits in 16 bits, and the time - // differences will generally be on the scale of a few - // milliseconds = thousands of microseconds. So the velocity - // figures will fit nicely into a 32-bit fixed point value with - // a 64K scale factor. - const PlungerReading &prv2 = nthHist(1); - int v = ((r.pos - prv2.pos) * 65536L)/int(r.t - prv2.t); - - // presume we'll report the latest instantaneous reading - z = r.pos; - - // Check firing events + // To detect a firing event, we look for forward motion that's + // fast enough to be a firing event. To determine how fast is + // fast enough, we use a simple model of the plunger motion where + // the acceleration is constant. This is only an approximation, + // as the spring force actually varies with spring's compression, + // but it's close enough for our purposes here. + // + // Do calculations in fixed-point 2^48 scale with 64-bit ints. + // acc2 = acceleration/2 for 50ms release time, units of unit + // distances per microsecond squared, where the unit distance + // is the overall travel from the starting retracted position + // to the park position. + const int32_t acc2 = 112590; // 2^48 scale switch (firing) { case 0: - // Default state - not in a firing event. - - // If we have forward motion from a position that's retracted - // beyond a threshold, enter phase 1. If we're not pulled back - // far enough, don't bother with this, as a release wouldn't - // be strong enough to require the synthetic firing treatment. - if (v < 0 && r.pos > JOYMAX/6) + // Not in firing mode. If we're retracted a bit, and the + // motion is forward at a fast enough rate to look like a + // release, enter firing mode. + if (r.pos > JOYMAX/6) { - // enter firing phase 1 - firingMode(1); - - // if in calibration state 1 (at rest), switch to state 2 (not - // at rest) - if (calState == 1) - calState = 2; - - // we don't have a freeze position yet, but note the start time - f1.pos = 0; - f1.t = r.t; - - // Figure the barrel spring "bounce" position in case we complete - // the firing event. This is the amount that the forward momentum - // of the plunger will compress the barrel spring at the peak of - // the forward travel during the release. Assume that this is - // linearly proportional to the starting retraction distance. - // The barrel spring is about 1/6 the length of the main spring, - // so figure it compresses by 1/6 the distance. (This is overly - // simplistic and not very accurate, but it seems to give good - // visual results, and that's all it's for.) - f2.pos = -r.pos/6; + const uint32_t dt = uint32_t(r.t - prv.t); + const uint32_t dt2 = dt*dt; // dt^2 + if (r.pos < prv.pos - int((prv.pos*acc2*uint64_t(dt2)) >> 48)) + { + // Tentatively enter firing mode. Use the prior reading + // as the starting point, and freeze reports for now. + firingMode(1); + f0 = prv; + z = f0.pos; + + // if in calibration state 1 (at rest), switch to + // state 2 (not at rest) + if (calState == 1) + calState = 2; + } } break; case 1: - // Phase 1 - acceleration. If we cross the zero point, trigger - // the firing event. Otherwise, continue monitoring as long as we - // see acceleration in the forward direction. + // Tentative firing mode: the plunger was moving forward + // at last check. To stay in firing mode, the plunger has + // to keep moving forward fast enough to look like it's + // moving under spring force. To figure out how fast is + // fast enough, we use a simple model where the acceleration + // is constant over the whole travel distance and the total + // travel time is 50ms. The acceleration actually varies + // slightly since it comes from the spring force, which + // is linear in the displacement; but the plunger spring is + // fairly compressed even when the plunger is all the way + // forward, so the difference in tension from one end of + // the travel to the other is fairly small, so it's not too + // far off to model it as constant. And the real travel + // time obviously isn't a constant, but all we need for + // that is an upper bound. So: we'll figure the time since + // we entered firing mode, and figure the distance we should + // have traveled to complete the trip within the maximum + // time allowed. If we've moved far enough, we'll stay + // in firing mode; if not, we'll exit firing mode. And if + // we cross the finish line while still in firing mode, + // we'll switch to the next phase of the firing event. if (r.pos <= 0) { - // switch to the synthetic firing mode + // We crossed the park position. Switch to the second + // phase of the firing event, where we hold the reported + // position at the "bounce" position (where the plunger + // is all the way forward, compressing the barrel spring). + // We'll stick here long enough to ensure that the PC + // client (Visual Pinball or whatever) sees the reading + // and processes the release motion via the simulated + // physics. firingMode(2); - z = f2.pos; - - // note the start time for the firing phase - f2.t = r.t; // if in calibration mode, and we're in state 2 (moving), // collect firing statistics for calibration purposes @@ -4676,142 +4772,96 @@ // come to rest calState = 0; - // collect average firing time statistics in millseconds, if - // it's in range (20 to 255 ms) - int dt = uint32_t(r.t - f1.t)/1000UL; - if (dt >= 20 && dt <= 255) + // collect average firing time statistics in millseconds, + // if it's in range (20 to 255 ms) + const int dt = uint32_t(r.t - f0.t)/1000UL; + if (dt >= 15 && dt <= 255) { calRlsTimeSum += dt; calRlsTimeN += 1; cfg.plunger.cal.tRelease = uint8_t(calRlsTimeSum / calRlsTimeN); } } - } - else if (v < vprv2) - { - // We're still accelerating, and we haven't crossed the zero - // point yet - stay in phase 1. (Note that forward motion is - // negative velocity, so accelerating means that the new - // velocity is more negative than the previous one, which - // is to say numerically less than - that's why the test - // for acceleration is the seemingly backwards 'v < vprv'.) - - // If we've been accelerating for at least 20ms, we're probably - // really doing a release. Jump back to the recent local - // maximum where the release *really* started. This is always - // a bit before we started seeing sustained accleration, because - // the plunger motion for the first few milliseconds is too slow - // for our sensor precision to reliably detect acceleration. - if (f1.pos != 0) - { - // we have a reset point - freeze there - z = f1.pos; - } - else if (uint32_t(r.t - f1.t) >= 20000UL) - { - // it's been long enough - set a reset point. - f1.pos = z = histLocalMax(r.t, 50000UL); - } + + // Figure the "bounce" position as forward of the park + // position by 1/6 of the starting retraction distance. + // This simulates the momentum of the plunger compressing + // the barrel spring on the rebound. The barrel spring + // can compress by about 1/6 of the maximum retraction + // distance, so we'll simply treat its compression as + // proportional to the retraction. (It might be more + // realistic to use a slightly higher value here, maybe + // 1/4 or 1/3 or the retraction distance, capping it at + // a maximum of 1/6, because the real plunger probably + // compresses the barrel spring by 100% with less than + // 100% retraction. But that won't affect the physics + // meaningfully, just the animation, and the effect is + // small in any case.) + z = f0.pos = -f0.pos / 6; + + // reset the starting time for this phase + f0.t = r.t; } else { - // We're not accelerating. Cancel the firing event. - firingMode(0); - calState = 1; + // check for motion since the start of the firing event + const uint32_t dt = uint32_t(r.t - f0.t); + const uint32_t dt2 = dt*dt; // dt^2 + if (dt < 50000 + && r.pos < f0.pos - int((f0.pos*acc2*uint64_t(dt2)) >> 48)) + { + // It's moving fast enough to still be in a release + // motion. Continue reporting the start position, and + // stay in the first release phase. + z = f0.pos; + } + else + { + // It's not moving fast enough to be a release + // motion. Return to the default state. + firingMode(0); + calState = 1; + } } break; case 2: - // Phase 2 - start of synthetic firing event. Report the fake - // bounce for 25ms. VP polls the joystick about every 10ms, so - // this should be enough time to guarantee that VP sees this - // report at least once. - if (uint32_t(r.t - f2.t) < 25000UL) + // Firing mode, holding at forward compression position. + // Hold here for 25ms. + if (uint32_t(r.t - f0.t) < 25000) { - // report the bounce position - z = f2.pos; + // stay here for now + z = f0.pos; } else { - // it's been long enough - switch to phase 3, where we - // report the park position until the real plunger comes - // to rest + // advance to the next phase, where we report the park + // position until the plunger comes to rest firingMode(3); z = 0; - - // set the start of the "stability window" to the rest position - f3s.t = r.t; - f3s.pos = 0; - - // set the start of the "retraction window" to the actual position - f3r = r; + + // remember when we started + f0.t = r.t; } break; case 3: - // Phase 3 - in synthetic firing event. Report the park position - // until the plunger position stabilizes. Left to its own devices, - // the plunger will usualy bounce off the barrel spring several - // times before coming to rest, so we'll see oscillating motion - // for a second or two. In the simplest case, we can aimply wait - // for the plunger to stop moving for a short time. However, the - // player might intervene by pulling the plunger back again, so - // watch for that motion as well. If we're just bouncing freely, - // we'll see the direction change frequently. If the player is - // moving the plunger manually, the direction will be constant - // for longer. - if (v >= 0) + // Firing event, holding at park position. Stay here for + // a few moments so that the PC client can simulate the + // full release motion, then return to real readings. + if (uint32_t(r.t - f0.t) < 250000) { - // We're moving back (or standing still). If this has been - // going on for a while, the user must have taken control. - if (uint32_t(r.t - f3r.t) > 65000UL) - { - // user has taken control - cancel firing mode - firingMode(0); - break; - } + // stay here a while longer + z = 0; } else { - // forward motion - reset retraction window - f3r.t = r.t; - } - - // Check if we're close to the last starting point. The joystick - // positive axis range (0..4096) covers the retraction distance of - // about 2.5", so 1" is about 1638 joystick units, hence 1/16" is - // about 100 units. - if (abs(r.pos - f3s.pos) < 100) - { - // It's at roughly the same position as the starting point. - // Consider it stable if this has been true for 300ms. - if (uint32_t(r.t - f3s.t) > 300000UL) - { - // we're done with the firing event - firingMode(0); - } - else - { - // it's close to the last position but hasn't been - // here long enough; stay in firing mode and continue - // to report the park position - z = 0; - } - } - else - { - // It's not close enough to the last starting point, so use - // this as a new starting point, and stay in firing mode. - f3s = r; - z = 0; + // it's been long enough - return to normal mode + firingMode(0); } break; } - // save the velocity reading for next time - vprv2 = vprv; - vprv = v; - // Check for auto-zeroing, if enabled if ((cfg.plunger.autoZero.flags & PlungerAutoZeroEnabled) != 0) { @@ -4837,10 +4887,8 @@ } } - // add the new reading to the history - hist[histIdx] = r; - if (++histIdx >= countof(hist)) - histIdx = 0; + // this new reading becomes the previous reading for next time + prv = r; } } @@ -4851,9 +4899,6 @@ return z; } - // get the timestamp of the current joystick report (microseconds) - uint32_t getTimestamp() const { return nthHist(0).t; } - // Set calibration mode on or off void setCalMode(bool f) { @@ -4942,6 +4987,12 @@ bool isFiring() { return firing == 3; } private: + // current reported joystick reading + int z; + + // previous reading + PlungerReading prv; + // Calibration state. During calibration mode, we watch for release // events, to measure the time it takes to complete the release // motion; and we watch for the plunger to come to reset after a @@ -4978,105 +5029,21 @@ firing = m; } - // Find the most recent local maximum in the history data, up to - // the given time limit. - int histLocalMax(uint32_t tcur, uint32_t dt) - { - // start with the prior entry - int idx = (histIdx == 0 ? countof(hist) : histIdx) - 1; - int hi = hist[idx].pos; - - // scan backwards for a local maximum - for (int n = countof(hist) - 1 ; n > 0 ; idx = (idx == 0 ? countof(hist) : idx) - 1) - { - // if this isn't within the time window, stop - if (uint32_t(tcur - hist[idx].t) > dt) - break; - - // if this isn't above the current hith, stop - if (hist[idx].pos < hi) - break; - - // this is the new high - hi = hist[idx].pos; - } - - // return the local maximum - return hi; - } - - // velocity at previous reading, and the one before that - int vprv, vprv2; - - // Circular buffer of recent readings. We keep a short history - // of readings to analyze during firing events. We can only identify - // a firing event once it's somewhat under way, so we need a little - // retrospective information to accurately determine after the fact - // exactly when it started. We throttle our readings to no more - // than one every 1ms, so we have at least N*1ms of history in this - // array. - PlungerReading hist[32]; - int histIdx; - - // get the nth history item (0=last, 1=2nd to last, etc) - inline const PlungerReading &nthHist(int n) const - { - // histIdx-1 is the last written; go from there - n = histIdx - 1 - n; - - // adjust for wrapping - if (n < 0) - n += countof(hist); - - // return the item - return hist[n]; - } - // Firing event state. // - // 0 - Default state. We report the real instantaneous plunger - // position to the joystick interface. - // - // 1 - Moving forward + // 0 - Default state: not in firing event. We report the true + // instantaneous plunger position to the joystick interface. // - // 2 - Accelerating + // 1 - Moving forward at release speed // - // 3 - Firing. We report the rest position for a minimum interval, - // or until the real plunger comes to rest somewhere. + // 2 - Firing - reporting the bounce position + // + // 3 - Firing - reporting the park position // int firing; - // Position/timestamp at start of firing phase 1. When we see a - // sustained forward acceleration, we freeze joystick reports at - // the recent local maximum, on the assumption that this was the - // start of the release. If this is zero, it means that we're - // monitoring accelerating motion but haven't seen it for long - // enough yet to be confident that a release is in progress. - PlungerReading f1; - - // Position/timestamp at start of firing phase 2. The position is - // the fake "bounce" position we report during this phase, and the - // timestamp tells us when the phase began so that we can end it - // after enough time elapses. - PlungerReading f2; - - // Position/timestamp of start of stability window during phase 3. - // We use this to determine when the plunger comes to rest. We set - // this at the beginning of phase 3, and then reset it when the - // plunger moves too far from the last position. - PlungerReading f3s; - - // Position/timestamp of start of retraction window during phase 3. - // We use this to determine if the user is drawing the plunger back. - // If we see retraction motion for more than about 65ms, we assume - // that the user has taken over, because we should see forward - // motion within this timeframe if the plunger is just bouncing - // freely. - PlungerReading f3r; - - // next Z value to report to the joystick interface (in joystick - // distance units) - int z; + // Starting position for current firing mode phase + PlungerReading f0; }; // plunger reader singleton @@ -5574,10 +5541,9 @@ // in a variable-dependent format. configVarSet(data); - // If updating the jitter window (variable 19), apply it immediately - // to the plunger sensor object - if (data[1] == 19) - plungerSensor->setJitterWindow(cfg.plunger.jitterWindow); + // notify the plunger, so that it can update relevant variables + // dynamically + plungerSensor->onConfigChange(data[1], cfg); } else if (data[0] == 67) { @@ -5775,6 +5741,9 @@ // set up the TLC5940 interface, if these chips are present init_tlc5940(cfg); + // initialize the TLC5916 interface, if these chips are present + init_tlc59116(cfg); + // set up 74HC595 interface, if these chips are present init_hc595(cfg); @@ -5787,7 +5756,7 @@ // start the TLC5940 refresh cycle clock if (tlc5940 != 0) tlc5940->start(); - + // Assume that nothing uses keyboard keys. We'll check for keyboard // usage when initializing the various subsystems that can send keys // (buttons, IR). If we find anything that does, we'll create the @@ -5927,6 +5896,8 @@ tlc5940->enable(true); if (hc595 != 0) hc595->enable(true); + if (tlc59116 != 0) + tlc59116->enable(true); // start the LedWiz flash cycle timer wizCycleTimer.start(); @@ -5985,6 +5956,10 @@ // send TLC5940 data updates if applicable if (tlc5940 != 0) tlc5940->send(); + + // send TLC59116 data updates + if (tlc59116 != 0) + tlc59116->send(); // collect diagnostic statistics, checkpoint 1 IF_DIAG(mainLoopIterCheckpt[1] += mainLoopTimer.read_us();) @@ -6255,6 +6230,8 @@ // the power first comes on. if (tlc5940 != 0) tlc5940->enable(false); + if (tlc59116 != 0) + tlc59116->enable(false); if (hc595 != 0) hc595->enable(false); } @@ -6263,7 +6240,7 @@ // if we have a reboot timer pending, check for completion if (saveConfigFollowupTimer.isRunning() - && saveConfigFollowupTimer.read() > saveConfigFollowupTime) + && saveConfigFollowupTimer.read_us() > saveConfigFollowupTime*1000000UL) { // if a reboot is pending, execute it now if (saveConfigRebootPending) @@ -6323,6 +6300,10 @@ // send TLC5940 data if necessary if (tlc5940 != 0) tlc5940->send(); + + // update TLC59116 outputs + if (tlc59116 != 0) + tlc59116->send(); // show a diagnostic flash every couple of seconds if (diagTimer.read_us() > 2000000) @@ -6364,10 +6345,9 @@ // Enable peripheral chips and update them with current output data if (tlc5940 != 0) - { tlc5940->enable(true); - tlc5940->update(true); - } + if (tlc59116 != 0) + tlc59116->enable(true); if (hc595 != 0) { hc595->enable(true);