Using deep sleep

A project log for ESP32 Soil Moisture Sensor with Flip-Dot

In this project I develop a small, battery-powered soil moisture sensor using the ESP32 with a status indicator and logging to the cloud!

MaakbaasMaakbaas 04/16/2021 at 19:390 Comments

In this post and the next I will present the software that is running on my ESP32 soil moisture sensor. In this initial version of the software I focus on the parts that allow the sensor to work in standalone mode. This includes the push button, the status indicator and the actual sensor implementation. In the next and final post I will dive into the connectivity and WiFi functions.

---------- more ----------

The functions that the ESP32 soil moisture sensor should have are:

The last two items will be implemented in the next post. The rest will be discussed below.

The two states of the status indicator.

Walking through the code

The basic principle of the code is that the ESP32 walks through a certain function once, and then goes to sleep again until the next wake event. Since I used the Arduino framework for this simple program, that means that all code is placed in the setup() function while the loop() function will remain empty.

The main architecture containing the deep sleep functionality is actually pretty simple and looks as follows:

#include <Arduino.h>
#include <Preferences.h>

Preferences preferences;

enum status_e{UNKNOWN, RED, GREEN}; 
RTC_DATA_ATTR uint16_t threshold = 0;
RTC_DATA_ATTR enum status_e status;
uint16_t output;

void setup() {        
    // put your setup code here, to run once:    
    // perform a soil measurement    
    esp_sleep_wakeup_cause_t wakeup_reason;    
    wakeup_reason = esp_sleep_get_wakeup_cause();

        case ESP_SLEEP_WAKEUP_EXT0 :
        case ESP_SLEEP_WAKEUP_TIMER :             
        default :             

    esp_sleep_enable_ext0_wakeup(GPIO_NUM_26, 0);    
    esp_sleep_enable_timer_wakeup(1000000 * 60 * 60);


void loop() {}

In the setup function, first the GPIO are configured. Pin 13 drives the H-bridge to switch the status indicator to red, pin 27 to green, and pin 26 is the push button. For this the internal pullup of the ESP32 is used.

Next, the function esp_sleep_get_wakeup_cause is used to determine what caused the ESP32 to wake up. There are three possibilities which are detected in a switch statement. The three possibilities are:

  1. Wake up from deep sleep by pushing the button
  2. Wake up from deep sleep because of the timer
  3. First startup of the device

These functions will be discussed in the next sections. Finally the deep sleep function is set up to wake from the button or by means of a timer.


Reading the sensor using the ESP32 touch functions happens here as was already discussed in the previous post:

touch_pad_set_voltage(TOUCH_HVOLT_2V4, TOUCH_LVOLT_0V5, TOUCH_HVOLT_ATTEN_1V);
touch_pad_config(TOUCH_PAD_NUM2, 0);

//do measurement 


When the button is pressed first a delay is placed to debounce the button. Otherwise the function might complete and retrigger multiple times before the button is released. After this, the threshold is updated with the current sensor value output subtracted by 5 to have some margin. The new threshold is stored into NVM by using the ESP32 preferences functions. Finally the status indicator is switched to red if it was not yet red before.

//debounce button press

//set threshold to current value      
threshold = output - 5;
preferences.begin("nvm", false); 

if (status!=RED)
    //set to red    


The timer function is even simpler. All it does is switch the status indicator where needed based on the current sensor reading.

//check the sensor
if (output > threshold && status!=RED)
    //set to red    
} else if (output <= threshold && status!=GREEN)  {    
    //set to green    


The final function handles initialization when the device is first powered on. In this case the status indicator is not changed, because it is quite likely that the device is not placed in the soil when it is first powered on. During this first initialization the threshold is read from the NVM and stored in a local variable. This local variable has theRTC_DATA_ATTR attribute, which means that it will be stored in such a way that it is persistent during deep sleep. This is not the case for normal memory.

//read EEPROM
preferences.begin("nvm", true); 
threshold = preferences.getUShort("threshold");

Next steps

In the next and final post I will present the connectivity and WiFi functions for this device which will enable it to push measurements and notifications to the cloud.