How is this possible? Frustrations with i2c, interrupts and sleep

I have been working on enabling interrupts for an accelerometer - MMA8452Q - and have extended the Sparkfun library to enable them. I have published the library here and in the Particle Library system:

Everything works well as long as the device is not allowed to sleep and even then it works well 99/100 times. But, every once in a while, the device is allowed to sleep without clearing the interrupt which then ensures it never wakes.

Here is the thing - this should be impossible as the line in code just before sleep is:

      if (pinReadFast(blueLED)) System.reset();

Yet, the device will (on occasion) sleep with the blueLED lit. I have tried all manner of delays and conditionals but I cannot seem to get this to be 100% reliable.

Here is a version of my code stripped down as much as I could. Any advice would be appreciated:

/*	Accelerometer-Interrupt-Test.ino
	Chip McClelland (chip@seeinsights.com)
	July 15, 2021
	https://github.com/sparkfun/SparkFun_MMA8452Q_Particle_Library
	
	This is a simple example sketch for the SparkFun MMA8452Q
	Particle library. It'll connect to an MMA8452Q and stream the
	values out the serial port as the become available.

	Development environment specifics:
	Particle Build environment (https://www.particle.io/build)
	Particle Photon
	Distributed as-is; no warranty is given. 
*/
// Include the library:
#include "ModMMA8452Q.h"

// Prototypes and system calls
SYSTEM_MODE(SEMI_AUTOMATIC);                        // This will enable user code to start executing automatically.
SYSTEM_THREAD(ENABLED);                             // Means my code will not be held up by Particle processes.
SystemSleepConfiguration config;                    // Initialize new Sleep 2.0 Api


Timer countSignalTimer(1000, countSignalTimerISR, true);  // This is how we will ensure the BlueLED stays on long enough for folks to see it.


// For monitoring / debugging, you can uncomment the next line
SerialLogHandler logHandler(LOG_LEVEL_ALL);

// Create an MMA8452Q object, used throughout the rest of the sketch.
MMA8452Q accel; // Default constructor, SA0 pin is HIGH

// The above works if the MMA8452Q's address select pin (SA0) is high.
// If SA0 is low (if the jumper on the back of the SparkFun MMA8452Q breakout
// board is closed), initialize it like this:
// MMA8452Q accel(MMA8452Q_ADD_SA0_); 

// Pin constants
const int blueLED =       D7;                       // This LED is on the Electron itself
const int userSwitch =    D4;                       // User switch with a pull-up resistor

// Pin Constants - Sensor
const int intPin =        D2;                      // Accelerometer Interrupt Pin - I2

// Timing constant
uint8_t stayAwakeSec = 30;                             // After reset, how long until we enable napping
int napDelay = 1;                                  // If awoken from a nap, how long till we nap again
unsigned long stayAwakeTimeStamp = 0;               // Can rest to stay awake longer

// Sensor Variables
volatile bool sensorDetect = false;                 // This is the flag that an interrupt is triggered
int tapCount = 0;

void setup() 
{
  pinMode(blueLED, OUTPUT);                         // declare the Blue LED Pin as an output
  pinMode(intPin,INPUT);                            // sensor interrupt (push/ pull)
  digitalWrite(blueLED,HIGH);                       // Turn on the led so we can see how long the Setup() takes
  delay(1000);                                      // delay so we don't miss early messages in logging
  // Initialize the accelerometer with begin():
	// begin can take two parameters: full-scale range, and output data rate (ODR).
	// Full-scale range can be: SCALE_2G, SCALE_4G, or SCALE_8G (2, 4, or 8g)
	// ODR can be: ODR_800, ODR_400, ODR_200, ODR_100, ODR_50, ODR_12, ODR_6 or ODR_1
  accel.begin(SCALE_2G, ODR_100); // Set up accel with +/-2g range, and 100Hz ODR
  accel.setupTapInts(9);                                    // Set up taps on x,y and z defaults otherwise
  accel.clearTapInts();                                                 // Clear accelerometer interrupt on reset
  attachInterrupt(intPin, sensorISR, RISING);                          // Accelerometer interrupt from low to high
  stayAwakeTimeStamp = millis();
  digitalWrite(blueLED,LOW);                                           // Signal the end of startup

}

