1

I'm calculating the acceleration magnitude vector from an accelerometer inside a thick PU mat. When the mat is hit, depending on the value I obtain, only one of two LEDs, later more, shall light up briefly. However, because the low(er) threshold(s) is(are) inevitably always crossed first, stronger hits mean both(all) LEDs light up, not only the single one that corresponds to the highest threshold crossed at the time. I hope the image explains the issue. Somehow, I would need to look ahead if, when a lower threshold is crossed, a higher one may be crossed right after, maybe an even higher one. How must I change/augment the code?

Explanatory graphic

  mAV = sqrt(ax * ax + ay * ay + az * az) - valueShift;

  if (mAV < 0)
  {
    mAV = 0;
  }

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

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

  if (mAVEMA >= thresholdC && previousmAVEMA < thresholdC)
  {
    digitalWrite(pinLEDR, HIGH);
    timeLEDROn = timeNow;
  }

  if (mAVEMA >= thresholdB && previousmAVEMA < thresholdB)
  {
    digitalWrite(pinLEDY, HIGH);
    timeLEDYOn = timeNow;
  }

  previousmAVEMA = mAVEMA;

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

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

Edit: Getting closer to the desired behaviour https://wokwi.com/projects/406459026144282625

const int pinAccel = A0; // simulate acceleration magnitude vector
const int pinsLED[] = {11, 12, 13}; // yellow, orange, red
const int threshold0 = 300;
const int threshold1 = 600;
const int threshold2 = 900;
const unsigned long timeSearch = 200;
const unsigned long timeLEDsOn = 500;

int mAVEMA = 0;
int active = -1;
unsigned long timersLED[] = {0, 0, 0};

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

  for (byte i = 0; i < 3; i++)
  {
    pinMode(pinsLED[i], OUTPUT);
  }
}

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

  mAVEMA = analogRead(pinAccel);
  int threshold = checkThresholds(mAVEMA);

  // only on rising signal
  if (threshold == 0)
  {
    unsigned long timerStart = millis();
    active = 0;
    timersLED[active] = millis();

    while (millis() - timerStart < timeSearch) // timeSearch user adjustable?
    {
      mAVEMA = analogRead(pinAccel);
      threshold = checkThresholds(mAVEMA);

      if (threshold == 1)
      {
        active = 1;
        timersLED[active] = millis();
      }

      if (threshold == 2) // reset to 0 once one of the three LEDs was lit
      {
        active = 2;
        timersLED[active] = millis();
      }
    }
    digitalWrite(pinsLED[active], HIGH);
  }

  if (timeNow - timersLED[active] >= timeLEDsOn)
    {
    digitalWrite(pinsLED[active], LOW);
    }

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

int checkThresholds(int accel) // later floats for real accelerometer!
{
  if (mAVEMA >= threshold2) return 2;
  if (mAVEMA >= threshold1) return 1;
  if (mAVEMA >= threshold0) return 0;
  return -1;
}

Improved graphic Test set-up

7
  • insert a layer of separation between the signal detection and the lighting of the LEDs ... when signal is detected, set a flag variable, nothing else ... light the LEDs in response to the value of the flag variables, not in response to the signal
    – jsotola
    Commented Aug 16 at 19:55
  • @jsotola Separated, see edit, but to no avail. I feel that I need to take the time of when the lowest threshold is crossed by the rising signal, and then wait to see if a higher threshold is crossed, and then wait to see if yet another higher threshold is crossed - and then set the corresponding LED pin to HIGH. But how to wait and for how long? Commented Aug 16 at 20:31
  • you're just blindly turning on the LEDs ... think about how the LEDs should behave
    – jsotola
    Commented Aug 16 at 21:23
  • Well, I did, as the explanatory graphic shows; only when the signal is rising: thresholdA -> LEDA, thresholdB -> only LEDB, thresholdC -> only LEDC, and so forth. The problem is that when, for example, thresholdC is crossed, thresholdA and thresholdB are inevitably crossed beforehand. Commented Aug 17 at 9:10
  • 1
    Are these leds only for a visual indication of the impact on this mat or are you later going to attempt to extract the led on periods into a calculation ? Assuming the simple case and looking at your sample, I'd probably wait until a rising edge crossed the lowest threshold and start a 30ms timer. Keep measuring the maximum value while the timer is running then, on expiry of the timer, light the appropriate led for X ms. The start of the led indication will slightly lag the peak by a few ms.
    – 6v6gt
    Commented Aug 18 at 11:13

2 Answers 2

2

I think chrisl's solution (let the LED change as you cross higher thresholds) is the simplest and probably the most effective for your problem. If you really want only one LED to light for each punch, then you will have add a delay. I would implement this using a finite state machine as follows:

The system can be in one of three states:

  • IDLE: the acceleration is below the first threshold, and all LEDs are off

  • WAITING: a threshold has just been crossed, and the Arduino is waiting some time for a higher threshold to be also crossed

  • LIT: one LED is on, and stays on for a fixed time

