Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
Diff: main.cpp
- Revision:
- 98:4df3c0f7e707
- Parent:
- 96:68d5621ff49f
- Child:
- 99:8139b0c274f4
--- a/main.cpp Wed Feb 07 00:01:21 2018 +0000 +++ b/main.cpp Fri Mar 01 23:53:59 2019 +0000 @@ -869,7 +869,7 @@ virtual void set(uint8_t level) { - // remebmber the new nominal level set by the client + // remember the new nominal level set by the client val = level; // update the physical output according to our current timing state @@ -1066,8 +1066,12 @@ // for the PWM duty cycle of the physical output. uint8_t params; + // Full-power time mapping. This maps from the 4-bit (0..15) time value + // in the parameters to the number of microseconds. + static const uint32_t paramToTime_us[]; + // Figure the initial full-power time in microseconds - inline uint32_t fullPowerTime_us() const { return ((params >> 4) + 1)*50000; } + inline uint32_t fullPowerTime_us() const { return paramToTime_us[params >> 4]; } // Figure the hold power PWM level (0-255) inline uint8_t holdPower() const { return (params & 0x0F) * 17; } @@ -1078,7 +1082,9 @@ static Timer timer; // Flipper logic pending timer list. Whenever a flipper logic output - // transitions from OFF to ON, tis timer + // transitions from OFF to ON, we add it to this list. We scan the + // list in our polling routine to find ports that have reached the + // expiration of their initial full-power intervals. static LwFlipperLogicOut **pending; static uint8_t nPending; }; @@ -1087,6 +1093,271 @@ Timer LwFlipperLogicOut::timer; LwFlipperLogicOut **LwFlipperLogicOut::pending; uint8_t LwFlipperLogicOut::nPending; +const uint32_t LwFlipperLogicOut::paramToTime_us[] = { + 1000, + 2000, + 5000, + 10000, + 20000, + 40000, + 80000, + 100000, + 150000, + 200000, + 300000, + 400000, + 500000, + 600000, + 700000, + 800000 +}; + +// Minimum On Time output. This is a filter output that we layer on +// a physical output to force the underlying output to stay on for a +// minimum interval. This can be used for devices that need to be on +// for a certain amount of time to trigger their full effect, such as +// slower solenoids or contactors. +class LwMinTimeOut: public LwOut +{ +public: + // Set up the output. 'param' is the configuration parameter + // for the mininum time span. + LwMinTimeOut(LwOut *o, uint8_t param) + : out(o), param(param) + { + // initially OFF + state = 0; + } + + virtual void set(uint8_t level) + { + // update the physical output according to our current timing state + switch (state) + { + case 0: + // We're currently off. If the new level is non-zero, switch + // to state 1 (initial minimum interval) and set the requested + // level. If the new level is zero, we're switching from off to + // off, so there's no change. + if (level != 0) + { + // switch to state 1 (initial minimum interval, port is + // logically on) + state = 1; + + // set the requested output level + out->set(level); + + // add myself to the pending timer list + pending[nPending++] = this; + + // note the starting time + t0 = timer.read_us(); + } + break; + + case 1: // min ON interval, port on + case 2: // min ON interval, port off + // We're in the initial minimum ON interval. If the new power + // level is non-zero, pass it through to the physical port, since + // the client is allowed to change the power level during the + // initial ON interval - they just can't turn it off entirely. + // Set the state to 1 to indicate that the logical port is on. + // + // If the new level is zero, leave the underlying port at its + // current power level, since we're not allowed to turn it off + // during this period. Set the state to 2 to indicate that the + // logical port is off even though the physical port has to stay + // on for the remainder of the interval. + if (level != 0) + { + // client is leaving the port on - pass through the new + // power level and set state 1 (logically on) + out->set(level); + state = 1; + } + else + { + // Client is turning off the port - leave the underlying port + // on at its current level and set state 2 (logically off). + // When the minimum ON time expires, the polling routine will + // see that we're logically off and will pass that through to + // the underlying physical port. Until then, though, we have + // to leave the physical port on to satisfy the minimum ON + // time requirement. + state = 2; + } + break; + + case 3: + // We're out of the minimum ON interval, so we can set any new + // level, including fully off. Pass the new power level through + // to the port. + out->set(level); + + // if the port is now off, return to state 0 (OFF) + if (level == 0) + state = 0; + break; + } + } + + // Class initialization + static void classInit(Config &cfg) + { + // Count the Minimum On Time outputs in the configuration. We + // need to allocate enough pending timer list space to accommodate + // all of these outputs. + int n = 0; + for (int i = 0 ; i < MAX_OUT_PORTS ; ++i) + { + // if this port is active and marked as Flipper Logic, count it + if (cfg.outPort[i].typ != PortTypeDisabled + && (cfg.outPort[i].flags & PortFlagMinOnTime) != 0) + ++n; + } + + // allocate space for the pending timer list + pending = new LwMinTimeOut*[n]; + + // there's nothing in the pending list yet + nPending = 0; + + // Start our shared timer. The epoch is arbitrary, since we only + // use it to figure elapsed times. + timer.start(); + } + + // Check for ports with pending timers. The main routine should + // call this on each iteration to process our state transitions. + static void poll() + { + // note the current time + uint32_t t = timer.read_us(); + + // go through the timer list + for (int i = 0 ; i < nPending ; ) + { + // get the port + LwMinTimeOut *port = pending[i]; + + // assume we'll keep it + bool remove = false; + + // check if we're in the minimum ON period for the port + if (port->state == 1 || port->state == 2) + { + // we are - check if the minimum ON time has elapsed + if (uint32_t(t - port->t0) > port->minOnTime_us()) + { + // This port has completed its initial ON interval, so + // it advances to the next state. + if (port->state == 1) + { + // The port is logically on, so advance to state 3, + // "on past minimum initial time". The underlying + // port is already at its proper level, since we pass + // through non-zero power settings to the underlying + // port throughout the initial ON interval. + port->state = 3; + } + else + { + // The port was switched off by the client during the + // minimum ON period. We haven't passed the OFF state + // to the underlying port yet, because the port has to + // stay on throughout the minimum ON period. So turn + // the port off now. + port->out->set(0); + + // return to state 0 (OFF) + port->state = 0; + } + + // we're done with the timer + remove = true; + } + } + + // if desired, remove the port from the timer list + if (remove) + { + // Remove the list entry by overwriting the slot with + // the last entry in the list. + pending[i] = pending[--nPending]; + + // Note that we don't increment the loop counter, since + // we now need to revisit this same slot. + } + else + { + // we're keeping this item; move on to the next one + ++i; + } + } + } + +protected: + // underlying physical output + LwOut *out; + + // Timestamp on 'timer' of start of full-power interval. We set this + // to the current 'timer' timestamp when entering state 1. + uint32_t t0; + + // Current port state: + // + // 0 = off + // 1 = initial minimum ON interval, logical port is ON + // 2 = initial minimum ON interval, logical port is OFF + // 3 = past the minimum ON interval + // + uint8_t state; + + // Configuration parameter. This encodes the minimum ON time. + uint8_t param; + + // Timer. This is a shared timer for all of the minimum ON time ports. + // When we transition from OFF to ON, we note the current time on this + // timer to establish the start of our minimum ON period. + static Timer timer; + + // translaton table from timing parameter in config to minimum ON time + static const uint32_t paramToTime_us[]; + + // Figure the minimum ON time + inline uint32_t minOnTime_us() const { return paramToTime_us[param & 0x0F]; } + + // Pending timer list. Whenever one of our ports transitions from OFF + // to ON, we add it to this list. We scan this list in our polling + // routine to find ports that have reached the ends of their initial + // ON intervals. + static LwMinTimeOut **pending; + static uint8_t nPending; +}; + +// Min Time Out statics +Timer LwMinTimeOut::timer; +LwMinTimeOut **LwMinTimeOut::pending; +uint8_t LwMinTimeOut::nPending; +const uint32_t LwMinTimeOut::paramToTime_us[] = { + 1000, + 2000, + 5000, + 10000, + 20000, + 40000, + 80000, + 100000, + 150000, + 200000, + 300000, + 400000, + 500000, + 600000, + 700000, + 800000 +}; // // The TLC5940 interface object. We'll set this up with the port @@ -1576,6 +1847,7 @@ int activeLow = flags & PortFlagActiveLow; int gamma = flags & PortFlagGamma; int flipperLogic = flags & PortFlagFlipperLogic; + int hasMinOnTime = flags & PortFlagMinOnTime; // cancel gamma on flipper logic ports if (flipperLogic) @@ -1692,6 +1964,10 @@ if (flipperLogic) lwp = new LwFlipperLogicOut(lwp, pc.flipperLogic); + // Layer on the Minimum On Time if desired + if (hasMinOnTime) + lwp = new LwMinTimeOut(lwp, pc.minOnTime); + // If it's a noisemaker, layer on a night mode switch if (noisy) lwp = new LwNoisyOut(lwp); @@ -1720,8 +1996,9 @@ // initialize the output pin array void initLwOut(Config &cfg) { - // Initialize the Flipper Logic outputs + // Initialize the Flipper Logic and Minimum On Time outputs LwFlipperLogicOut::classInit(cfg); + LwMinTimeOut::classInit(cfg); // Count the outputs. The first disabled output determines the // total number of ports. @@ -3271,7 +3548,18 @@ // assigned to the shifted or unshifted version of the // button. bool pressed; - if ((cfg.nightMode.flags & 0x02) != 0) + if (shiftButton.index == i) + { + // This button is both the Shift button AND the Night + // Mode button. This is a special case in that the + // Shift status is irrelevant, because it's obviously + // identical to the Night Mode status. So it doesn't + // matter whether or not the Night Mode button has the + // shifted flags; the raw button state is all that + // counts in this case. + pressed = true; + } + else if ((cfg.nightMode.flags & 0x02) != 0) { // Shift bit is set - night mode is assigned to the // shifted version of the button. This is a Night @@ -5737,6 +6025,7 @@ true, // we support the new accelerometer settings true, // we support the "flash write ok" status bit in joystick reports true, // we support the configurable joystick report timing features + true, // we use the new flipper logic timing table mallocBytesFree()); // remaining memory size break; @@ -6019,10 +6308,10 @@ // say hello to the debug console, in case it's connected printf("\r\nPinscape Controller starting\r\n"); - // Set the default PWM period to 1ms. This will be used for PWM - // channels on PWM units whose periods aren't changed explicitly, - // so it'll apply to LW outputs assigned to GPIO pins. Note that - // the KL25Z only allows the period to be set at the TPM unit + // Set the default PWM period to 0.5ms = 2 kHz. This will be used + // for PWM channels on PWM units whose periods aren't changed + // explicitly, so it'll apply to LW outputs assigned to GPIO pins. + // The KL25Z only allows the period to be set at the TPM unit // level, not per channel, so all channels on a given unit will // necessarily use the same frequency. We (currently) have two // subsystems that need specific PWM frequencies: TLC5940NT (which @@ -6320,8 +6609,9 @@ // update PWM outputs pollPwmUpdates(); - // update Flipper Logic outputs + // update Flipper Logic and Min On Time outputs LwFlipperLogicOut::poll(); + LwMinTimeOut::poll(); // poll the accelerometer accel.poll(); @@ -6690,8 +6980,9 @@ // try to recover the connection js.recoverConnection(); - // update Flipper Logic outputs + // update Flipper Logic and Min Out Time outputs LwFlipperLogicOut::poll(); + LwMinTimeOut::poll(); // send TLC5940 data if necessary if (tlc5940 != 0)