void loop() 
{
  if (sensorDetect == true) {                                         // Interrupt flag raised - need to report a tap
    recordCount();
  }  

  if (millis() - stayAwakeTimeStamp > stayAwakeSec * 1000) {         // 30 seconds initially then 1 second of awake operations before we allow napping
    if (!(sensorDetect || countSignalTimer.isActive())) {         // Don't nap until we are done with event

      stayAwakeSec = 1;
      config.mode(SystemSleepMode::ULTRA_LOW_POWER)
        .gpio(userSwitch,CHANGE)
        .gpio(intPin,RISING);

      if (pinReadFast(blueLED)) System.reset();

      SystemSleepResult result = System.sleep(config);                   // Put the device to sleep
      
      if (result.wakeupReason() == SystemSleepWakeupReason::BY_GPIO) {   // Awoken by GPIO pin
        if (result.wakeupPin() == intPin) {                              // Executions starts here after sleep - time or sensor interrupt?
          stayAwakeTimeStamp = millis();
        }
        else if (result.wakeupPin() == userSwitch) stayAwakeTimeStamp = 0;
      }
    }
  }
}

void sensorISR() {
  sensorDetect = true;                                              // sets the sensor flag for the main loop
  pinSetFast(blueLED);                                              // Turn on the blue LED
}

void countSignalTimerISR() {
  digitalWrite(blueLED,LOW);
}

void recordCount() // This is where we check to see if an interrupt is set when not asleep or act on a tap that woke the device
{
  static unsigned long lastTapTime;                                     // When did we last record a count?
  char data[64];                                                        // Store the date in this character array - not global

  if (sensorDetect) {
    detachInterrupt(intPin);                                            // Detach so there are no interruptions for this part
    Log.info("Cleared Interrupt");
    sensorDetect = false;                                               // Reset the flag
    delay(1000);                                                        // This slows the interrupts as there can be "ringing"
    accel.clearTapInts();                                               // "Ringing" stopped so we can clear the interrupt
    attachInterrupt(intPin, sensorISR, RISING);                         // Sensor interrupt from low to high
  }

  if (Time.now() - lastTapTime > 5) {
    lastTapTime = Time.now();
    tapCount++;                                                         // Increment the counterount
    countSignalTimer.reset();                                           // Keep the LED on for a set time so we can see it.
    snprintf(data, sizeof(data), "Count = %i",tapCount);
    Log.info(data);
  }
}

Thank you in advance for any clues on this.

Chip

Here is my guess. :wink:

attachInterrupt(intPin, sensorISR, RISING);

Occasionally, the above line, may be firing an interrupt immediately.

Try adding these two lines after the attachInterrupt() statement just to see if it is a quick solve:

Hope that helps!

1 Like

@robc,

Thank you very much for taking a look. I have continued to look at this and I have some additional questions.

First, since the attachInterrupt is on a RISING signal, it would not fire if the pin was already HIGH or LOW - only if it was in the process of transitioning from LOW to HIGH when the interrupt is attached. Am I thinking of this correctly.

I tried your suggestion and I think you are on to something - to clear an interrupt, two things need to be done:

  • The sensorDetect flag needs to be cleared
  • The intPin from the sensor needs to be cleared by calling accel.clearTapInts() as it is a latching interrupt

