ADXL345 punch level detection

I have this state machine to detect three levels of punch impacts on a wall-mounted PU-mat (no scientific project), using two thresholds and two yellow and red LEDs (later there will be three thresholds and three LEDs). The trouble is that with a strong impact, the lower threshold is crossed first, hence both LEDs light up, but only the second one should. Is there some method to code for waiting if after thresholdA is crossed thresholdB is also crossed right after, to then light up only the red LED?

How should I solve this issue? Thanks for some ideas.


#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_ADXL345_U.h>

Adafruit_ADXL345_Unified accel = Adafruit_ADXL345_Unified(12345);

const float alphaEMA = 0.3;
const float valueShift = 8.8;
const float thresholdA = 7;
const float thresholdB = 21;

const byte pinLEDY = 12;
const byte pinLEDR = 13;
const int timeLEDOn = 150;

enum State {BELOW_A, ABOVE_A, ABOVE_B};
State systemState = BELOW_A;

// Variables that can change
float ax = 0;
float ay = 0;
float az = 0;
float mAV = 0;
float mAVEMA = 9.81;

unsigned long timeLEDYOn = 0;
unsigned long timeLEDROn = 0;

void setup()
{
  Serial.begin(115200);

  pinMode(pinLEDY, OUTPUT);
  pinMode(pinLEDR, OUTPUT);

  digitalWrite(pinLEDY, LOW);
  digitalWrite(pinLEDR, LOW);

  accel.begin();

  accel.setRange(ADXL345_RANGE_8_G);
  accel.setDataRate(ADXL345_DATARATE_25_HZ); // Sampling rate too high for this purpose?
}

void loop()
{
  unsigned long timeNow = millis();

  sensors_event_t event;
  accel.getEvent(&event);

  ax = event.acceleration.x;
  ay = event.acceleration.y;
  az = event.acceleration.z;

  mAV = abs(sqrt(ax * ax + ay * ay + az * az) - valueShift); // Rectify and base on 0

  mAVEMA = (alphaEMA * mAV) + ((1 - alphaEMA) * mAVEMA);

  Serial.print("mAVEMA:"); Serial.println(mAVEMA);

  switch (systemState)
  {
    case BELOW_A:
      if (mAVEMA > thresholdA && mAVEMA < thresholdB)
      {
        systemState = ABOVE_A;
        timeLEDYOn = timeNow;
        digitalWrite(pinLEDY, HIGH);
      }
      else if (mAVEMA >= thresholdB)
      {
        systemState = ABOVE_B;
        timeLEDROn = timeNow;
        digitalWrite(pinLEDY, LOW);
        digitalWrite(pinLEDR, HIGH);
      }
      break;

    case ABOVE_A:
      if (mAVEMA > thresholdB)
      {
        systemState = ABOVE_B;
        timeLEDROn = timeNow;
        digitalWrite(pinLEDY, LOW);
        digitalWrite(pinLEDR, HIGH);
      }
      else if (mAVEMA <= thresholdA)
      {
        systemState = BELOW_A;
      }
      break;

    case ABOVE_B:
      if (mAVEMA <= thresholdA)
      {
        systemState = BELOW_A;
      }
      else if (mAVEMA < thresholdB && mAVEMA > thresholdA)
      {
        systemState = ABOVE_A;
      }
      break;
  }

  if (digitalRead(pinLEDY) == HIGH && timeNow - timeLEDYOn >= timeLEDOn)
  {
    digitalWrite(pinLEDY, LOW);
  }

  if (digitalRead(pinLEDR) == HIGH && timeNow - timeLEDROn >= timeLEDOn)
  {
    digitalWrite(pinLEDR, LOW);
  }

  delay(10); // Only for the serial plotter
}

Try avoiding order precedence:

      if ((mAVEMA > thresholdA) && (mAVEMA < thresholdB))

All right, operator precedence. Had not thought of that. However, the yellow LED still comes on for a very brief moment sometimes.

