0

$USD

$USD
TUTORIALS ESP32

ESP32 Arduino Tutorial: Digital shake sensor

DFRobot Mar 10 2019 1124

On this ESP32 tutorial we will check how to interact with a digital shake sensor, using the ESP32 and the Arduino core. The tests were performed using a DFRobot’s ESP32 module integrated in a ESP32 development board.

Introduction

On this tutorial we will check how to interact with a digital shake sensor, using the ESP32 and the Arduino core.

We will be using this Digital Shake sensor from DFRobot. This is a very simple device that allows to detect a shaking movement in a specific direction, outputting a digital signal [1] that can be processed with a microcontroller, such as the ESP32.

The device can be powered with a voltage between 3.3 V and 5 V. In our case, since we will be using an ESP32, we will use the 3.3 V level. When the device is powered, an onboard red LED should be turned on.
Note that the sensor also contains a blue onboard LED, which will turn on when shaking it in the correct direction. This helps confirming if the device is working well independently of the software that is running on the microcontroller.

You can read more about the sensor on its wiki.
In this tutorial we will be using interrupts and a binary semaphore to detect the shaking movement and to print a message indicating the detection. By using interrupts, we avoid constantly polling the sensor, leaving the ESP32 CPU free for other useful computation.

So, our Arduino main loop will stay blocked in a semaphore until the sensor is shaken (this event will be signaled by an interrupt). During the time it will be blocked, other tasks can execute useful computation.
In our case, we are simply interacting with the sensor, which means there’s nothing else to do. Nonetheless, in a real application scenario, there could be other tasks doing useful computation, which is why we are avoiding polling and using interrupts and semaphores for a more efficient implementation than can be used in other applications.
You can read more about external interrupts on the ESP32 here.
The tests were performed using a DFRobot’s ESP32 module integrated in a ESP32 development board.

The electric diagram

The electric diagram for the connection between the ESP32 and the sensor is very simple.

Basically, we just need to connect the digital output pin of the sensor to a GPIO of the ESP32. As indicated in the figure, the output of the sensor is labeled with the letter “D” in the board.
Then, we need to power the sensor with 3.3 V (the VCC pin is labeled with a “+” sign) and connect both the ESP32 and the sensor to a common GND (the GND pin is labeled with a “–” sign).
As already mentioned in the introductory section, after powering on the sensor, the onboard power indicator LED should turn on with a red color.

The code

Setup

We will start our code by declaring a global variable to store the number of the digital pin of the ESP32 that will be connected to the sensor. This way, we can easily change it here, at the top of the code. I will be using pin 22.

const byte interruptPin = 22;

We will also declare a variable of type SemaphoreHandle_t, which corresponds to a handle that can be used to reference a semaphore, which we will create later. Since this handle is being declared as a global variable, both our main code and the interrupt service routine will be able to access it.

SemaphoreHandle_t syncSemaphore;

Moving on to the Arduino setup, the first thing we will do is opening a serial connection, to output the results of our program, more precisely, to print a message when we detect shaking on the sensor.

Serial.begin(115200);

Then, we will create the semaphore that will be used to perform the synchronization between the main loop and the interrupt service routine. We can create a binary semaphore by calling the xSemaphoreCreateBinary function.

This function takes no arguments and, on success, it will return a handle to the created semaphore, which we will assign to our previously declared global variable.

Note that binary semaphores should be used instead of mutexes for interrupts / tasks synchronization, which is why we are using this primitive. You can read more about it here.

syncSemaphore = xSemaphoreCreateBinary();

Then, we need to declare the operating mode of the pin that will be connected to the sensor, since GPIO pins can be used both as input or output. In our case, it will be working as an input pin.

We set the operating mode of the pin by calling the pinMode function, passing as first argument the pin number and as second the mode of operation.

We will use the INPUT_PULLUP constant for the operation mode, which means the pin will be in input pull-up mode. This will guarantee that the pin will be at a known state (VCC) when no signal is applied to it.

If we don’t do this and we leave the pin unconnected, it can float between GND and VCC voltage levels, causing undesired interrupts.

pinMode(interruptPin, INPUT_PULLUP);

To finalize the setup function, we still need to attach the interrupt to the digital pin connected to the sensor. This can be done by calling the attachInterrupt function.

This function receives as first input the interrupt number, as second the interrupt handling function and as third input a constant specifying what type of change in the digital level of the input signal triggers the interrupt.

To guarantee that we have the correct interrupt number for the pin we are using, we can call the digitalPinToInterrupt function, which maps the number of the pin to the corresponding internal interrupt number.

So, as first input of the attachInterrupt function, we should pass the output of the digitalPinToInterrupt function.

As second parameter we will pass a function called handleInterrupt, which we will define later.

As third and final parameter, we will pass the value FALLING, which indicates that the interrupt should be triggered when a falling edge is detected in the input signal.

In other words, we will detect when the input signal that comes from the sensor changes from HIGH to LOW. This makes sense since the sensor will output a low level pulse when shaken [1].

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);
 
}

The main loop

The Arduino main loop will be very simple. The first thing we will do is trying to obtain the previously created semaphore, with a call to the xSemaphoreTake function.

As first input of this function we will pass our semaphore handle. As second input, we will pass the value portMAX_DELAY, which means that the main loop task should block indefinitely until the semaphore is available.

With this approach, we won’t be wasting CPU cycles trying to check if the sensor has detected movement and we leave the processor free o execute other useful computation. Note that this is a simple example where we are not doing anything else in our code but, in a real application scenario, we could have other tasks running in parallel.

xSemaphoreTake(syncSemaphore, portMAX_DELAY);

After this point, we know that the interrupt service routine has released the semaphore, meaning that the sensor detected shaking. So, we print a message indicating this to the user.

Serial.println("Shake detected");

After this, the main loop function ends and it will execute again from the beginning,. This means it will block again waiting for the semaphore, until another shake of the sensor triggers a new interrupt. The main loop complete code can be seen below.

void loop() {
 
    xSemaphoreTake(syncSemaphore, portMAX_DELAY);
    Serial.println("Shake detected");
 
}

The Interrupt Handling function

To finalize, we just need to write our interrupt handling function. As we have seen before, we called it handleInterrupt. This function should receive no arguments and return void.

We should also add the IRAM_ATTR attribute to our ISR function, so it is placed in IRAM by the compiler. You can read more about it here.

In the implementation of the function, we will just unblock the Arduino main loop. To do it, we simply need to add a unit to the semaphore, by calling the xSemaphoreGiveFromISR function.

As first input, this function receives the semaphore handle. It receives as second input an additional parameter that we won’t need, so we can simply pass the value NULL. You can read more about the use of this parameter here.

The full function code can be seen below.

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

The final code

The final complete code can be seen below.

const byte interruptPin = 22;
 
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);
    Serial.println("Shake detected");
 
}

Testing the code

After finishing the connection between the devices and powering them, simply compile the code and upload it to your ESP32. Then, when the procedure finishes, open the Arduino IDE serial monitor.

After that, shake the sensor in the direction illustrated on the top of the sensor board. The onboard blue LED should be turned on while shaking the sensor and, on the Arduino IDE serial monitor.

Note that due to the fact that the shake sensor is a mechanical device, when we shake it once it will trigger multiple interrupts due to the bouncing effect, which we did not consider in our code. You can read more about the bouncing effect here. We will learn how to solve this issue in another tutorial.

References
[1]https://www.dfrobot.com/wiki/index.php/Gravity:_Digital_Shake_Sensor_SKU:_SEN0289#More_Documents