I tried clearing these three things after attaching the interrupt and I am still seeing this issue. ;-(

Here is the current version of the recordCount function:

void recordCount() // This is where we check to see if an interrupt is set when not asleep or act on a tap that woke the device
{
  static unsigned long lastTapTime;                                     // When did we last record a count?
  char data[64];                                                        // Store the date in this character array - not global

  if (sensorDetect) {
    detachInterrupt(intPin);                                            // Detach so there are no interruptions for this part
    Log.info("Cleared Interrupt");
    sensorDetect = false;                                               // Reset the flag
    delay(1000);                                                        // This slows the interrupts as there can be "ringing"
    noInterrupts();
    attachInterrupt(intPin, sensorISR, RISING);                         // Sensor interrupt from low to high
    accel.clearTapInts();                                               // "Ringing" stopped so we can clear the interrupt
    interrupts();
  }

  if (Time.now() - lastTapTime > 5) {
    lastTapTime = Time.now();
    tapCount++;                                                         // Increment the counterount
    countSignalTimer.reset();                                           // Keep the LED on for a set time so we can see it.
    snprintf(data, sizeof(data), "Count = %i",tapCount);
    Log.info(data);
  }
}

I did figure out one thing - using pinReadFast() on a pin configured as OUTPUT does not work. So, this line is now:

      if (digitalRead(blueLED)) System.reset();

and I can see it firing in the event log.

But, it still manages to get to sleep with the blueLED HIGH. I just don’t see how this is possible.

So, at least I won’t have hung devices. Still, would like to make it work as intended. Here you can see that the device was reset after failing to clear the blueLED before sleep.

Screen Shot 2021-07-29 at 5.45.16 PM

Thank you again and please let me know if you have any other ideas.

Chip

1 Like

From the logging, it seems there there is some correlation between the device going into this state and system.sleep.

Notice the device restarted a couple times when the TRACE entries showed up. Again, this does not happen if there is no sleep.

If anyone has an idea, please let me know else I will open a ticket.

Thanks,

Chip

1 Like

Sorry that did not help you. I think I focused on the wrong thing. False positives for the ISR was not the main issue. I see you tried enabling/disabling interrupts. Yes, you read my mind there.

But, I try to avoid this enabling/disabling interrupts now on the Particle platform. Particularly when using

SYSTEM_THREAD(ENABLED);                             // Means my code will not be held up by Particle processes.

Some things I would try:
Set the value of the blue LED ( and reset Timer) outside of the ISR.
Avoid the Timer you are using and perform a manual millis() timer in your code.
But, before doing the above, perhaps the Timer was not initiated properly? Ignoring my previous posts, maybe you could try adding this to setup() and see if the firmware behaves as you expect:

  digitalWrite(blueLED,LOW);   // Signal the end of startup
  ...
  delay(250);
  digitalWrite(blueLED,HIGH); 
  countSignalTimer.start();    // Flash Blue LED once
}

@robc , Thank you for the suggestions. I have taken your advice

  • Eliminated the System Timer to simplify things
  • Got rid of the noInterrupts stuff

I am now looking at the intPin not its proxy (blueLED) and only reattaching the interrupt if it is cleared.

All this has yielded some progress got to 108 consecutive proper iterations of sleep / wake:
Screen Shot 2021-07-30 at 9.34.43 AM

But, occasionally, the device will still go to sleep with the intPin HIGH - this is the main problem as the device will not now wake. Also, this should be impossible as we test this pin immediately before going to sleep!

I have significantly simplified the code and it is not “only” 100 lines long:

/*	Accelerometer-Interrupt-Test.ino
	Chip McClelland (chip@seeinsights.com)
	July 15, 2021
	https://github.com/chipmc/ModMMA8452Q
  
  This is a modification of the Sparkfun Library that adds interrupt capabilities.
  Based on the original Sparkfun Library - https://github.com/sparkfun/SparkFun_MMA8452Q_Particle_Library
*/
#include "ModMMA8452Q.h"

// Prototypes and system calls
SYSTEM_MODE(SEMI_AUTOMATIC);                        // This will enable user code to start executing automatically.
SYSTEM_THREAD(ENABLED);                             // Means my code will not be held up by Particle processes.
SystemSleepConfiguration config;                    // Initialize new Sleep 2.0 Api


SerialLogHandler logHandler(LOG_LEVEL_ALL);         // For monitoring / debugging, you can uncomment the next line

// Create an MMA8452Q object, used throughout the rest of the sketch.
MMA8452Q accel; // Default constructor, SA0 pin is HIGH

