Introduction

The ESP32 system on a chip (SoC) is a popular internet of things (IoT) platform with a rich radio communication suite including WiFi, Bluetooth/Bluetooth Low Energy (BLE), and the proprietary ESP-NOW protocol. With its dual cores, 4MB of onboard flash, built-in sensors, advanced power-saving capabilities, and array of radio communication facilities, it is a worthy successor to the already impressive ESP8266.

In this article, we will explore many core features of this platform and help you start building incredible connected gadgets.

Prerequisites

Before starting this project, you should have:

  • An ESP32-based development board, such as the DOIT Devkit 01, the NodeMCU-32s, or the Adafruit Huzzah32 Feather
  • A Linux, Windows, or Mac-based computer
  • The latest Arduino integrated development environment (IDE) and the " esp32" board manager
  • A microUSB cable
  • A WiFi 802.11 b/g/n network with Internet access

Concepts

Unboxing the Development Board

First, install the Arduino IDE. Especially if you’re on Linux, you must carefully follow the installation instructions at the Arduino website. Next, connect your microUSB cable from your computer to your new ESP32 dev board.

Now, let’s identify your board’s pinout. This is easier with a name brand board than it is with a generic because generic boards don’t always ship with a pinout. However, almost always, the pins will either be marked on the board itself or they will be laid out the same as on the board’s non-generic counterpart. The trouble is, there’s often no way to readily tell what your board clones. If the above two suggestions don’t help, then you’ll need to look up the datasheet for the board. At that point, you can compare the generic board’s pinouts with those of the major brands to find out which board yours matches. A search engine is your best friend here.

Fortunately, each of the pins for any board share common, official names. For example, GPIO5/D5 is always GPIO5/D5 regardless of the board, and irrespective of where the pin is physically located on the board. Always identify the I/O pins by their official general-purpose input/output (GPIO) number so you’ll wire things consistently regardless of which specific board you are using.

Features

The ESP32’s central processing unit (CPU) is a Tensilica Xtensa LX6 microprocessor with one or two 32-bit cores, and 4MB to 16MB of flash memory, depending on the configuration. The operating speed is usually 160MHz or sometimes 240Mhz, although you can change this programmatically. The most common configuration is dual core with 4MB of flash memory. The system also features a coprocessor that operates in deep sleep mode, listening for a wake event.

For connectivity, the system supports WiFi 802.11 b/g/n, Bluetooth and Bluetooth BLE, and ESP-NOW. ESP-NOW is a proprietary radio communication protocol these systems use. The WiFi supports both station mode and soft-AP mode. The Bluetooth operation can either be in client or server mode.

In terms of power management, you can control the CPU speed and you can put the system in a " deep sleep" state. The deep sleep state powers down everything except 8KB of SRAM and the coprocessor, which wakes the system upon input from one of its touch sensors, from one or more of its GPIO pins being driven high or low, or after a certain amount of time passes. Each time the system wakes, it goes through a sort of " warm" reboot where everything restarts except for the 8KB of SRAM which was preserved.

The ESP32 contains a built-in Hall effect sensor which can detect changes in magnetic fields, and touch capacitive sensors which respond to being touched by anything that carries a slight current (like you or me). In addition, the older chips contain a temperature sensor, which for whatever reason was not carried over to newer chip versions.

If you’re coming from the Arduino world, you may be used to having certain pins dedicated to certain features. The ESP32 in part does away with this model and allows most pins to be remapped to whatever you like, meaning you can put your serial universal asynchronous receiver-transmitter (UART), Serial Peripheral Interface (SPI), or Inter-Integrated Circuit (I2C) busses on nearly any pins you like. The capacitive touch sensors like the analog-to-digital converters (ADCs) and digital-to-analog converters (DACs) cannot be remapped. They will always be attached to the same GPIO pins. Also, a few of the GPIO pins are input only.

The Starter Project

Finally, we’re getting to the project. We’re going to keep it simple and create a project that connects to a web service and submits a value whenever a specific pin on the board is touched, as well as once every minute. It doesn’t require any external sensors, servo motors, screens, or other fancy gadgets – just the dev board itself. There are already projects all over the web for building specific gadgets. Rather than leave you with a final product, this project instead is intended as a starting point for creating your own devices.

The idea is as follows: when the system first boots, it will simply set itself to wake up in one minute or when GPIO15/TOUCH0 is touched. Then, it puts itself to sleep. If the system was woken for some reason, it will initiate an HTTP request to api.thingspeak.com and write a value that simply indicates the number of successful connections since the system first powered on or booted. It then sets the wake up conditions and goes to sleep again.

