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