// Pin constants
const int blueLED =       D7;                       // This LED is on the Electron itself
const int userSwitch =    D4;                       // User switch with a pull-up resistor

// Pin Constants - Sensor
const int intPin =        D2;                       // Accelerometer Interrupt Pin - I2

// Timing constant
uint8_t stayAwakeSec;                               // After reset, how long until we enable napping
uint8_t longNapDelay = 30;                          // Long initial delay to allow for testing without sleep
uint8_t shortNapDelay = 1;                          // After initial delay, shorter stay awake time to support testing
unsigned long stayAwakeTimeStamp = 0;               // When did we wake up

// Sensor Variables
volatile bool sensorDetect = false;                 // This is the flag that an interrupt is triggered
int tapCount = 0;                                   // Counter to show how long a run we had before locking up

void setup() 
{
  pinMode(blueLED, OUTPUT);                         // declare the Blue LED Pin as an output
  pinMode(intPin,INPUT);                            // sensor interrupt (push/ pull)
  delay(2000);                                      // delay so we don't miss early messages in logging
  Log.info("Starting up");
  accel.begin(SCALE_2G, ODR_100);                   // Set up accel with +/-2g range, and 100Hz ODR
  accel.setupTapInts(9);                            // Set up taps on x,y and z defaults otherwise
  accel.clearTapInts();                             // Clear accelerometer interrupt on reset
  attachInterrupt(intPin, sensorISR, RISING);       // Accelerometer interrupt from low to high
  stayAwakeTimeStamp = millis();
  stayAwakeSec = longNapDelay;                      // Initially we will stay awake longer to test interrupt clearing without sleep
}

void loop() 
{
  if (sensorDetect) recordCount();                  // Interrupt flag raised - need to report a tap
  else if (millis() - stayAwakeTimeStamp > stayAwakeSec * 1000) {    // Test to see if it is time to nap
    stayAwakeSec = shortNapDelay;                   // After the initial long nap delay, we will sleep more frequently
    config.mode(SystemSleepMode::ULTRA_LOW_POWER)   // Napping with only GPIO wakeup
      .gpio(userSwitch,CHANGE)
      .gpio(intPin,RISING);
    if (digitalRead(intPin)) {                      // This should have been cleared
      Log.info("Tried to sleep without clearing the intPin...");
      delay(200);                                   // Ensure log gets written before reset - needed?
      System.reset();                               // Should never sleep with the interrupt HIGH - THIS IS THE PROBLEM I NEED TO SOLVE
    }
    SystemSleepResult result = System.sleep(config);// Put the device to sleep
    if (result.wakeupPin() == userSwitch) {
      stayAwakeSec = longNapDelay;
    }
    stayAwakeTimeStamp = millis();
  }
}

void sensorISR() {
  sensorDetect = true;                              // sets the sensor flag for the main loop
  pinSetFast(blueLED);                              // Turn on the blue LED - so we can see the tap
}

void recordCount() { // This is where we check to see if an interrupt is set when not asleep or act on a tap that woke the device
  char data[32];                                    // Store the date in this character array - not global
  if (sensorDetect) {
    detachInterrupt(intPin);                        // Detach so there are no interruptions for this part
    delay(1000);                                    // This slows the interrupts as there can be "ringing"
    sensorDetect = false;                           // Reset the flag
    digitalWrite(blueLED,LOW);                      // Reset the blueLED
    accel.clearTapInts();                           // "Ringing" stopped so we can clear the interrupt
    if (!digitalRead(intPin)) {
      Log.info("intPin Cleared, reattaching interrupt..");
      attachInterrupt(intPin, sensorISR, RISING);   // Interrupt is cleared - reattach interrupt
    }
    else {
      Log.info("Attempt to clear the intPin failed...");
      delay(200);                                   // Ensure log gets written before reset - needed?
      System.reset();
    }
  }
  tapCount++;                                       // Increment the counter
  snprintf(data, sizeof(data), "Count = %i",tapCount);
  Log.info(data);
}

