ESP32 Arduino: Shake sensor debounce

In this tutorial we will learn how to detect shaking in a digital shaking sensor and debounce the signal produced by the sensor in our program. The tests were performed using a DFRobot’s ESP32 module integrated in a ESP32 development board.

Introduction

In this tutorial we will learn how to detect shaking in a digital shaking sensor and debounce the signal produced by the sensor in our program. We will connect the sensor to the ESP32 and use the Arduino core.

We have already covered on this previous post the connection diagram between both devices and the software needed to interact with it using interrupts and a semaphore. We have also seen that, when a shaking movement is detected, the digital signal produced by the sensor goes from HIGH to LOW.

Nonetheless, when testing the code, we realized that shaking the sensor once produced multiple interrupts. This occurs due to the fact that the the shake sensor is a mechanical device and thus, when actuated, there’s a transitory behavior where the signal oscillates between HIGH and LOW, before stabilizing. You can read more about this effect here.

Since our microcontroller is triggering an interrupt when the signal goes from HIGH to LOW, it means that it will trigger multiple interrupts during this bouncing period. If we just ignore this effect, we will be detecting multiple shakes on the sensor when only one has occurred, which is incorrect.

A simple way of solving this bouncing behavior is accounting for it on our software. Since we know that, after shaking the sensor, multiple interrupts will be triggered, we treat the first and we can just ignore subsequent interrupts during a predefined period of time (we will call it debounce time in our program).

After that debounce time elapses, if a new interrupt is triggered, we treat it again as a new shake movement and then we ignore the next ones again during the debounce time.

Note that there is no global formula to calculate this debounce time, as it depends on the behavior of the device we are using. This time should be set with a value big enough to cover the time interval during which the input signal will be unstable due to the bouncing effect, but also small enough to not discard subsequent shakes on the sensor.

I’ll be using a value of 250 milliseconds, which is reasonable for the sensor we are using. Naturally, if you have access to equipment that allows you to analyze in detail the transitory behavior of the signal, you can get to a more precise value.

The tests were performed using a DFRobot’s ESP32 module integrated in a ESP32 development board.

The code

We will start by declaring a variable to hold the number of the pin that will be connected to the shake sensor.

const byte interruptPin = 22;

Then, we will declare a variable that will store the time when the last shake detection interrupt was detected. This should only store “correct” interrupts and not the discarded ones during the bouncing time.

We are going to store the time as milliseconds elapsed since the ESP32 started working, which can be obtained using the millis function. This is much simpler than using actual timestamps (with date and time), since we simply want to check the time interval elapsed since the last interrupt.

Note that the millis function returns the elapsed time as an unsigned long, which is the type we should use to our variable.

unsigned long lastDetection = 0;

Additionally, we will store the debounce time that needs to be elapsed in order for us to acknowledge a new interrupt as a shake event, since the last one. Since this corresponds to a time interval, we should also declare it as unsigned.

As already mentioned in the introductory section, we will use a value of 250 milliseconds. Nonetheless, you can try to change this value to optimize the results.

unsigned long debounceTime = 250;

Finally, we will declare a variable of type SemaphoreHandle_t, which corresponds to a handle to a semaphore that we will create later. This semaphore will be used to synchronize the main loop with the interrupt handling function.

SemaphoreHandle_t syncSemaphore;

Moving on to the setup function, we will start by initializing a serial connection, to output the results of our program.

Serial.begin(115200);

Then we will create our semaphore with a call to the xSemaphoreCreateBinary function and we will store the returning value in our previously declared SemaphoreHandle_t global variable.

syncSemaphore = xSemaphoreCreateBinary();

After this, we will setup the pin attached to the sensor to work in INPUT_PULLUP mode, which will guarantee that it will be in a HIGH state when no signal is applied to it. This avoids false interrupts from its value floating between HIGH and LOW if it is left unconnected.

To finalize, we will attach the interrupt to the mentioned pin. Since the sensor outputs a low level pulse when shaken, we will configure the interrupt to be triggered on a falling edge of the input signal (in order words, when the signal goes from HIGH to LOW).

pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);

The complete setup function can be seen below.

void setup() {

  Serial.begin(115200);

  syncSemaphore = xSemaphoreCreateBinary();

  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);

}

Moving on to the main loop, the first thing we will do is trying to obtain the semaphore, by calling the xSemaphoreTake function.

As first input, we will pass our semaphore handle and as second input the value portMAX_DELAY, which means that the task should wait indefinitely until the semaphore can be obtained.

With this approach, the main loop task stays blocked while there are no interrupts to handle. Thus, we avoid polling the sensor and the processor is free to execute other useful computation.

xSemaphoreTake(syncSemaphore, portMAX_DELAY);

When this task obtains the semaphore, it will unblock. Thus, we know that an interrupt has occurred and we need to treat it. Our actuation in response to the detection of a shake movement will simply be printing a message to the serial port.

Nonetheless, as already mentioned, we need to debounce the sensor, because a single shake of the sensor will trigger multiple interrupts due to the bouncing effect.

So, our debouncing logic will simply correspond to checking if the time elapsed since the last time a shake in the sensor was detected is greater than the debounce time.

If it is not greater, then we assume that the interrupt was triggered due to the bouncing effect in the signal, and we simply discard it and we do not do anything else.

Otherwise, if the elapsed time is greater than the debounce time, then we can acknowledge it as a shake in the sensor and we can treat it properly by printing the “shake detected” message.

Note that, in case we acknowledge a new shake event, we need to update the variable that contains the time of the last detection with the current time, returned by the millis function.

This way, we will now be able to ignore the next interrupts that will be triggered due to the bouncing effect caused by this new shake event.

if(millis() - lastDetection > debounceTime){

   Serial.println("Shake detected");
   lastDetection = millis();
}

Important: As shown above, you should do the comparison by subtracting the last shake detection time to the current millis value, and comparing it against the debouncing interval. This way, the code will keep working correctly even when the millis function overflows. You can check here a great explanation about why this works and why using the addition is not recommended.

The complete loop code can be seen below.

void loop() {

  xSemaphoreTake(syncSemaphore, portMAX_DELAY);

  if(millis() - lastDetection > debounceTime){

    Serial.println("Shake detected");
    lastDetection = millis();
  }
}

The interrupt handling function implementation will be very simple and equal to what we have already covered in the previous post. It will simply add a unit to the semaphore to unblock the main loop task.

This is done with the xSemaphoreGiveFromISR function, passing as first input the semaphore handle and as second input the value NULL (since we won’t need to make use of this second parameter).

void IRAM_ATTR handleInterrupt() {
    xSemaphoreGiveFromISR(syncSemaphore, NULL);
}

The final complete code can be seen below.

const byte interruptPin = 22;

unsigned long lastDetection = 0;
unsigned long debounceTime = 250;

SemaphoreHandle_t syncSemaphore;

void IRAM_ATTR handleInterrupt() {
    xSemaphoreGiveFromISR(syncSemaphore, NULL);
}

void setup() {

  Serial.begin(115200);

  syncSemaphore = xSemaphoreCreateBinary();

  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);

}

void loop() {

  xSemaphoreTake(syncSemaphore, portMAX_DELAY);

  if(millis() - lastDetection > debounceTime){

    Serial.println("Shake detected");
    lastDetection = millis();
  }
}

Testing the code

To test the code, simply compile it and upload it to your device. When the procedure finishes, open the Arduino IDE serial monitor. Then, shake the sensor in the direction indicated in the PCB of the module.

You should get an output similar to figure 1, which shows the message when the sensor shake is detected. Note however that, every time you shake the sensor, you should only get a single message because the bouncing effect is being treated in our program.

Output of digital shake sensor interrupt, using the ESP32 and the Arduino core

Figure 1 – Output of the shake detection digital sensor, with debouncing.

Related posts

Leave a Reply

Discover more from techtutorialsx

Subscribe now to keep reading and get access to the full archive.

Continue reading