Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: mbed FastIO FastPWM USBDevice
Fork of Pinscape_Controller by
Diff: main.cpp
- Revision:
- 112:8ed709f455c0
- Parent:
- 111:42dc75fbe623
- Child:
- 113:7330439f2ffc
diff -r 42dc75fbe623 -r 8ed709f455c0 main.cpp
--- a/main.cpp Mon Feb 22 06:57:59 2021 +0000
+++ b/main.cpp Thu Apr 29 19:56:49 2021 +0000
@@ -306,6 +306,13 @@
// --------------------------------------------------------------------------
//
+// placement new
+//
+void* operator new (size_t, void *p) { return p; }
+
+
+// --------------------------------------------------------------------------
+//
// OpenSDA module identifier. This is for the benefit of the Windows
// configuration tool. When the config tool installs a .bin file onto
// the KL25Z, it will first find the sentinel string within the .bin file,
@@ -4119,9 +4126,9 @@
// I2C address of the accelerometer (this is a constant of the KL25Z)
const int MMA8451_I2C_ADDRESS = (0x1d<<1);
-// SCL and SDA pins for the accelerometer (constant for the KL25Z)
-#define MMA8451_SCL_PIN PTE25
-#define MMA8451_SDA_PIN PTE24
+// I2C pins for the accelerometer (constant for the KL25Z)
+#define MMA8451_SDA_PIN PTE25
+#define MMA8451_SCL_PIN PTE24
// Digital in pin to use for the accelerometer interrupt. For the KL25Z,
// this can be either PTA14 or PTA15, since those are the pins physically
@@ -4164,18 +4171,13 @@
class Accel
{
public:
- Accel(PinName sda, PinName scl, int i2cAddr, PinName irqPin,
- int range, int autoCenterMode)
- : mma_(sda, scl, i2cAddr)
+ Accel(const Config &cfg) : mma_(MMA8451_SDA_PIN, MMA8451_SCL_PIN, MMA8451_I2C_ADDRESS)
{
- // remember the interrupt pin assignment
- irqPin_ = irqPin;
-
// remember the range
- range_ = range;
+ range_ = cfg.accel.range;
// set the auto-centering mode
- setAutoCenterMode(autoCenterMode);
+ setAutoCenterMode(cfg.accel.autoCenterTime);
// no manual centering request has been received
manualCenterRequest_ = false;
@@ -4184,6 +4186,62 @@
reset();
}
+ // Do a full reset of the object. This tries to clear the I2C
+ // bus, and then re-creates the Accel object in place, running
+ // through all of the constructors again. This is only a "soft"
+ // reset, since the KL25Z doesn't give us any way to do a power
+ // cycle on the MMA8451Q from software - its power connection is
+ // hardwired to the KL25Z's main board power connection, so the
+ // only way to power cycle the accelerometer is to power cycle
+ // the whole board.
+ //
+ // We use this to try to reset the accelerometer if it stops
+ // sending us new samples. I've received a few reports from
+ // people who say their accelerometers seem to stop working even
+ // though the rest of the firmware is still functioning normally,
+ // which suggests that there's either a problem in the Accel class
+ // itself, or that the MMA8451Q can get into a non-responsive state
+ // under some circumstances. Since the reports have been extremely
+ // rare and isolated, and since I've never myself seen this happen
+ // on any of the multiple KL25Z boards I've tested with (even after
+ // leaving them running for days at a time), my best guess is that
+ // it's actually a fault in the MMA8451Q. The fact that everyone
+ // who's experienced the accelerometer freeze says that the rest of
+ // the firwmare is still working supports this hypothesis - given
+ // that the firmware is single-threaded, it seems unlikely that a
+ // "crash" of some kind in the accelerometer code wouldn't crash
+ // the firmware as a whole. This soft reset code is an attempt to
+ // recover from a scenario where the MMA8451Q hardware is still
+ // functioning properly, but its internal state machine is somehow
+ // out of sync with the host in such a way that it can no longer
+ // send us samples - either its I2C state machine is stuck in the
+ // middle of a transaction, or its sample processing state machine
+ // is no longer taking samples. The soft reset doesn't have any
+ // hope of rebooting the chip if the freeze is due to some kind
+ // of hardware fault, because our only connection to the chip is
+ // the I2C bus, and there's no reason to think its I2C state
+ // machine would even be running in the event of a hardware fault.
+ // Hopefully we can find out which it is by testing this fix on
+ // boards where the problem is known to have occurred, since it
+ // seems to be readily repeatable for the people who experience
+ // it at all.
+ static void softReset(Accel *accel, const Config &config)
+ {
+ // save the current centering position, so that the user
+ // doesn't see a jump across the reset
+ int cx = accel->cx_, cy = accel->cy_;
+
+ // try to reset the I2C bus, in case that's
+ accel->clear_i2c();
+
+ // re-construct the Accel object
+ new (accel) Accel(config);
+
+ // restore the center point
+ accel->cx_ = cx;
+ accel->cy_ = cy;
+ }
+
// Request manual centering. This applies the trailing average
// of recent measurements and applies it as the new center point
// as soon as we have enough data.
@@ -4220,6 +4278,81 @@
}
}
+ // Clear the I2C bus for the MMA8451Q. This seems necessary some of the time
+ // for reasons that aren't clear to me. Doing a hard power cycle has the same
+ // effect, but when we do a soft reset, the hardware sometimes seems to leave
+ // the MMA's SDA line stuck low. Presumably, the MMA8451Q's internal state
+ // machine is still in the middle of an I2C transaction, and it expects the
+ // host to clock in/out the rest of the bits for the transaction. Forcing a
+ // series of clock pulses through SCL is the standard remedy for this type
+ // of situation, since it should force the state machine to the end of the
+ // I2C state it's stuck in so that it's ready to start a new transaction.
+ // This really shouldn't be necessary, because the mbed library I2C code that
+ // we're using in the MMA8451Q driver appears to do the same thing when it
+ // sets up the I2C pins, but it should at least be harmless. What we really
+ // need is a way to power-cycle the MMA8451Q, but the KL25Z simply isn't
+ // wired to do that from software; the only way is to power-cycle the whole
+ // board.
+ //
+ // If the accelerometer does get stuck, and a software reboot doesn't reset
+ // it, the only workaround is to manually power cycle the whole KL25Z by
+ // unplugging both of its USB connections.
+ //
+ // The entire Accel object must be re-constructed after calling this,
+ // because this reconfigures the I2C SDA/SCL pins as plain digital in/out
+ // pins. They have to be reconfigured as I2C pins again by the I2C
+ // constructor after this is called.
+ static bool clear_i2c()
+ {
+ // set up both pints as input pins
+ DigitalInOut pin_sda(MMA8451_SDA_PIN, PIN_INPUT, PullNone, 1);
+ DigitalInOut pin_scl(MMA8451_SCL_PIN, PIN_INPUT, PullNone, 1);
+
+ // if SCL is being held low, the bus is locked by another device;
+ // wait a couple of milliseconds and then give up
+ Timer t;
+ t.start();
+ while (pin_scl == 0 && t.read_us() < 2000) { }
+ if (pin_scl == 0)
+ return false;
+
+ // if SDA and SCL are both high, the bus is free
+ if (pin_sda == 1)
+ return true;
+
+ // Send a series of clock pulses to try to knock the device out
+ // of whatever I2C transaction it thinks it's in the middle of.
+ // 9 pulses should be sufficient for a device with byte commands,
+ // but do some extra for good measure, in case it's in some kind
+ // of multi-byte transaction.
+ pin_scl.mode(PullNone);
+ pin_scl.output();
+ for (int count = 0; count < 35; count++)
+ {
+ pin_scl.mode(PullNone);
+ pin_scl = 0;
+ wait_us(5);
+ pin_scl.mode(PullUp);
+ pin_scl = 1;
+ wait_us(5);
+ }
+
+ // Send Stop
+ pin_sda.output();
+ pin_sda = 0;
+ wait_us(5);
+ pin_scl = 1;
+ wait_us(5);
+ pin_sda = 1;
+ wait_us(5);
+
+ // confirm that both SDA and SCL are now high, indicating that
+ // the bus is free
+ pin_sda.input();
+ pin_scl.input();
+ return (pin_scl != 0 && pin_sda != 0);
+ }
+
void reset()
{
// clear the center point
@@ -4244,16 +4377,34 @@
// read the current registers to clear the data ready flag
mma_.getAccXYZ(ax_, ay_, az_);
+
+ // start the FIFO timer
+ fifoTimer.reset();
+ fifoTimer.start();
+ tLastSample = tLastChangedSample = fifoTimer.read_us();
}
- void poll()
+ // Poll the accelerometer. Returns true on success, false if the
+ // device appears to be wedged (because we haven't received a unique
+ // sample in a long time). The caller can try re-creating the Accel
+ // object if the device is wedged.
+ bool poll()
{
// read samples until we clear the FIFO
while (mma_.getFIFOCount() != 0)
{
+ // read the raw data
int x, y, z;
mma_.getAccXYZ(x, y, z);
+ // note the time
+ tLastSample = fifoTimer.read_us();
+
+ // note if this sample differs from the last one, to see if
+ // the accelerometer appears to be stuck
+ if (x != ax_ || y != ay_ || z != az_)
+ tLastChangedSample = tLastSample;
+
// add the new reading to the running total for averaging
xSum_ += (x - cx_);
ySum_ += (y - cy_);
@@ -4264,8 +4415,59 @@
ay_ = y;
az_ = z;
}
+
+ // If we haven't seen a new sample in a while, the device
+ // might be stuck. Some people have observed an apparent
+ // freeze in the accelerometer readings even while the
+ // pluger and key inputs continue working, which seems
+ // like it must be due to something stuck on the MMA8451Q.
+ // The caller can try a software reset in that case, by
+ // re-creating the Accel object. That will go through
+ // all of the I2C and MMA8451Q intialization code again
+ // to try to get things back to a good state.
+ //
+ // We poll about every 2.5ms (or more often, depending on
+ // the plunger sensor type), and we have the accelerometer
+ // set to generate samples at 800 Hz = every 1.25ms, so it
+ // would definitely indicate trouble if the last samples
+ // from the device are older than 5ms. As for *unique*
+ // samples, that's a harder call, since it depends on how
+ // much background noise there is. Given the sensitivity
+ // of the device, though, my experience is that nearly
+ // every sample will have at least one bit of difference
+ // from the last, so it's unlikely to see more than a few
+ // identical samples in a row, and extremely unlikely to
+ // see, say, 10 or 20 consecutive identical readings. To
+ // be conservative, we'll time out the existence of a
+ // reading at 100ms, and unique readings at 2s. This
+ // should reset a non-responsive device well before the
+ // freeze becomes apparent to the user (unless they're
+ // deliberately looking for it), but should also ensure
+ // that we don't reset unnecessarily - 2s represents 1600
+ // consecutive identical samples, and I think the odds of
+ // that happening for real are practically zero, barring
+ // some kind of test bed with extreme vibration suppression.
+ uint32_t tNow = fifoTimer.read_us();
+ if (static_cast<uint32_t>(tNow - tLastSample) > 100000 // 100 ms
+ || static_cast<uint32_t>(tNow - tLastChangedSample) > 2000000) // 2 seconds
+ {
+ // appears to be wedged
+ return false;
+ }
+
+ // okay
+ return true;
}
-
+
+ // timer, for monitoring incoming FIFO samples
+ Timer fifoTimer;
+
+ // time of last sample from FIFO
+ uint32_t tLastSample;
+
+ // time of last *different* sample from FIFO
+ uint32_t tLastChangedSample;
+
void get(int &x, int &y)
{
// read the shared data and store locally for calculations
@@ -4407,7 +4609,7 @@
// atuo-centering timer
Timer tCenter_;
-
+
// Auto-centering history. This is a separate history list that
// records results spaced out sparsely over time, so that we can
// watch for long-lasting periods of rest. When we observe nearly
@@ -4423,43 +4625,8 @@
uint8_t iAccPrv_, nAccPrv_;
static const uint8_t maxAccPrv = 5;
AccHist accPrv_[maxAccPrv];
-
- // interurupt pin name
- PinName irqPin_;
};
-// ---------------------------------------------------------------------------
-//
-// Clear the I2C bus for the MMA8451Q. This seems necessary some of the time
-// for reasons that aren't clear to me. Doing a hard power cycle has the same
-// effect, but when we do a soft reset, the hardware sometimes seems to leave
-// the MMA's SDA line stuck low. Forcing a series of 9 clock pulses through
-// the SCL line is supposed to clear this condition. I'm not convinced this
-// actually works with the way this component is wired on the KL25Z, but it
-// seems harmless, so we'll do it on reset in case it does some good. What
-// we really seem to need is a way to power cycle the MMA8451Q if it ever
-// gets stuck, but this is simply not possible in software on the KL25Z.
-//
-// If the accelerometer does get stuck, and a software reboot doesn't reset
-// it, the only workaround is to manually power cycle the whole KL25Z by
-// unplugging both of its USB connections.
-//
-void clear_i2c()
-{
- // set up general-purpose output pins to the I2C lines
- DigitalOut scl(MMA8451_SCL_PIN);
- DigitalIn sda(MMA8451_SDA_PIN);
-
- // clock the SCL 9 times
- for (int i = 0 ; i < 9 ; ++i)
- {
- scl = 1;
- wait_us(20);
- scl = 0;
- wait_us(20);
- }
-}
-
// ---------------------------------------------------------------------------
//
@@ -6534,7 +6701,7 @@
NewPwmUnit::defaultPeriod = 0.0005f;
// clear the I2C connection
- clear_i2c();
+ Accel::clear_i2c();
// Elevate GPIO pin interrupt priorities, so that they can preempt
// other interrupts. This is important for some external peripherals,
@@ -6745,8 +6912,7 @@
acTimer.start();
// create the accelerometer object
- Accel accel(MMA8451_SCL_PIN, MMA8451_SDA_PIN, MMA8451_I2C_ADDRESS,
- MMA8451_INT_PIN, cfg.accel.range, cfg.accel.autoCenterTime);
+ Accel accel(cfg);
// initialize the plunger sensor
plungerSensor->init();
@@ -6815,7 +6981,8 @@
LwChimeLogicOut::poll();
// poll the accelerometer
- accel.poll();
+ if (!accel.poll())
+ Accel::softReset(&accel, cfg);
// Note the "effective" plunger enabled status. This has two
// components: the explicit "enabled" bit, and the plunger sensor