Any other ideas are welcome! My best goes now is that every once in a while an interrupt comes in between the conditional check on the intPin and the sleep statement. That is the only place - but then how to protect against that?

Chip

1 Like

Chip, I’d have to agree after a quick read of this thread.
Can you swap to a CHANGE interrupt instead of RISING ?
You’d need a little bit more logic for the counting though.

2 Likes

@Rftop ,

Interesting idea and I appreciate you taking a look. I tried using CHANGE instead of RISING but, unfortunately, got the same result.

I keep coming back to this point - there has to be a way to keep an interrupt from firing before the device is put to sleep. I can’t be the only one who has faced this issue and it is key to realizing the benefit of sleep. I have opened a service ticket on this as there either is a solution that is not in the docs or there needs to be something created.

Thanks,

Chip

It makes sense to me to assume the issue is the rare occurrence of the interrupt firing while the Particle device is actively preparing for sleep.

But from the outside looking in : the fact that a CHANGE trigger doesn’t help appears to suggest a different issue that what we expect is happening.

What do you think about removing the accelerometer to stress test the basic CODE?
You could have another Slave Photon/etc fake the transitions.
If the problem persists, at least you know its not something squirrely with the accelerometer.
And your bench-test can then drill down on the Interrupt Timing at the time of the sleep request to gain insight on the vulnerability.

I appoligize in advance if this is a stupid suggestion…just brainstorming :grin:

2 Likes

I agree. When working on an Arduino platform, I used the statements nointerrupts() & interrupts() for control. Before and after an attachInterrupt() statement:

  detachInterrupt(); // stop LOW level interrupt on designated PIN
  ...
  nointerrupts();    // make sure we don't get interrupted before we sleep
  attachInterrupt(); // wake up on low level interrupt on designated PIN
  interrupts();      // interrupts allowed now, next instruction WILL be executed
  goToSleep();       // here the device is put to sleep before any interrupt occurs

I knew all interrupts had been dealt with. Without the implementation of those two statements I need to manually, in code, deal with any late comer interrupts generated by the execution of attachInterrupt().

Here is an interesting link & quote from Nick Gammon about dealing with interrupts before going into sleep mode:
https://gammon.com.au/interrupts

Something to be aware of is that these flags can be set before you attach the interrupt handler. For example, it is possible for a rising or falling level interrupt on pin D2 to be “flagged”, and then as soon as you do an attachInterrupt the interrupt immediately fires, even if the event occurred an hour ago. To avoid this you can manually clear the flag.

2 Likes

Pulse outputs for wake are frequently going to be a problem. For your IMU situation, it’s a common issue, but it can happen for any asynchronous pulse-based interrupt output, both rising and falling. Some situations are worse than others.

The reason as you pointed out, is that there’s a window of time from the time you’ve checked/cleared the interrupt and the time the device is actually asleep.

The two situations that come into play that are problematic are:

  1. If the interrupt source will only generate another interrupt after clearing the old. In this case, the first interrupt will be lost because it occurred before the device was asleep. The interrupt will never be cleared and the device won’t wake up on the next movement. The only solution is to periodically wake, check the interrupt status, and go immediately back to sleep. This can be done really quickly if cellular is off.
  2. If you’re using a rising WKP to wake from hibernate mode on the Gen 2 devices (STM32F205) you’re super stuck. If WKP is high at the moment the device goes to sleep, even if it falls and rises again, the device will not wake up, even based on time. This was an issue with the LIS3DH on the old Electron AssetTracker 2. The solution is to use ultra low power mode instead of SLEEP_MODE_DEEP, combined with the solution for #1.

If the device sends another interrupt always, you’re usually OK, but if you’re trying to count interrupts (for example, rain gauge), you will miss one click.

One solution is to insert an even smaller microcontroller (like a small PIC) as the interrupt handler. If you go a little larger like a PIC18 you can even make a dedicated I2C device to pre-filter interrupts. This works well with things like anemometers that interrupt too frequently to wake a sleeping Particle device safely. You just use the PIC to count the anemometer clicks and periodically get min/max/average from it by I2C.