Maybe I need some kind of peak detection to see if in a certain timeframe (maybe 50 milliseconds?) the value continues to rise or starts falling? Something along those lines maybe, but that's only for one threshold and one LED, quite different from the state machine approach...

  if (mAVEMA > mAVPeak)
  {
    mAVPeak = mAVEMA; // New peak
  }

    if (mAVEMA < thresholdA) // Below A
  {

    if (mAVPeak > thresholdA) // Above A
    {
      digitalWrite(pinLEDY, HIGH);
      timeNow = millis();
      mAVPeak = 0;
    }

    if (millis() - timeNow >= timeLEDOn)
      digitalWrite(pinLEDY, LOW);
  }

The graph transitions through yellow... so "brief yellow" sounds valid... maybe if you wait one sample before displaying the LED?

... on second thought...

Light RED first, then YEL.

Well, nothing, yellow and red must be independent, hence three independent states that can occur any time in any order.

.
.

Sorry, but could you explain? On the way to the red state, the yellow state will inevitably be passed. I increased the sampling rate to 800 MHz, and that's better, but still no cigar.

If you put the test for the red state first, the yellow state should be bypassed.

Not really. A rising signal that crosses the strong threshold must, at some point, have crossed the intermediate threshold, and the reverse when the strong signal falls off.

It sounds like the real problem is measurement timing, and keeping track of whether the signal is rising or falling.

For that, you need to retain a bit of the measurement history, one or more "last measured" points and their measurement times for comparison. Look up "peak detection algorithms" for some ideas.

Of course all this depends on carefully defining what you want the indicators to represent.

Unfortunately, having the thresholdB test case first doesn't change a thing.

switch (systemState)
  {
    case ABOVE_A:
      if (mAVEMA > thresholdB)
      {
        systemState = ABOVE_B;
        timeLEDROn = timeNow;
        digitalWrite(pinLEDY, LOW);
        digitalWrite(pinLEDR, HIGH);
      }
      else if (mAVEMA <= thresholdA)
      {
        systemState = BELOW_A;
      }
      break;

    case BELOW_A:
      if (mAVEMA > thresholdA && mAVEMA < thresholdB)
      {
        systemState = ABOVE_A;
        timeLEDYOn = timeNow;
        digitalWrite(pinLEDY, HIGH);
        digitalWrite(pinLEDR, LOW);
      }
      else if (mAVEMA >= thresholdB)
      {
        systemState = ABOVE_B;
        timeLEDROn = timeNow;
        digitalWrite(pinLEDY, LOW);
        digitalWrite(pinLEDR, HIGH);
      }
      break;

    case ABOVE_B:
      if (mAVEMA <= thresholdA)
      {
        systemState = BELOW_A;
      }
      else if (mAVEMA < thresholdB && mAVEMA > thresholdA)
      {
        systemState = ABOVE_A;
      }
      break;
  }

I think you're right. It's the rising/falling that can help to differentiate. What I cobbled together here ADXL345 punch level detection - #3 by Lagom works, but only for one threshold - but I want to have three, eventually.

Maybe

  • take the time of when the lowest threshold is crossed by the rising signal
  • then wait to see if a higher threshold is crossed
  • then wait to see if an even higher threshold is crossed
  • and then wait some more to see if yet another higher threshold is crossed.

And finally set the corresponding LED pin to HIGH. So, a bit like with peak detection in an audio signal, I'd need a while loop, long enough to find potentially higher thresholds, short enough to be ready for the next punch. From my sampling rate and the serial monitor x-axis tick marks, I guesstimate that a 50 milliseconds while loop should suffice. Not sure how to code this, but that seems like a viable approach?

Peak detection algorithms have to be tuned by experiment, e.g. to choose the best sample rate.

Most people use an array to store some number of samples, which is continuously updated and analyzed for peaks at regular intervals.

Peak detection with a sampling window worked rather well the two times I used it, and nearly all examples I saw used that, so I haven't seen an array approach yet (apart from getHighest() with this library. But in my case here, I'm not after peaks, but a number of thresholds that are crossed before a peak occurs, successive thresholds the signal may cross while it rises.

Are there some multiple threshold finding examples with continuously updating arrays one can learn from?

I mentioned the peak detection algorithms as a source of ideas. You should develop an approach that works for your particular application.

A circular buffer works to keep a history of the most recent sensor data.

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