When in the WAITING state, if a higher threshold is indeed crossed, the timer is reset in order to wait for the next threshold. If the timer expires, an LED is lit and we go to the LIT state.

The state diagram:

state diagram

For programming this, I would use this helper function:

/*
 * The punch "level" is:
 *   (0, 1 or 2) if the acceleration reaches the threshold (A, B or C)
 *   -1 otherwise
 */
int get_level(float accel)
{
    if (accel >= threshold_C) return 2;
    if (accel >= threshold_B) return 1;
    if (accel >= threshold_A) return 0;
    return -1;
}

And here is the code for the state machine:

enum { IDLE, WAITING, LIT } state;
int punch_level = -1;
uint32_t timer_start;

void loop() {
    float accel = get_acceleration();
    int level = get_level(accel);
    int32_t now = millis();
    switch (state) {
        case IDLE:
            if (level >= 0) {  // a threshold has been crossed
                punch_level = level;
                state = WAITING;
                timer_start = now;
            }
            break;
        case WAITING:
            if (level > punch_level) {  // crossed a higher threshold
                punch_level = level;
                timer_start = now;
            }
            if (now - timer_start >= wait_timeout) {
                digitalWrite(led_pins[punch_level], HIGH);
                state = LIT;
                timer_start = now;
            }
            break;
        case LIT:
            if (now - timer_start >= light_timeout) {
                digitalWrite(led_pins[punch_level], LOW);
                state = IDLE;
            }
            break;
    }
}
9
  • Thanks, I began with a state machine, and failed miserably. I will study your suggestion. Commented Aug 18 at 16:29
  • Thanks, again; I put your suggestion in WOKWI wokwi.com/projects/406583203546521601 to try it out and understand the logic, but it is not working. Do you mind having another look? Commented Aug 20 at 11:49
  • With your helper function idea, I got closer to the desired system behaviour, see edit in original question. Still needs testing for rising edge only. Commented Aug 21 at 0:03
  • 1
    @Systembolaget digitalWrite(led_pins[level], HIGH); I believe that instead of level what as meant here is punch_level since that retains the highest value of level seen in the session. And it is the same when you are switching off the led. Very elegant solution by the way.
    – 6v6gt
    Commented Aug 21 at 4:15
  • 1
    As a kluge, add a delay(3000) after switching the led off. Better would be to add a new state say LOCKOUT. Edit. Post crossed with Edgar.
    – 6v6gt
    Commented Aug 21 at 7:34
2

As you are sampling an analog signal, you will cross the lower threshold first. That cannot be changed. But it might be enough to only light the currently relevant LED, turning all others off, like the following form:

if(mAVEMA >= thresholdC){
  digitalWrite(pin_led_c, HIGH);
  digitalWrite(pin_led_b, LOW);
} else if (mAVEMA >= thresholdB) {
  digitalWrite(pin_led_c, LOW);
  digitalWrite(pin_led_b, HIGH);
}

Or, if you want to generalize for more thresholds you can also turn off all LEDs, then check for the thresholds and turn the corresponding LED on:

// on global scope
int pin_led[] = {4,5,6}; //defining all LED pins in order
int N_LEDs = sizeof(pin_led)/sizeof(pin_led[0]) // getting number of LEDs
int threshold[] = {100, 200, 500}; //defining all thresholds in order

// in your measurement function or in loop()
for(int i=0;i<N_LEDs;i++) digitalWrite(pin_led[i], LOW); //turning all LEDs off
for(int i=N_LEDs-1;i>0;i--){ //looping backwards through thresholds
  if(mAVEMA >= threshold[i]){ //checking from highest to lowest threshold
    digitalWrite(pin_led[i], HIGH);
    break; // breaking, since threshold was met
  }
}

Note, that this will light up the LED for the lower threshold briefly in the time, that the signal spends between both thresholds. When the higher threshold is reached, the LED for the lower one is turned off again.

If you don't want that, you need to change how you handle your measurements. As you cannot look into the future, you need to change your simple threshold measurements to a peak detection algorithm. How you implement it depends on your requirements, though it will always mean, that the LEDs turn only on shortly after the peak (otherwise you cannot know, if it is a peak). For example you can check, if the value sinks below its current maximum value (in a defined time frame) for a specific minimum time, so checking the maximum in 500ms and checking, if the value sank at least 100 points in 100ms after the maximum was reached.

2
  • Thanks, so it's like: timestamp when lowest thresholdA is crossed -> wait for possibly higher threshold crossings until signal peak was detected -> only then switch on the LED for the highest threshold crossed. A trained boxer can throw a jab, left hook and right cross in one second, and impact time is up to 200 milliseconds on a slow punch, so that should be my "wait and see" threshold sampling window, right? Commented Aug 18 at 13:36
  • Edited original question with a partial solution, I think. But now comes the part to wait for possibly higher thresholds, before the signal peak is reached. The Edit code should work in WOKWI. Commented Aug 18 at 16:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.