3 Likes

@rickkas7 ,

You summarized my plight exactly! Luckily I am on a Boron so your case #1. It does seem that the only solution is to periodically wake as you suggested as the interrupt will be cleared immediately upon waking and these devices will be operating off-line so it is quick as you pointed out.

@Rftop,

I like your suggestion to eliminate the sensor and its i2c library for two reasons: 1) It saves my fingernail which was getting sore tapping on the sensor and 2) Since I am using a library I modified, it eliminates it and i2c as the source of error.


I used an old Photon to emulate the sensor and send interrupts. I used a digital IO line from the Boron to the Photon to “clear” the interrupt. It went a lo farther than the other device but, as you can see in the picture above, it “flatlined” as well with the blueLED on and the device asleep.

If anyone is interested, here is the code I use for the Photon which I may use in future projects.

/*
 * Project Photon-Exerciser
 * Description: Simple code to randomly generate digital inputs
 * Author: Chip McClelland  
 * Date: 7/30/21
 */

// Pin Constants
const int blueLED = D7;
const int intPin = D6;

// Timing constants 
unsigned long baseRateMillis = 100;                   // What is the base frequency of tinerrupts that we will vary around
unsigned long lastPulseMillis = 0;          // When did we last send a pulse

// setup() runs once, when the device is first turned on.
void setup() {
  pinMode(blueLED,OUTPUT);
  pinMode(intPin,INPUT_PULLDOWN);
  attachInterrupt(intPin,sensorISR,RISING);
    digitalWrite(blueLED,LOW);
}

// loop() runs over and over again, as quickly as it can execute.
void loop() {
  if ((millis() - lastPulseMillis > baseRateMillis + random(10) - random(10)) && !digitalRead(blueLED)) {
    digitalWrite(blueLED,HIGH);
    lastPulseMillis = millis();
  }
}

void sensorISR() {
  digitalWrite(blueLED,LOW);
}

And here is the simplified code for the Boron - no special hardware needed other than a “user” button to wake the device when it gets stuck in sleep

/*	Accelerometer-Interrupt-Test-Noi2c.ino
	Chip McClelland (chip@seeinsights.com)
	July 30, 2021
  This is a bare-bones test to see if the issues with hardware interrupts and sleep are due to i2c comms or my ModMMA8452Q Library
*/

// Prototypes and system calls
SYSTEM_MODE(SEMI_AUTOMATIC);                        // This will enable user code to start executing automatically.
SYSTEM_THREAD(ENABLED);                             // Means my code will not be held up by Particle processes.
SystemSleepConfiguration config;                    // Initialize new Sleep 2.0 Api

SerialLogHandler logHandler(LOG_LEVEL_ALL);         // For monitoring / debugging, you can uncomment the next line

// Pin constants
const int blueLED =       D7;                       // This LED is on the Electron itself
const int userSwitch =    D4;                       // User switch with a pull-up resistor

// Pin Constants - Sensor
const int intPin =        D2;                       // Accelerometer Interrupt Pin - I2
const int outPin =        D3;

// Timing constant
uint8_t stayAwakeSec;                               // After reset, how long until we enable napping
uint8_t longNapDelay = 30;                          // Long initial delay to allow for testing without sleep
uint8_t shortNapDelay = 1;                          // After initial delay, shorter stay awake time to support testing
unsigned long stayAwakeTimeStamp = 0;               // When did we wake up

// Sensor Variables
volatile bool sensorDetect = false;                 // This is the flag that an interrupt is triggered
int tapCount = 0;                                   // Counter to show how long a run we had before locking up

void setup() 
{
  pinMode(blueLED, OUTPUT);                         // declare the Blue LED Pin as an output
  pinMode(intPin,INPUT);                            // sensor interrupt (push/ pull)
  pinMode(outPin,OUTPUT);
  clearInterrupt();
  delay(2000);                                      // delay so we don't miss early messages in logging
  Log.info("Starting up");
  attachInterrupt(intPin, sensorISR, RISING);       // Accelerometer interrupt from low to high
  stayAwakeTimeStamp = millis();
  stayAwakeSec = longNapDelay;                      // Initially we will stay awake longer to test interrupt clearing without sleep
}