By my tests, the deep sleep capabilities are effective. While sleeping, the system draws 2.5mA for the board. When it wakes, it goes to 6.8mA and then 9.3mA when the WiFi radio engages. Again, that’s for the entire board. The ESP32 chip itself is documented to draw as little as 20uA when sleeping, so if you use a dev board for prototyping and then sitt the ESP32 directly on your final product’s board, your power savings could be even more substantial.

When adapting this method for your own projects, just add whatever sensors you need to your circuit and send those to your own application programming interface (API) account at thingspeak.com. Then, the board will log and sleep regularly.

Creating the Code

We’ll be working with the Arduino IDE. In your Tools menu, you need to install the " esp32" board manager if you haven’t already. Then, depending on which board you have, you’ll have to set the appropriate board. I found the Node32s seems to work with several of my generic ESP32s, so if nothing else works, you can try that setting.

At the top, we have the includes followed by a bunch of #defines containing various configuration settings. Make sure to set the Service Set IDentifier (SSID) and password to valid credentials for your network. You’ll also want to create your own Thingspeak account and get an API key. Replace the API key in REST_PATH with your own so that you can view the data. If you use the API key provided, it will work, but you won’t be able to view the results because you’re not the account owner.

Next, we have the report() method:

void report() {
  char sz[1024];
  // change to match your parameters
  // here we just report the success count
  sprintf(sz,REST_PATH,_successes);
  char sz2[1024];
  sprintf(sz2,"GET %s HTTP/1.1",sz);
    if (!client.connect(REST_SERVER, 443))
    Serial.println("Connection failed!");
  else {
    Serial.println("Connected to server!");
    // Make a HTTP request:
    client.println(sz2);
    sprintf(sz2,"Host: %s",REST_SERVER);
    client.println(sz2);
    client.println("Connection: close");
    client.println();

    while (client.connected()) {
      String line = client.readStringUntil('\n');
      if (line == "\r") {
        Serial.println("headers received");
        ++_successes;
        break;
      }
    }
    client.stop();
  }
  Serial.print("Success count: ");
  Serial.println(_successes);
}

What we’re doing here is building an HTTP request manually by using sprintf() to fill our GET request and our host header. Then, we read the response, but we ignore everything after the headers because we don’t need it. You usually won’t ignore the rest for JSON-based REST commands that write. However, if you need to read data from a JSON REST server, you’ll probably want to install the ArduinoJson library or equivalent to parse the returned data.

Finally, if we’re successful, we increment the _successes global variable. This variable is interesting in that it is declared with the RTC_DATA_ATTR attribute. This attribute indicates that the data is to reside in the 8KB of SRAM preserved during sleep. That way, we don’t lose the value every time it goes to sleep. Use this attribute sparingly since you don’t have much room.

Next, we have touchCallback():

// gets called when wire is touched
void touchCallback() {
  // reset the timeout
  touched.clear();
}

This callback’s job is to reset the atomic touched flag. Basically, any time the GPIO15 pin is touched, we get a cascading series of interrupts and callbacks instead of just one when it goes high. To handle this, we have a timeout. The timeout waits for RELEASE_TIME_SECONDS (one second) of no activity before it reports the touch. That way, when we get spammed with spurious interrupts while the pin is touched, we won’t be firing the handler code each time. Instead, we’ll only invoke the handler once the pin has been released for one second. The flag is atomic because interrupts can happen at any time, even while the flag is being read. Using an atomic flag prevents a race condition.

The next method we encounter is setup():

// runs on initial boot, or when 
// woken up from deep sleep
void setup() {
  Serial.begin(115200);
  // set the touched flag
  touched.test_and_set();
  // attach interrupt on GPIO15 touchpad
  // with to 40 sensitivity. callback()
  // gets fired when wire is touched
  touchAttachInterrupt(T3, touchCallback, 40);
  esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();

  // was this booted or woken up?
  if (ESP_SLEEP_WAKEUP_TOUCHPAD !=  cause &&
    ESP_SLEEP_WAKEUP_TIMER!=cause) {

    // this is a fresh boot - get the chip ID for display
    uint64_t cid=ESP.getEfuseMac();
    // format the chip ID which is basically the MAC address
    Serial.printf("ESP32 chip ID = %04X", (uint16_t)(cid >> 32));
    Serial.printf("%08X\n", (uint32_t)cid); 
    // set our wake conditions
    esp_sleep_enable_timer_wakeup(SLEEP_SECONDS * uS_TO_S);
    esp_sleep_enable_touchpad_wakeup();
    // go right to sleep
    esp_deep_sleep_start();
    // code path never gets here
  }

  // if we got here, the CPU was awoken:
  
  // reset the timeout
  _timestamp = millis();
  // turn on the (usually blue) LED
  pinMode(2, OUTPUT);
  digitalWrite(2, HIGH);
}

