ESP8266: Using a rain sensor

Introduction

The objective of this post is to explain how to detect rain drops using the ESP8266, external interrupts and a rain sensor.

The rain sensor that we will be using is shown in figure 1. This is a generic rain sensor that can be bought at Ebay for less than 1 euro.

Picture of the rain sensor used for the tests.
Figure 1 – Rain sensor.

A more detailed explanation about this type of rain sensor is conducted on this previous post.

We will be using external interrupts to detect when rain falls on the sensor, without having to constantly pole it to check its state. This way, we can leave the processor free to do more useful computation and only interact with the sensor when needed.

The hardware

The hardware configuration needed for this tutorial is illustrated in figure 2.

Electric diagram of the connections between the ESP8266 and the rain sensor.
Figure 2 – Connection diagram between the ESP8266 and the rain sensor.

Basically, we just need to power the rain sensor and connect its digital output pin to the GPIO of the ESP8266 to which the interrupt will be attached. The analog pin of the sensor may be left unconnected.

For this tutorial, we will use GPIO13, as indicated in the figure. Please take in consideration that, in the NodeMCU board, the pins of the microcontroller don’t map to the pins of the board. The correct mapping can be seen here and, as described, the GPIO13 will map to the D7 pin of the board.

The code

The code for this tutorial is very simple and is based on a previous post that explains how to use external interrupts in the ESP8266.

We will start by declaring some global variables. First, we declare the number of the pin where we will be attaching the interrupt, so it’s easy to change later.

Next, we will declare a flag that will be used by the interrupt routine to signal the main loop that an interrupt has occurred. We will use this approach to move the handling logic to the main loop, so the interrupt routine is as short as possible, which is the best practice.

Since this variable will be changed in the interrupt routine, we need to declare it as volatile.

To keep track of how many interrupts occur, we will also declare a variable to store this number, which will be incremented in the main loop code that implements the interrupt handling logic.

To make this example useful, we will tackle the bouncing problem, which would make us detect more interrupts than we should. To do so, we will declare two additional global variables. Their purpose will be explained later.

const byte interruptPin = 13;
volatile boolean checkInterrupt = false;
int numberOfInterrupts = 0;

unsigned long debounceTime = 1000;
unsigned long lastDebounce = 0;

In the setup function, we will begin the serial communication, so we can output a message when rain is detected.

Additionally, we will attach the interrupt to the digital pin. To to so, we declare the pin as input, with the pinMode function, which receives as first argument the pin and as second argument the mode. We will use the INPUT_PULLUP mode, so the pin will be in a known state when no signal is connected to it. In this case, since we are working with interrupts, this is very important to avoid false interrupts due to the pin being floating.

pinMode(interruptPin, INPUT_PULLUP);

Finally, we attach the interrupt to the pin with the attachInterrupt function. It receives as first argument the interrupt number, as second argument the interrupt service routine, and as third the interrupt mode.

In the first argument, we will use the digitalPinToInterrupt function, which receives as input the interrupt pin, to translate the actual digital pin to the specific interrupt number [1].

As explained in the previous post where we described the rain sensor, its digital pin is active-low, meaning it will have a voltage equal to GND when rain is detected. So, the interrupt mode will be FALLING, which means the interrupt will be triggered when the pin changes from HIGH (no rain detected, voltage is VCC) to LOW (rain detected, voltage is GND).

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

The whole setup function is shown bellow.

void setup() {

  Serial.begin(115200);
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);

}

Now, we will define the interrupt service routine to handle the external interrupt. As stated, interrupt service routines should be as fast as possible, so we will only set the checkInterrupt flag to true, to signal the main loop that it needs to do something in result of an external interrupt.

void handleInterrupt() {
  checkInterrupt = true;
}

Finally, we will define our main loop function, where we will print a message in result of an interrupt being detected.

So, we will check if the checkInterrupt flag is set to true to run the handling code. If it is, we increment the number of interrupts of the global counter, set the flag to false and print the message to the serial port.

Additionally, we will implement our debouncing logic in the main loop. Since the bouncing corresponds to a transitory regime where the signal is bouncing between HIGH (VCC) and LOW (GND), multiple interrupts will be triggered for just one event of rain detection.

So,  when we enter the interrupt code, we will register the current number of milliseconds with the millis function. This function returns the number of milliseconds since the board began running the current program [3]. This value will be stored in the lastDebounce global variable, declared early.

Then, we will consider a debouncing time during which we will ignore other interrupts that may occurred. This debouncing time is configured in the debounceTime global variable and, in our case, has the value of 1000 milliseconds (1 second). You may need to tune it a little bit to get optimal results for your testing scenario.

The portion of the conditional code used to control the entrance in the handling logic is shown bellow.

if (checkInterrupt == true && ( (millis() - lastDebounce)  > debounceTime )) {

    // handling code

} else checkInterrupt = false;

For the debouncing part, we check if the time elapsed from the last interrupt is greater than the debounce time. To do so, we subtract the current milliseconds from the last interrupt millisseconds and compare it against the debouncing time.

If the conditions to enter the handling logic don’t verify, we set the checkInterrupt flag to false. Otherwise, we would always enter the handling logic after the bouncing time, since the flag would have the value true from the last interrupt triggered by the bouncing effect.

The final complete code with the handle logic is shown bellow. We are just printing a simple “Rain detected” message with the total number of interrupts appended.

const byte interruptPin = 13;
volatile boolean checkInterrupt = false;
int numberOfInterrupts = 0;

unsigned long debounceTime = 1000;
unsigned long lastDebounce = 0;

void setup() {

  Serial.begin(115200);
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);

}

void handleInterrupt() {
  checkInterrupt = true;
}

void loop() {

  if (checkInterrupt == true && ( (millis() - lastDebounce)  > debounceTime )) {

    lastDebounce = millis();
    checkInterrupt = false;
    numberOfInterrupts++;

    Serial.print("Rain detected ");
    Serial.println(numberOfInterrupts);

  } else checkInterrupt = false;

}

Testing the code

To test the code we can simply use a glass of water and spill some drops on the rain sensor. Just be careful to avoid spilling water on top of the other electronic components.

The sensor will only output a LOW value on the digital pin when the conductivity between the conductive stripes of the sensor surface is high enough. If you are having troubles with detecting the water drops, you may have to change the sensitivity of the sensor by changing the position of its potenciometer. You can also try to add some salt to the water to increase its conductivity. This article shows a very interesting chart of the water conductivity versus its concentration of salt.

Since these rain sensor boards typically have a LED that is on when water is detected, it’s also easy to check if the problem is in the sensor or in the code.

Figure 3 illustrates the expected output of the serial console in the Arduino IDE

Output of the ESP8266 Arduino program, showing the detection of the rain drops.
Figure 3 – Output of the rain detecting program.

Final notes

As seen in this post, external interrupts are very useful to interact with sensors. Nevertheless, they bring some challenges, such as the need to handle the bouncing effect. There are multiple techniques to mitigate this problem and the one presented here is a very simple approach, but that works very well. Nevertheless, a simple improvement to the technique used is to inhibit the actual interrupts during the bouncing interval, avoiding the unnecessary execution of the interrupt service routine during this period.

References

[1] https://www.arduino.cc/en/Reference/AttachInterrupt

[2] https://www.arduino.cc/en/Reference/AttachInterrupt

[3] https://www.arduino.cc/en/Reference/Millis

Technical details

  • ESP8266 libraries: v2.3.0

1 thought on “ESP8266: Using a rain sensor”

Leave a Reply to kiranshashinyCancel reply

Discover more from techtutorialsx

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

Continue reading