void loop() 
{
  if (sensorDetect) recordCount();                  // Interrupt flag raised - need to report a tap
  else if (millis() - stayAwakeTimeStamp > stayAwakeSec * 1000) {    // Test to see if it is time to nap
    stayAwakeSec = shortNapDelay;                   // After the initial long nap delay, we will sleep more frequently
    config.mode(SystemSleepMode::ULTRA_LOW_POWER)   // Napping with only GPIO wakeup
      .gpio(userSwitch,CHANGE)
      .gpio(intPin,RISING);
    if (digitalRead(intPin)) {                      // This should have been cleared
      Log.info("Tried to sleep without clearing the intPin...");
      delay(200);                                   // Ensure log gets written before reset - needed?
      System.reset();                               // Should never sleep with the interrupt HIGH - THIS IS THE PROBLEM I NEED TO SOLVE
    }
    SystemSleepResult result = System.sleep(config);// Put the device to sleep
    if (result.wakeupPin() == userSwitch) {
      stayAwakeSec = longNapDelay;
    }
    stayAwakeTimeStamp = millis();
  }
}

void sensorISR() {
  sensorDetect = true;                              // sets the sensor flag for the main loop
  pinSetFast(blueLED);                              // Turn on the blue LED - so we can see the tap
}

void recordCount() { // This is where we check to see if an interrupt is set when not asleep or act on a tap that woke the device
  char data[32];                                    // Store the date in this character array - not global
  if (sensorDetect) {
    detachInterrupt(intPin);                        // Detach so there are no interruptions for this part
    clearInterrupt();
    if (!digitalRead(intPin)) {
      Log.info("intPin Cleared, reattaching interrupt..");
      attachInterrupt(intPin, sensorISR, RISING);   // Interrupt is cleared - reattach interrupt
    }
    else {
      Log.info("Attempt to clear the intPin failed...");
      delay(200);                                   // Ensure log gets written before reset - needed?
      System.reset();
    }
  }
  tapCount++;                                       // Increment the counter
  snprintf(data, sizeof(data), "Count = %i",tapCount);
  Log.info(data);
}

void clearInterrupt() {
  digitalWrite(outPin,HIGH);                      // Sending a HIGH here will reset the interrupt
  delay(50);
  digitalWrite(outPin,LOW);                       // Ready for next time
  sensorDetect = false;                           // Reset the flag
  digitalWrite(blueLED,LOW);                      // Reset the blueLED
}

So, unless anyone sees something I have missed, I think I am going to declare defeat and accept the solution Rick suggested - periodic waking and checking to make sure the interrupt has been cleared. It does seem that this is a general problem so, perhaps this thread will save someone else from the angst I went through.

Thank you all!

Chip

3 Likes

@rickkas7 , @robc , @Rftop

So, there is a happy ending to this story after all. I was a bit disappointed that I would need to live with the fact that my devices would get hung every once in a while. And then, I did something I should have done right up front - I re-read the data sheet for the MMA8452Q. Specially, the Application note on tap interrupts:

https://www.datasheetarchive.com/pdf/download.php?id=85c47274fc5d91d7cb17d645c45e9d25cad54d&type=P&term=AN4072

It turns out that in the Pulse Configuration register there is a bit that determines if your interrupts will be pulses or latched. I changed that one bit and now the interrupt will clear itself. I tested this and was unable to lock up the device.

I have updated the library so now you can enable pulsed or latched interrupts. This is in the Particle system ModMMA8452Q v1.0.4.

I will do some more testing but, so far, this seems to be a solution and a good lesson:

  • If you are using hardware interrupts and Sleep, make sure the interrupts will clear themselves as you cannot rely on the device to do it before sleeping.

Thank you all again,

Chip

8 Likes

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.