After initializing the serial port, we set an interrupt to handle our touch-sensitive GPIO15 pin. This is required since, without the handler in place, the system will not wake when the pin is touched. To awake, it doesn’t matter what the handler does, even if it does nothing. It simply must have an interrupt handler, any interrupt handler, to be a wake source.

Next, we check the wake up cause to see why it happened. We’re primarily concerned with whether this was a standard boot (such as after a reset) or whether it was from a touch or a timer event. If it’s a fresh boot, we display the ESP32’s chip ID, which is derived from its media access control (MAC) address. Otherwise, if the device was woken from sleep, it turns on the LED to indicate that it’s working.

Finally, we come to the loop() routine:

// called for each iteration of the CPU's loop
void loop() {

  if (!touched.test_and_set())
    _timestamp = millis();

  // if more than RELEASE_TIME_SECONDS has elapsed....
  if (millis() - _timestamp > (RELEASE_TIME_SECONDS * 1000)) {
    // reset the timestamp
    _timestamp = millis();
    // connect to the network
    Serial.print("Connecting to ");
    Serial.print(SSID);
    WiFi.begin(SSID, PASSWORD);
    // wait for connection
    for (int i = 0; i < (CONNECTION_TIMEOUT_SECONDS * 2) && WL_CONNECTED != WiFi.status(); ++i) {
      Serial.print(".");
      delay(500);
    }
    if (WL_CONNECTED != WiFi.status()) {
      Serial.println("failed.");
    } else {
      Serial.println("succeeded.");
      // report the event
      report();
    }
    WiFi.disconnect();
    // blink the LED while waiting
    for (int i = 0; i < FINAL_WAIT_SECONDS; ++i) {
      digitalWrite(2, LOW);
      delay(500);
      digitalWrite(2, HIGH);
      delay(500);
    }

    Serial.println("Sleeping");
    // set up our wake conditions:
    esp_sleep_enable_timer_wakeup(SLEEP_SECONDS * uS_TO_S);
    esp_sleep_enable_touchpad_wakeup();
    // turn off the LED
    digitalWrite(2, LOW);
    // now we can sleep
    esp_deep_sleep_start();
  }
}

The first thing we do is check the touched flag to see if it was set by the callback. If it was, then we start the timeout over. Next, we try to connect to the network. If we do so successfully, then we call report() to report our data to the REST server at api.thingspeak.com.

Finally, we disconnect and set our wake up conditions (again) to be based on a touch or SLEEP_SECONDS (one minute) and then turn off the LED, indicating that our work is done. After that, the device goes to sleep again. The process repeats starting at setup() each time.

Future Directions

One obvious improvement to this code would be WiFi Protected Setup (WPS) support to populate the SSID and password. I left it out because, unlike with the ESP8266 WiFi library, ESP32 doesn’t have WPS built in. You have to do a significant amount of work yourself. On the other hand, the result is totally asynchronous, unlike the previous WiFi library.

Another improvement would be to store the data offline, like flash or a secure digital (SD) card. Doing this from inside report() instead of always sending to the server would allow the device to be resilient and still gather data in the absence of an Internet connection. Once the Internet connection is established, the stored data could then upload, possibly in bulk.

In a real-world project, you will almost certainly want to add sensors of some sort, like a motion detector or environmental sensors, instead of just sending an arbitrary integer value to the server like we did here. You can then do low-power, once-daily logging, for example, then send that data off to a server once an Internet connection is available.

Conclusion

Hopefully, you’ve now got your feet under you when it comes to ESP32 boards. In terms of performance, size, power consumption, and cost, they simply can’t be beat. As a whole, they are extremely capable little IoT systems that you can use to do nearly anything you can imagine. Now, get out there and create!

Further Reading

How to work with us

  • Contact us to set up a call.
  • We will analyze your needs and recommend a content contract solution.
  • Sign on with ContentLab.
  • We deliver topic-curated, deeply technical content to you.

To get started, complete the form to the right to schedule a call with us.

Send this to a friend