HTML Iframes


An HTML iframe is used to display a web page within a web page.



HTML Iframe Syntax

The HTML <iframe> tag specifies an inline frame.

An inline frame is used to embed another document within the current HTML document.

Syntax

<iframe src="url" title="description"></iframe>

Tip: It is a good practice to always include a title attribute for the <iframe>. This is used by screen readers to read out what the content of the iframe is.


Iframe - Set Height and Width

Use the height and width attributes to specify the size of the iframe.

The height and width are specified in pixels by default:

Example

<iframe src="demo_iframe.htm" height="200" width="300" title="Iframe Example"></iframe>

Or you can add the style attribute and use the CSS height and width properties:

Example

<iframe src="demo_iframe.htm" style="height:200px;width:300px;" title="Iframe Example"></iframe>


Iframe - Remove the Border

By default, an iframe has a border around it.

To remove the border, add the style attribute and use the CSS border property:

Example

<iframe src="demo_iframe.htm" style="border:none;" title="Iframe Example"></iframe>

With CSS, you can also change the size, style and color of the iframe's border:

Example

<iframe src="demo_iframe.htm" style="border:2px solid red;" title="Iframe Example"></iframe>

Iframe - Target for a Link

An iframe can be used as the target frame for a link.

The target attribute of the link must refer to the name attribute of the iframe:

Example

<iframe src="demo_iframe.htm" name="iframe_a" title="Iframe Example"></iframe>

<p><a href="https://www.w3schools.com" target="iframe_a">W3Schools.com</a></p>

Chapter Summary

  • The HTML <iframe> tag specifies an inline frame
  • The src attribute defines the URL of the page to embed
  • Always include a title attribute (for screen readers)
  • The height and width attributes specify the size of the iframe
  • Use border:none; to remove the border around the iframe

A few years ago, the ESP8266 revolutionized the embedded IoT world. For less than $3, you could get a programmable, WiFi-enabled microcontroller capable of monitoring and controlling things from anywhere in the world.

After the overwhelming success of the ESP8266, Espressif (the semiconductor company that created the ESP8266) has released a perfect supercharged upgrade � the ESP32. It incorporates not only WiFi but also Bluetooth 4.0 (BLE/Bluetooth Smart), making it ideal for any Internet of Things (IoT) application.

Introducing the ESP32

The ESP32 is actually a series of microcontrollers created and developed by Espressif Systems, a Shanghai-based Chinese company. The following features are the primary reasons why people like the ESP32:

dualcore ico

Dual-core Processor

The ESP32 is equipped with a Tensilica Xtensa� Dual-Core 32-bit LX6 microprocessor that operates at an adjustable clock frequency of 80 to 240 MHz and performs at up to 600 DMIPS (Dhrystone Million Instructions Per Second)

power ico

Ultra-Low Power Consumption

With its multiple power modes and dynamic power scaling, ESP32 achieves ultra-low power consumption, making it ideal for use in mobile devices, wearable electronics and IoT applications.

wifi ico

Wi-Fi Capabilities

The ESP32 incorporates an 802.11b/g/n HT40 Wi-Fi transceiver, allowing it to connect to a Wi-Fi network to access the internet (Station mode) or to create its own Wi-Fi wireless network (Soft access point mode) to which other devices can connect.

bluetooth ico

Dual-mode Bluetooth Capabilities

The ESP32 supports Bluetooth 4.0 (BLE/Bluetooth Smart) as well as Bluetooth Classic (BT), making it suitable for a wide range of IoT applications.

peripherals ico

Rich set of Peripherals

The ESP32 includes a plethora of built-in peripherals such as capacitive touch, ADCs, DACs, UART, SPI, I2C, PWM, and much more.

ways ico

Many Development Platforms

There are several development platforms available for programming the ESP32. You can use the Arduino IDE, MicroPython, Espruino, Espressif SDK, or one of the platforms listed on WikiPedia.

temperature ico

Robust Design

The ESP32 can operate reliably in industrial environments with operating temperatures ranging from -40�C to +125�C.

lowcost ico

Low Cost

It is one of the cheap microcontrollers, with prices beginning at $6; this makes them accessible to a wide audience.

ESP32 Development Boards

To get started with the ESP32, you�ll need a good development board that includes all the extras. There are numerous varieties of development boards out there. While they all function similarly, some boards may be better suited to specific projects than others.

The following are just a few of the many different types of ESP32 boards you can try out.

esp32 devkit v1

ESP32 DevKit V1

Because of its immense popularity, this is the best board for beginners. It comes with everything you need to get started with ESP32. And this is what we will use for our experiments.

esp32 oled board

ESP32 OLED Kit

Compared to the previous board, this one includes an OLED display, making it a neat addition for Internet of Things applications. Additionally, it has a u.FL connector for an external antenna and a lipo battery connector for creating battery-operated projects.

esp32 cam

ESP32-CAM

Built-in OV2640 2MP camera and microSD card slot make this one of the most interesting boards available; it�s ideal for IoT projects that require a camera with advanced functions like image tracking and recognition.

esp32 sim800l ttgo t call

ESP32 SIM800L TTGO T-Call

Don�t have WiFi? No problem. The ESP32 SIM800L TTGO T-Call board allows you to connect to the Internet via a SIM card data plan and push information to the cloud, without the need for Wi-Fi.

esp32 m5stack

M5Stack

M5Stack is a stackable modular product development toolkit based on ESP32. The M5 ecosystem includes a main controller and additional modules that can be stacked (similar to Arduino shields) to expand the functionality of the projects.

ESP32 DevKit V1 Board Hardware Overview

When it comes to ESP32 development boards, the ESP32 DevKit v1 is by far the most widely used option. This is what we will use for our experiments. Don�t worry if you have a different board; the information on this page should still be useful.

Let�s take a deep dive into the hardware overview of the ESP32 DevKit v1 board.

ESP-WROOM-32 Module

This development board is equipped with the ESP-WROOM-32 module, which contains the Tensilica Xtensa� Dual-Core 32-bit LX6 microprocessor. This processor is similar to the one used in the ESP8266, but it has two CPU cores (which can be individually controlled), operates at an adjustable clock frequency of 80 to 240 MHz, and can perform at up to 600 DMIPS (Dhrystone Million Instructions Per Second).

ESP-WROOM-32 Chip
  • Xtensa� Dual-Core 32-bit LX6
  • Upto 240MHz Clock Freq.
  • 520kB internal SRAM
  • 4MB external flash
  • 802.11b/g/n Wi-Fi transceiver
  • Bluetooth 4.2/BLE
esp32 hardware specifications esp wroom 32 chip

There�s also 448 KB of ROM, 520 KB of SRAM, and 4 MB of Flash memory (for program and data storage), which is sufficient to handle the long strings that make up web pages or JSON/XML data.

The ESP32 incorporates an 802.11b/g/n HT40 Wi-Fi transceiver, allowing it to connect to a Wi-Fi network to access the internet (Station mode) or to create its own Wi-Fi wireless network (Soft access point mode) to which other devices can connect.

The ESP32 also supports WiFi Direct, which is a good option for peer-to-peer connections that do not require an access point. WiFi Direct is simpler to set up and has much faster data transfer speeds than Bluetooth.

The chip supports both Bluetooth 4.0 (BLE/Bluetooth Smart) and Bluetooth Classic (BT), which makes it even more versatile.

Power

Because the ESP32�s operating voltage range is 2.2V to 3.6V, the board includes an LDO voltage regulator to keep the voltage stable at 3.3V. It can reliably provide up to 600 mA, which should be sufficient even when the ESP32 is drawing its maximum current during RF transmissions.

Power Requirement
  • Operating Voltage: 2.2V to 3.6V
  • On-board 3.3V 600mA regulator
  • 5 �A during Sleep Mode
  • 250mA during RF transmissions
esp32 hardware specifications power supply

The output of the 3.3V regulator is broken out to the header pin labeled 3V3. This pin can be used to power external circuitry.

The ESP32 development board is typically powered by the on-board MicroB USB connector. Alternatively, if you have a regulated 5V power supply, you can use the VIN pin to power the ESP32 and its peripherals directly.

Peripherals and I/O

Although the ESP32 has 48 GPIO pins in total, only 25 of them are broken out to pin headers. These pins can be assigned a variety of peripheral duties, including:

  • 15 ADC channels � 15 channels of 12-bit SAR ADC with selectable ranges of 0-1V, 0-1.4V, 0-2V, or 0-4V
  • 2 UART interfaces � 2 UART interfaces with flow control and IrDA support
  • 25 PWM outputs � 25 PWM pins to control things like motor speed or LED brightness.
  • 2 DAC channels � Two 8-bit DACs to generate true analog voltages
  • SPI, I2C & I2S interface � Three SPI and one I2C interfaces for connecting various sensors and peripherals, as well as two I2S interfaces for adding sound to your project.
  • 9 Touch Pads � 9 GPIOs with capacitive touch sensing.
Multiplexed I/Os
  • 15 ADC channels
  • 2 UART interfaces
  • 25 PWM outputs
  • 2 DAC channels
  • SPI, I2C & I2S interface
  • 9 Touch Pads
esp32 hardware specifications multiplexed gpio pins

Thanks to the ESP32�s pin multiplexing feature, which allows multiple peripherals to share a single GPIO pin. For example, a single GPIO pin can act as an ADC input, DAC output, or touch pad.

Input Only GPIOs

Pins GPIO34, GPIO35, GPIO36(VP) and GPIO39(VN) cannot be configured as outputs. They can be used as digital or analog inputs, or for other purposes. They also lack internal pull-up and pull-down resistors, unlike the other GPIO pins.

On-board Switches & LED Indicators

There are two buttons on the ESP32 development board. The Reset button, labeled EN, is used to reset the ESP32 chip. The other button is the Boot button, which is used to download new software or programs.

Switches & Indicators
  • EN � Reset the ESP32 chip
  • Boot � Download new programs
  • Red LED � Power Indicator
  • Blue LED � User Programmable
esp32 hardware specifications reset boot buttons led indicators

The board also includes two LED indicators. The Red LED indicates that the board is turned on, whereas the Blue LED is user programmable and connected to the board�s D2 pin.

Serial Communication

The board includes Silicon Labs� CP2102 USB-to-UART Bridge Controller, which converts USB signals to serial and allows you to program the ESP32 chip.

Serial Communication
  • CP2102 USB-to-UART converter
  • 5 Mbps communication speed
  • IrDA support
esp32 hardware specifications usb to ttl converter

ESP32 Development Board Pinout

The ESP32 DevKit V1 development board has 30 pins in total. For convenience, pins with similar functionality are grouped together. The pinout is as follows:

esp32 pinout

Power Pins There are two power pins: the VIN pin and the 3V3 pin. The VIN pin can be used to directly power the ESP32 and its peripherals, if you have a regulated 5V power supply. The 3V3 pin is the output from the on-board voltage regulator; you can get up to 600 mA from it.

GND is the ground pin.

GPIO Pins The ESP32 development board has 25 GPIO pins that can be assigned different functions by programming the appropriate registers. There are several kinds of GPIOs: digital-only, analog-enabled, capacitive-touch-enabled, etc. Analog-enabled GPIOs and capacitive-touch-enabled GPIOs can be configured as digital GPIOs. All GPIOs can be configured as interrupts.

ADC Channels ESP32 integrates two 12-bit SAR ADCs and supports measurements on 15 channels (analog-enabled pins). Some of these pins can be used to build a programmable gain amplifier for measuring small analog signals. Additionally, the ESP32 is designed to measure voltages while in sleep mode.

DAC Channels The ESP32 includes two 8-bit DAC channels for converting digital signals to true analog voltages. It can be used as a �digital potentiometer� to control analog devices.

Touch Pads The board has 9 capacitive touch-sensing GPIOs. When a capacitive load (such as a human finger) is in close proximity to the GPIO, the ESP32 detects the change in capacitance.

SPI Pins ESP32 has three SPIs (SPI, HSPI, and VSPI) in slave and master modes. These SPIs also support the general-purpose SPI features listed below:

  • 4 timing modes of the SPI format transfer
  • Up to 80 MHz and the divided clocks of 80 MHz
  • Up to 64-Byte FIFO

Only VSPI and HSPI are usable SPI interfaces, and the third SPI bus is used by the integrated flash memory chip. VSPI pins are commonly used in standard libraries.

I2C Pins The ESP32 has a single I2C bus that allows you to connect up to 112 sensors and peripherals. The SDA and SCL pins are, by default, assigned to GPIO21 and GPIO22, respectively. However, you can bit-bang the I2C protocol on any GPIO pin with wire.begin(SDA, SCL) command.

UART Pins The ESP32 dev. board has two UART interfaces, UART0 and UART2, that support asynchronous communication (RS232 and RS485) and IrDA at up to 5 Mbps. UART provides hardware management of the CTS and RTS signals and software flow control (XON and XOFF) as well.

~ PWM Pins The board has 25 channels (nearly all GPIO pins) of PWM pins controlled by a PWM controller. The PWM output can be used for driving digital motors and LEDs. The PWM controller consists of the PWM timers and the PWM operator. Each timer provides timing in synchronous or independent form, and each PWM operator generates the waveform for one PWM channel.

EN Pin is used to enable the ESP32. When pulled HIGH, the chip is enabled; when pulled LOW, the chip operates at low power.

For more information, refer to our comprehensive ESP32 pinout guide. This guide also explains which ESP32 GPIO pins are safe to use and which pins should be used with caution.

ESP32 Development Platforms

There are several development platforms available for programming the ESP32.

You can go with Espruino (JavaScript SDK and firmware closely emulating Node.js), Mongoose OS (an operating system for IoT devices that is recommended by Espressif Systems and Google Cloud IoT), MicroPython (implementation of Python 3 for microcontrollers), an SDK provided by Espressif, or one of the platforms listed on WiKiPedia.

Fortunately, the amazing ESP32 community recently took the IDE selection a step further by creating an Arduino add-on. If you�re just getting started with the ESP32, we recommend starting with this environment, which we�ll cover in this tutorial. For more information, visit the ESP32 Arduino GitHub repository.

Installing the ESP32 Arduino Core

The first step in installing the ESP32 core Arduino is to have the latest version of the Arduino IDE (Arduino 1.8.5 or higher) installed on your computer. If you haven�t already, we recommend that you do so right away.

Latest Arduino IDE

Then, launch the Arduino IDE and navigate to File > Preferences.

arduino ide preferences

Fill in the �Additional Board Manager URLs� field with the following. This will include support for both ESP32 and ESP8266 boards.

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json, http://arduino.esp8266.com/stable/package_esp8266com_index.json

Then, click the �OK� button.

arduino ide esp32 json url

Now navigate to Tools > Board > Boards Manager�

arduino ide boards manager

Filter your search by entering �esp32�. Look for ESP32 by Espressif Systems. Click on that entry, and then choose Install.

arduino ide esp32 package

After installing, restart your Arduino IDE and navigate to Tools > Board to ensure you have ESP32 boards available.

arduino ide boards manager esp32 option

Now select your board in the Tools > Board menu (in our case, it�s the DOIT ESP32 DEVKIT V1). If you are unsure which board you have, select ESP32 Dev Module.

arduino ide boards manager esp32 boards

Finally, connect the ESP32 board to your computer and select the Port (if the COM port does not appear in your Arduino IDE, you will need to install the CP210x USB to UART Bridge VCP Drivers):

arduino ide esp32 port selection

That�s it! You can now begin writing code for your ESP32 in the Arduino IDE.

You should make sure you always have the most recent version of the ESP32 Arduino core installed.

Simply navigate to Tools > Board > Boards Manager, search for ESP32, and verify the version you have installed. If a newer version is available, you should install it.

ESP32 Example Sketches

The ESP32 Arduino core includes several example sketches. To access the example sketches, navigate to File > Examples > ESP32.

You will see a selection of example sketches. You can choose any of them to load the sketch into your IDE and start experimenting with it.

arduino ide esp32 package examples

To ensure that everything is properly configured, we�ll upload the most basic sketch of all � Blink!

This test uses the on-board LED. As previously mentioned in this tutorial, the D2 pin of the board is connected to the on-board Blue LED and is user programmable.

int ledPin = 2;

void setup() {
    pinMode(ledPin, OUTPUT);
}

void loop() {
	digitalWrite(ledPin, HIGH);
	delay(500);
	digitalWrite(ledPin, LOW);
	delay(500);
}

When the code is uploaded, the LED will begin to blink. To get your ESP32 to run the sketch, you may need to press the EN button.

ESP32 Development Board Blink Sketch Working Arduino IDE

Troubleshooting � Booting up ESP32

Some ESP32 boards enter flashing mode automatically, and the code is uploaded successfully, while others do not, and you may receive the �A fatal error occurred: Failed to connect to ESP32� Timed out waiting for packet header�� error:

Failed to connect to ESP32 Error while Uploading Sketch in Arduino IDE

This is a common issue, and it indicates that your ESP32 is not in flashing or uploading mode. You can resolve this issue by following the steps outlined below.

  • Hold down the BOOT button on your ESP32 board.
  • Press the Upload button in the Arduino IDE to upload a new sketch.
  • Release the BOOT button when the Writing at 0x00001000� (100%) message appears in the Arduino IDE log after Connecting�.
  • You should now see the �Done uploading� message.

That�s it. Now, press the EN button to restart the ESP32 and run the newly uploaded sketch.

Remember! You�ll have to repeat that button sequence every time you want to upload a new sketch.

ESP32 vs. ESP8266. If you have an Internet of Things project in mind, you�ll inevitably face the choice between these two MCU heavyweights. Both are well-known microcontrollers, and for good reason. They are small, low-power Wi-Fi-enabled microcontrollers with 32-bit CPUs ideal for controlling a wide range of electronics projects.

But how do you determine which one is better suited for your project?

Let�s begin with a comparison of the two to help you make a decision. We won�t focus solely on the fastest MCU, the most power-efficient one, or the one offering the best value for money. Instead, we�ll consider various factors, including processing power, connectivity features, hardware specifications, development environments, pricing, and more.

So, let�s go the rounds with ESP32 vs. ESP8266.

Introduction

ESP8266

30pin esp32 dev board

The ESP8266 is a low-cost Wi-Fi microchip with a built-in TCP/IP stack and microcontroller capability, released in 2014 by Espressif Systems in Shanghai, China.

It quickly gained popularity due to its simplicity and low cost. It operates on a Tensilica Xtensa LX3 processor and is primarily intended for smart devices, wearables, and Wi-Fi-enabled projects.

ESP32

30pin esp32 dev board

The ESP32, which was introduced in 2016, is the successor to the ESP8266 and offers several improvements over it. These include a faster processor, faster Wi-Fi, Bluetooth connectivity, more GPIO pins, and several other features. More importantly, it features a dual-core Tensilica Xtensa LX6 microprocessor, which significantly enhances its computational capabilities.

Processing Power and Memory

The ESP8266 features a 32-bit Tensilica Xtensa L106 single-core processor, running at 80 MHz, which can be overclocked to 160 MHz. It comes with 32 KB of instruction RAM, 80 KB of user-data RAM, and external QSPI flash � 512 KB to 4 MB.

The ESP32 has a slightly more powerful processor. It boasts a dual-core Tensilica Xtensa LX6 microprocessor that can run at up to 240 MHz. It includes 520 KB of SRAM and supports external flash memory of up to 16 MB.

This increased processing power and memory make the ESP32 suitable for more demanding tasks.

Winner: ESP32

Wireless Connectivity

Both microcontrollers support Wi-Fi, but the ESP32 includes extra features such as Wi-Fi Direct.

Furthermore, the ESP32 has Bluetooth v4.2 BR/EDR and BLE (Bluetooth Low Energy) capabilities, which the ESP8266 lacks. This makes the ESP32 more versatile for wireless communication.

Winner: ESP32

Peripheral Interfaces

When comparing two microcontrollers, it�s important to know what types of peripheral interfaces they have. These interfaces are the primary means by which the microcontrollers communicate with sensors and other devices, and they have a big impact on the complexity and type of projects that you can build.

Below is a comparison of the peripheral interfaces provided by the ESP8266 and the ESP32:

Peripheral InterfaceESP8266ESP32
GPIO17 programmable GPIOs34 programmable GPIOs
ADC10-bit SAR ADC on 1 channel12-bit SAR ADC up to 18 channels
PWM4 channels16 channels
I2C Interface12
I2S Interface22
SPI Interface24
UART Interface23
CAN InterfaceNo1
EthernetNo10/100 Mbps
Touch SensorNo10 GPIOs
Infrared Remote ControllerNoTX/RX, up to 8 channels
SDIO/SPI slave controllerNoYes
Pulse CounterNoFull quadrature decoding

Winner: ESP32

Built-in Sensors

When it comes to onboard sensors, the ESP8266 is quite minimalistic; it does not have any built-in sensors.

The ESP32, on the other hand, is more advanced and includes several built-in sensors. It typically comes with:

  • A Hall effect sensor capable of detecting magnetic fields. This can be useful for applications such as detecting the presence of a magnet or measuring the strength of a magnetic field.
  • A capacitive touch sensor which can be used for touch-sensitive input.
  • A Temperature sensor meant to measure the internal temperature of the chip and not the ambient temperature, which can be useful for monitoring the microcontroller�s health.

These integrated sensors in the ESP32 provide more functionality out of the box, allowing you to create projects without the need for additional components.

Winner: ESP32

Power Management

Both boards have advanced power-saving technologies to minimize power consumption effectively.

The ESP8266 offers four power modes: active, modem-sleep, light-sleep, and deep-sleep. According to the datasheet, in active mode, with all features running concurrently, it can draw as much as 80 mA. In contrast, the deep-sleep mode is extremely efficient, consuming as little as 20 �A.

The ESP32, on the other hand, with its faster processor and increased flash memory, has a higher power requirement, consuming up to 240 mA in active mode. To offset this, it introduces two additional low-power modes: hibernation and power-off. Hibernation mode has an impressively low consumption of just 5 �A, while power-off mode reduces this further to a mere 1 �A.

Due to its lower power consumption in active mode, the ESP8266 is better suited for simple, low-power projects powered by a battery pack.

Winner: ESP8266

Programming and Community Support

Both the ESP8266 and ESP32 are supported by the Arduino IDE and the native ESP-IDF (Espressif IoT Development Framework). The Arduino IDE is suitable for small projects, but for larger and more complex projects, it�s advisable to use the ESP-IDF through the Visual Studio Code extension or the Eclipse plugin.

Furthermore, both boards support programming in MicroPython, Lua, JavaScript, and other languages (a full list is available on the wiki), making them accessible to a wide range of programmers. In addition, the developer community around both modules is robust and active, with an abundance of online forums, extensive libraries, and tutorials.

So, in terms of programming support and community resources, there is little to no competition between the two boards; both provide an extensive range of supported languages and resources.

Winner: Tie

Price: Which Board Offers the Most Value?

The ESP8266 is usually cheaper than the ESP32, making it a more cost-effective option for simple projects. The ESP32, with its additional features and capabilities, comes at a slightly higher cost, justified by its increased processing power and versatility.

However, the price difference between the two has been reduced in recent years. Depending on where you�re buying from, you can generally find both boards for sale at more or less the same price. In such cases, the ESP32, with its better connectivity features, can be a better deal at the same price.

Winner: Tie

Full Comparison

Here�s a table that compares the ESP8266 and ESP32 specifications and features in depth. This table serves as a reference point, summarizing each microcontroller�s capabilities and offerings.

FeatureESP8266ESP32
ProcessorTensilica Xtensa L106 32 bit single-coreTensilica Xtensa LX6 32 bit dual-core
RAM128 KB520 KB
Clock Speed80/160 MHz160/240 MHz
Operating Voltage2.5-3.6V2.2-3.6V
Operating Temperature�40 �C to +125 �C�40 �C to +125 �C
FlashUp to 4MB (usually 512KB to 1MB included)4MB
External Flash SupportUp to 16MBUp to 16MB
RTC Memory512 Bytes (inside the system RAM)16KB
Wi-Fi802.11b/g/n802.11b/g/n
BluetoothNoBluetooth 4.2, BLE
EthernetNo10/100 Mbps
I2C Interface12
I2S Interface22
SPI Interface24
UART Interface23
CAN InterfaceNo1
ADC10-bit SAR ADC on 1 channel12-bit SAR ADC up to 18 channels
PWM4 channels16 channels
Touch SensorNoYes
Temperature SensorNoYes
Hall effect sensorNoYes
GPIO17 programmable pins34 programmable pins
Infrared Remote ControllerNoTX/RX, up to 8 channels
SDIO/SPI slave controllerNoYes
Pulse CounterNofull quadrature decoding
Price$ ($5 � $6)$$ ($6 � $9)

Which One Should You Buy?

So, ESP32 squeaks out a victory and lays claim to the king of the Wi-Fi-enabled MCU world crown. Cue the band and light the fireworks. ???

But this doesn�t mean the ESP32 is a better choice in every case. In the end, your selection of a microcontroller will depend more on your project�s specific requirements than hard numbers. ESP32 clearly wins most match ups, but it falls behind in simplicity and power efficiency. This explains why the ESP8266 remains a popular choice for many, despite not being the top performer.

In summary, for simple, low-power Wi-Fi-based projects, the ESP8266 is the preferred choice. On the other hand, if you need more processing power, extra memory, and features like Bluetooth, the ESP32 is the clear winner.

One of the advantages of the ESP32 is that it has a lot more GPIOs than the ESP8266. You won�t have to juggle or multiplex your IO pins. However, there are a few things to keep in mind, so please read the pinout carefully.

Note:

Please note that the following pinout reference is for the popular 30-pin ESP32 devkit v1 development board.

30pin esp32 dev board

Not every ESP32 development board exposes every pin, but each pin works exactly the same no matter which development board you use.

ESP32 Peripherals and I/O

Although the ESP32 has 48 GPIO pins in total, only 25 of them are broken out to the pin headers on both sides of the development board. These pins can be assigned a variety of peripheral duties, including:

15�ADC�channels15 channels of 12-bit SAR ADC with selectable ranges of 0-1V, 0-1.4V, 0-2V, or 0-4V
2�UART�interfaces2 UART interfaces with flow control and IrDA support
25�PWM�outputs25 PWM pins to control things like motor speed or LED brightness
2�DAC�channelsTwo 8-bit DACs to generate true analog voltages
SPI,�I2C�and�I2S interfaceThree SPI and one I2C interfaces for connecting various sensors and peripherals, as well as two I2S interfaces for adding sound to your project
9�Touch�Pads9 GPIOs with capacitive touch sensing

Thanks to the ESP32�s pin multiplexing feature, which allows multiple peripherals to share a single GPIO pin. For example, a single GPIO pin can act as an ADC input, DAC output, or touch pad.

For extensive information about the ESP32, please refer to the datasheet.

ESP32 Pinout

The ESP32 DevKit V1 development board has 30 pins in total. For convenience, pins with similar functionality are grouped together. The pinout is as follows:

ESP32 Pinout

Let�s take a closer look at the ESP32 pins and their functions one by one.

ESP32 GPIO Pins

The ESP32 development board has 25 GPIO pins that can be assigned different functions by programming the appropriate registers. There are several kinds of GPIOs: digital-only, analog-enabled, capacitive-touch-enabled, etc. Analog-enabled GPIOs and Capacitive-touch-enabled GPIOs can be configured as digital GPIOs. Most of these digital GPIOs can be configured with internal pull-up or pull-down, or set to high impedance.

ESP32 GPIO Pins

Which ESP32 GPIOs are safe to use?

Although the ESP32 has a lot of pins with various functions, some of them may not be suitable for your projects. The table below shows which pins are safe to use and which pins should be used with caution.

  • � Your top priority pins. They are perfectly safe to use.
  • � Pay close attention because their behavior, particularly during boot, can be unpredictable. Use them only when absolutely necessary.
  • � It is recommended that you avoid using these pins.
��Label����GPIO����Safe�to�use?��Reason
D00must be HIGH during boot and LOW for programming
TX01Tx pin, used for flashing and debugging
D22must be LOW during boot and also connected to the on-board LED
RX03Rx pin, used for flashing and debugging
D44
D55must be HIGH during boot
D66Connected to Flash memory
D77Connected to Flash memory
D88Connected to Flash memory
D99Connected to Flash memory
D1010Connected to Flash memory
D1111Connected to Flash memory
D1212must be LOW during boot
D1313
D1414
D1515must be HIGH during boot, prevents startup log if pulled LOW
RX216
TX217
D1818
D1919
D2121
D2222
D2323
D2525
D2626
D2727
D3232
D3333
D3434Input only GPIO, cannot be configured as output
D3535Input only GPIO, cannot be configured as output
VP36Input only GPIO, cannot be configured as output
VN39Input only GPIO, cannot be configured as output

The image below shows which GPIO pins can be used safely.

ESP32 GPIO Pins that are Safe to Use

Input Only GPIOs

Pins GPIO34, GPIO35, GPIO36(VP) and GPIO39(VN) cannot be configured as outputs. They can be used as digital or analog inputs, or for other purposes. They also lack internal pull-up and pull-down resistors, unlike the other GPIO pins.

ESP32 Interrupt Pins

All GPIOs can be configured as interrupts. For more information, please refer to this tutorial.

ESP32 ADC Pins

ESP32 integrates two 12-bit SAR ADCs and supports measurements on 15 channels (analog-enabled pins).

ESP32 ADC Pins

The ESP32�s ADC is a 12-bit ADC, which means it can detect 4096 (2^12) discrete analog levels. In other words, it will convert input voltages ranging from 0 to 3.3V (operating voltage) into integer values ranging from 0 to 4095. This results in a resolution of 3.3 volts / 4096 units, or 0.0008 volts (0.8 mV) per unit.

Moreover, the ADC resolution and channel range can be set programmatically.

Warning:

The ADC2 pins cannot be used when Wi-Fi is enabled. If your project requires Wi-Fi, consider using the ADC1 pins instead.

ESP32 DAC Pins

The ESP32 includes two 8-bit DAC channels for converting digital signals to true analog voltages. It can be used as a �digital potentiometer� to control analog devices.

ESP32 DAC Pins

These DACs have an 8-bit resolution, which means that values ranging from 0 to 256 will be converted to an analog voltage ranging from 0 to 3.3V.

The DAC�s 8-bit resolution may be insufficient for use in audio applications, in which case an external DAC with a higher resolution (12-24 bits) is preferable.

ESP32 Touch Pins

The ESP32 has 9 capacitive touch-sensing GPIOs. When a capacitive load (such as a human finger) is in close proximity to the GPIO, the ESP32 detects the change in capacitance.

ESP32 Touch Pins

You can make a touch pad by attaching any conductive object to these pins, such as aluminum foil, conductive cloth, conductive paint, and so on. Because of the low-noise design and high sensitivity of the circuit, relatively small pads can be made.

Additionally, these capacitive touch pins can be used to wake the ESP32 from deep sleep.

ESP32 I2C Pins

The ESP32 has two I2C bus interfaces, but no dedicated I2C pins. Instead, it allows for flexible pin assignment, meaning any GPIO pin can be configured as I2C SDA (data line) and SCL (clock line).

However, GPIO21 (SDA) and GPIO22 (SCL) are commonly used as the default I2C pins to make it easier for people using existing Arduino code, libraries, and sketches.

ESP32 I2C Pins

ESP32 SPI Pins

ESP32 features three SPIs (SPI, HSPI, and VSPI) in slave and master modes. These SPIs also support the general-purpose SPI features listed below:

  • 4 timing modes of the SPI format transfer
  • Up to 80 MHz and the divided clocks of 80 MHz
  • Up to 64-Byte FIFO
ESP32 SPI Pins

Only VSPI and HSPI are usable SPI interfaces, and the third SPI bus is used by the integrated flash memory chip. VSPI pins are commonly used in standard libraries.

HSPI vs. VSPI

HSPI is sometimes misinterpreted as �Hardware� SPI and VSPI as �Virtual or Software� SPI. In reality, however, they are identical!

As with I2C, you can bit-bang the SPI protocol on any GPIO pins with the bus.begin(CLK_PIN, MISO_PIN, MOSI_PIN, SS_PIN); command.

ESP32 UART Pins

The ESP32 dev. board has three UART interfaces, UART0, UART1, and UART2, that support asynchronous communication (RS232 and RS485) and IrDA at up to 5 Mbps.

  • UART0 pins are connected to the USB-to-Serial converter and are used for flashing and debugging. Therefore, the UART0 pins are not recommended for use.
  • UART1 pins are reserved for the integrated flash memory chip.
  • UART2, on the other hand, is a safe option for connecting to UART-devices such as GPS, fingerprint sensor, distance sensor, and so on.
ESP32 UART Pins

In addition, UART provides hardware management of the CTS and RTS signals and software flow control (XON and XOFF) as well.

ESP32 PWM Pins

The board has 21 channels (all GPIOs except input-only GPIOs) of PWM pins controlled by a PWM controller. The PWM output can be used for driving digital motors and LEDs.

ESP32 PWM Pins

The PWM controller consists of PWM timers, the PWM operator and a dedicated capture sub-module. Each timer provides timing in synchronous or independent form, and each PWM operator generates a waveform for one PWM channel. The dedicated capture sub-module can accurately capture events with external timing.

ESP32 RTC GPIO Pins

Some GPIOs are routed to the RTC low-power subsystem and are referred to as RTC GPIOs. These pins are used to wake the ESP32 from deep sleep when the Ultra Low Power (ULP) co-processor is running. The GPIOs highlighted below can be used as external wake up sources.

ESP32 RTC GPIO Pins

ESP32 Strapping Pins

There are five strapping pins: GPIO0, GPIO2, GPIO5, GPIO12, and GPIO15.

esp32 strapping pins

These pins are used to put the ESP32 into BOOT mode (to run the program stored in the flash memory) or FLASH mode (to upload the program to the flash memory). Depending on the state of these pins, the ESP32 will enter BOOT mode or FLASH mode at power on.

On most development boards with built-in USB/Serial, you don�t need to worry about the state of these pins, as the board puts them in the correct state for flashing or boot mode.

However, if peripherals are connected to these pins, you may encounter issues when attempting to upload new code or flash the ESP32 with new firmware, as these peripherals prevent the ESP32 from entering the correct mode.

The strapping pins function normally after reset release, but they should still be used with caution.

ESP32 Power Pins

There are two power pins: the VIN pin and the 3V3 pin. The VIN pin can be used to directly power the ESP32 and its peripherals, if you have a regulated 5V power supply. The 3V3 pin is the output from the on-board voltage regulator; you can get up to 600mA from it. GND is the ground pin.

ESP32 Power Pins

ESP32 Enable Pin

The EN pin is the enable pin for the ESP32, pulled high by default. When pulled HIGH, the chip is enabled; when pulled LOW, the chip is disabled.

The EN pin is also connected to a pushbutton switch that can pull the pin LOW and trigger a reset.

esp32 enable pin

For those looking to add wireless connectivity to embedded projects or to build IoT devices, the ESP32 is arguably the most popular choice. It offers a dual-core option for processor intensive applications, while built-in WiFi and Bluetooth simplify designs. It also has plenty of I/O, memory, and interoperability to meet the needs of most applications.

Espressif, the company behind the ESP-32 chip, also manufactures modules and development boards. If you intend to design a custom PCB with an ESP32, the ESP32-WROOM-32 module is an excellent choice. Opting the module over the bare chip has several advantages. Notably, the module is already FCC-approved, which means you won�t need to take any extra measures to achieve FCC compliance. This simplifies the manufacturing process considerably.

When it comes to custom PCB design, understanding the pinout of the ESP32-WROOM-32 becomes crucial. This article will go over the ESP32-WROOM-32 pinout in great detail.

ESP32-WROOM-32 Pinout

The ESP32-WROOM-32 module has 38 pins in total. The pinout is as follows:

esp32 wroom 32 pinout

Let�s take a closer look at the ESP32 pins and their functions one by one.

ESP32-WROOM-32 GPIO Pins

The ESP32-WROOM-32 module has 32 GPIO pins that can be assigned different functions by programming the appropriate registers. There are several kinds of GPIOs: digital-only, analog-enabled, capacitive-touch-enabled, etc. Most of the digital GPIOs can be configured with internal pull-up or pull-down, or set to high impedance.

esp32 wroom 32 gpio pins

Which GPIOs are safe to use?

Although the module has a lot of pins with various functions, some of them may not be suitable for your projects. The table below shows which pins are safe to use and which pins should be used with caution.

  • � Your top priority pins. They are perfectly safe to use.
  • � Pay close attention because their behavior, particularly during boot, can be unpredictable. Use them only when absolutely necessary.
  • � It is recommended that you avoid using these pins.
��Pin����Pin�Label����GPIO����Safe�to�use?��Reason
4SENSOR_VPGPIO36Input only GPIO, cannot be configured as output
5SENSOR_VNGPIO39Input only GPIO, cannot be configured as output
6IO34GPIO34Input only GPIO, cannot be configured as output
7IO35GPIO35Input only GPIO, cannot be configured as output
8IO32GPIO32
9IO33GPIO33
10IO25GPIO25
11IO26GPIO26
12IO27GPIO27
13IO14GPIO14
14IO12GPIO12must be LOW during boot
16IO13GPIO13
17SHD/SD2GPIO9Connected to Flash memory
18SWP/SD3GPIO10Connected to Flash memory
19SCS/CMDGPIO11Connected to Flash memory
20SCK/CLKGPIO6Connected to Flash memory
21SDO/SD0GPIO7Connected to Flash memory
22SDI/SD1GPIO8Connected to Flash memory
23IO15GPIO15must be HIGH during boot, prevents startup log if pulled LOW
24IO2GPIO2must be LOW during boot and also connected to the on-board LED
25IO0GPIO0must be HIGH during boot and LOW for programming
26IO4GPIO4
27IO16GPIO16
28IO17GPIO17
29IO5GPIO5must be HIGH during boot
30IO18GPIO18
31IO19GPIO19
33IO21GPIO21
34RXD0GPIO3Rx pin, used for flashing and debugging
35TXD0GPIO1Tx pin, used for flashing and debugging
36IO22GPIO22
37IO23GPIO23

The image below shows which GPIO pins can be used safely.

esp32 wroom 32 safe gpio pins

Input Only GPIOs

Pins 4, 5, 6 and 7 cannot be configured as outputs. They can be used as digital or analog inputs, or for other purposes. They also lack internal pull-up and pull-down resistors, unlike the other GPIO pins.

ESP32 Interrupt Pins

All GPIOs can be configured as interrupts. For more information, please refer to this tutorial.

ESP32-WROOM-32 ADC Pins

The ESP32-WROOM-32 module has two ADC (Analog to Digital Converter) blocks; ADC1 and ADC2. Each block has multiple channels:

  • ADC1: contains 6 channels (labeled as ADC1_CH0 and ADC1_CH3 to ADC1_CH7).
  • ADC2: contains 10 channels (labeled as ADC2_CH0 to ADC2_CH9).
esp32 wroom 32 adc pins

The resolution of the ADCs on the ESP32 can be configured up to 12 bits. This means that the ADC can detect 4096 (2^12) discrete analog levels. This results in a resolution of 3.3 V (operating voltage) / 4096 units, or 0.0008 volts (0.8 mV) per unit.

Warning:

When Wi-Fi is enabled, the ADC2 pins cannot be used. If you need Wi-Fi, consider using the ADC1 pins instead.

ESP32-WROOM-32 DAC Pins

The ESP32-WROOM-32 module contains two 8-bit Digital-to-Analog Converters (DACs). These DACs are useful for converting digital signals into analog voltages.

esp32 wroom 32 dac pins

ESP32-WROOM-32 Touch Pins

The ESP32-WROOM-32 module has ten capacitive touch-enabled GPIOs labeled TOUCH0 through TOUCH9. These pins work by measuring the change in capacitance when a finger or conductive object is near the surface of the pin.

They can be used for various applications, such as touch buttons, touch sliders, or even basic gesture recognition. They can also be used to wake the ESP32 from deep sleep, which is particularly useful in power-sensitive applications.

esp32 wroom 32 touch pins

ESP32-WROOM-32 I2C Pins

The ESP32-WROOM-32 module has two I2C bus interfaces, but no dedicated I2C pins. Instead, it allows for flexible pin assignment, meaning any GPIO pin can be configured as I2C SDA (data line) and SCL (clock line).

However, GPIO21 (SDA) and GPIO22 (SCL) are commonly used as the default I2C pins to make it easier for people using existing Arduino code, libraries, and sketches.

esp32 wroom 32 i2c pins

ESP32-WROOM-32 SPI Pins

The ESP32-WROOM-32 module features three SPIs (SPI, HSPI, and VSPI). HSPI and VSPI are commonly used for general purposes, while the third one is used for interfacing with the SPI flash memory integrated on the module.

Similar to I2C, the ESP32 allows flexible pin assignment for SPI. This means that any GPIO pin can be configured as SPI pins.

esp32 wroom 32 spi pins

ESP32-WROOM-32 UART Pins

The ESP32-WROOM-32 module has three UART interfaces: UART0, UART1, and UART2. These interfaces enable serial communication with various peripherals or for logging and debugging purposes.

esp32 wroom 32 uart pins

Please note that the UART1 pins (GPIO 9 and GPIO 10) are used for interfacing with the SPI flash memory integrated on the module, so you can�t use them. However, you can still use UART1 by bit-banging the UART protocol on other GPIO pins.

Besides the basic TX and RX pins, UART interfaces on the ESP32 also support RTS (Request To Send) and CTS (Clear To Send) for hardware flow control, though these are less commonly used.

ESP32-WROOM-32 PWM Pins

Almost all GPIO pins on the module can be configured to generate PWM output.

esp32 wroom 32 pwm pins

The PWM on the ESP32 can be configured with high resolution, typically up to 16 bits, allowing for fine control over the PWM signals. PWM frequency can also be adjusted, with a typical range from a few Hz to tens of MHz, making it suitable for a wide range of applications, from controlling motors to dimming LEDs.

ESP32-WROOM-32 RTC GPIO Pins

Some GPIOs are routed to the RTC low-power subsystem and are known as RTC GPIOs. These GPIOs can be used for waking up the ESP32 from deep sleep and for interfacing with RTC peripherals.

esp32 wroom 32 rtc gpio pins

ESP32-WROOM-32 Strapping Pins

There are five strapping pins on the ESP32: GPIO0, GPIO2, GPIO5, GPIO12, and GPIO15. The state of these pins determines whether the ESP32 enters BOOT mode (to run the program stored in flash memory) or FLASH mode (to upload a program to flash memory) upon power-up.

esp32 wroom 32 strapping pins

Be aware that if peripherals are connected to these pins, you might face issues when trying to flash the ESP32 with new firmware. This is because these peripherals could prevent the ESP32 from entering the correct mode.

ESP32-WROOM-32 Power Pins

The power pins provide the necessary voltage to the module to operate:

3V3 is the main supply voltage pin. It should be provided with a stable 3.3V power supply.

GND is the ground pin.

esp32 wroom 32 power pins

ESP32-WROOM-32 Enable Pin

The EN pin is the enable pin for the ESP32. When pulled HIGH, the chip is enabled and operational; when pulled LOW, the chip is disabled.

esp32 wroom 32 en pin

This article from our ESP32 Basics series demonstrates how to read analog values with the ESP32 using Arduino IDE.

This is useful for reading in a wide variety of sensors and variable components, including, but not limited to, trimpots, joysticks, sliders, and force-sensitive resistors.

ESP32 ADC Pins

The ESP32 includes two 12-bit SAR ADCs � ADC1 and ADC2 � and supports measurements on 18 channels (analog-enabled pins). ADC1 is available on eight GPIOs (32 to 39), while ADC2 is available on ten GPIOs (0, 2, 4, 12 to 15 and 25 to 27).

However, the DEVKIT V1 DOIT board (the version with 30 GPIOs) has only 15 ADC channels, as shown in the figure below.

ESP32 ADC Pins

The ADC in your ESP32 has a resolution of 12 bits, meaning that it can detect 4096 (2^12) discrete analog levels. In other words, it will convert input voltages ranging from 0 to 3.3V (operating voltage) into integer values ranging from 0 to 4095. This results in a resolution of 3.3 volts / 4096 units, or 0.0008 volts (0.8 mV) per unit.

Moreover, the ADC resolution and channel range can be set programmatically.

Limitations of the ESP32�s ADC

Truth be told, ADC is not the ESP32�s strongest point. There are several limitations that you should be aware of.

Unusable when WiFi is Enabled

The ADC2 pins cannot be used when Wi-Fi is enabled. Since there is a good chance of using WiFi on a microcontroller designed to use it, only the ADC1 can be used.

ADC Input Range

The ESP32�s ADC can only measure voltages ranging from 0 to 3.3V. You cannot measure analog voltages between 0 and 5V directly.

ADC Accuracy

Ideally, you would expect a linear behavior when using the ADC, but this is not the case. The ADC converters on the ESP32 are non-linear in nature. You can find more information about this in a discussion on GitHub.

In the graph below, the non-linearities at the lower and upper ends of the input voltage are clearly visible.

esp32 adc nonlinearity

This basically means that the ESP32 cannot distinguish 3.2V from 3.3V; the measured value will be the same (4095). Similarly, it cannot distinguish between 0V and 0.13V signals; the measured value will be the same (0).

Electrical Noise

The electrical noise of the ADC implies a slight fluctuation of the measurements.

esp32 adc electrical noise

However, this can be corrected by adding a capacitor at the output and by oversampling.

The analogRead() Function

Reading the analog values from a GPIO pin is straightforward. In the Arduino IDE, you use the analogRead() function, which accepts as an argument the GPIO pin number you want to read.

analogRead(GPIO);

Reading a potentiometer

To demonstrate how to use the ADC on the ESP32, we will use a simple example that reads an analog value from a potentiometer.

Hardware Hookup

Let�s setup a simple potentiometer circuit for this example.

Start by inserting the potentiometer into your breadboard. Wire the middle pin to pin GPIO 34 on your ESP32. Finally, wire one of the potentiometer�s outer pins � it doesn�t matter which � to ESP32�s 3V3 pin and the other to ground.

wiring potentiometer to esp32 adc

Example Code

Load the following sketch onto your ESP32. This sketch simply reads the potentiometer and prints the results to the Serial Monitor.

// Potentiometer is connected to GPIO 34 (Analog ADC1_CH6) 
const int potPin = 34;

// variable for storing the potentiometer value
int potValue = 0;

void setup() {
  Serial.begin(115200);
  delay(1000);
}

void loop() {
  // Reading potentiometer value
  potValue = analogRead(potPin);
  Serial.print("Analog value: ");
  Serial.println(potValue);
  delay(500);
}

Once you have uploaded the sketch, open the serial monitor at baud rate 115200 and press the EN button on the ESP32.

You should see a value between 0 and 4095, depending on the current rotation of the knob, being printed out to the serial monitor. Try turning the knob on the potentiometer to see how the values change.

esp32 adc output

Code Explanation:

The sketch begins by defining the GPIO pin to which the potentiometer is connected, which in this case is GPIO 34.

const int potPin = 34;

A variable is also defined to store the potentiometer values.

int potValue = 0;

In the setup(), we initialize the serial communication with the PC.

Serial.begin(115200);

In the loop, the analogRead() function is used to read the voltage on the potPin. The returned value is stored in the variable potValue.

potValue = analogRead(potPin);

Finally, the values read from the potentiometer are printed to the serial monitor.

Serial.print("Analog value: ");
Serial.println(potValue);

potPin does not need to be set as input. This is done for you automatically each time you call analogRead().

Other ADC Functions

There are other ADC functions that may be useful in other projects:

  • analogReadMilliVolts(pin): get ADC value for a given pin/ADC channel in millivolts.
  • analogReadResolution(bits): sets the sample bits and read resolution. Default is 12-bit resolution. Range: 9 (0 � 511) to 12 bits (0 � 4095).
  • analogSetWidth(bits): sets the hardware sample bits and read resolution. Default is 12-bit resolution. Range: 9 to 12 bits. 9-bit = 0-511, 10-bit = 0-1023, 11-bit = 0-2047 and 12-bit = 0-4095.
  • analogSetCycles(cycles): sets the number of cycles per sample. Default is 8. Range: 1 to 255.
  • analogSetSamples(samples): sets the number of samples in the range. Default is 1 sample. It has an effect of increasing sensitivity.
  • analogSetClockDiv(clockDiv): sets the divider for the ADC clock. Default is 1. Range: 1 to 255.
  • analogSetAttenuation(attenuation): sets the input attenuation for all ADC pins. Default is ADC_11db. Accepted values:
  • ADC_0db: sets no attenuation (Measurable input voltage range = 100 mV ~ 950 mV).
  • ADC_2_5db: sets an attenuation of 1.34 (Measurable input voltage range = 100 mV ~ 1250 mV)
  • ADC_6db: sets an attenuation of 1.5 (Measurable input voltage range = 150 mV ~ 1750 mV)
  • ADC_11db: sets an attenuation of 3.6 (Measurable input voltage range = 150 mV ~ 2450 mV)
  • analogSetPinAttenuation(pin, attenuation): This function is the same as the previous one, except it sets the input attenuation for the specified pin.
  • adcAttachPin(pin): Attaches a pin to ADC (also clears any other analog mode that could be on) and returns true if configuration is successful, else returns false.
  • adcStart(pin): starts an ADC conversion on the attached pin�s bus.
  • adcBusy(pin): checks if conversion on the pin�s ADC bus is currently running (returns TRUE or FALSE).
  • resultadcEnd(pin): gets the result of the conversion (waits if ADC has not finished), returns 16-bit integer.

More information can be found at readthedocs.

A Hall effect sensor (or simply Hall sensor) is a type of sensor which detects the presence and strength of a magnetic field using the Hall effect, and is commonly used in applications such as proximity sensing, positioning, speed detection, and current sensing.

These sensors are so inexpensive that you can get a dozen for a dollar. But, when such an inexpensive sensor comes built-in with a feature-rich WiFi-enabled microcontroller, such as the ESP32, it may seem unnecessary at first. Nevertheless, who knows? You might come up with an idea to use it at some point in the future. A WiFi door status sensor, perhaps. See? You�re already getting some ideas.

So, let�s learn how to read the Hall sensor on the ESP32. But first, let�s go over how Hall-effect sensors work.

How Hall-effect Sensors Work?

A Hall-effect sensor uses a phenomenon called the Hall effect, which was discovered by Edwin Hall in 1879. The basic concept is simple:

Consider a conductive sheet shaped like a dollar bill. When a constant voltage source is connected across the left and right sides, it causes electrons to flow through the conductor in a straight line. Without any magnetic field present, if you were to measure the voltage across the top and bottom of the sheet, you�d find it to be almost zero.

hall eelement without magnetic field present

However, when a magnetic field is present with flux lines at right angles to the current, Lorentz force acts on the electrons. This force makes them deviate from their straight-line path, causing them to accumulate on one side of the conductor while being absent on the other. As a result, one side of the conductor becomes more electron-dense than the other. This leads to a potential difference (known as the Hall voltage) across the conductor. This phenomenon is referred to as the Hall effect.

hall effect explained

The stronger the magnetic field, the greater the deflection of electrons; the larger the current, the more electrons there are to deflect. In either case, the Hall voltage will be larger. In other words, the Hall voltage is directly proportional to both the electric current flowing through the conductor and the strength of the magnetic field.

Therefore, by measuring the Hall voltage for a known current, the strength of the magnetic field can be determined.

ESP32 Hall Effect Sensor

The ESP32 has a built-in hall effect sensor located beneath the metal lid of the ESP32-WROOM-32 module itself.

esp32 hall sensor

Being integrated into the ESP32 means that you can easily connect the sensor readings with WiFi or Bluetooth functionalities, making remote monitoring and control easier.

While the onboard Hall sensor might not replace dedicated external sensors for precise applications due to its positioning and sensitivity, it still offers a range of potential uses. This includes basic magnetic field detection, triggering specific functions when a magnet is nearby, or even building simple educational projects to understand the Hall effect.

Because the sensor is located beneath the metal lid, it is less sensitive to weak magnetic fields than standalone Hall sensors, so magnets of significant strength are usually required to obtain noticeable readings.

Reading the Hall Sensor

Reading the hall sensor on the ESP32 is straightforward. In the Arduino IDE, you use the hallRead() function. This function returns an integer value that represents the Hall voltage.

hallRead();

Example Code

Let�s read the hall sensor using an example from the library. Open your Arduino IDE, and navigate to File > Examples > ESP32, and open the HallSensor sketch.

This example simply reads the internal hall sensor on the ESP32 and shows the result on the serial monitor.

int val = 0;
void setup() {
  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
  val = hallRead();
  // print the results to the serial monitor:
  //Serial.print("sensor = ");
  Serial.println(val);//to graph 
}

Once you have uploaded the sketch, open the serial monitor at baud rate 9600 and press the EN button on the ESP32.

Now try to bring a magnet close to the ESP32 chip, you will see that the readings change depending on the distance and polarity of the magnet.

esp32 hall sensor output

Visualizing the signal in the Serial Plotter will help you understand that: when no magnetic field is detected, the output remains at approximately 100. If the south pole of a magnet is brought near, the output increases toward 200, and if the north pole of a magnet is brought close, the output decreases toward 0.

esp32 hall sensor serial plotter output

ESP32 is a great development board for creating smart IoT projects and adding touch functionality will make them even smarter.

ESP32 offers 10 capacitive touch-sensing GPIOs. You can use these GPIOs to update existing simple push button projects or to create light switches, musical instruments, or custom interactive surfaces.

Let�s learn how to handle these touch-sensing pins and use them in projects.

Touch detection in ESP32 is managed by the ULP coprocessor. So these touch pins can also be used to wake ESP32 from deep sleep.

How ESP32 senses Touch?

ESP32 uses the electrical properties of the human body as input. When the touch-sensing pin is touched with a finger, a small electric charge is drawn to the point of contact.

This triggers capacitance variation resulting in an analog signal. Two successive approximation ADCs (SAR ADCs) then convert this analog signal into a digital number.

ESP32 Touch Pins

ESP32 has 10 capacitive touch-sensing GPIOs. When a capacitive load (such as a human skin) is in close proximity to the GPIO, ESP32 detects a change in capacitance.

Although the ESP32 has total 10 capacitive touch-sensing GPIO pins, only 9 of them are broken out to the pin headers on both sides of the 30 pin ESP32 development board.

esp32 touch pins

Reading the Touch Sensor

Reading the touch sensor is straightforward. In the Arduino IDE, you use the touchRead() function, which accepts as an argument the GPIO pin number you want to read.

touchRead(GPIOPin);

Hardware Hookup

Enough of the theory! Let�s look at a practical example.

Let�s wire a cable to Touch #0 (GPIO #4). You can attach any conductive object like aluminum foil, conductive cloth, conductive paint etc. to this pin and turn it into a touch pad.

connecting wire to esp32 for reading touch gpio

Example Code

Let�s see how it works, using an example from the library. Open your Arduino IDE, and navigate to File > Examples > ESP32 > Touch, and open the TouchRead sketch.

This example simply reads touch pin 0 and shows the result on the serial monitor.

// ESP32 Touch Test
// Just test touch pin - Touch0 is T0 which is on GPIO 4.

void setup() {
  Serial.begin(115200);
  delay(1000); // give me time to bring up serial monitor
  Serial.println("ESP32 Touch Test");
}

void loop() {
  Serial.print("Touch: ");
  Serial.println(touchRead(4));  // get touch value on GPIO 4
  delay(1000);
}

Once you have uploaded the sketch, open the serial monitor at baud rate 115200 and press the EN button on the ESP32.

Now try touching the metal part of the wire and see how it reacts to the touch.

esp32 touch test output on serial monitor

Code Explanation:

The code is pretty straightforward. In the setup(), we first initialize the serial communication with the PC.

Serial.begin(115200);

In loop() we use touchRead() function, and pass as argument the pin we want to read. In this case, the GPIO #4. You can also pass the touch sensor number T0.

Serial.println(touchRead(4));

ESP32 Project � Touch-activated LED

Let�s quickly create a project to demonstrate how the ESP32�s touch pins can be used to control devices. In this example, we are going to make a simple touch-activated LED that will light up when you touch the GPIO pin.

Of course this project can be expanded to open doors, switch relays, light LEDs, or anything else you can think of.

Finding the Threshold

Before proceeding, you should see what readings you are actually getting from the ESP32. Note the output you get when you touch the pin and when you don�t.

When you run the previous sketch, you�ll see the close to the following readings in the serial monitor:

  • when you touch the pin (~3)
  • when you don�t touch the pin (~71)

Based on the values, we can set a threshold, so that when the reading drops below the threshold, we will toggle the LED. 30 may be a good threshold value in this case.

Example Code

Below is a simple code that turns on the on-board LED when you touch the pin once and turns it off when you touch it again.

// set pin numbers
const int touchPin = 4;
const int ledPin = 2;

const int threshold = 30;  // set the threshold

int ledState = LOW;         // the current state of the output pin
int touchState;             // the current reading from the input pin
int lastTouchState = LOW;   // the previous reading from the input pin

unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

void setup() {
  pinMode(ledPin, OUTPUT);

  // set initial LED state
  digitalWrite(ledPin, ledState);
}

void loop() {
  // read the state of the pin
  int reading = touchRead(touchPin);

  // binarize touch reading for easy operation
  if (reading < threshold) {
    reading = HIGH;
  } else{
    reading = LOW;
  }
  
  // If the pin is touched:
  if (reading != lastTouchState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer than the debounce
    // delay, so take it as the actual current state:

    // if the touch state has changed:
    if (reading != touchState) {
      touchState = reading;

      // only toggle the LED if the new touch state is HIGH
      if (touchState == HIGH) {
        ledState = !ledState;
      }
    }
  }

  // set the LED:
  digitalWrite(ledPin, ledState);

  // save the reading. Next time through the loop, it'll be the lastTouchState:
  lastTouchState = reading;
}

Upload the sketch to your ESP32. You should see the LED toggle every time you touch the wire.

If you�ve played around with Arduino before, you�re familiar with how simple it is to generate a PWM signal using the analogWrite() function�just specify the pin to use and the duty cycle, and you�re good to go.

But with the ESP32, it�s like playing a game on a slightly harder level. We get more controls (yay!), but we also have to manage them wisely (which is a little tricky). The ESP32 asks us to be specific about a few more things, such as the PWM frequency, PWM resolution, the channel to be used, and, of course, the duty cycle and pin number. Phew, that sounds like a lot, but don�t worry!

This guide will teach you everything you need to know about PWM on the ESP32, from core concepts to practical examples.

ESP32 PWM Pins

On the ESP32, PWM output is possible on all GPIO pins except for four input-only GPIO pins. The GPIOs highlighted below support PWM.

ESP32 PWM Pins

PWM on ESP32

The ESP32 has two PWM peripherals: the LED Control Peripheral (LEDC) and the Motor Control Pulse Width Modulator Peripheral (MCPWM).

The MCPWM peripheral is intended for motor control and includes additional features such as a dead zone and auto-braking. On the other hand, the LEDC peripheral is specifically designed for driving LEDs and includes features such as auto-dimming as well as more advanced features. It can, however, be used to generate PWM signals for a variety of other purposes.

In this tutorial, we will be focusing primarily on the LEDC peripheral.

PWM Frequency

The LEDC peripheral, like most PWM controllers, uses a timer to generate PWM signals.

Think of a timer as �ticking along,� counting up until it reaches a maximum number, at which point it resets itself to zero and the next counting cycle begins again. The time between these resets (i.e., how long it takes to reach the maximum value) represents the PWM Frequency and is measured in Hertz (Hz). For example, if we specify a frequency of 1 Hz, the timer will take 1 second to count from 0 to the maximum value before starting the next cycle. If we specify a frequency of 1000 Hz , the timer will only take 1 millisecond to count from 0 to the maximum value.

esp32 pwm hardware working frequency

The ESP32 can generate a PWM signal with a frequency of up to 40 MHz.

PWM Resolution

So, what exactly is this �maximum� value? The �maximum� value is determined by the PWM Resolution. If the PWM resolution is �n� bits, the timer counts from 0 to 2n-1 before it resets. For example, if we configure the timer with a frequency of 1 Hz and a resolution of 8 bits, the timer will take 1 second to count from 0 to 255 (28). In the case of a frequency of 1 Hz and a resolution of 16 bits, the timer will still take 1 second, but it will count from 0 to 65,535 (216).

esp32 pwm hardware working 8bit resoluation
esp32 pwm hardware working 16bit resoluation

It is important to understand that when we have higher resolution, we essentially have more �timer increments� within the same specified period of time. What we thus have is more �granularity� in the timings.

The ESP32�s PWM resolution can be adjusted from 1 to 16 bits. This means that the duty cycle can be set at up to 65,536 (216) different levels. This gives you fine control over things like LEDs, enabling them to glow with subtle variations in brightness, or motors, allowing them to run at very precise speeds.

Duty Cycle

Next, we define the Duty Cycle of the PWM output. The duty cycle indicates how many timer ticks the PWM output will remain high before it goes low. This value is stored in the timer�s capture/compare register (CCR).

When the timer resets, the PWM output goes high. When the timer reaches the value stored in the capture/compare register, the PWM output goes low. The timer, however, continues counting. Once the timer reaches its maximum value, the PWM output goes high again, and the timer resets to start counting for the next period.

For example, imagine we wish to generate a PWM signal with a frequency of 1000 Hz, an 8-bit resolution, and a 75% duty cycle. Given the 8-bit resolution, the timer�s maximum value will be 255 (28-1). With a frequency of 1000 Hz, the timer will take 1 ms (0.001 s) to count from 0 to 255. The duty cycle of the PWM is set at 75%, meaning that the value 256 * 75% = 192 will be stored in the capture/compare register. In this case, when the timer resets, the PWM output will be set high. The PWM output will remain high until the counter reaches 192, at which point it will toggle to low. Once the timer hits 255, the PWM output will toggle back to high, and the timer will reset to commence counting for the next period.

esp32 pwm hardware working duty cycle

PWM Channels

Now let us turn our attention to the concept of a channel. A channel represents a unique PWM output signal.

The ESP32 has 16 channels, which means it can generate 16 unique PWM waveforms. These channels are divided into two groups, each containing 8 channels: 8 high-speed channels and 8 low-speed channels.

esp32 pwm timers and channels

The high-speed channels are implemented in hardware and are therefore able to provide automatic and glitch-free changes to the PWM duty cycle. Low-speed channels, on the other hand, lack these features and rely on software to change their duty cycle.

Within each group, there are 4 timers shared among 8 channels, which means that every two channels share the same timer. Since the timer determines the frequency, it�s important to understand that we cannot adjust the frequency of each channel independently within a pair. However, we can control the PWM duty cycle of each channel independently.

To summarize, an ESP32 has 16 PWM channels that can operate at eight distinct frequencies, and each of these channels can operate with a different duty cycle.

To generate a PWM signal on a specific pin, you �attach� that pin to a channel. This linkage tells the ESP32 to output the PWM waveform generated by the channel on the specified pin. Multiple pins can be attached to the same channel, which means they can all output the same PWM signal. Despite the fact that all GPIO pins support PWM output, the ESP32 has only 16 channels, so only 16 different PWM waveforms can be produced at the same time. This does not limit the number of pins that can output PWM signals, but it does limit the variety of signals that can be output simultaneously.

Practically speaking, if you have a set of LEDs that you wish to blink in perfect sync, you can set up a single channel with specific frequency and duty cycle, and then attach all the relevant pins (which are connected to the LEDs) to this channel. However, when working with servos, especially in situations like a robotic arm where each joint (servo) must be controlled independently, it becomes advantageous to assign different pins to different channels.

Choosing PWM Frequency and Resolution

The ESP32 can generate a PWM signal with a frequency of up to 40 MHz, and the PWM resolution can be adjusted from 1 to 16 bits. But this doesn�t mean you can set a frequency of 40 MHz and a resolution of 16 bits at the same time. This is due to the fact that the maximum PWM frequency and resolution are both bound by the clock source.

To illustrate this, consider a clock (whether it�s a CPU clock or a timer doesn�t matter) running at a frequency of 40 MHz. In this case, the maximum achievable PWM frequency is also 40 MHz. We cannot generate a PWM wave faster than our clock allows.

And what about the resolution? Well, resolution is really about how finely we can slice up one period of the PWM wave into different duty cycles. And here is the insight: slicing up the PWM wave requires a CPU clock running at PWM_freq * 2PWM_resolution. Why? Because to create those duty cycles, you need to be able to create those time slices.

From this, two important points become clear:

  • PWM_freq * 2PWM_resolution cannot exceed the clock speed.
  • PWM frequency and resolution are interdependent. The higher the PWM frequency, the lower the duty cycle resolution (and vice versa).

According to Espressif documentation, the LEDC low-speed timer clock source is an 80 MHz APB clock. As a general guideline, you should aim to keep PWM_freq * 2PWM_resolution below 80 MHz.

Additionally, the Espressif documentation includes examples to back this up:

  • A PWM frequency of 5 kHz can have a maximum duty resolution of 13 bits, which results in a resolution of ~0.012%, or 213=8192 discrete levels.
  • A PWM frequency of 20 MHz can have a maximum duty resolution of 2 bits, which results in a resolution of 25%, or 22=4 discrete levels.
  • A PWM frequency of 40 MHz can have a duty resolution of just 1 bit, meaning the duty cycle remains fixed at 50% and cannot be adjusted.

If none of this makes sense to you, consider this: The Arduino Uno provides a ~490 Hz PWM waveform at 8 bits. This is more than enough to fade an LED smoothly. So, you can always start there (500 Hz frequency, 8-bit resolution) and then play around.

Generating PWM signal with the LEDC Library

Let�s get right to it! The ESP32 Arduino core includes the LEDC Library, which makes it easier to manage Pulse Width Modulation (PWM) on the ESP32. While the LEDC library was designed to control LEDs, it can also be used for other applications where PWM waveforms are useful, such as emitting �music� through piezo speakers and driving motors.

The steps below show how to use the LEDC library to generate a PWM signal with the ESP32 using Arduino IDE.

  1. Select a PWM Channel: There are 16 channels to choose from, numbered from 0 to 15.
  2. Determine the PWM Frequency: It can go up to 40 MHz, but for our LED fading example, a frequency of 500 Hz should suffice.
  3. Determine the PWM Resolution: It ranges from 1 to 16 bits. The number of discrete duty cycle levels is determined by 2resolution. For example, setting the resolution to 8 bits results in 256 discrete duty cycle levels [0�255]. On the other hand, a resolution of 16 bits provides 65,536 discrete duty cycle levels [0�65535].
  4. Choose the GPIO Pin(s): Choose one or more GPIO pins on the ESP32 to output the PWM signal.
  5. Configure the PWM Channel: Configure the selected PWM channel with the selected frequency and resolution using the ledcSetup(channel, freq, resolution) function.
  6. Attach the Pin(s) to the Channel: Attach the selected pin(s) to the selected channel using the ledcAttachPin(pin, channel) function.
  7. Set the Duty Cycle: Finally, set the actual duty cycle value for a given channel using the ledcWrite(channel, dutycycle) function.

Example 1 � Fading an LED

Here�s a quick example sketch that shows how to fade an LED�perfect for demonstrating the PWM generation on the ESP32.

Wiring

The wiring is quite simple. Take an LED and a 330 ? current-limiting resistor, and put them in the breadboard as shown in the figure below. Wire the longer leg of the LED, the anode, to pin GP18 via the 330 ? resistor, and wire the shorter leg to the ground pin of your ESP32.

wiring led to esp32 pwm control

Code

Copy the code below to your Arduino IDE.

const int PWM_CHANNEL = 0;    // ESP32 has 16 channels which can generate 16 independent waveforms
const int PWM_FREQ = 500;     // Recall that Arduino Uno is ~490 Hz. Official ESP32 example uses 5,000Hz
const int PWM_RESOLUTION = 8; // We'll use same resolution as Uno (8 bits, 0-255) but ESP32 can go up to 16 bits 

// The max duty cycle value based on PWM resolution (will be 255 if resolution is 8 bits)
const int MAX_DUTY_CYCLE = (int)(pow(2, PWM_RESOLUTION) - 1); 

const int LED_OUTPUT_PIN = 18;

const int DELAY_MS = 4;  // delay between fade increments

void setup() {

  // Sets up a channel (0-15), a PWM duty cycle frequency, and a PWM resolution (1 - 16 bits) 
  // ledcSetup(uint8_t channel, double freq, uint8_t resolution_bits);
  ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RESOLUTION);

  // ledcAttachPin(uint8_t pin, uint8_t channel);
  ledcAttachPin(LED_OUTPUT_PIN, PWM_CHANNEL);
}

void loop() {

  // fade up PWM on given channel
  for(int dutyCycle = 0; dutyCycle <= MAX_DUTY_CYCLE; dutyCycle++){   
    ledcWrite(PWM_CHANNEL, dutyCycle);
    delay(DELAY_MS);
  }

  // fade down PWM on given channel
  for(int dutyCycle = MAX_DUTY_CYCLE; dutyCycle >= 0; dutyCycle--){
    ledcWrite(PWM_CHANNEL, dutyCycle);   
    delay(DELAY_MS);
  }
}

Testing the Example

Now, upload the code to your ESP32. You will observe the LED�s brightness change smoothly from completely off to fully lit and back again.

Code Explanation:

Several constants are defined at the beginning of the sketch to configure the PWM characteristics. First, the constant PWM_CHANNEL is defined and set to 0. ESP32 has 16 channels (0 to 15), and each can generate independent waveforms.

Then, PWM_FREQ is defined and set to 500. This is the frequency of our PWM signal. Recall that the Arduino Uno uses ~490 Hz. This is sufficient to fade an LED smoothly.

Next, PWM_RESOLUTION is set to 8. This is the resolution (in bits) of the PWM signal. While we�re using 8 bits (same as Arduino Uno), ESP32 can go up to 16 bits.

const int PWM_CHANNEL = 0;
const int PWM_FREQ = 500;
const int PWM_RESOLUTION = 8;

After that, MAX_DUTY_CYCLE is calculated using the formula 2PWM_RESOLUTION?1. This value determines the maximum achievable duty cycle based on the chosen resolution.

const int MAX_DUTY_CYCLE = (int)(pow(2, PWM_RESOLUTION) - 1);

Following this, LED_OUTPUT_PIN is set to 18. This is the ESP32 GPIO pin to which the LED is connected.

And finally, DELAY_MS is defined and set to 4. This is the delay (in milliseconds) between increments to control the speed of the LED�s fading.

const int LED_OUTPUT_PIN = 18;
const int DELAY_MS = 4;

During setup, the ledcSetup() function is called to configure the PWM properties using the previously defined constants. This function takes three arguments: the PWM channel, the PWM frequency, and the PWM resolution.

ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RESOLUTION);

Next, the ledcAttachPin() function is used to attach the GPIO pin to the PWM channel responsible for generating the PWM signal. In this case, the PWM signal generated by PWM_CHANNEL, which corresponds to channel 0, will appear upon LED_OUTPUT_PIN, which corresponds to GPIO 16.

ledcAttachPin(LED_OUTPUT_PIN, PWM_CHANNEL);

In the loop, the first for loop iteratively increases the duty cycle from 0 to its maximum possible value (MAX_DUTY_CYCLE). This gradually brightens the LED.

for(int dutyCycle = 0; dutyCycle <= MAX_DUTY_CYCLE; dutyCycle++){   
  ledcWrite(PWM_CHANNEL, dutyCycle);
  delay(DELAY_MS);
}

The second for loop decrements the duty cycle from MAX_DUTY_CYCLE to 0: This gradually dims the LED.

for(int dutyCycle = MAX_DUTY_CYCLE; dutyCycle >= 0; dutyCycle--){
  ledcWrite(PWM_CHANNEL, dutyCycle);   
  delay(DELAY_MS);
}

In both for loops, the ledcWrite() function is used to set the brightness of the LED. This function accepts as arguments the channel that is generating the signal and the duty cycle.

ledcWrite(ledChannel, dutyCycle);

Example 2 � Same PWM Signal on Multiple GPIOs

You can get the same PWM signal on multiple GPIOs at the same time. To do so, you simply need to attach those GPIOs to the same channel.

Wiring

Add two more LEDs to your circuit in the same way you did the first. Connect them to GPIO 19 and 21.

The image below shows how to connect everything.

wiring multiple leds to esp32 simultaneous pwm control

Code

Now, let�s modify the previous example to fade three LEDs using the same PWM signal from the same channel.

const int PWM_CHANNEL = 0;    // ESP32 has 16 channels which can generate 16 independent waveforms
const int PWM_FREQ = 500;     // Recall that Arduino Uno is ~490 Hz. Official ESP32 example uses 5,000Hz
const int PWM_RESOLUTION = 8; // We'll use same resolution as Uno (8 bits, 0-255) but ESP32 can go up to 16 bits 

// The max duty cycle value based on PWM resolution (will be 255 if resolution is 8 bits)
const int MAX_DUTY_CYCLE = (int)(pow(2, PWM_RESOLUTION) - 1); 

const int LED_1_OUTPUT_PIN = 18;
const int LED_2_OUTPUT_PIN = 19;
const int LED_3_OUTPUT_PIN = 21;

const int DELAY_MS = 4;  // delay between fade increments

void setup() {

  // Sets up a channel (0-15), a PWM duty cycle frequency, and a PWM resolution (1 - 16 bits) 
  // ledcSetup(uint8_t channel, double freq, uint8_t resolution_bits);
  ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RESOLUTION);

  // ledcAttachPin(uint8_t pin, uint8_t channel);
  ledcAttachPin(LED_1_OUTPUT_PIN, PWM_CHANNEL);
  ledcAttachPin(LED_2_OUTPUT_PIN, PWM_CHANNEL);
  ledcAttachPin(LED_3_OUTPUT_PIN, PWM_CHANNEL);
}

void loop() {

  // fade up PWM on given channel
  for(int dutyCycle = 0; dutyCycle <= MAX_DUTY_CYCLE; dutyCycle++){   
    ledcWrite(PWM_CHANNEL, dutyCycle);
    delay(DELAY_MS);
  }

  // fade down PWM on given channel
  for(int dutyCycle = MAX_DUTY_CYCLE; dutyCycle >= 0; dutyCycle--){
    ledcWrite(PWM_CHANNEL, dutyCycle);   
    delay(DELAY_MS);
  }
}

Testing the Example

Now, upload the code to your ESP32. You will observe all three LEDs fade simultaneously, because all GPIOs are outputting the same PWM signal.

Code Explanation:

If you compare this sketch to the first one, you�ll notice that they are very similar, with just a few differences. Let�s take a look at these differences.

In the global area, three additional constants named LED_1_OUTPUT_PIN, LED_2_OUTPUT_PIN, and LED_3_OUTPUT_PIN are defined and set to 18, 19, and 21 respectively. This indicates that we are dealing with three separate LEDs, each connected to its own GPIO pin on the ESP32.

const int LED_1_OUTPUT_PIN = 18;
const int LED_2_OUTPUT_PIN = 19;
const int LED_3_OUTPUT_PIN = 21;

Then, in the setup, the ledcAttachPin() function is invoked three times instead of just once, as in the previous code. Each function call attaches a different GPIO pin (LED_1_OUTPUT_PIN, LED_2_OUTPUT_PIN, LED_3_OUTPUT_PIN) with the same PWM channel (PWM_CHANNEL), meaning the same PWM signal will be output to all three LEDs.

ledcAttachPin(LED_1_OUTPUT_PIN, PWM_CHANNEL);
ledcAttachPin(LED_2_OUTPUT_PIN, PWM_CHANNEL);
ledcAttachPin(LED_3_OUTPUT_PIN, PWM_CHANNEL);

Note that despite the addition of more LEDs, there is no change to the loop() function. This is because the same PWM channel controls all LEDs.

Example 3 � Fading an LED using the Potentiometer

This example sketch shows you how to fade an LED using the potentiometer.

Wiring

Remove the two extra LEDs you added to your circuit and add a potentiometer. Attach one outer pin of the potentiometer to 3.3V, the opposite outer pin to GND, and its middle pin (wiper) to GPIO 34.

The image below shows how to connect everything.

wiring led to esp32 pwm control with potentiometer

Code

const int PWM_CHANNEL = 0;    // ESP32 has 16 channels which can generate 16 independent waveforms
const int PWM_FREQ = 500;     // Recall that Arduino Uno is ~490 Hz. Official ESP32 example uses 5,000Hz
const int PWM_RESOLUTION = 8; // We'll use same resolution as Uno (8 bits, 0-255) but ESP32 can go up to 16 bits 

// The max duty cycle value based on PWM resolution (will be 255 if resolution is 8 bits)
const int MAX_DUTY_CYCLE = (int)(pow(2, PWM_RESOLUTION) - 1); 

const int LED_OUTPUT_PIN = 18;
const int POT_PIN = 34;

const int DELAY_MS = 100;  // delay between fade increments

void setup() {

  // Sets up a channel (0-15), a PWM duty cycle frequency, and a PWM resolution (1 - 16 bits) 
  // ledcSetup(uint8_t channel, double freq, uint8_t resolution_bits);
  ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RESOLUTION);

  // ledcAttachPin(uint8_t pin, uint8_t channel);
  ledcAttachPin(LED_OUTPUT_PIN, PWM_CHANNEL);
}

void loop() {
  int dutyCycle = analogRead(POT_PIN);
  dutyCycle = map(dutyCycle, 0, 4095, 0, MAX_DUTY_CYCLE);
  ledcWrite(PWM_CHANNEL, dutyCycle);

  delay(DELAY_MS);
}

Testing the Example

Now try turning the potentiometer all the way one way, then all the way the other. Watch the LED; this time, you�ll see the LED�s brightness change smoothly from completely off at one end of the potentiometer knob�s limit to fully lit at the other.

Code Explanation:

Again! There are only a few differences between this sketch and the first one. Let�s take a look at these differences.

An additional constant named POT_PIN is defined in the global area. It is set to 34, indicating that the potentiometer is connected to GPIO 34 on the ESP32 and will be used to dynamically determine the duty cycle and, therefore, the brightness of the LED.

const int POT_PIN = 34;

Then, in the loop, instead of using for loops to gradually increase and decrease the LED�s brightness, a function analogRead(POT_PIN) is called, which takes a raw reading from the potentiometer.

int dutyCycle = analogRead(POT_PIN);

The reading from the potentiometer, which ranges from 0 to 4095, is then mapped to a new range that spans from 0 to MAX_DUTY_CYCLE using the map() function. This mapping aligns the potentiometer values to the PWM signal�s allowed duty cycle values. This makes sure that the LED brightness can be changed across its whole range.

dutyCycle = map(dutyCycle, 0, 4095, 0, MAX_DUTY_CYCLE);

Finally, the ledcWrite() function takes this mapped value and applies it directly to the PWM signal, adjusting the LED brightness in real-time based on the potentiometer position.

ledcWrite(PWM_CHANNEL, dutyCycle);

Often in a project you want the ESP32 to perform its normal program while continuously monitoring for some kind of event. One widely adopted solution for this is the use of interrupts.

Interrupts In ESP32

ESP32 provides up to 32 interrupt slots for each core. Each interrupt has a certain priority level and can be classified into two types.

Hardware interrupts � These occur in response to an external event. For example, a GPIO interrupt (when a key is pressed) or a touch interrupt (when touch is detected).

Software Interrupt � These occur in response to a software instruction. For example, a simple timer interrupt or a watchdog timer interrupt (when the timer times out).

ESP32 GPIO Interrupt

In ESP32 we can define an interrupt service routine function that will be called when the GPIO pin changes its logic level.

All GPIO pins in an ESP32 board can be configured to act as interrupt request inputs.

esp32 interrupt pins

Attaching an Interrupt to a GPIO Pin

In the Arduino IDE, we use a function called attachInterrupt() to set an interrupt on a pin by pin basis. The syntax looks like below.

attachInterrupt(GPIOPin, ISR, Mode);

This function accepts three arguments:

GPIOPin � sets the GPIO pin as the interrupt pin, which tells ESP32 which pin to monitor.

ISR � is the name of the function that will be called each time the interrupt occurs.

Mode � defines when the interrupt should be triggered. Five constants are predefined as valid values:

LOWTriggers the interrupt whenever the pin is LOW
HIGHTriggers the interrupt whenever the pin is HIGH
CHANGETriggers the interrupt whenever the pin changes value, from HIGH to LOW or LOW to HIGH
FALLINGTriggers the interrupt when the pin goes from HIGH to LOW
RISINGTriggers the interrupt when the pin goes from LOW to HIGH

Detaching an Interrupt from a GPIO Pin

When you want ESP32 to no longer monitor the pin, you can call the detachInterrupt() function. The syntax looks like below.

detachInterrupt(GPIOPin);

Interrupt Service Routine

The Interrupt Service Routine (ISR) is a function that is invoked every time an interrupt occurs on the GPIO pin.

Its syntax looks like below.

void IRAM_ATTR ISR() {
    Statements;
}

ISRs in ESP32 are special kinds of functions that have some unique rules that most other functions do not have.

  1. An ISR cannot have any parameters, and they should not return anything.
  2. ISRs should be as short and fast as possible as they block normal program execution.
  3. They should have the IRAM_ATTR attribute, according to the ESP32 documentation.

What is IRAM_ATTR?

When we flag a piece of code with the IRAM_ATTR attribute, the compiled code is placed in the ESP32�s Internal RAM (IRAM). Otherwise the code is kept in Flash. And Flash on ESP32 is much slower than internal RAM.

If the code we want to run is an Interrupt Service Routine (ISR), we generally want to execute it as soon as possible. If we had to �wait� for the ISR to load from Flash then things could go horribly wrong.

Hardware Hookup

Enough of the theory! Let�s look at a practical example.

Let�s connect a push button to GPIO#18 (D18) on the ESP32. You do not need any pullup for this pin as we will be pulling the pin up internally.

Wiring Push Buttons to ESP32 For GPIO Interrupt
Wiring Push Buttons to ESP32 For GPIO Interrupt

Example Code: Simple Interrupt

The following sketch demonstrates the use of interrupts and the correct way to write an interrupt service routine.

This program watches GPIO#18 (D18) for the FALLING edge. In other words, it looks for a voltage change going from logic HIGH to logic LOW that occurs when the button is pressed. When this happens the function isr is called. The code within this function counts the number of times the button has been pressed.

struct Button {
	const uint8_t PIN;
	uint32_t numberKeyPresses;
	bool pressed;
};

Button button1 = {18, 0, false};

void IRAM_ATTR isr() {
	button1.numberKeyPresses++;
	button1.pressed = true;
}

void setup() {
	Serial.begin(115200);
	pinMode(button1.PIN, INPUT_PULLUP);
	attachInterrupt(button1.PIN, isr, FALLING);
}

void loop() {
	if (button1.pressed) {
		Serial.printf("Button has been pressed %u times\n", button1.numberKeyPresses);
		button1.pressed = false;
	}
}

Once you have uploaded the sketch, press the EN button on the ESP32 and open the serial monitor at baud rate 115200. On pressing the button you will get the following output.

esp32 gpio interrupt output on serial monitor

Code Explanation

At the beginning of the sketch we create a structure called Button. This structure has three members � the pin number, the number of key presses, and the pressed state. FYI, a structure is a collection of variables of different types (but logically related to each other) under a single name.

struct Button {
  const uint8_t PIN;
  uint32_t numberKeyPresses;
  bool pressed;
};

We then create an instance of the Button structure and initialize the pin number to 18, the number of key presses to 0 and the default pressed state to false.

Button button1 = {18, 0, false};

The following code is an interrupt service routine. As mentioned earlier, the ISR in ESP32 must have the IRAM_ATTR attribute.

In the ISR we simply increment the KeyPresses counter by 1 and set the button pressed state to True.

void IRAM_ATTR isr() {
  button1.numberKeyPresses += 1;
  button1.pressed = true;
}

In the setup section of the code, we first initialize the serial communication with the PC and then we enable the internal pullup for the D18 GPIO pin.

Next we tell ESP32 to monitor the D18 pin and to call the interrupt service routine isr when the pin goes from HIGH to LOW i.e. FALLING edge.

Serial.begin(115200);
pinMode(button1.PIN, INPUT_PULLUP);
attachInterrupt(button1.PIN, isr, FALLING);

In the loop section of the code, we simply check if the button has been pressed and then print the number of times the key has been pressed so far and set the button pressed state to false so that we can continue to receive interrupts.

if (button1.pressed) {
      Serial.printf("Button 1 has been pressed %u times\n", button1.numberKeyPresses);
      button1.pressed = false;
}

Managing Switch Bounce

A common problem with interrupts is that they often get triggered multiple times for the same event. If you look at the serial output of the above example, you will notice that even if you press the button only once, the counter is incremented several times.

esp32 gpio interrupt bounce problem

To find out why this happens, you have to take a look at the signal. If you monitor the voltage of the pin on the signal analyzer while you press the button, you will get a signal like this:

switch bounce signal

You may feel like contact is made immediately, but in fact the mechanical parts within the button come into contact several times before they settle into a particular state. This causes multiple interrupts to be triggered.

It is purely a mechanical phenomenon known as a �switch bounce�, like dropping a ball � it bounces several times before finally landing on the ground.

The time for the signal to stabilize is very fast and seems almost instantaneous to us, but for an ESP32 this is a huge period of time. It can execute multiple instructions in that time period.

The process of eliminating switch bounce is called �debouncing�. There are two ways to achieve this.

  • Through hardware: by adding an appropriate RC filter to smooth the transition.
  • Through software: by temporarily ignoring further interrupts for a short period of time after the first interrupt is triggered.

Example Code: Debouncing an Interrupt

Here the above sketch is rewritten to demonstrate how to debounce an interrupt programmatically. In this sketch we allow the ISR to be executed only once on each button press, instead of executing it multiple times.

Changes to the sketch are highlighted in green.

struct Button {
    const uint8_t PIN;
    uint32_t numberKeyPresses;
    bool pressed;
};

Button button1 = {18, 0, false};

//variables to keep track of the timing of recent interrupts
unsigned long button_time = 0;  
unsigned long last_button_time = 0; 

void IRAM_ATTR isr() {
    button_time = millis();
if (button_time - last_button_time > 250)
{
        button1.numberKeyPresses++;
        button1.pressed = true;
       last_button_time = button_time;
}
}

void setup() {
    Serial.begin(115200);
    pinMode(button1.PIN, INPUT_PULLUP);
    attachInterrupt(button1.PIN, isr, FALLING);
}

void loop() {
    if (button1.pressed) {
        Serial.printf("Button has been pressed %u times\n", button1.numberKeyPresses);
        button1.pressed = false;
    }
}

Let�s look at the serial output again as you press the button. Note that the ISR is called only once for each button press.

esp32 gpio interrupt debounce

Code Explanation:

This fix works because each time the ISR is executed, it compares the current time returned by the millis() function to the time the ISR was last called.

If it is within 250ms, ESP32 ignores the interrupt and immediately goes back to what it was doing. If not, it executes the code within the if statement incrementing the counter and updating the last_button_time variable, so the function has a new value to compare against when it�s triggered in the future.

The ESP32 is undeniably a worthy competitor to many WiFi/MCU SoCs, outperforming them in both performance and price. However, depending on which mode it is in, the ESP32 can be a relatively power-hungry device.

When your IoT project is powered by an electrical outlet, power consumption is of little concern; however, if you plan to power your project with a battery, every mA counts.

The solution here is to take advantage of one of the ESP32�s sleep modes to reduce power consumption. This is an excellent strategy for significantly increasing the battery life of a project that does not need to be active all of the time.

What exactly is the ESP32 Sleep Mode?

The ESP32 sleep mode is a power-saving mode. When not in use, the ESP32 can enter this mode, storing all data in RAM. At this point, all unnecessary peripherals are disabled while the RAM receives enough power to retain its data.

Inside the ESP32 chip

Knowing what is inside the chip will help us better understand how the ESP32 manages power savings. The block diagram of the ESP32 chip is shown below.

Block Diagrams

The ESP32 chip contains a dual-core 32-bit microprocessor along with 448 KB of ROM, 520 KB of SRAM, and 4 MB of flash memory.

In addition, the chip contains a WiFi module, a Bluetooth module, a cryptographic accelerator (a co-processor designed specifically to perform cryptographic operations), an RTC module, and a number of peripherals.

ESP32 Power Modes

Thanks to the ESP32�s advanced power management, it offers five configurable power modes. According to the power requirement, the chip can switch between different power modes. These modes are:

  • Active Mode
  • Modem Sleep Mode
  • Light Sleep Mode
  • Deep Sleep Mode
  • Hibernation Mode

Each mode has distinct features and power-saving capabilities. Let�s take a look at them one by one.

ESP32 Active Mode

Normal mode is also referred to as Active Mode. In this mode, all peripherals of the chip remain active.

Since everything is always active in this mode (especially the WiFi module, processing core, and Bluetooth module), the chip consumes about 240 mA of power. It has also been observed that the chip draws more than 790 mA at times, particularly when both WiFi and Bluetooth are used simultaneously.

ESP32 Active Mode Functional Block Diagram

According to the ESP32 datasheet, the power consumption during RF operations in active mode is as follows:

ModePower Consumption
Wi-Fi Tx packet 13dBm~21dBm160~260mA
Wi-Fi/BT Tx packet 0dBm120mA
Wi-Fi/BT Rx and listening80~90mA

This mode, without a doubt, consumes the most current and is the least efficient. In order to save power, you must disable features that are not in use by switching to another power mode.

ESP32 Modem Sleep

In modem sleep mode, everything is active except for the WiFi, Bluetooth, and the radio. The CPU remains active, and the clock is configurable.

In this mode, the chip consumes approximately 3 mA at slow speed and 20 mA at high speed.

ESP32 Modem Sleep Functional Block Diagram

To keep the connection alive, Wi-Fi, Bluetooth, and the radio are woken up at predefined intervals. This is referred to as the Association Sleep Pattern.

During this sleep pattern, ESP32 switches between active mode and modem sleep mode.

To accomplish this, the ESP32 connects to the router in station mode using the DTIM beacon mechanism. The Wi-Fi module is disabled between two DTIM beacon intervals and then automatically enabled just before the next beacon arrives. This results in power conservation.

The sleeping time is determined by the router�s DTIM beacon interval time, which is typically 100 ms to 1000 ms.

What is the DTIM Beacon Mechanism?

DTIM stands for Delivery Traffic Indication Message.

DTIM-Beacon

In this mechanism, the access point (AP)/router broadcasts beacon frames periodically. Each frame contains network-related information. It is used to announce the presence of a wireless network as well as to synchronize all connected members.

ESP32 Light Sleep

Light sleep is similar to modem sleep in that the chip follows the Association Sleep Pattern. The only difference is that in light sleep mode, the CPU, most of the RAM, and digital peripherals are clock-gated.

What is Clock Gating?

Clock gating is a popular power management technique for reducing dynamic power dissipation by removing or ignoring the clock signal when the circuit is not in use.

Clock gating reduces power consumption by pruning the clock tree. Pruning the clock disables portions of the circuitry, preventing the flip-flops in them from switching states. Since switching states consumes power, when not switched, the power consumption drops to zero.

During light sleep mode, the CPU is paused by disabling its clock pulse. The RTC and ULP-coprocessor, on the other hand, remain active. This results in a lower power consumption than the modem sleep mode, which is around 0.8 mA.

ESP32 Light Sleep Functional Block Diagram

Before entering light sleep mode, the ESP32 stores its internal state in RAM and resumes operation upon waking from sleep. This is referred to as Full RAM Retention.

ESP32 Deep Sleep

In deep sleep mode, the CPUs, most of the RAM, and all digital peripherals are disabled. Only the following parts of the chip remain operational:

  • ULP Coprocessor
  • RTC Controller
  • RTC Peripherals
  • RTC fast and slow memory

In deep sleep mode, the chip consumes anywhere between 0.15 mA (when the ULP coprocessor is on) and 10 �A.

ESP32 Deep Sleep Functional Block Diagram

During deep sleep mode, the primary CPU is turned off, whereas the Ultra-Low-Power (ULP) Coprocessor can take sensor readings and wake up the CPU as needed. This sleep pattern is referred to as the ULP sensor-monitored pattern. This is useful for designing applications where the CPU needs to be woken up by an external event, a timer, or a combination of these events, while maintaining minimal power consumption.

Along with the CPU, the main memory of the chip is also disabled. As a result, everything stored in that memory is erased and cannot be accessed.

Because RTC memory is kept active, its contents are preserved even during deep sleep and can be retrieved once the chip is woken up. This is why the chip stores Wi-Fi and Bluetooth connection data in RTC memory before entering deep sleep.

If you want to use the data after a reboot, store it in RTC memory by defining a global variable with the RTC_DATA_ATTR attribute. For example, RTC_DATA_ATTR int myVar = 0;

When the chip wakes up from deep sleep, it performs a reset and begins program execution from the beginning.

When waking up from deep sleep, the ESP32 can run a deep sleep wake stub. It�s a piece of code that executes as soon as the chip wakes up, before any normal initialization, bootloader, or ESP-IDF code is executed. After executing the wake stub, the chip can either return to sleep or continue to start ESP-IDF normally.

If you�re interested in learning more about ESP32 Deep Sleep and its wake-up sources, please visit our in-depth tutorial below.

ESP32 Hibernation mode

Hibernate mode is very similar to deep sleep. The only difference is that in hibernation mode, the chip disables the internal 8 MHz oscillator as well as the ULP-coprocessor, leaving only one RTC timer (on slow clock) and a few RTC GPIOs to wake the chip up.

Because the RTC recovery memory is also turned off, we cannot save any data while in hibernation mode.

ESP32 Hibernation Mode Functional Block Diagram

As a result, the chip�s power consumption is reduced even further; in hibernation mode, it consumes only about 2.5 ?A.

This mode is especially useful if you�re working on a project that doesn�t need to be active all the time.

When your IoT project is powered by a wall adapter, you don�t care too much about power consumption. But if you�re going to power your project from a battery, every mA counts.

ESP32 can be a relatively power hungry device depending on what state it is in. It typically draws around 75mA for normal operations and around 240mA when transmitting data over WiFi.

The solution here is to reduce the ESP32�s power usage by taking advantage of Deep Sleep Mode.

To learn more about ESP32�s other sleep modes and their power consumption, please visit the tutorial below.

ESP32 Deep Sleep

In deep sleep mode, the CPUs, most of the RAM and all digital peripherals are powered off. The only parts of the chip that remain operational are:

  • ULP Coprocessor
  • RTC Controller
  • RTC Peripherals
  • RTC fast and slow memory

The chip consumes around 0.15 mA (if the ULP coprocessor is on) to 10�A.

ESP32 Deep Sleep Functional Block Diagram & Current Consumption

During deep sleep mode the main CPU is shut down, while the Ultra�Low�Power (ULP) Coprocessor can take sensor readings and wake up the CPU whenever necessary. This sleep pattern is known as the ULP sensor-monitored pattern. This is useful for designing applications where the CPU needs to be woken by an external event, or timer, or a combination of both, while maintaining minimal power consumption.

Along with the CPU, the main memory of the chip is also disabled. As a result everything stored in that memory is erased and cannot be accessed.

Because RTC memory is kept on, its contents are preserved even during deep sleep and can be retrieved after the chip is woken up. This is why the chip stores Wi-Fi and Bluetooth connection data in RTC memory before entering deep sleep.

If you want to use the data after reboot, store it in RTC memory by defining a global variable with RTC_DATA_ATTR attribute. For example, RTC_DATA_ATTR int myVar = 0;

After coming out of deep sleep the chip restarts with a reset and starts program execution from the beginning.

ESP32 supports running a deep sleep wake stub when coming out of deep sleep. This function runs immediately as soon as the chip wakes up � before any normal initialization, bootloader, or ESP-IDF code has run. After the wake stub runs, the chip can go back to sleep or continue to start ESP-IDF normally.

Unlike other sleep modes, the system cannot automatically go into deep-sleep mode. The esp_deep_sleep_start() function is used to enter deep sleep immediately after configuring wake-up sources.

ESP32 Deep Sleep Wake-up sources

The ESP32 can be woken from deep sleep mode using multiple sources. These sources are:

  • Timer
  • Touch pad
  • External wakeup(ext0 & ext1)

Multiple wake-up sources can be combined, in which case the chip will wake up when one of the sources is triggered.

Warning:

It is possible to put the ESP32 into deep sleep with no wake-up sources configured, in which case the chip remains in deep sleep mode indefinitely until an external reset is applied.

ESP32 Wake-up Source : Timer

The ESP32 RTC controller has a built-in timer that you can use to wake up the ESP32 after a predefined amount of time.

This feature is especially useful in a project that requires time stamping or daily tasks while maintaining low power consumption.

The esp_sleep_enable_timer_wakeup(time_in_us) function is used to configure the timer as a wakeup source. This function accepts an amount of time in microseconds (?s).

Example Code

Let�s see how it works, using an example from the library. Open your Arduino IDE, and navigate to File > Examples > ESP32 > Deep Sleep, and open the TimerWakeUp sketch.

This sketch demonstrates the most basic deep sleep example with a timer as the wake-up source and how to store the data in RTC memory to use it after a reboot.

#define uS_TO_S_FACTOR 1000000ULL  /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP  5        /* Time ESP32 will go to sleep (in seconds) */

RTC_DATA_ATTR int bootCount = 0;

/*
Method to print the reason by which ESP32
has been awaken from sleep
*/
void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;

  wakeup_reason = esp_sleep_get_wakeup_cause();

  switch(wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
  }
}

void setup(){
  Serial.begin(115200);
  delay(1000); //Take some time to open up the Serial Monitor

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32
  print_wakeup_reason();

  /*
  First we configure the wake up source
  We set our ESP32 to wake up every 5 seconds
  */
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
  Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) +
  " Seconds");

  /*
  Next we decide what all peripherals to shut down/keep on
  By default, ESP32 will automatically power down the peripherals
  not needed by the wakeup source, but if you want to be a poweruser
  this is for you. Read in detail at the API docs
  http://esp-idf.readthedocs.io/en/latest/api-reference/system/deep_sleep.html
  Left the line commented as an example of how to configure peripherals.
  The line below turns off all RTC peripherals in deep sleep.
  */
  //esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
  //Serial.println("Configured all RTC Peripherals to be powered down in sleep");

  /*
  Now that we have setup a wake cause and if needed setup the
  peripherals state in deep sleep, we can now start going to
  deep sleep.
  In the case that no wake up sources were provided but deep
  sleep was started, it will sleep forever unless hardware
  reset occurs.
  */
  Serial.println("Going to sleep now");
  Serial.flush(); 
  esp_deep_sleep_start();
  Serial.println("This will never be printed");
}

void loop(){
  //This is not going to be called
}

Once the sketch is uploaded, open your serial monitor, set the baud rate to 115200 bps.

The ESP32 wakes up every 5 seconds, prints the wake up reason and bootCount on the serial monitor and goes into deep sleep again.

esp32 deep sleep timer wakeup output

Now try resetting the ESP32 by pressing the EN button, it should reset the bootCount to 1 again indicating that the RTC memory is completely wiped.

Code Explanation:

These first two lines of code define the time for which ESP32 will be asleep.

This example uses the conversion factor from microseconds to seconds, so you can set the sleep time in seconds in the TIME_TO_SLEEP variable. Here the ESP32 is put into deep sleep mode for 5 seconds.

#define uS_TO_S_FACTOR 1000000ULL  /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP  5        /* Time ESP32 will go to sleep (in seconds) */

As specified earlier you can save data in the ESP32�s RTC memory (8kB SRAM) which is not erased during deep sleep. However, it is erased when the ESP32 is reset.

To save data on RTC memory, you just need to add RTC_DATA_ATTR attribute before defining a variable. In this example the bootCount variable is saved in the RTC memory. It will count how many times ESP32 has woken up from deep sleep.

RTC_DATA_ATTR int bootCount = 0;

Next, the print_wakeup_reason() function is defined that prints the reason by which ESP32 has been woken up from deep sleep.

void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;

  wakeup_reason = esp_sleep_get_wakeup_cause();

  switch(wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
  }
}

In the setup, we first initialize the serial communication with the PC.

Serial.begin(115200);

The bootCount variable is then incremented by one and printed to the serial monitor to show the number of times the ESP32 has woken up from deep sleep.

++bootCount;
Serial.println("Boot number: " + String(bootCount));

Then the print_wakeup_reason() function is called, but you can call any function you want to perform the desired task, for example, reading the value of a sensor.

print_wakeup_reason();

Next we configure the timer wake up source using the esp_sleep_enable_timer_wakeup(time_in_us) function. Here the ESP32 is set to wake up every 5 seconds.

esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);

Finally the ESP32 is put to sleep by calling the esp_deep_sleep_start() function.

esp_deep_sleep_start();

In this sketch the ESP32 enters deep sleep in the setup() function itself, so it never reaches the loop() function. Hence the loop() function is left blank.

void loop(){
  //This is not going to be called
}

ESP32 Wake-up Source : Touch Pad

You can wake ESP32 from deep sleep using the following touch pins.

esp32 touch pins

Enabling the ESP32 to wake up using the touch pin is simple. In Arduino IDE you just need to use esp_sleep_enable_touchpad_wakeup() function.

Wiring

Let�s wire a cable to GPIO#15 (Touch#3) which will act as the touch wake-up source. You can attach any conductive object like wire, aluminum foil, conductive cloth, conductive paint etc. to the touch sensitive pin and turn it into a touchpad.

connecting wire to esp32 for touch wakeup source

Example Code

Let�s see how it works, using an example from the library. Open your Arduino IDE, and navigate to File > Examples > ESP32 > Deep Sleep, and open the TouchWakeUp sketch.

This sketch demonstrates the most basic deep sleep example with touch as the wake-up source and how to store the data in RTC memory to use it after a reboot.

#define Threshold 40 /* Greater the value, more the sensitivity */

RTC_DATA_ATTR int bootCount = 0;
touch_pad_t touchPin;
/*
Method to print the reason by which ESP32
has been awaken from sleep
*/
void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;

  wakeup_reason = esp_sleep_get_wakeup_cause();

  switch(wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
  }
}

/*
Method to print the touchpad by which ESP32
has been awaken from sleep
*/
void print_wakeup_touchpad(){
  touchPin = esp_sleep_get_touchpad_wakeup_status();

  switch(touchPin)
  {
    case 0  : Serial.println("Touch detected on GPIO 4"); break;
    case 1  : Serial.println("Touch detected on GPIO 0"); break;
    case 2  : Serial.println("Touch detected on GPIO 2"); break;
    case 3  : Serial.println("Touch detected on GPIO 15"); break;
    case 4  : Serial.println("Touch detected on GPIO 13"); break;
    case 5  : Serial.println("Touch detected on GPIO 12"); break;
    case 6  : Serial.println("Touch detected on GPIO 14"); break;
    case 7  : Serial.println("Touch detected on GPIO 27"); break;
    case 8  : Serial.println("Touch detected on GPIO 33"); break;
    case 9  : Serial.println("Touch detected on GPIO 32"); break;
    default : Serial.println("Wakeup not by touchpad"); break;
  }
}

void callback(){
  //placeholder callback function
}

void setup(){
  Serial.begin(115200);
  delay(1000); //Take some time to open up the Serial Monitor

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32 and touchpad too
  print_wakeup_reason();
  print_wakeup_touchpad();

  //Setup interrupt on Touch Pad 3 (GPIO15)
  touchAttachInterrupt(T3, callback, Threshold);

  //Configure Touchpad as wakeup source
  esp_sleep_enable_touchpad_wakeup();

  //Go to sleep now
  Serial.println("Going to sleep now");
  esp_deep_sleep_start();
  Serial.println("This will never be printed");
}

void loop(){
  //This will never be reached
}

Once the sketch is uploaded, open your serial monitor, set the baud rate to 115200 bps.

Now when the pin is touched, the ESP32 will display the boot count, wake up reason, and which GPIO was touched on the serial monitor.

esp32 deep sleep touch wakeup output

Code Explanation:

The first line of code sets the threshold value for the touch pin to 40. The higher the threshold value, the higher the sensitivity. You can change this value according to your project.

#define Threshold 40 /* Greater the value, more the sensitivity */

As specified earlier you can save data in the ESP32�s RTC memory (8kB SRAM) which is not erased during deep sleep. However, it is erased when the ESP32 is reset.

To save data on RTC memory, you just need to add RTC_DATA_ATTR attribute before defining a variable. In this example the bootCount variable is saved in the RTC memory. It will count how many times ESP32 has woken up from deep sleep.

RTC_DATA_ATTR int bootCount = 0;

After this a variable named touchPin of type touch_pad_t (type enum) is defined which will later help us to print the GPIO by which ESP32 is woken up from sleep.

touch_pad_t touchPin;

Next, the print_wakeup_reason() function is defined that prints the reason by which ESP32 has been woken up from deep sleep.

void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;

  wakeup_reason = esp_sleep_get_wakeup_cause();

  switch(wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
  }
}

The print_wakeup_touchpad() function is also defined that prints the GPIO number by which ESP32 has been woken up from deep sleep.

void print_wakeup_touchpad(){
  touchPin = esp_sleep_get_touchpad_wakeup_status();

  switch(touchPin)
  {
    case 0  : Serial.println("Touch detected on GPIO 4"); break;
    case 1  : Serial.println("Touch detected on GPIO 0"); break;
    case 2  : Serial.println("Touch detected on GPIO 2"); break;
    case 3  : Serial.println("Touch detected on GPIO 15"); break;
    case 4  : Serial.println("Touch detected on GPIO 13"); break;
    case 5  : Serial.println("Touch detected on GPIO 12"); break;
    case 6  : Serial.println("Touch detected on GPIO 14"); break;
    case 7  : Serial.println("Touch detected on GPIO 27"); break;
    case 8  : Serial.println("Touch detected on GPIO 33"); break;
    case 9  : Serial.println("Touch detected on GPIO 32"); break;
    default : Serial.println("Wakeup not by touchpad"); break;
  }
}

Next a callback() function is defined. This is nothing but an Interrupt Service Routine (ISR), which will be called every time a touch interrupt is triggered. But unfortunately this function is not executed if ESP32 is in deep sleep. Therefore this function is left blank.

void callback(){
  //placeholder callback function
}

In the setup, we first initialize the serial communication with the PC.

Serial.begin(115200);

The bootCount variable is then incremented by one and printed to the serial monitor to show the number of times the ESP32 has woken up from deep sleep.

++bootCount;
Serial.println("Boot number: " + String(bootCount));

Then the print_wakeup_reason() and print_wakeup_touchpad() functions are called, but you can call any function you want to perform the desired task, for example, reading the value of a sensor.

print_wakeup_reason();
print_wakeup_touchpad();

Now the interrupt has to be attached to one of the touch pins with the desired sensitivity threshold. Here the interrupt is attached to touch pad 3 (GPIO15).

touchAttachInterrupt(T3, callback, Threshold);

Next we configure the touch wake up source using the esp_sleep_enable_touchpad_wakeup() function.

esp_sleep_enable_touchpad_wakeup();

Finally the ESP32 is put to sleep by calling the esp_deep_sleep_start() function.

esp_deep_sleep_start();

In this sketch the ESP32 enters deep sleep in the setup() function itself, so it never reaches the loop() function. Hence the loop() function is left blank.

void loop(){
  //This is not going to be called
}

ESP32 Wake-up Source : External Wake-up

There are two kinds of external triggers to wake ESP32 from deep sleep.

  • ext0 � Use this when you want to wake up the chip only by a specific GPIO pin.
  • ext1 � Use this when you want to wake up the chip using multiple GPIO pins.

If you want to use an interrupt pin to wake ESP32 from deep sleep then you have to use so called RTC_GPIO pins. These GPIOs are routed to the RTC low-power subsystem so they can be used when the ESP32 is in deep sleep.

The RTC_GPIO pins are:

esp32 rtc gpio pins

ext0 External Wake-up Source

The ESP32 can be configured to wake from deep sleep when one of the RTC_GPIO pins changes its logic level.

The esp_sleep_enable_ext0_wakeup(GPIO_PIN, LOGIC_LEVEL) function is used to enable this wake-up source. This function takes two parameters. The first is the GPIO pin number and the second is the logic level (LOW or HIGH) by which we want to trigger the wake-up.

Since ext0 uses RTC IO to wake up ESP32, the RTC peripherals are kept running during deep sleep.

And since the RTC IO module is enabled you can take advantage of the internal pullup or pulldown resistors. They need to be configured using the rtc_gpio_pullup_en() and rtc_gpio_pulldown_en() functions before esp_deep_sleep_start() is called.

Wiring

Let�s wire a push button to GPIO#33 using a 10K pull down resistor.

Connecting Button to ESP32 for ext0 External Wakeup Source

Example Code

Let�s see how it works, using an example from the library. Open your Arduino IDE, and navigate to File > Examples > ESP32 > Deep Sleep, and open the ExternalWakeUp sketch.

This sketch demonstrates the most basic deep sleep example with an ext0 as the wake-up source.

#define BUTTON_PIN_BITMASK 0x200000000 // 2^33 in hex

RTC_DATA_ATTR int bootCount = 0;

/*
Method to print the reason by which ESP32
has been awaken from sleep
*/
void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;

  wakeup_reason = esp_sleep_get_wakeup_cause();

  switch(wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
  }
}

void setup(){
  Serial.begin(115200);
  delay(1000); //Take some time to open up the Serial Monitor

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32
  print_wakeup_reason();

  /*
  First we configure the wake up source
  We set our ESP32 to wake up for an external trigger.
  There are two types for ESP32, ext0 and ext1 .
  ext0 uses RTC_IO to wakeup thus requires RTC peripherals
  to be on while ext1 uses RTC Controller so doesnt need
  peripherals to be powered on.
  Note that using internal pullups/pulldowns also requires
  RTC peripherals to be turned on.
  */
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_33,1); //1 = High, 0 = Low

  //If you were to use ext1, you would use it like
  //esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ANY_HIGH);

  //Go to sleep now
  Serial.println("Going to sleep now");
  esp_deep_sleep_start();
  Serial.println("This will never be printed");
}

void loop(){
  //This is not going to be called
}

Once the sketch is uploaded, open your serial monitor, set the baud rate to 115200 bps.

Now when you press the pushbutton, the ESP32 will display the boot count and wake up reason on the serial monitor. Try it several times and watch the boot count increase with each button press. Also notice that ext0 uses RTC IO to wake up the ESP32.

esp32 deep sleep ext0 wakeup output

Code Explanation:

The first line of code sets the bit mask. It�s not needed for ext0 external wake-up so you can ignore it for now. We will learn about this during the ext1 external wake-up code explanation.

#define BUTTON_PIN_BITMASK 0x200000000 // 2^33 in hex

As specified earlier you can save data in the ESP32�s RTC memory (8kB SRAM) which is not erased during deep sleep. However, it is erased when the ESP32 is reset.

To save data on RTC memory, you just need to add RTC_DATA_ATTR attribute before defining a variable. In this example the bootCount variable is saved in the RTC memory. It will count how many times ESP32 has woken up from deep sleep.

RTC_DATA_ATTR int bootCount = 0;

Next, the print_wakeup_reason() function is defined that prints the reason by which ESP32 has been woken up from deep sleep.

void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;

  wakeup_reason = esp_sleep_get_wakeup_cause();

  switch(wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
  }
}

In the setup, we first initialize the serial communication with the PC.

Serial.begin(115200);

The bootCount variable is then incremented by one and printed to the serial monitor to show the number of times the ESP32 has woken up from deep sleep.

++bootCount;
Serial.println("Boot number: " + String(bootCount));

Then the print_wakeup_reason() function is called, but you can call any function you want to perform the desired task, for example, reading the value of a sensor.

print_wakeup_reason();

Now the ext0 external wake-up source is configured by using the esp_sleep_enable_ext0_wakeup(GPIO_PIN, LOGIC_LEVEL) function. This function takes two parameters. The first is the GPIO pin number and the second is the logic level (LOW or HIGH) by which we want to trigger the wake-up. In this example the ESP32 is configured to wake up when the logic level of GPIO#33 becomes HIGH.

esp_sleep_enable_ext0_wakeup(GPIO_NUM_33,1);

Finally the ESP32 is put to sleep by calling the esp_deep_sleep_start() function.

esp_deep_sleep_start();

In this sketch the ESP32 enters deep sleep in the setup() function itself, so it never reaches the loop() function. Hence the loop() function is left blank.

void loop(){
  //This is not going to be called
}

ext1 External Wake-up Source

The ESP32 can be configured to wake from deep sleep using multiple pins. Remember those pins must be among the RTC GPIO pins.

Since the ext1 wake-up source uses an RTC controller, it does not require RTC peripherals and RTC memory to be powered on. In this case the internal pullup and pulldown resistors will not be available.

In order to use the internal pullup or pulldown resistors, we need to request the RTC peripherals to be kept on during sleep, and configure the pullup/pulldown resistors using the rtc_gpio_pullup_en() and rtc_gpio_pulldown_en() functions before entering sleep.

The esp_sleep_enable_ext1_wakeup(BUTTON_PIN_MASK, LOGIC_LEVEL) function is used to enable this wake-up source. This function takes two parameters. The first is a bit mask that tells the ESP32 which pins we want to use and the second parameter can be one of the two logic levels mentioned below to trigger the wake-up:

  • Wake up if one of the selected pins is HIGH (ESP_EXT1_WAKEUP_ANY_HIGH)
  • Wake up if all selected pins are LOW (ESP_EXT1_WAKEUP_ALL_LOW)

Bitmask

The easiest way to understand the bit mask is to write it in binary format. You can see that the bit numbering is based on the normal GPIO numbering. The least significant bit (LSB) represents GPIO#0 and the most significant bit (MSB) represents GPIO#39.

ext1 pin bit mask representation in binary
  • 0 represents masked pins
  • 1 represents pins that will be enabled as a wake-up source

So if you want to enable a GPIO to wake up you must write a 1 to its corresponding location and a 0 to each of the remaining pins. And finally you have to convert it to HEX.

For example, if you want to use GPIO#32 and GPIO#33 as external wake-up sources, the bitmask would be like this:

ext1 External Wakeup Source Pin Bit Mask Representation in Binary

Wiring

Let�s wire the two push buttons to GPIO#33 and GPIO#32 using 10K pull down resistors.

Connecting Multiple Buttons to ESP32 for ext1 External Wakeup Source

Example Code

Let�s see how it works using the same ExternalWakeUp example from the library. Once again open your Arduino IDE, and navigate to File > Examples > ESP32 > Deep Sleep, and open the ExternalWakeup sketch.

Let�s make three changes to the sketch to make it work for us:

  1. Change the BUTTON_PIN_BITMASK constant
  2. Comment the code for ext0
  3. Uncomment the code for ext1

Changes to the sketch are highlighted ingreen.

#define BUTTON_PIN_BITMASK 0x300000000

RTC_DATA_ATTR int bootCount = 0;

/*
Method to print the reason by which ESP32
has been awaken from sleep
*/
void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;

  wakeup_reason = esp_sleep_get_wakeup_cause();

  switch(wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
  }
}

void setup(){
  Serial.begin(115200);
  delay(1000); //Take some time to open up the Serial Monitor

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32
  print_wakeup_reason();

  /*
  First we configure the wake up source
  We set our ESP32 to wake up for an external trigger.
  There are two types for ESP32, ext0 and ext1 .
  ext0 uses RTC_IO to wakeup thus requires RTC peripherals
  to be on while ext1 uses RTC Controller so doesnt need
  peripherals to be powered on.
  Note that using internal pullups/pulldowns also requires
  RTC peripherals to be turned on.
  */
  //esp_sleep_enable_ext0_wakeup(GPIO_NUM_33,1); //1 = High, 0 = Low

  //If you were to use ext1, you would use it like
  esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ANY_HIGH);

  //Go to sleep now
  Serial.println("Going to sleep now");
  esp_deep_sleep_start();
  Serial.println("This will never be printed");
}

void loop(){
  //This is not going to be called
}

Once the sketch is uploaded, open your serial monitor, set the baud rate to 115200 bps.

Now when you press the pushbutton, you will get something similar on the serial monitor. Also notice that ext1 uses the RTC controller to wake up the ESP32.

esp32 deep sleep ext1 wakeup output

Code Explanation:

This code is identical to ext0�s code except for two changes.

At the beginning of the code, the bit mask is defined. Since we�re using pins GPIO#32 and GPIO#33 in the example, the mask has 1�s at their respective positions, with 32 0�s on the right and 6 0�s on the left.

00000011 00000000 00000000 00000000 00000000 BIN = 0x300000000 HEX

#define BUTTON_PIN_BITMASK 0x300000000

And finally, ext1 is enabled as wake up source.

esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ANY_HIGH);

The ESP32, the newly released successor to the ESP8266, has been a rising star in IoT or WiFi-related projects. It�s a low-cost WiFi module that can be programmed to run a standalone web server with a little extra work. How cool is that?

This tutorial will walk you through the process of creating a simple ESP32 web server in the Arduino IDE.�So, let�s get started.

What exactly is a Web server and how does it work?

A web server is a place where web pages are stored, processed, and served to web clients. A web client is just a web browser that we use on our computers and phones. A web client and a web server communicate using a special protocol known as Hypertext Transfer Protocol (HTTP).

HTTP Web Server Client Illustration

In this protocol, a client starts a conversation by sending an HTTP request for a specific web page. The server then sends back the content of that web page or an error message if it can�t find it (like the famous 404 Error).

ESP32 Operating Modes

One of the most useful features of the ESP32 is its ability to not only connect to an existing WiFi network and act as a Web Server, but also to create its own network, allowing other devices to connect directly to it and access web pages. This is possible because the ESP32 can operate in three modes: Station (STA) mode, Soft Access Point (AP) mode, and both simultaneously.

Station (STA) Mode

In Station (STA) mode, the ESP32 connects to an existing WiFi network (the one created by your wireless router).

ESP32 Web Server Station STA Mode Demonstration

In STA mode, the ESP32 obtains an IP address from the wireless router to which it is connected. With this IP address, it can set up a web server and serve web pages to all connected devices on the existing WiFi network.

Soft Access Point (AP) Mode

In Access Point (AP) mode, the ESP32 sets up its own WiFi network and acts as a hub, just like a WiFi router. However, unlike a WiFi router, it does not have an interface to a wired network. So, this mode of operation is called Soft Access Point (soft-AP). Also, no more than five stations can connect to it at the same time.

ESP32 Web Server Soft Access Point AP Mode Demonstration

In AP mode, the ESP32 creates a new WiFi network and assigns it an SSID (the network�s name) and an IP address. With this IP address, it can serve web pages to all connected devices.

Wiring LEDs to an ESP32

Now that we understand the fundamentals of how a web server works and the modes in which the ESP32 can create one, it�s time to connect some LEDs to the ESP32 that we want to control via WiFi.

Begin by placing the ESP32 on your breadboard, making sure that each side of the board is on a different side of the breadboard. Next, connect two LEDs to digital GPIO 4 and 5 using a 220? current limiting resistor.

When you are finished, you should have something that looks like the image below.

Simple ESP32 Web Server Wiring Fritzing Connections with LED
Wiring LEDs to ESP32

The Idea Behind Using an ESP32 Web Server to Control Things

So you might be wondering, �How do I control things from a web server that only processes and serves web pages?�.

It�s extremely simple. We�re going to control things by visiting a specific URL.

When you enter a URL into a web browser, it sends an HTTP request (also known as a GET request) to a web server. It is the web server�s responsibility to handle this request.

Assume you entered a URL like http://192.168.1.1/ledon into a browser. The browser then sends an HTTP request to the ESP32. When the ESP32 receives this request, it recognizes that the user wishes to turn on the LED. As a result, it turns on the LED and sends a dynamic webpage to a browser that displays the LED�s status as �on.� Quite simple, right?

Example 1 � Configuring the ESP32 Web Server in Access Point (AP) mode

Let�s get to the interesting stuff now!

This example, as the title suggests, shows how to configure the ESP32 Web Server in Access Point (AP) mode and serve web pages to any connected client. To begin, connect your ESP32 to your computer and run the sketch. Then we will look at it in more detail.

#include <WiFi.h>
#include <WebServer.h>

/* Put your SSID & Password */
const char* ssid = "ESP32";  // Enter SSID here
const char* password = "12345678";  //Enter Password here

/* Put IP Address details */
IPAddress local_ip(192,168,1,1);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);

WebServer server(80);

uint8_t LED1pin = 4;
bool LED1status = LOW;

uint8_t LED2pin = 5;
bool LED2status = LOW;

void setup() {
  Serial.begin(115200);
  pinMode(LED1pin, OUTPUT);
  pinMode(LED2pin, OUTPUT);

  WiFi.softAP(ssid, password);
  WiFi.softAPConfig(local_ip, gateway, subnet);
  delay(100);
  
  server.on("/", handle_OnConnect);
  server.on("/led1on", handle_led1on);
  server.on("/led1off", handle_led1off);
  server.on("/led2on", handle_led2on);
  server.on("/led2off", handle_led2off);
  server.onNotFound(handle_NotFound);
  
  server.begin();
  Serial.println("HTTP server started");
}
void loop() {
  server.handleClient();
  if(LED1status)
  {digitalWrite(LED1pin, HIGH);}
  else
  {digitalWrite(LED1pin, LOW);}
  
  if(LED2status)
  {digitalWrite(LED2pin, HIGH);}
  else
  {digitalWrite(LED2pin, LOW);}
}

void handle_OnConnect() {
  LED1status = LOW;
  LED2status = LOW;
  Serial.println("GPIO4 Status: OFF | GPIO5 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,LED2status)); 
}

void handle_led1on() {
  LED1status = HIGH;
  Serial.println("GPIO4 Status: ON");
  server.send(200, "text/html", SendHTML(true,LED2status)); 
}

void handle_led1off() {
  LED1status = LOW;
  Serial.println("GPIO4 Status: OFF");
  server.send(200, "text/html", SendHTML(false,LED2status)); 
}

void handle_led2on() {
  LED2status = HIGH;
  Serial.println("GPIO5 Status: ON");
  server.send(200, "text/html", SendHTML(LED1status,true)); 
}

void handle_led2off() {
  LED2status = LOW;
  Serial.println("GPIO5 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,false)); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

String SendHTML(uint8_t led1stat,uint8_t led2stat){
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr +="<title>LED Control</title>\n";
  ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
  ptr +=".button {display: block;width: 80px;background-color: #3498db;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
  ptr +=".button-on {background-color: #3498db;}\n";
  ptr +=".button-on:active {background-color: #2980b9;}\n";
  ptr +=".button-off {background-color: #34495e;}\n";
  ptr +=".button-off:active {background-color: #2c3e50;}\n";
  ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr +="</style>\n";
  ptr +="</head>\n";
  ptr +="<body>\n";
  ptr +="<h1>ESP32 Web Server</h1>\n";
  ptr +="<h3>Using Access Point(AP) Mode</h3>\n";
  
   if(led1stat)
  {ptr +="<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n";}
  else
  {ptr +="<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";}

  if(led2stat)
  {ptr +="<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n";}
  else
  {ptr +="<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";}

  ptr +="</body>\n";
  ptr +="</html>\n";
  return ptr;
}

Accessing the Web Server in AP Mode

After uploading the sketch, open the Serial Monitor at 115200 baud and press the RESET button on the ESP32. If everything is fine, it will show the �HTTP server started� �message.

ESP32 Web Server Access Point Mode Serial Monitor Output - Server Started

Now, get a phone, laptop, or other device that can connect to a WiFi network, and look for a network called �ESP32�. Connect to the network using the password 12345678.

ESP32 Web Server Access Point Mode - Joining Server

After connecting to your ESP32 AP network, open a browser and navigate to 192.168.1.1. The ESP32 should return a web page displaying the current status of the LEDs and buttons. At the same time, you can check the serial monitor to see the status of the ESP32�s GPIO pins.

ESP32 Web Server Access Point Mode - Web Page
ESP32 Web Server Access Point Mode Serial Monitor Output - Webpage Accessed

Now, while keeping an eye on the URL, click the button to turn LED1 ON. Once you click the button, the ESP32 receives a request for the /led1on URL. It then turns on LED1 and serves a web page with the LED status updated. It also prints the GPIO pin status on the serial monitor.

ESP32 Web Server Access Point Mode Web Page - LED Control
ESP32 Web Server Access Point Mode Serial Monitor Output - LED Control

You can test the LED2 button to see if it works similarly.

Let�s take a closer look at the code to see how it works so you can modify it to suit your needs.

Detailed Code Explanation

The sketch begins by including the WiFi.h library. This library contains ESP32-specific methods that we use to connect to the network. Following that, we include the WebServer.h library, which contains some methods that will assist us in configuring a server and handling incoming HTTP requests without having to worry about low-level implementation details.

#include <WiFi.h>
#include <WebServer.h>

Because we are configuring the ESP32 web server in Access Point (AP) mode, it will create its own WiFi network. So, we need to set the SSID, password, IP address, IP subnet mask, and IP gateway.

/* Put your SSID & Password */
const char* ssid = "ESP32";  // Enter SSID here
const char* password = "12345678";  //Enter Password here

/* Put IP Address details */
IPAddress local_ip(192,168,1,1);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);

Following that, we create an object of the WebServer library so that we can access its functions. The constructor of this object accepts as a parameter the port to which the server will be listening. Since HTTP uses port 80 by default, we�ll use this value. This allows us to connect to the server without specifying the port in the URL.

// declare an object of WebServer library
WebServer server(80);

Next, we declare the ESP32�s GPIO pins to which LEDs are connected, as well as their initial state.

uint8_t LED1pin = 4;
bool LED1status = LOW;

uint8_t LED2pin = 5;
bool LED2status = LOW;

Inside the Setup() Function

In the setup function, we configure the ESP32 Web Server in soft Access Point (AP) mode. First, we establish a serial connection for debugging purposes and configure the GPIO pins to behave as an OUTPUT.

Serial.begin(115200);
pinMode(LED1pin, OUTPUT);
pinMode(LED2pin, OUTPUT);

Then, we configure a soft access point to create a Wi-Fi network by providing an SSID, password, IP address, IP subnet mask, and IP gateway.

WiFi.softAP(ssid, password);
WiFi.softAPConfig(local_ip, gateway, subnet);
delay(100);

To handle incoming HTTP requests, we must specify which code should be executed when a specific URL is accessed. For this, we use the .on() method. This method accepts two parameters: a relative URL path and the name of the function to be executed when that URL is visited.

The first line of the code snippet below, for example, indicates that when a server receives an HTTP request on the root (/) path, it will call the handle_OnConnect() function. It is important to note that the URL specified is a relative path.

Similarly, we must specify four more URLs to handle the two states of two LEDs.

server.on("/", handle_OnConnect);
server.on("/led1on", handle_led1on);
server.on("/led1off", handle_led1off);
server.on("/led2on", handle_led2on);
server.on("/led2off", handle_led2off);

We haven�t specified what the server should serve if the client requests a URL that isn�t specified with server.on() . It should give a 404 error (Page Not Found) as a response. To accomplish this, we use the server.onNotFound() method.

server.onNotFound(handle_NotFound);

Now, to start the server, we call the server object�s begin() method.

server.begin();
Serial.println("HTTP server started");

Inside the Loop() Function

Actual incoming HTTP requests are handled in the loop function. For this, we use the server object�s handleClient() method. We also change the state of LEDs based on the request.

void loop() {
  server.handleClient();
  if(LED1status)
  {digitalWrite(LED1pin, HIGH);}
  else
  {digitalWrite(LED1pin, LOW);}
  
  if(LED2status)
  {digitalWrite(LED2pin, HIGH);}
  else
  {digitalWrite(LED2pin, LOW);}
}

Now we must write the handle_OnConnect() function, which we previously attached to the root (/) URL with server.on. We begin this function by setting the status of both LEDs to LOW (initial state of LEDs) and printing it on the serial monitor.

We use the send method to respond to an HTTP request. Although the method can be called with a number of different arguments, the simplest form requires the HTTP response code, the content type, and the content.

The first parameter we pass to the send method is the code 200 (one of the HTTP status codes), which corresponds to the OK response. Then we specify the content type as �text/html,� and finally we pass the SendHTML() custom function, which generates a dynamic HTML page with the LED status.

void handle_OnConnect() {
  LED1status = LOW;
  LED2status = LOW;
  Serial.println("GPIO4 Status: OFF | GPIO5 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,LED2status)); 
}

Similarly, we write five more functions to handle LED ON/OFF requests and the 404 Error page.

void handle_led1on() {
  LED1status = HIGH;
  Serial.println("GPIO4 Status: ON");
  server.send(200, "text/html", SendHTML(true,LED2status)); 
}

void handle_led1off() {
  LED1status = LOW;
  Serial.println("GPIO4 Status: OFF");
  server.send(200, "text/html", SendHTML(false,LED2status)); 
}

void handle_led2on() {
  LED2status = HIGH;
  Serial.println("GPIO5 Status: ON");
  server.send(200, "text/html", SendHTML(LED1status,true)); 
}

void handle_led2off() {
  LED2status = LOW;
  Serial.println("GPIO5 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,false)); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

Displaying the HTML Web Page

Whenever the ESP32 web server receives a request from a web client, the sendHTML() function generates a web page. It simply concatenates HTML code into a long string and returns to the server.send() function we discussed earlier. The function uses the status of the LEDs as a parameter to generate HTML content dynamically.

The first text you should always send is the <!DOCTYPE> declaration, which indicates that we�re sending HTML code.

String SendHTML(uint8_t led1stat,uint8_t led2stat){
String ptr = "<!DOCTYPE html> <html>\n";

The <meta> viewport element makes the web page responsive, ensuring that it looks good on all devices. The title tag determines the page�s title.

ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
ptr +="<title>LED Control</title>\n";

Styling the Web Page

Following that, we have some CSS to style the buttons and the overall appearance of the web page. We selected the Helvetica font and defined the content to be displayed as an inline-block, center-aligned.

ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";

The code that follows then sets the color, font, and margin around the body, H1, H3, and p tags.

ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";

The buttons are also styled with properties such as color, size, margin, and so on. The :active selector changes the look of buttons while they are being clicked.

ptr +=".button {display: block;width: 80px;background-color: #3498db;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
ptr +=".button-on {background-color: #3498db;}\n";
ptr +=".button-on:active {background-color: #2980b9;}\n";
ptr +=".button-off {background-color: #34495e;}\n";
ptr +=".button-off:active {background-color: #2c3e50;}\n";

Setting the Web Page Heading

Next, the heading of the web page is set. You can change this text to anything that fits your application.

ptr +="<h1>ESP32 Web Server</h1>\n";
ptr +="<h3>Using Access Point(AP) Mode</h3>\n";

Displaying the Buttons and Corresponding State

The if statement is used to dynamically update the status of the buttons and LEDs.

if(led1stat)
 {ptr +="<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n";}
else
 {ptr +="<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";}

if(led2stat)
 {ptr +="<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n";}
else
 {ptr +="<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";}

Example 2 � Configuring the ESP32 Web Server in WiFi Station (STA) mode

Let�s move on to the next example, which shows how to configure the ESP32 web server in Station (STA) mode and serve web pages to any connected client on the existing network.

Before you proceed with uploading the sketch, you must make some changes to ensure that it works for you. To connect ESP32 to an existing network, you must modify the following two variables with your network credentials.

Change SSID & Password before trying STA mode web server sketch

Once you�re done, go ahead and try out the sketch.

#include <WiFi.h>
#include <WebServer.h>

/*Put your SSID & Password*/
const char* ssid = " YourNetworkName";  // Enter SSID here
const char* password = " YourPassword";  //Enter Password here

WebServer server(80);

uint8_t LED1pin = 4;
bool LED1status = LOW;

uint8_t LED2pin = 5;
bool LED2status = LOW;

void setup() {
  Serial.begin(115200);
  delay(100);
  pinMode(LED1pin, OUTPUT);
  pinMode(LED2pin, OUTPUT);

  Serial.println("Connecting to ");
  Serial.println(ssid);

  //connect to your local wi-fi network
  WiFi.begin(ssid, password);

  //check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

  server.on("/", handle_OnConnect);
  server.on("/led1on", handle_led1on);
  server.on("/led1off", handle_led1off);
  server.on("/led2on", handle_led2on);
  server.on("/led2off", handle_led2off);
  server.onNotFound(handle_NotFound);

  server.begin();
  Serial.println("HTTP server started");
}
void loop() {
  server.handleClient();
  if(LED1status)
  {digitalWrite(LED1pin, HIGH);}
  else
  {digitalWrite(LED1pin, LOW);}
  
  if(LED2status)
  {digitalWrite(LED2pin, HIGH);}
  else
  {digitalWrite(LED2pin, LOW);}
}

void handle_OnConnect() {
  LED1status = LOW;
  LED2status = LOW;
  Serial.println("GPIO4 Status: OFF | GPIO5 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,LED2status)); 
}

void handle_led1on() {
  LED1status = HIGH;
  Serial.println("GPIO4 Status: ON");
  server.send(200, "text/html", SendHTML(true,LED2status)); 
}

void handle_led1off() {
  LED1status = LOW;
  Serial.println("GPIO4 Status: OFF");
  server.send(200, "text/html", SendHTML(false,LED2status)); 
}

void handle_led2on() {
  LED2status = HIGH;
  Serial.println("GPIO5 Status: ON");
  server.send(200, "text/html", SendHTML(LED1status,true)); 
}

void handle_led2off() {
  LED2status = LOW;
  Serial.println("GPIO5 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,false)); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

String SendHTML(uint8_t led1stat,uint8_t led2stat){
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr +="<title>LED Control</title>\n";
  ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
  ptr +=".button {display: block;width: 80px;background-color: #3498db;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
  ptr +=".button-on {background-color: #3498db;}\n";
  ptr +=".button-on:active {background-color: #2980b9;}\n";
  ptr +=".button-off {background-color: #34495e;}\n";
  ptr +=".button-off:active {background-color: #2c3e50;}\n";
  ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr +="</style>\n";
  ptr +="</head>\n";
  ptr +="<body>\n";
  ptr +="<h1>ESP32 Web Server</h1>\n";
    ptr +="<h3>Using Station(STA) Mode</h3>\n";
  
   if(led1stat)
  {ptr +="<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n";}
  else
  {ptr +="<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";}

  if(led2stat)
  {ptr +="<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n";}
  else
  {ptr +="<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";}

  ptr +="</body>\n";
  ptr +="</html>\n";
  return ptr;
}

Accessing the Web Server in STA mode

After uploading the sketch, open the Serial Monitor at 115200 baud and press the RESET button on the ESP32. If everything is fine, it will display the dynamic IP address obtained from your router as well as the �HTTP server started� message.

ESP32 Web Server Station Mode Serial Monitor Output - Server Started

Next, launch a browser and navigate to the IP address displayed on the serial monitor. The ESP32 should display a web page with the current status of the LEDs and two buttons for controlling them. At the same time, you can check the serial monitor to see the status of the ESP32�s GPIO pins.

ESP32 Web Server Station Mode - Web Page
ESP32 Web Server Station Mode Serial Monitor Output - Webpage Accessed

Now, while keeping an eye on the URL, click the button to turn LED1 ON. Once you click the button, the ESP32 receives a request for the /led1on URL. It then turns on LED1 and serves a web page with the LED status updated. It also prints the GPIO pin status on the serial monitor.

ESP32 Web Server Station Mode Web Page - LED Control
ESP32 Web Server Station Mode Serial Monitor Output - LED Control

You can test the LED2 button to see if it works similarly.

Let�s take a closer look at the code to see how it works so you can modify it to suit your needs.

Code Explanation

The only difference between this code and the previous code is that we are not creating our own WiFi network but rather joining an existing network using the WiFi.begin() function.

//connect to your local wi-fi network
WiFi.begin(ssid, password);

While the ESP32 attempts to connect to the network, we can use the WiFi.status() function to check the connectivity status.

//check wi-fi is connected to wi-fi network
while (WiFi.status() != WL_CONNECTED) 
{
delay(1000);
Serial.print(".");
}

For your information, this function returns the following statuses:

  • WL_CONNECTED: when connected to a Wi-Fi network
  • WL_NO_SHIELD: when no Wi-Fi shield is present
  • WL_IDLE_STATUS: a temporary status assigned when WiFi.begin() is called and remains active until the number of attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED)
  • WL_NO_SSID_AVAIL: when no SSID are available
  • WL_SCAN_COMPLETED: when the scan networks is completed
  • WL_CONNECT_FAILED: when the connection fails for all the attempts
  • WL_CONNECTION_LOST: when the connection is lost
  • WL_DISCONNECTED: when disconnected from a network

Once connected to the network, the WiFi.localIP() function is used to print the ESP32�s IP address.

Serial.println("");
Serial.println("WiFi connected..!");
Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

The only difference between AP and STA mode is that one creates its own network while the other joins an existing one. So, the rest of the code for handling HTTP requests and serving web pages in STA mode is the same as explained above for AP mode. This includes the following:

  • Declaring ESP32�s GPIO pins to which LEDs are connected
  • Defining multiple server.on() methods to handle incoming HTTP requests
  • Defining server.onNotFound() method to handle HTTP 404 error
  • Creating custom functions that are executed when specific URL is hit
  • Creating HTML page
  • Styling the web page
  • Creating buttons and displaying their status

Let�s say we need to create a project that uses the ESP32 to control a light bulb over WiFi. The implementation is quite simple: we will set up the ESP32 in soft-AP or STA mode, allowing it to serve a web page displaying the light switch�s state as either �on� or �off.� When a user toggles the light switch in the browser, the ESP32 will receive an HTTP request. In response, it will adjust the light bulb�s state accordingly, and send a response back to the browser. That�s exactly what we did when building a simple ESP32 web server, and it works perfectly.

However, there is a small problem with this solution. The web is a multi-user system, which means that many people can connect to the same web server. To put it another way, the server is a shared resource. What happens if two users try to switch the light bulb on or off at the same time? The two browser interfaces will fall out of sync and won�t accurately show the actual state of the light bulb. For this reason, it is not a suitable solution for such a system.

Instead, we can take advantage of the bi-directional capabilities of the WebSocket communication protocol to build a responsive system in which a click on the light switch is immediately relayed to the server and broadcast to all connected browsers, so that the light switch state is always synchronized for all users.

That�s precisely what you�ll learn in this tutorial: how to create a web server with the ESP32 using the WebSocket communication protocol. For a practical demonstration, we�ll create a web page to control the on-board LED of the ESP32. The state of the LED will be displayed on the web page. When someone toggles the LED switch on the page, the LED�s state is instantly updated across all browsers connected to the server.

Ready? Let�s get started! First things first,

What Is WebSocket?

Even though the name �WebSocket� doesn�t seem to make sense at first glance, the concept behind it is actually quite simple, and you can get a basic understanding of it in no time.

WebSocket is the name of a communication protocol that enables bi-directional (full-duplex, to be more precise) communication between a client and a web server. Simply put, WebSocket is a technology that allows both the client and the server to establish a connection through which either side can send messages to the other at any time.

This is different from a regular HTTP connection, where the client initiates a request, the server sends a response, and then the connection terminates. In fact, WebSocket is a completely different communication protocol�when a client establishes a connection with a server, both ends of the connection can send and receive data. So just as the server is listening for new messages, all connected clients are also actively listening. As a result, the server can send data to a specific client or broadcast it to all clients without requiring a request. On top of that, the connection stays alive until either the client or the server closes it, allowing for continuous communication between them.

http vs websocket protocol

So, WebSocket is fantastic, but that doesn�t mean it should be used everywhere. Implementing WebSocket can add complexity to your project, especially on the server side. So it should not be done unless your project involves data broadcasting (where you want to transmit the same data to multiple clients at the same time); otherwise, polling might be sufficient.

Project Overview

Let�s now create a simple project where we build a WebSocket server with the ESP32 to remotely control the on-board LED of the ESP32 through a websocket connection.

We will design a web page featuring a toggle switch that allows users to change the state of GPIO 2, which corresponds to the on-board LED. The web interface will also display the LED�s current status.

The ESP32 will actively listen on port 80 for incoming WebSocket connections and messages. When a user toggles the LED switch on the web page, a �toggle� message will be sent to the ESP32. When the ESP32 receives this message, it will toggle the LED and immediately notify all connected clients (browsers) by sending either a �1� (indicating LED on) or a �0� (indicating LED off). As a result, all active clients will immediately update the LED�s status on their webpages.

To better illustrate how this project works, we�ve included an animation below that shows multiple clients connecting to the ESP32 through a websocket connection and exchanging messages.

esp32 websocket server project overview

While this project is quite basic, it can serve as the basis for more practical experiments and projects. For instance, it can be used as a starting point for creating a smart home lighting system.

Setting Up the Arduino IDE

We will be using the Arduino IDE to program the ESP32, so please ensure you have the ESP32 add-on installed before you proceed:

To build a WebSocket server, we�ll use the ESPAsyncWebServer library. This library requires the AsyncTCP library to work. Unfortunately, neither of these libraries is available in the Arduino IDE Library Manager for installation. Therefore, you must install them manually.

Click the links below to download the libraries.

Once downloaded, in your Arduino IDE, navigate to Sketch > Include Library > Add .ZIP Library and select the libraries you�ve just downloaded.

Uploading the Code

Copy the code below to your Arduino IDE. But before you upload the code, you need to make one essential modification. Update the following two variables with your network credentials so that the ESP32 can connect to your network.

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

After making these changes, go ahead and upload the code.

// Import required libraries
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

bool ledState = 0;
const int ledPin = 2;

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>ESP32 WebSocket Server</title>
    <style>
    html{font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}
    body{margin-top: 50px;}
    h1{color: #444444;margin: 50px auto;}
    p{font-size: 19px;color: #888;}
    #state{font-weight: bold;color: #444;}
    .switch{margin:25px auto;width:80px}
    .toggle{display:none}
    .toggle+label{display:block;position:relative;cursor:pointer;outline:0;user-select:none;padding:2px;width:80px;height:40px;background-color:#ddd;border-radius:40px}
    .toggle+label:before,.toggle+label:after{display:block;position:absolute;top:1px;left:1px;bottom:1px;content:""}
    .toggle+label:before{right:1px;background-color:#f1f1f1;border-radius:40px;transition:background .4s}
    .toggle+label:after{width:40px;background-color:#fff;border-radius:20px;box-shadow:0 2px 5px rgba(0,0,0,.3);transition:margin .4s}
    .toggle:checked+label:before{background-color:#4285f4}
    .toggle:checked+label:after{margin-left:42px}
    </style>
  </head>
  <body>
    <h1>ESP32 WebSocket Server</h1>
    <div class="switch">
      <input id="toggle-btn" class="toggle" type="checkbox" %CHECK%>
      <label for="toggle-btn"></label>
    </div>
    <p>On-board LED: <span id="state">%STATE%</span></p>

    <script>
	  window.addEventListener('load', function() {
		var websocket = new WebSocket(`ws://${window.location.hostname}/ws`);
		websocket.onopen = function(event) {
		  console.log('Connection established');
		}
		websocket.onclose = function(event) {
		  console.log('Connection died');
		}
		websocket.onerror = function(error) {
		  console.log('error');
		};
		websocket.onmessage = function(event) {
		  if (event.data == "1") {
			document.getElementById('state').innerHTML = "ON";
			document.getElementById('toggle-btn').checked = true;
		  }
		  else {
			document.getElementById('state').innerHTML = "OFF";
			document.getElementById('toggle-btn').checked = false;
		  }
		};
		
		document.getElementById('toggle-btn').addEventListener('change', function() { websocket.send('toggle'); });
	  });
	</script>
  </body>
</html>
)rawliteral";

void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
  AwsFrameInfo *info = (AwsFrameInfo*)arg;
  if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
    data[len] = 0;
    if (strcmp((char*)data, "toggle") == 0) {
      ledState = !ledState;
      ws.textAll(String(ledState));
    }
  }
}

void eventHandler(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
  switch (type) {
    case WS_EVT_CONNECT:
      Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
      break;
    case WS_EVT_DISCONNECT:
      Serial.printf("WebSocket client #%u disconnected\n", client->id());
      break;
    case WS_EVT_DATA:
      handleWebSocketMessage(arg, data, len);
      digitalWrite(ledPin, ledState);
      break;
    case WS_EVT_PONG:
    case WS_EVT_ERROR:
      break;
  }
}

String processor(const String& var){
  if(var == "STATE"){
      return ledState ? "ON" : "OFF";
  }
  if(var == "CHECK"){
    return ledState ? "checked" : "";
  }
  return String();
}

void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);

  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);

  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  
  Serial.println("");
  Serial.println("Connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

  ws.onEvent(eventHandler);
  server.addHandler(&ws);

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });

  // Start server
  server.begin();
}

void loop() {
  ws.cleanupClients();
}

Demonstration

After uploading the code, open the Serial Monitor and set the baud rate to 115200. Then, press the EN button on the ESP32. It may take a few moments to connect to your network, after which it will display the dynamic IP address obtained from your router.

esp32 websocket server started message

Next, launch a web browser and navigate to the IP address displayed on the serial monitor. The ESP32 should display a web page with the current status of the on-board LED and a toggle switch to control it.

esp32 websocket interactive web application

Now, grab your phone or tablet and navigate to the same IP address, making sure it�s connected to the same network as your computer. Click on the toggle switch to toggle the LED. You�ll observe that not only does the on-board LED toggle, but the LED state automatically updates across all web browsers.

At the same time, you can keep an eye on the Serial Monitor to see which clients are connecting to and disconnecting from the ESP32.

esp32 websocket list of connected clients and ip address

Detailed Code Explanation

Let�s break this code down.

Importing Required Libraries

The sketch begins by including the following libraries:

  • WiFi.h: offers ESP32-specific WiFi methods we use to connect to the network.
  • ESPAsyncWebServer.h: is used to create an HTTP server that supports websocket endpoints.
  • AsyncTCP.h: the ESPAsyncWebServer library relies on this library.
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

Defining Constants and Variables

Next, the network credentials (SSID and password) are specified, which you should replace with your own Wi-Fi network credentials.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

In that same global area, two variables are defined: one for tracking the current GPIO state (ledState) and another for specifying the GPIO pin connected to the LED (ledPin). In this case, we�ll control the on-board LED (that is connected to GPIO 2).

bool ledState = 0;
const int ledPin = 2;

Initializing Web Server and WebSocket

Next, an object of the AsyncWebServer class named server is created. The constructor of this class accepts as an argument the port number on which the HTTP server will listen for incoming requests. In this case, we�ll use the default HTTP port, which is port 80.

AsyncWebServer server(80);

Then, to set up the WebSocket endpoint, an object of the AsyncWebSocket class named ws is created. The path of the websocket endpoint must be passed as an argument to the constructor of this class. This path is defined as a string, and in our code, it�s set to /ws.

So that the WebSocket will listen for connections on the path: ws://[esp ip]/ws

AsyncWebSocket ws("/ws");

Creating the Web Page

The index_html constant is then defined. It holds a raw string literal containing the HTML code for displaying a toggle switch on a web page for controlling the ESP32�s on-board LED, as well as CSS for styling the web page and JavaScript to establish a WebSocket connection and monitor changes in the LED�s state. This entire code will execute on the client side.

Note that the PROGMEM macro is used, which stores the content in the ESP32�s program memory (flash memory) rather than the SRAM, to optimize memory.

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>ESP32 WebSocket Server</title>
    <style>
    html{font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}
    body{margin-top: 50px;}
    h1{color: #444444;margin: 50px auto;}
    p{font-size: 19px;color: #888;}
    #state{font-weight: bold;color: #444;}
    .switch{margin:25px auto;width:80px}
    .toggle{display:none}
    .toggle+label{display:block;position:relative;cursor:pointer;outline:0;user-select:none;padding:2px;width:80px;height:40px;background-color:#ddd;border-radius:40px}
    .toggle+label:before,.toggle+label:after{display:block;position:absolute;top:1px;left:1px;bottom:1px;content:""}
    .toggle+label:before{right:1px;background-color:#f1f1f1;border-radius:40px;transition:background .4s}
    .toggle+label:after{width:40px;background-color:#fff;border-radius:20px;box-shadow:0 2px 5px rgba(0,0,0,.3);transition:margin .4s}
    .toggle:checked+label:before{background-color:#4285f4}
    .toggle:checked+label:after{margin-left:42px}
    </style>
  </head>
  <body>
    <h1>ESP32 WebSocket Server</h1>
    <div class="switch">
      <input id="toggle-btn" class="toggle" type="checkbox" %CHECK%>
      <label for="toggle-btn"></label>
    </div>
    <p>On-board LED: <span id="state">%STATE%</span></p>

    <script>
      window.addEventListener('load', function() {
        var websocket = new WebSocket(`ws://${window.location.hostname}/ws`);
        websocket.onmessage = function () {
          if (event.data == "1") {
            document.getElementById('state').innerHTML = "ON";
            document.getElementById('toggle-btn').checked = true;
          }
          else {
            document.getElementById('state').innerHTML = "OFF";
            document.getElementById('toggle-btn').checked = false;
          }
        };
        document.getElementById('toggle-btn').addEventListener('change', function() { websocket.send('toggle'); });
      });
    </script>
  </body>
</html>
)rawliteral";

The CSS

Between the <style></style> tags is the CSS code used to style the web page. Feel free to change it to make the website look the way you want it to.

<style>
html{font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}
body{margin-top: 50px;}
h1{color: #444444;margin: 50px auto;}
p{font-size: 19px;color: #888;}
#state{font-weight: bold;color: #444;}
.switch{margin:25px auto;width:80px}
.toggle{display:none}
.toggle+label{display:block;position:relative;cursor:pointer;outline:0;user-select:none;padding:2px;width:80px;height:40px;background-color:#ddd;border-radius:40px}
.toggle+label:before,.toggle+label:after{display:block;position:absolute;top:1px;left:1px;bottom:1px;content:""}
.toggle+label:before{right:1px;background-color:#f1f1f1;border-radius:40px;transition:background .4s}
.toggle+label:after{width:40px;background-color:#fff;border-radius:20px;box-shadow:0 2px 5px rgba(0,0,0,.3);transition:margin .4s}
.toggle:checked+label:before{background-color:#4285f4}
.toggle:checked+label:after{margin-left:42px}
</style>

The HTML

Between the <body></body> tags is the actual web page content.

<h1>ESP32 WebSocket Server</h1>
<div class="switch">
  <input id="toggle-btn" class="toggle" type="checkbox" %CHECK%>
  <label for="toggle-btn"></label>
</div>
<p>On-board LED: <span id="state">%STATE%</span></p>

The first line of code sets the page header using the <h1> tag; you can change this text to whatever suits your application.

<h1>ESP32 WebSocket Server</h1>

Next block of code creates a toggle switch.

This is actually a checkbox with some styling to make it look like a toggle switch.

<div class="switch">
  <input id="toggle-btn" class="toggle" type="checkbox" %CHECK%>
  <label for="toggle-btn"></label>
</div>

Finally, there�s a paragraph that displays the state of the on-board LED. The actual state of the LED (�ON� or �OFF�) is shown within a <span> element.

<p>On-board LED: <span id="state">%STATE%</span></p>

The placeholders %CHECK% and %STATE% are essentially variables that the ESP32 will replace with the appropriate values when serving the web page, based on the LED�s current state. The placeholders ensure that the LED�s state remains synchronized across all users. If one user modifies the LED�s state, the next user who connects to the ESP32 should see the LED�s updated state, right?

The %STATE% placeholder will be replaced with the appropriate text (either �ON� or �OFF�) based on the LED�s current status. And the %CHECK% placeholder determines whether the checkbox should be checked or unchecked (toggle switch is either on or off) when the page loads.

Note that the placeholders come into play when a client connects to the ESP32 for the very first time. Once the websocket connection is established, JavaScript takes care of all the updating. That�s why each element with a placeholder has an id attribute. The id attribute gives each element a unique identifier. This allows JavaScript to access and manipulate these elements whenever the WebSocket receives a message from the server.

The JavaScript

The JavaScript code is placed within the <script></script> tags. It is responsible for initializing a WebSocket connection with the server and for managing data exchange through WebSockets, as well as updating all web page elements accordingly.

<script>
  window.addEventListener('load', function() {
	var websocket = new WebSocket(`ws://${window.location.hostname}/ws`);
	websocket.onopen = function(event) {
	  console.log('Connection established');
	}
	websocket.onclose = function(event) {
	  console.log('Connection died');
	}
	websocket.onerror = function(error) {
	  console.log('error');
	};
	websocket.onmessage = function(event) {
	  if (event.data == "1") {
		document.getElementById('state').innerHTML = "ON";
		document.getElementById('toggle-btn').checked = true;
	  }
	  else {
		document.getElementById('state').innerHTML = "OFF";
		document.getElementById('toggle-btn').checked = false;
	  }
	};
	
	document.getElementById('toggle-btn').addEventListener('change', function() { websocket.send('toggle'); });
  });
</script>

First, an event listener is attached to the window object to ensure that the entire JavaScript code executes only once the page has fully loaded.

window.addEventListener('load', function() {

Upon loading, a new WebSocket connection is established to the server using the current hostname (ESP32�s IP address) and the special protocol ws:// in the URL. FYI, there�s also encrypted wss:// protocol; it�s like HTTPS for websockets.

var websocket = new WebSocket(`ws://${window.location.hostname}/ws`);

Once the connection is established, several events are fired on the WebSocket instance on the client side. And, we should listen to these events. There are four events in total:

  • onopen � Fired when a connection with a WebSocket is established.
  • onclose � Fired when a connection with a WebSocket is closed.
  • onerror � Fired when a connection with a WebSocket has been closed because of an error, such as when some data couldn�t be sent.
  • onmessage � Fired when the WebSocket receives data from the server.

When the connection is opened, closed, or encounters an error, we simply print a message to the web console. However, you can use these events to add more functionality if needed.

websocket.onopen = function(event) {
  console.log('Connection established');
}
websocket.onclose = function(event) {
  console.log('Connection died');
}
websocket.onerror = function(error) {
  console.log('error');
};

Now, we need to handle what happens when the WebSocket receives data from the server, i.e. when the onmessage event fires. The server (your ESP32) will either broadcast a �1� or a �0� message depending on the LED�s current state. Based on the received message, we need to take appropriate action.

  • If �1� is received: the text within the element with the ID �state� should be set to �ON� and the checkbox with the ID �toggle-btn� should be marked as checked.
  • If �0� is received: the text within the element with the ID �state� should be set to �OFF� and the checkbox with the ID �toggle-btn� should remain unchecked.
websocket.onmessage = function(event) {
  if (event.data == "1") {
	document.getElementById('state').innerHTML = "ON";
	document.getElementById('toggle-btn').checked = true;
  }
  else {
	document.getElementById('state').innerHTML = "OFF";
	document.getElementById('toggle-btn').checked = false;
  }
};

Finally, an event listener is attached to our toggle switch. When the switch is toggled on or off (i.e., when the state of the �toggle-btn� checkbox changes), a message �toggle� is sent over the WebSocket connection, informing the ESP32 to toggle the LED.

document.getElementById('toggle-btn').addEventListener('change', function() { websocket.send('toggle'); });

The setup() Function

You�ve seen how to handle the WebSocket connection on the client side (browser) up to this point. Let�s now look at how to handle it on the server side.

In setup(), the Serial Monitor is initialized for debugging purposes.

Serial.begin(115200);

The LED pin is then configured to behave as an OUTPUT and set to LOW.

pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);

After that, the ESP32 attempts to connect to the WiFi network. The connection progress and the obtained IP address are printed to the serial monitor.

Serial.print("Connecting to ");
Serial.println(ssid);

// Connect to Wi-Fi
WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
}

Serial.println("");
Serial.println("Connected..!");
Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

The next line sets up an event listener for the WebSocket. This means that whenever an event related to the WebSocket (ws object) occurs, the eventHandler() function will be triggered to handle that event. The eventHandler() function contains logic to determine what type of WebSocket event has occurred (like a new client connection, data received, client disconnected, etc.) and how to respond to that event. We�ll look into that later.

ws.onEvent(eventHandler);

The next line attaches or registers the WebSocket (ws object) to the web server (server object). This tells the server that it has a WebSocket handler, and any incoming WebSocket connections or requests should be directed to this handler. The &ws is a pointer to the WebSocket object, allowing the server to know where to send and from where to receive WebSocket-related data.

server.addHandler(&ws);

The next line sets up an endpoint or route on the web server. In this case, the root path (�/�) is being defined. When a client sends an HTTP_GET request to this root path, the lambda function (inside the curly braces { � }) gets executed.

Within this function, there�s a call to request->send_P(...). This function sends a response back to the client.

  • 200 is the HTTP status code, indicating a successful request.
  • "text/html" tells the client that the server is sending back HTML content.
  • index_html is the content to be sent, which is your HTML web page stored in the program memory. More on this later.
  • processor is a function that processes the HTML content before sending it (to replace placeholders, for example). More on this later.
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/html", index_html, processor);
});

Finally, the web server is started. Once executed, the server starts listening for incoming client requests on the previously defined routes (like the root path we set up).

server.begin();

The loop() Function

In the case of a synchronous web server, you will normally have a call server.handleClient(); within the loop, which checks to see if there are any client requests to be handled. If there are any, the code is expected to process these requests and send responses before returning control to the rest of the loop.

In the case of an asynchronous web server, nothing is required in the loop. The AsyncWebServer is set up with event handlers and also uses the AsyncTCP library. With this, it can actively listen for connections in the background, parse incoming requests, and trigger the appropriate event handler. This is why, when creating an asynchronous web server, the loop() function is generally left empty.

However, in our case, the cleanupClients() method is called. This method is a built-in method of the AsyncWebSocket class. When working with websockets, clients may frequently connect and disconnect, or their connections may drop unexpectedly. Over time, you may have WebSocket clients that are no longer active but still occupy resources. This will eventually exhaust the web server�s resources, causing the server to crash.

The cleanupClients() method iterates through all the connected WebSocket clients and removes those that are no longer active or connected. Periodically calling the cleanClients() function from the main loop() ensures that your ESP32 is not burdened with old, unused client connections and remains efficient when handling new or active clients.

void loop() {
  ws.cleanupClients();
}

Handling Server-side WebSocket Events

If you recall, in the setup() function, we set up an event listener for the WebSocket using the ws.onEvent(eventHandler) method, so that whenever an event related to the WebSocket (ws object) occurs, the eventHandler() function gets triggered to handle that event.

The eventHandler() function simply checks the type argument to determine the type of event that has occurred and responds to that event. The type argument represents the event that occurs. It can take the following values:

  • WS_EVT_CONNECT � when a new client connects to the WebSocket server
  • WS_EVT_DISCONNECT � when a client disconnects from the WebSocket server
  • WS_EVT_DATA � when the WebSocket server receives data from a client
  • WS_EVT_PONG � when the WebSocket receives a ping request
  • WS_EVT_ERROR � when the client reports an error
void eventHandler(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
  switch (type) {
    case WS_EVT_CONNECT:
      Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
      break;
    case WS_EVT_DISCONNECT:
      Serial.printf("WebSocket client #%u disconnected\n", client->id());
      break;
    case WS_EVT_DATA:
      handleWebSocketMessage(arg, data, len);
      digitalWrite(ledPin, ledState);
      break;
    case WS_EVT_PONG:
    case WS_EVT_ERROR:
      break;
  }
}

As you can see, when the event type is WS_EVT_CONNECT or WS_EVT_DISCONNECT, the Serial monitor displays the IP address and unique ID of the clients as they connect or disconnect. Whereas no actions are taken for the WS_EVT_PONG and WS_EVT_ERROR event types, though they could be further developed if needed.

The most important event type is WS_EVT_DATA (when the WebSocket server receives data from a client): the received data is passed to the handleWebSocketMessage() function for further processing and the LED�s state is updated with the latest ledState value.

Reading WebSocket Messages and Broadcasting

The function handleWebSocketMessage() is defined to handle incoming WebSocket messages. When a valid �toggle� message is received, this function toggles the LED, and notifies all connected clients of this change using the textAll() method.

The textAll() method of the AsyncWebSocket class allows the ESP32 to send the same message to all connected clients at the same time, ensuring that the LED�s state remains synchronized across all clients.

void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
  AwsFrameInfo *info = (AwsFrameInfo*)arg;
  if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
    data[len] = 0;
    if (strcmp((char*)data, "toggle") == 0) {
      ledState = !ledState;
      ws.textAll(String(ledState));
    }
  }
}

Processing the web page Placeholders

If you recall, our web page has two placeholders: %CHECK% and %STATE%. The processor() function is responsible for searching for such placeholders within the HTML text and replacing them with the appropriate values before sending the web page to the browser.

The %STATE% placeholder will be replaced with either �ON� or �OFF� based on the LED�s current state. And the %CHECK% placeholder determines whether the checkbox should be checked or unchecked (toggle switch is either on or off) when the page loads.

String processor(const String& var){
  if(var == "STATE"){
      return ledState ? "ON" : "OFF";
  }
  if(var == "CHECK"){
    return ledState ? "checked" : "";
  }
  return String();
}

Most people associate the ESP family of microcontrollers with WiFi, which makes sense, as they�ve become the go-to solution for getting your project online quickly and easily. However, while WiFi capability might be the star of the show, the ESP32 is also equipped with Bluetooth. It�s just that we don�t see it being used as frequently.

If you�re interested in using Bluetooth on the ESP32, this tutorial is an excellent starting point.

About ESP32 Bluetooth

The ESP32 supports dual-mode Bluetooth, which means it supports both Bluetooth Classic and Bluetooth Low Energy (BLE). While these two protocols share many important things such as architecture, and both operate in 2.4 GHz ISM (Industrial, Scientific, and Medical) band, they�re quite different from each other.

Bluetooth Classic

The original Bluetooth technology�what we�re familiar with from our smartphones linking to our wireless earbuds�is now known as Bluetooth Classic. If you�ve ever used Bluetooth-to-Serial-Bridge modules like the HC-05 or HC-06 with an Arduino, you�ve unknowingly used Bluetooth Classic.

Bluetooth Classic is designed for continuous two-way data transfer with high application throughput (up to 3 Mbps); highly effective, but only for short distances. It uses over 79 channels in the 2.4 GHz ISM band.

This type of Bluetooth is mainly employed in projects where you need to continuously stream data like audio streaming or file transfer.

This tutorial will teach you how to use Bluetooth Classic on the ESP32.

Bluetooth Low Energy (BLE)

Bluetooth LE, originally marketed as Bluetooth Smart and commonly referred to as just BLE, is designed for very low power operation while maintaining a similar communication range. However, BLE is much more than a low-power version of Bluetooth Classic. BLE operates in the same radio spectrum range as Bluetooth Classic, but uses a different set of 40 channels at a lower data rate (up to 1 Mbps).

It�s well-suited for IoT projects where power consumption is a main concern�for example, a project where you need to wake up the device periodically, gather sensor data, transmit it using Bluetooth, and then return to sleep mode.

As using ESP32 BLE is a little more complicated than using Bluetooth Classic, we�ve covered that in a separate tutorial.

Bluetooth Classic vs. BLE

BLE is primarily used to save power, but there are actually several important differences.

Power Consumption: Bluetooth Classic typically consumes more power (around 1 W), while BLE is designed for low power consumption (typically between 0.01 and 0.5 W), making it suitable for battery-powered devices that need to operate for extended periods.

Data Transfer Rates: Bluetooth Classic provides higher data rates than BLE, making it suitable for projects that require continuous and high-speed data transmission, while BLE is optimized for short bursts of data transmission.

Range: Both BLE and Classic can cover up to 100 meters, but the exact distance varies depending on the environment and implementation.

Latency: Bluetooth Classic connections have a latency of up to 100 ms, while BLE connections have a latency of 6 ms. Keep in mind that lower is better.

Compatibility: Bluetooth Classic is more prevalent in older devices, while BLE is commonly used in modern smartphones and other gadgets.

Take a look at the table below that compares BLE and Bluetooth Classic in more detail.

Bluetooth ClassicBluetooth Low Energy (BLE)
Data Rate1 Mbps for BR2-3 Mbps for EDR500 kbps � 1 Mbps
Frequency Band2.4 GHz ISM band2.4 GHz ISM band
Number of Channels79 Channels with 1 MHz spacing40 Channels with 2 MHz spacing
Communication Range8 m up to 100 m8 m up to 100 m
Power ConsumptionHigh (up to 1 W)Low (0.01 W up to 0.5 W)
Device Pairing is Mandatory?YESNOT Mandatory
Supported TopologiesPoint-to-Point (1:1)Point-to-Point (1:1)Broadcast (1:many)Mesh (many:many)
Modulation TechniqueGFSK?/4 DQPSK8 DPSKGFSK
Latency35ms2-16ms (Avg. 9ms)

ESP32 Bluetooth Protocol Stack

Before we proceed, it�s important to understand the Bluetooth protocol stack. This will help you understand why we�re using certain functions in our code.

Basically, the Bluetooth protocol stack is split into two parts: a �host stack� and a �controller stack�.

The host is the main CPU that runs a Bluetooth stack. ESP-IDF currently supports two host stacks. The BLUEDROID-based stack (default) supports Bluetooth Classic as well as BLE. On the other hand, the Apache NimBLE-based stack is BLE only.

The controller stack controls the Bluetooth radio. It acts, essentially, like a data pipeline: serial data that it receives is passed out the Bluetooth connection, and data coming in from the Bluetooth side is passed out the host.

esp32 bluetooth protocol stack

The VHCI interface (software-implemented virtual HCI interface) enables communication between the host and the controller.

For more information, check out Espressif�s Bluetooth architecture description.

Bluetooth Serial Sketch

Now let�s program the ESP32 so that it can communicate with our smartphone and exchange data with it.

We will be using the Arduino IDE to program the ESP32, so please ensure you have the ESP32 add-on installed before you proceed:

Open your Arduino IDE, and go to File > Examples > BluetoothSerial > SerialtoSerialBT. The following code should load.

#include "BluetoothSerial.h"

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

#if !defined(CONFIG_BT_SPP_ENABLED)
#error Serial Bluetooth not available or not enabled. It is only available for the ESP32 chip.
#endif

BluetoothSerial SerialBT;

void setup() {
  Serial.begin(115200);
  SerialBT.begin("ESP32test"); //Bluetooth device name
  Serial.println("The device started, now you can pair it with bluetooth!");
}

void loop() {
  if (Serial.available()) {
    SerialBT.write(Serial.read());
  }
  if (SerialBT.available()) {
    Serial.write(SerialBT.read());
  }
  delay(20);
}

Once you have uploaded the sketch, open the serial monitor at baud rate 115200. You should see a message saying: �The device started, now you can pair it with bluetooth!�.

esp32 bluetooth classic output screenshot1

Code Explanation:

This code establishes two-way serial Bluetooth communication between two devices. The code begins by including the BluetoothSerial.h library, which provides the necessary functionalities for serial operations over Bluetooth.

#include "BluetoothSerial.h"

The next piece of code checks if Bluetooth is properly enabled.

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

#if !defined(CONFIG_BT_SPP_ENABLED)
#error Serial Bluetooth not available or not enabled. It is only available for the ESP32 chip.
#endif

Next, an object of the BluetoothSerial class, named SerialBT, is created. This initializes the Bluetooth stack on the ESP32 and handles the transmission and reception of data. You can check out the implementation file for this class here. Internally, this class uses IDF�s Bluetooth Classic API.

BluetoothSerial SerialBT;

In the setup, a serial communication is established at a baud rate of 115200.

Serial.begin(115200);

Following that, the begin() method of the BluetoothSerial object is called, taking care of the lower-level initialization of the Bluetooth stack. This method accepts a string input containing the desired Bluetooth device name. This name is visible to other Bluetooth-enabled devices during their discovery process. By default, it�s called ESP32test, but you can rename it and give it a unique name.

Note that this method returns a Boolean value, indicating whether the initialization succeeded or not. For now, we have not checked that value, but for a robust code you should always include error checks.

SerialBT.begin("ESP32test"); //Bluetooth device name

The loop section allows bidirectional data transfer between the Serial Monitor and the ESP32 Bluetooth serial.

The first if statement checks if there is any data available on the standard serial port (like when you send data from a serial monitor on a PC). If data is available, it is read using Serial.read() and sent over the Bluetooth serial connection using SerialBT.write(). So, any data you send from your computer via the serial port is forwarded to the connected Bluetooth device.

if (Serial.available()) {
  SerialBT.write(Serial.read());
}

The next if statement checks if there�s any data available from the Bluetooth serial connection (like when a paired Bluetooth device sends some data). If data is available, it is read using SerialBT.read() and sent to the Serial Monitor using Serial.write(). So, any data received from the Bluetooth device is forwarded to your Serial Monitor.

if (SerialBT.available()) {
  Serial.write(SerialBT.read());
}

Connecting to the Android Phone

Let�s set up a wireless connection between the ESP32 and an Android phone. The process may vary depending on the device, but the general steps are quite similar.

1. Make sure the ESP32 is powered up and ready to establish a connection.

2. Now, swipe down from the top of your Android phone�s screen and make sure Bluetooth is turned on.

turn mobile bluetooth on

3. Touch and hold the Bluetooth icon, then tap �Pair new device� and wait a few seconds.

pair a new bluetooth device

4. Tap the name of the Bluetooth device you want to pair with your device (in our case, ESP32test). Follow any on-screen instructions.

select esp32test from all discovered devices

5. For the next steps in this tutorial, you need a Bluetooth Terminal application installed on your smartphone. We recommend using the Android app �Serial Bluetooth Terminal,� available in the Play Store.

serial bluetooth terminal app

6. After installing, launch the �Serial Bluetooth Terminal� app. Click on the icon in the top left corner and choose �Devices�.

open menu and choose devices

7. You should see a list of devices you�ve previously paired with. Select �ESP32test� from this list.

select esp32test from a list of paired devices

8. You should get a �Connected� message. That�s it! Your smartphone is now successfully paired with the ESP32 and ready to communicate.

mobile connects to esp32test

9. Now, type something in the input box located at the bottom of the app, for example, �Hi!�

send a message from app to esp32

10. You should instantly receive that message in the Arduino IDE Serial Monitor.

esp32 bluetooth classic output screenshot2

11. You can also exchange data between your Serial Monitor and your smartphone. Type something in the Serial Monitor�s top input box and press the �Send� button.

send a message from serial monitor to app

12. You should instantly receive that message in the Serial Bluetooth Terminal App.

app shows received message

ESP32 Project: Bluetooth-Controlled Relay

Let�s create a simple project that lets you control relays wirelessly using Bluetooth. This can be useful for home automation, smart lighting, security systems, and other similar applications.

Wiring

Connect the relay module to the ESP32 as depicted in the following diagram. The diagram shows the wiring for a 2-channel relay module; wiring for a different number of channels is similar.

wiring relay module to esp32

ESP32 Code

Once you are done with the wiring, try the below sketch.

#include "BluetoothSerial.h"

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

#if !defined(CONFIG_BT_SPP_ENABLED)
#error Serial Bluetooth not available or not enabled. It is only available for the ESP32 chip.
#endif

// GPIO where relay is connected to
const int relayPin =  5;

// Handle received messages
String message = "";

// Create BluetoothSerial object
BluetoothSerial SerialBT;

void setup() {
  // Begin serial communication with Arduino and Arduino IDE (Serial Monitor)
  Serial.begin(115200);

  // Initialize relayPin as an output
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, HIGH);
  
  // Initialize the Bluetooth stack
  SerialBT.begin("ESP32test"); //Bluetooth device name
  Serial.println("The device started, now you can pair it with bluetooth!");
}

void loop() {
  if (SerialBT.available()){
    char incomingChar = SerialBT.read();
    if (incomingChar != '\n'){
      message += String(incomingChar);
    }
    else{
      message = "";
    }
    Serial.write(incomingChar);  
  }
  // Check received message and control output accordingly
  if (message == "on"){
    digitalWrite(relayPin, LOW);
  }
  else if (message == "off"){
    digitalWrite(relayPin, HIGH);
  }
  delay(20);
}

Testing the Code

Once you have uploaded the sketch, open the serial monitor at baud rate 115200 and press the EN button. You should see a message saying: �The device started, now you can pair it with bluetooth!�.

esp32 bluetooth classic output screenshot1

Launch the Serial Bluetooth Terminal app and connect to your ESP32.

Now, as you type �on� in the input box at the bottom of the app, the relay should activate instantly. Likewise, typing �off� will deactivate the relay immediately.

send on off messages to control relay

Additionally, you will receive these messages in the Arduino IDE Serial Monitor for monitoring purposes.

esp32 bluetooth classic output screenshot3

The app offers several macros that you can customize to save default messages. For instance, you can associate M1 with the �on� message and M2 with the �off� message. This way, you can easily control the relay using the predefined macros, adding an extra layer of convenience to your project.

serial bluetooth terminal app macros
edit macros

Bluetooth Low Energy (BLE) is everywhere these days. If you fire up a scanner on your phone and walk around the neighborhood, you�ll undoubtedly find dozens, if not hundreds, of BLE devices.

If you�re curious and want to start working with BLE, this tutorial is an excellent starting point. It will give you a quick overview of BLE (specifically how data is organized in BLE, how two BLE devices communicate with each other), and how to use BLE on the ESP32.

Basics of Bluetooth Low Energy

Bluetooth Low Energy (BLE), sometimes referred to as �Bluetooth Smart,� is a light-weight subset of classic Bluetooth introduced as part of the Bluetooth 4.0 core specification.

BLE is designed for devices that send small amounts of data infrequently and operate on small batteries. It uses the same frequency band as classic Bluetooth�the 2.4GHz ISM (industrial, scientific, and medical) band, which does not require a license to use. The ISM band is divided into 40 channels, and BLE devices hop between them to avoid interference.

To save power, BLE has lower transmission power (0.01 to 10 mW) compared to classic Bluetooth (up to 100 mW for class 1 and 1 mW for class 3 devices). Data is sent in the same way (using Gaussian Frequency Shift Keying), but the speed of data transmission is lower�a maximum of 1 Mb/s, compared to the 24 Mb/s maximum of classic Bluetooth. BLE devices can also switch between standby and active modes much faster than classic Bluetooth devices, saving even more power.

In a nutshell, BLE is designed to offer many of the same features as Bluetooth Classic, but with a focus on low power. As a result, it has become the standard technology for a wide range of applications, including smart lighting, smart homes, beacons, fitness trackers, insulin pumps, hearing aids, and other energy-sensitive applications.

bluetooth low energy

Bluetooth Profiles

Bluetooth profiles are additional protocols that build upon the basic Bluetooth standard. They help specify the type of data a Bluetooth module transmits. While Bluetooth specifications define how the technology works, profiles define how it is used.

The profiles a Bluetooth device supports determine the applications it is designed for. For instance, a hands-free Bluetooth headset uses the headset profile (HSP), whereas a wireless keyboard uses the human interface device (HID) profile. These profiles are developed by either the Bluetooth SIG (Bluetooth Special Interest Group) or the peripheral designers. The complete list of officially adopted profiles can be seen here.

The profiles that you should be familiar with are GAP and GATT, as all standard BLE profiles are based on them. So let�s get to know them better.

GAP (Generic Access Profile)

GAP controls connections and advertising in Bluetooth. GAP is what makes your device visible to the outside world and determines how two devices discover and establish a connection with each other.

GAP defines various roles for devices, but the two key concepts to keep in mind are Central devices and Peripheral devices.

Peripheral devices are usually small, low-power, resource-constrained devices, such as heart rate monitors or proximity tags, that can connect to a much more powerful central device.

Central devices are usually devices with far more processing power and memory, such as mobile phones or tablets, to which you connect.

generic access profile roles

A peripheral device advertises by sending out advertising packets at set intervals to inform nearby central devices of its presence. Once a connection is established between a peripheral and a central device, the advertising process stops and GATT comes into play, allowing communication to occur in both directions.

GATT (Generic ATTribute Profile)

GATT specifies how data should be organized and how two BLE devices should share data. Unlike GAP, which defines the low-level interactions with devices, GATT deals only with actual data transfer procedures and formats.

The data is organized hierarchically into sections called services, which group conceptually related pieces of user data called characteristics, as shown in the illustration below:

generic attribute profile

Services

A GATT service is a collection of conceptually related data called characteristics. Each service can have one or more characteristics and has its own unique numeric identifier, or UUID, which is either 16 bits (for officially adopted BLE Services) or 128 bits (for custom services). More on that later.

For instance, let�s consider the Heart Rate Service. This officially adopted service has a 16-bit UUID of 0x180D, and contains up to 3 characteristics: Heart Rate Measurement, Body Sensor Location and Heart Rate Control Point. You can find more Bluetooth SIG-defined services here.

Characteristics

A GATT characteristic is a group of information called Attributes. Attributes are the information actually transferred between BLE devices. A typical characteristic is composed of the following attributes:

gatt characteristic
  • Value: is the actual data that is stored in the characteristic. The value can be any type of data, such as a number, a string, or an array of bytes.
  • Descriptor: provides additional information or configuration options for the characteristic.

In addition to the value, the following properties are associated with each characteristic:

  • Handle: a 16-bit number used to access the characteristic on the server device.
  • UUID: a 128-bit universally unique identifier that indicates what the characteristic represents.
  • Permissions: defines which operations on the characteristic are permitted, such as read, write, or notify.

UUID (Universally Unique Identifier)

UUIDs ensure that services and characteristics can be uniquely identified. They play an essential role in defining and accessing the data on a BLE device. There are two types of UUIDs in BLE:

  • 16-bit UUID: is used for official BLE profiles, services, and characteristics. These are standardized by the Bluetooth-SIG. For example, the �Heart Rate Service� has a standardized 16-bit UUID of 0x180D, while the �Heart Rate Measurement� characteristic within the Heart Rate Service uses a UUID of 0x2A37.
  • 128-bit UUID: is used for custom (vendor-specific) services and characteristics. If a company develops its own service not covered by the official BLE services, they would use a unique 128-bit UUID. A 128-bit UUID looks like this: 4fafc201-1fb5-459e-8fcc-c5c9c331914b.

GATT Server and GATT Client

From a GATT perspective, when two devices are connected, they are each in one of two roles.

  • The GATT server is the device that contains the characteristic database.
  • The GATT client is the device that reads from or writes data to the GATT server.

The following figure illustrates this relationship in a sample BLE connection, where the peripheral device (an ESP32) is the GATT server, while the central device (a smartphone) is the GATT client.

gatt server and gatt client

It�s important to note that the GATT roles of client and server are independent from the GAP roles of peripheral and central. A peripheral can be either a GATT client or a GATT server or both, and similarly, a central can be either a GATT client or a GATT server.

Using BLE on the ESP32

Now that you have learned about the Bluetooth Low Energy (BLE) wireless communication protocol, including its features, profiles, and how it communicates with devices, it�s time to give it a try.

There are several example sketches available for the ESP32 in the ESP32 BLE library. This library comes installed when you install the ESP32 core on the Arduino IDE.

To access the example sketches, navigate to File > Examples > ESP32 BLE Arduino.

You will see a selection of example sketches. You can choose any of them to load the sketch into your IDE. Let�s get started with the BLE_server example.

This example configures the ESP32 as a BLE server with a specific service and characteristic. It then advertises this service, making it discoverable and accessible by BLE clients like your smartphone.

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");

  BLEDevice::init("MyESP32");  // set the device name
  BLEServer *pServer = BLEDevice::createServer();
  BLEService *pService = pServer->createService(SERVICE_UUID);
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );

  pCharacteristic->setValue("Hello World!");
  pService->start();
  // BLEAdvertising *pAdvertising = pServer->getAdvertising();  // this still is working for backward compatibility
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMinPreferred(0x12);
  BLEDevice::startAdvertising();
  Serial.println("Characteristic defined! Now you can read it in your phone!");
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(2000);
}

Code Explanation:

Let�s go over the BLE_server code in detail. It starts by including the necessary libraries for BLE operations on the ESP32.

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

Next, UUIDs for the Service and the Characteristic are defined. UUIDs (Universally Unique Identifiers) uniquely identify services and characteristics in BLE.

You can use the default UUIDs or go to uuidgenerator.net to generate random UUIDs.

#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

In the setup, the serial communication is initialized at a baud rate of 115200.

Serial.begin(115200);

Then, a BLE device called MyESP32 is created. You are free to change the name to whatever you want.

// Create the BLE Device
BLEDevice::init("MyESP32");

After that, our BLE device is configured as a server.

BLEServer *pServer = BLEDevice::createServer();

Next, a new service for our server is created with the previously defined UUID.

BLEService *pService = pServer->createService(SERVICE_UUID);

Then, a new characteristic for the service is created. As you can see, the properties of the characteristic, in this case READ and WRITE, are passed as arguments along with the CHARACTERISTIC_UUID.

BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                     CHARACTERISTIC_UUID,
                                     BLECharacteristic::PROPERTY_READ |
                                     BLECharacteristic::PROPERTY_WRITE
                                     );

After creating the characteristic, use the setValue() method to set its value. In this case, the value is set to the text �Hello World!�. You can change this text to whatever you want; it could be a sensor reading, the state of a button, or anything else.

pCharacteristic->setValue("Hello World!");

Next, the service is started so that it can respond to incoming requests.

pService->start();

Finally, advertising parameters are configured, and the service is then advertised. Advertising makes the ESP32 discoverable by other BLE devices.

BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06);
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();

This is a simple example of how to set up a BLE server, so nothing is executed within the loop(). Depending on the application, however, you can include actions for when a new client connects (refer to the BLE_notify example for guidance).

void loop() {
  delay(2000);
}

Using the nRF Connect to Test

To test the BLE connection, you�ll have to pair the ESP32 with your smartphone. You�ll also need a Bluetooth debugging application installed on it. There are several options available; one of our favorites is Nordic�s nRF Connect, which is available for both iOS and Android devices. It�s a powerful tool that allows you to scan and explore your BLE devices and communicate with them.

1. Go to the Google Play Store or the Apple App Store and search for �nRF Connect for Mobile�. Install the app and open it.

nrf connect for mobile app

2. Ensure that your phone�s Bluetooth is turned on.

turn mobile bluetooth on

3. In the app, tap on the �SCAN� button. The app will start scanning for nearby BLE devices.

scan for nearby ble devices

4. A list of available devices with their respective signal strengths and other details will appear. Look for �MyESP32�, and click the �Connect� button next to that.

connect to esp32 ble

5. You�ll be sent over to the �Services� view. There, you�ll see a list of available services. Click �Unknown Service��the UUID string should match SERVICE_UUID in your example code.

look for service uuid

6. Tap on a service to see its associated characteristics. Next to each characteristic, there will be two icons. The down arrow allows you to read a characteristic, whereas the up arrow allows you to write to it.

click down arrow to read the ble characteristic

7. Tap on the down arrow to read the characteristic. You�ll see the characteristic�s UUID, READ and WRITE properties, and the value �Hello World!�, exactly as specified in our code.

read the ble characteristic value

Most people associate the ESP family of microcontrollers with WiFi, which makes sense, as they�ve become the go-to solution for getting your project online quickly and easily. However, while WiFi capability might be the star of the show, the ESP32 is also equipped with a CAN controller. It�s just that we don�t see it being used as frequently.

Learning how to read and parse CAN messages on your ESP32 can be incredibly useful. For instance, you can obtain data from your car, such as coolant temperature, throttle position, vehicle speed, and engine rpm, and display them on a self-hosted web page, accessible over WiFi. Or, you can create your own CAN network.

In this tutorial, we�re going to cover the essentials. You�ll learn about the basics of the CAN bus system, how to configure the ESP32�s integrated CAN bus controller, how to connect the ESP32 with the TJA1050 external CAN transceiver, and importantly, how to make two ESP32 boards communicate via the CAN bus.

Basics of CAN Bus System

In the old days, cars had more than 2,000 meters of cable in them, connecting switches on the dashboard directly to the headlights and taillights, for instance. As cars became more complex, this simple logic would no longer work.

In 1986, Bosch introduced the CAN bus system, which solved this problem and made car manufacturing cheaper and easier. CAN is now the industry standard and is used in everything from cars and trucks to buses and tractors, and even airplanes and ships.

To better understand the CAN bus, think of your car as a human body. In this context, the CAN bus is the nervous system.

CAN Bus Nervous System Analogy

Similar to how the nervous system allows communication between different parts of the body, the CAN bus allows communication between different CAN bus Nodes, also known as Electronic Control Units (or ECUs).

A modern car has over 70 ECUs, each of which is responsible for performing a specific task. Although these ECUs can perform a single task efficiently, they must share information with one another. For example, the engine control module sends the current engine speed to the instrument cluster, where it is displayed on a tachometer; similarly, the driver�s door controller sends a message to the passenger�s door controller to actuate the window.

Car ECU CAN Network

ECUs are connected to the bus in a multi-master configuration. Meaning, each ECU can take control of the bus and broadcast information (such as sensor data) over it. The broadcasted data is accepted by all other ECUs on the CAN bus. Each ECU can then read the data and decide whether to accept it or ignore it.

CAN Bus Topology

The physical communication happens via the CAN bus wiring harness, consisting of two wires, CAN low and CAN high. Both wires are twisted tightly together so that electromagnetic interference affects the signal in both wires uniformly, thereby minimizing errors.

CAN Twisted Pair

The far ends of the cable are terminated with 120-ohm resistors. Because the CAN bus is a high-speed data bus, if the bus is not terminated, the signal will reflect back and interfere with the next data signal coming down the line, potentially disrupting communications and causing the bus to fail.

CAN Bus

CAN Signaling

To enable data transmission on these wires, their voltage levels are changed. These changes in voltage levels are then translated to logic levels enabling nodes on the network to communicate with one another.

CAN Signaling

To transmit a �logic 1� on the CAN bus, the voltage on both lines is set to 2.5 volts (i.e. there is no voltage difference). This state is known as the Recessive State, and it indicates that the CAN bus is available for use by any node.

In contrast, to transmit �logic 0�, the CAN high line is set to 3.5 volts and the CAN low line to 1.5 volts (i.e. there is a 2V voltage difference). This state of the bus is known as the Dominant State, which tells every node on the bus that another node is transmitting and that it should wait until the transmission is finished before transmitting its message.

CAN Bus Node

Each CAN node contains a CAN transceiver, a CAN controller, and a microcontroller.

CAN Node ECU

A CAN transceiver

  • When receiving: It converts the voltage levels on the CAN bus to levels that the CAN controller can understand.
  • When transmitting: It converts the data stream from the CAN controller to CAN bus levels.

A CAN controller

  • When transmitting: It transmits the message from the microcontroller serially onto the bus when the bus is free.
  • When receiving: It stores the received serial bits from the bus until an entire message is available, and instructs the microcontroller to retrieve it (usually by triggering an interrupt).

A microcontroller

It decides what the received messages mean and what messages it wants to transmit. Sensors, actuators and control devices are connected to it.

A Standard CAN Frame

Communication over the CAN bus is done via CAN frames. Here is a standard CAN frame with an 11 bit identifier. Let�s take a quick look at each of the eight message fields:

CAN Frame
  • SOF: The Start of Frame is a �dominant 0� to tell the other nodes that a CAN node intends to talk.
  • ID: The ID is the frame identifier. It is used to specify what the message means, and who�s sending it. The ID also defines the priority: the lower the ID, the higher the message�s priority.
  • RTR: The Remote Transmission Request indicates whether a node sends data or requests data from another node.
  • Control: The Control contains the Identifier Extension Bit (IDE) which is a �dominant 0� for 11-bit. It also contains the 4 bit Data Length Code (DLC) that specifies how many bytes of data will be in the message.
  • Data: The 8 bytes of data contain the actual information.
  • CRC: The Cyclic Redundancy Check is for error detection.
  • ACK: The ACK slot indicates if the node has acknowledged and received the data correctly.
  • EOF: The EOF marks the end of the CAN frame.

The CAN bus is unique in that it is a message-based protocol. Typically, on a distributed network, each device has a unique ID to distinguish it from other devices on the same bus, and messages are sent from device A to device B based on their IDs.

On the contrary, nodes on the CAN Bus do not have IDs. Rather, each message is assigned a unique CAN ID that indicates what the message is about. All nodes receive all messages, and each node filters the messages that are relevant to it.

ESP32 CAN Controller a.k.a. TWAI Controller

Espressif Systems refers to its CAN bus-compatible controller as TWAI, which stands for Two-Wire Automotive Interface.

Now, you might wonder why they made this change from the more familiar term CAN to TWAI. It�s a common question many have asked since Espressif decided to rename it.

Previously, Espressif used the term CAN in all its documentation to describe this feature. However, in their most recent documentation, they have begun to use the term TWAI. This change might seem a bit confusing at first, but keep in mind that TWAI and CAN basically mean the same thing when it comes to the ESP32.

The TWAI controller is designed to be compatible with the CAN 2.0 protocol. It supports both Standard Frame Format (11-bit ID) and Extended Frame Format (29-bit ID).

It includes features for error detection and handling, ensuring robust communication in noisy environments, which is particularly crucial in automotive applications.

The TWAI controller also supports interrupt-driven communication, which allows for efficient handling of messages and events on the CAN bus without needing to continuously poll the status.

Block Diagram

The diagram below shows the major functional blocks of the TWAI Controller.

esp32 can twai controller functional block diagram

Signal Lines

The TWAI controller�s interface consists of four signal lines: TX, RX, BUS-OFF, and CLKOUT. A nice thing about the ESP32 is that all four signals can be mapped to any GPIO pin.

  • TX and RX: The TX and RX signal lines must be connected to an external transceiver. Both signal lines represent/interpret a dominant bit as a low logic level (0 V), and a recessive bit as a high logic level (3.3 V).
  • BUS-OFF: The BUS-OFF signal line is not always necessary, but it�s useful in certain situations. This line is set to a low logic level (0 V), when the TWAI controller enters a bus-off state, indicating a disruption in communication. In normal operation, the BUS-OFF line maintains a high logic level (3.3 V).
  • CLKOUT: The CLKOUT signal line is another optional feature. It provides an output of a prescaled version of the controller�s source clock.

Operating Modes

The TWAI supports the following modes of operations:

  • Normal Mode: In this mode, the TWAI controller can participate in bus activities such as transmitting and receiving messages/error frames. When transmitting a message, acknowledgement from another node is required.
  • No Ack Mode: This mode is similar to the normal mode, with one key difference: transmissions are considered successful without the need for an acknowledgement. This mode is particularly useful for self-testing the TWAI controller, as it allows for loopback of transmissions without external confirmations.
  • Listen Only Mode: In this mode, the TWAI controller takes a passive role. It does not influence the bus, meaning it will not transmit any messages, acknowledgements, or error frames. The controller can still receive messages but will not acknowledge them. This mode is suited for bus monitoring applications.

Features

The main features of the ESP32 TWAI controller are:

  • Compatible with ISO 11898-1 protocol (CAN Specification 2.0)
  • Supports Standard Frame Format (11-bit ID) and Extended Frame Format (29-bit ID)
  • Bit rates:
    • from 25 Kbit/s to 1 Mbit/s in chip revision v0.0/v1.0/v1.1
    • from 12.5 Kbit/s to 1 Mbit/s in chip revision v3.0/v3.1
  • Multiple modes of operation
    • Normal
    • Listen Only (no influence on the bus)
    • Self Test (transmissions do not require acknowledgment)
  • 64-byte Receive FIFO
  • Special transmissions
    • Single-shot transmissions (does not automatically re-transmit upon error)
    • Self-Reception (the TWAI controller transmits and receives messages simultaneously)
  • Acceptance Filter (supports single and dual filter modes)
  • Error detection and handling
    • Error counters
    • Configurable Error Warning Limit
    • Error Code Capture
    • Arbitration Lost Capture

An excellent resource for learning about the ESP32 TWAI controller is the ESP32 Technical Reference Manual. It includes a section that is dedicated to the TWAI, providing extensive information and guidance.

TJA1050 High-Speed CAN Transceiver

Although the ESP32 has a built-in CAN bus-compatible controller, it doesn�t have a built-in CAN transceiver, so we must use an external one to connect to a CAN network.

The type of external transceiver you�ll need depends on the specific requirements of your project. For example, if you need compatibility with ISO 11898-2 standards, then something like the TJA1050 High-Speed CAN transceiver from NXP or SN65HVD23x from Texas Instruments would be excellent choices.

In this tutorial, we�ll be using the TJA1050 CAN bus transceiver module.

tja1050 can transceiver

This module serves as an interface between the ESP32 and the physical two-wire CAN bus, and meets the automotive requirements for high-speed (up to 1Mb/s), low quiescent current, electromagnetic compatibility, and electrostatic discharge.

For more information about the TJA1050 CAN transceiver, please refer to the datasheet below.

Hardware Hookup

Now that we know everything about the TWAI controller, let�s construct our own CAN network.

Example 1: Simple two-node CAN Network

In this example, a simple two-node CAN bus network is constructed�one node transmits a message, the other receives it.

To begin, take the TJA1050 CAN transceiver module and connect its VCC pin to the VIN on your ESP32. Then, connect the GND pin to the ground. Next, connect the TX to GPIO5 and the RX to GPIO4 on the ESP32.

You�ll need to create two of these circuits. They�ll be identical in wiring. One will function as the transmitter, sending out messages, and the other will serve as the receiver, picking up those messages.

Connecting the modules is straightforward: CAN L connects to CAN L and CAN H connects to CAN H. The wires should ideally be twisted pair, but for simple breadboard testing or other short runs, this is not required.

It�s important to keep in mind that as the bus length increases or environmental electrical noise increases, using twisted pair and adding shielding becomes more important.

Construct the network as shown.

esp32 two node can network wiring

Example 2: Multi-Node CAN Network

In this example, a larger CAN network is constructed�multiple nodes send messages and one node relays them to a PC over a serial port.

Other nodes can be added between the two end nodes. These can be spliced in-line or attached to the main bus using a short stub cable, as long as the length is kept under 12 inches.

One important note about the TJA1050 CAN transceiver module is that it comes with a 120 Ohm terminating resistor already soldered on. So for these additional nodes in your network, you�ll need to remove this resistor.

Construct the network as shown.

esp32 multi node can network wiring

Library Installation

The Arduino CAN library by Sandeep Mistry is really an excellent library for working with the TWAI controller. You will need to download and install it in your Arduino IDE.

To install the library, navigate to Sketch > Include Library > Manage Libraries� Wait for the Library Manager to download the library index and update the list of installed libraries.

Manage Libraries 2

Filter your search by entering �mcp2515�. Look for CAN by Sandeep Mistry. Click on that entry and then choose Install.

CAN Arduino Library Installation

Example Code

In this simple test, we�ll attempt to transmit a �Hello World� message on the CAN bus to see if it can be decoded. It will help you learn how to use the modules and can serve as the foundation for more practical experiments and projects.

Code for the Transmitter Node

Upload this sketch to the transmitter node.

If you have multiple nodes on a CAN bus, upload this sketch to each of the transmitter nodes. Make sure to change the message IDs to unique values for each node.

#include <CAN.h>

#define TX_GPIO_NUM   5
#define RX_GPIO_NUM   4

void setup() {
  Serial.begin (115200);
  while (!Serial);
  delay (1000);

  Serial.println ("CAN Sender");

  // Set the pins
  CAN.setPins (RX_GPIO_NUM, TX_GPIO_NUM);

  // start the CAN bus at 500 kbps
  if (!CAN.begin(500E3)) {
    Serial.println("Starting CAN failed!");
    while (1);
  }
}

void loop() {
  // send packet: id is 11 bits, packet can contain up to 8 bytes of data
  Serial.print("Sending packet ... ");

  CAN.beginPacket(0x12);
  CAN.write('h');
  CAN.write('e');
  CAN.write('l');
  CAN.write('l');
  CAN.write('o');
  CAN.endPacket();

  Serial.println("done");

  delay(1000);

  // send extended packet: id is 29 bits, packet can contain up to 8 bytes of data
  Serial.print("Sending extended packet ... ");

  CAN.beginExtendedPacket(0xabcdef);
  CAN.write('w');
  CAN.write('o');
  CAN.write('r');
  CAN.write('l');
  CAN.write('d');
  CAN.endPacket();

  Serial.println("done");

  delay(1000);
}

Code for the Receiver Node

Upload this sketch to the receiver node.

#include <CAN.h>

#define TX_GPIO_NUM   5
#define RX_GPIO_NUM   4

void setup() {
  Serial.begin (115200);
  while (!Serial);
  delay (1000);

  Serial.println ("CAN Receiver");

  // Set the pins
  CAN.setPins (RX_GPIO_NUM, TX_GPIO_NUM);

  // start the CAN bus at 500 kbps
  if (!CAN.begin(500E3)) {
    Serial.println("Starting CAN failed!");
    while (1);
  }
}

void loop() {
  // try to parse packet
  int packetSize = CAN.parsePacket();

  if (packetSize) {
    // received a packet
    Serial.print ("Received ");

    if (CAN.packetExtended()) {
      Serial.print ("extended ");
    }

    if (CAN.packetRtr()) {
      // Remote transmission request, packet contains no data
      Serial.print ("RTR ");
    }

    Serial.print ("packet with id 0x");
    Serial.print (CAN.packetId(), HEX);

    if (CAN.packetRtr()) {
      Serial.print (" and requested length ");
      Serial.println (CAN.packetDlc());
    } else {
      Serial.print (" and length ");
      Serial.println (packetSize);

      // only print packet data for non-RTR packets
      while (CAN.available()) {
        Serial.print ((char) CAN.read());
      }
      Serial.println();
    }

    Serial.println();
  }
}

Demonstration

After uploading the sketch, open the serial monitor at a baud rate of 115200. The transmitter node sends a standard CAN packet and an extended CAN packet every second.

esp32 can transmitter node output

The receiver node receives it and passes it on to the PC over the serial port.

esp32 can receiver node output

The ESP32, the newly released successor to the ESP8266, has been a rising star in IoT or WiFi-related projects. It�s a low-cost WiFi module that can be programmed to run a standalone web server with a little extra work. How cool is that?

This tutorial will walk you through the process of creating a simple ESP32 web server in the Arduino IDE.�So, let�s get started.

What exactly is a Web server and how does it work?

A web server is a place where web pages are stored, processed, and served to web clients. A web client is just a web browser that we use on our computers and phones. A web client and a web server communicate using a special protocol known as Hypertext Transfer Protocol (HTTP).

HTTP Web Server Client Illustration

In this protocol, a client starts a conversation by sending an HTTP request for a specific web page. The server then sends back the content of that web page or an error message if it can�t find it (like the famous 404 Error).

ESP32 Operating Modes

One of the most useful features of the ESP32 is its ability to not only connect to an existing WiFi network and act as a Web Server, but also to create its own network, allowing other devices to connect directly to it and access web pages. This is possible because the ESP32 can operate in three modes: Station (STA) mode, Soft Access Point (AP) mode, and both simultaneously.

Station (STA) Mode

In Station (STA) mode, the ESP32 connects to an existing WiFi network (the one created by your wireless router).

ESP32 Web Server Station STA Mode Demonstration

In STA mode, the ESP32 obtains an IP address from the wireless router to which it is connected. With this IP address, it can set up a web server and serve web pages to all connected devices on the existing WiFi network.

Soft Access Point (AP) Mode

In Access Point (AP) mode, the ESP32 sets up its own WiFi network and acts as a hub, just like a WiFi router. However, unlike a WiFi router, it does not have an interface to a wired network. So, this mode of operation is called Soft Access Point (soft-AP). Also, no more than five stations can connect to it at the same time.

ESP32 Web Server Soft Access Point AP Mode Demonstration

In AP mode, the ESP32 creates a new WiFi network and assigns it an SSID (the network�s name) and an IP address. With this IP address, it can serve web pages to all connected devices.

Wiring LEDs to an ESP32

Now that we understand the fundamentals of how a web server works and the modes in which the ESP32 can create one, it�s time to connect some LEDs to the ESP32 that we want to control via WiFi.

Begin by placing the ESP32 on your breadboard, making sure that each side of the board is on a different side of the breadboard. Next, connect two LEDs to digital GPIO 4 and 5 using a 220? current limiting resistor.

When you are finished, you should have something that looks like the image below.

Simple ESP32 Web Server Wiring Fritzing Connections with LED
Wiring LEDs to ESP32

The Idea Behind Using an ESP32 Web Server to Control Things

So you might be wondering, �How do I control things from a web server that only processes and serves web pages?�.

It�s extremely simple. We�re going to control things by visiting a specific URL.

When you enter a URL into a web browser, it sends an HTTP request (also known as a GET request) to a web server. It is the web server�s responsibility to handle this request.

Assume you entered a URL like http://192.168.1.1/ledon into a browser. The browser then sends an HTTP request to the ESP32. When the ESP32 receives this request, it recognizes that the user wishes to turn on the LED. As a result, it turns on the LED and sends a dynamic webpage to a browser that displays the LED�s status as �on.� Quite simple, right?

Example 1 � Configuring the ESP32 Web Server in Access Point (AP) mode

Let�s get to the interesting stuff now!

This example, as the title suggests, shows how to configure the ESP32 Web Server in Access Point (AP) mode and serve web pages to any connected client. To begin, connect your ESP32 to your computer and run the sketch. Then we will look at it in more detail.

#include <WiFi.h>
#include <WebServer.h>

/* Put your SSID & Password */
const char* ssid = "ESP32";  // Enter SSID here
const char* password = "12345678";  //Enter Password here

/* Put IP Address details */
IPAddress local_ip(192,168,1,1);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);

WebServer server(80);

uint8_t LED1pin = 4;
bool LED1status = LOW;

uint8_t LED2pin = 5;
bool LED2status = LOW;

void setup() {
  Serial.begin(115200);
  pinMode(LED1pin, OUTPUT);
  pinMode(LED2pin, OUTPUT);

  WiFi.softAP(ssid, password);
  WiFi.softAPConfig(local_ip, gateway, subnet);
  delay(100);
  
  server.on("/", handle_OnConnect);
  server.on("/led1on", handle_led1on);
  server.on("/led1off", handle_led1off);
  server.on("/led2on", handle_led2on);
  server.on("/led2off", handle_led2off);
  server.onNotFound(handle_NotFound);
  
  server.begin();
  Serial.println("HTTP server started");
}
void loop() {
  server.handleClient();
  if(LED1status)
  {digitalWrite(LED1pin, HIGH);}
  else
  {digitalWrite(LED1pin, LOW);}
  
  if(LED2status)
  {digitalWrite(LED2pin, HIGH);}
  else
  {digitalWrite(LED2pin, LOW);}
}

void handle_OnConnect() {
  LED1status = LOW;
  LED2status = LOW;
  Serial.println("GPIO4 Status: OFF | GPIO5 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,LED2status)); 
}

void handle_led1on() {
  LED1status = HIGH;
  Serial.println("GPIO4 Status: ON");
  server.send(200, "text/html", SendHTML(true,LED2status)); 
}

void handle_led1off() {
  LED1status = LOW;
  Serial.println("GPIO4 Status: OFF");
  server.send(200, "text/html", SendHTML(false,LED2status)); 
}

void handle_led2on() {
  LED2status = HIGH;
  Serial.println("GPIO5 Status: ON");
  server.send(200, "text/html", SendHTML(LED1status,true)); 
}

void handle_led2off() {
  LED2status = LOW;
  Serial.println("GPIO5 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,false)); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

String SendHTML(uint8_t led1stat,uint8_t led2stat){
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr +="<title>LED Control</title>\n";
  ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
  ptr +=".button {display: block;width: 80px;background-color: #3498db;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
  ptr +=".button-on {background-color: #3498db;}\n";
  ptr +=".button-on:active {background-color: #2980b9;}\n";
  ptr +=".button-off {background-color: #34495e;}\n";
  ptr +=".button-off:active {background-color: #2c3e50;}\n";
  ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr +="</style>\n";
  ptr +="</head>\n";
  ptr +="<body>\n";
  ptr +="<h1>ESP32 Web Server</h1>\n";
  ptr +="<h3>Using Access Point(AP) Mode</h3>\n";
  
   if(led1stat)
  {ptr +="<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n";}
  else
  {ptr +="<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";}

  if(led2stat)
  {ptr +="<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n";}
  else
  {ptr +="<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";}

  ptr +="</body>\n";
  ptr +="</html>\n";
  return ptr;
}

Accessing the Web Server in AP Mode

After uploading the sketch, open the Serial Monitor at 115200 baud and press the RESET button on the ESP32. If everything is fine, it will show the �HTTP server started� �message.

ESP32 Web Server Access Point Mode Serial Monitor Output - Server Started

Now, get a phone, laptop, or other device that can connect to a WiFi network, and look for a network called �ESP32�. Connect to the network using the password 12345678.

ESP32 Web Server Access Point Mode - Joining Server

After connecting to your ESP32 AP network, open a browser and navigate to 192.168.1.1. The ESP32 should return a web page displaying the current status of the LEDs and buttons. At the same time, you can check the serial monitor to see the status of the ESP32�s GPIO pins.

ESP32 Web Server Access Point Mode - Web Page
ESP32 Web Server Access Point Mode Serial Monitor Output - Webpage Accessed

Now, while keeping an eye on the URL, click the button to turn LED1 ON. Once you click the button, the ESP32 receives a request for the /led1on URL. It then turns on LED1 and serves a web page with the LED status updated. It also prints the GPIO pin status on the serial monitor.

ESP32 Web Server Access Point Mode Web Page - LED Control
ESP32 Web Server Access Point Mode Serial Monitor Output - LED Control

You can test the LED2 button to see if it works similarly.

Let�s take a closer look at the code to see how it works so you can modify it to suit your needs.

Detailed Code Explanation

The sketch begins by including the WiFi.h library. This library contains ESP32-specific methods that we use to connect to the network. Following that, we include the WebServer.h library, which contains some methods that will assist us in configuring a server and handling incoming HTTP requests without having to worry about low-level implementation details.

#include <WiFi.h>
#include <WebServer.h>

Because we are configuring the ESP32 web server in Access Point (AP) mode, it will create its own WiFi network. So, we need to set the SSID, password, IP address, IP subnet mask, and IP gateway.

/* Put your SSID & Password */
const char* ssid = "ESP32";  // Enter SSID here
const char* password = "12345678";  //Enter Password here

/* Put IP Address details */
IPAddress local_ip(192,168,1,1);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);

Following that, we create an object of the WebServer library so that we can access its functions. The constructor of this object accepts as a parameter the port to which the server will be listening. Since HTTP uses port 80 by default, we�ll use this value. This allows us to connect to the server without specifying the port in the URL.

// declare an object of WebServer library
WebServer server(80);

Next, we declare the ESP32�s GPIO pins to which LEDs are connected, as well as their initial state.

uint8_t LED1pin = 4;
bool LED1status = LOW;

uint8_t LED2pin = 5;
bool LED2status = LOW;

Inside the Setup() Function

In the setup function, we configure the ESP32 Web Server in soft Access Point (AP) mode. First, we establish a serial connection for debugging purposes and configure the GPIO pins to behave as an OUTPUT.

Serial.begin(115200);
pinMode(LED1pin, OUTPUT);
pinMode(LED2pin, OUTPUT);

Then, we configure a soft access point to create a Wi-Fi network by providing an SSID, password, IP address, IP subnet mask, and IP gateway.

WiFi.softAP(ssid, password);
WiFi.softAPConfig(local_ip, gateway, subnet);
delay(100);

To handle incoming HTTP requests, we must specify which code should be executed when a specific URL is accessed. For this, we use the .on() method. This method accepts two parameters: a relative URL path and the name of the function to be executed when that URL is visited.

The first line of the code snippet below, for example, indicates that when a server receives an HTTP request on the root (/) path, it will call the handle_OnConnect() function. It is important to note that the URL specified is a relative path.

Similarly, we must specify four more URLs to handle the two states of two LEDs.

server.on("/", handle_OnConnect);
server.on("/led1on", handle_led1on);
server.on("/led1off", handle_led1off);
server.on("/led2on", handle_led2on);
server.on("/led2off", handle_led2off);

We haven�t specified what the server should serve if the client requests a URL that isn�t specified with server.on() . It should give a 404 error (Page Not Found) as a response. To accomplish this, we use the server.onNotFound() method.

server.onNotFound(handle_NotFound);

Now, to start the server, we call the server object�s begin() method.

server.begin();
Serial.println("HTTP server started");

Inside the Loop() Function

Actual incoming HTTP requests are handled in the loop function. For this, we use the server object�s handleClient() method. We also change the state of LEDs based on the request.

void loop() {
  server.handleClient();
  if(LED1status)
  {digitalWrite(LED1pin, HIGH);}
  else
  {digitalWrite(LED1pin, LOW);}
  
  if(LED2status)
  {digitalWrite(LED2pin, HIGH);}
  else
  {digitalWrite(LED2pin, LOW);}
}

Now we must write the handle_OnConnect() function, which we previously attached to the root (/) URL with server.on. We begin this function by setting the status of both LEDs to LOW (initial state of LEDs) and printing it on the serial monitor.

We use the send method to respond to an HTTP request. Although the method can be called with a number of different arguments, the simplest form requires the HTTP response code, the content type, and the content.

The first parameter we pass to the send method is the code 200 (one of the HTTP status codes), which corresponds to the OK response. Then we specify the content type as �text/html,� and finally we pass the SendHTML() custom function, which generates a dynamic HTML page with the LED status.

void handle_OnConnect() {
  LED1status = LOW;
  LED2status = LOW;
  Serial.println("GPIO4 Status: OFF | GPIO5 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,LED2status)); 
}

Similarly, we write five more functions to handle LED ON/OFF requests and the 404 Error page.

void handle_led1on() {
  LED1status = HIGH;
  Serial.println("GPIO4 Status: ON");
  server.send(200, "text/html", SendHTML(true,LED2status)); 
}

void handle_led1off() {
  LED1status = LOW;
  Serial.println("GPIO4 Status: OFF");
  server.send(200, "text/html", SendHTML(false,LED2status)); 
}

void handle_led2on() {
  LED2status = HIGH;
  Serial.println("GPIO5 Status: ON");
  server.send(200, "text/html", SendHTML(LED1status,true)); 
}

void handle_led2off() {
  LED2status = LOW;
  Serial.println("GPIO5 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,false)); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

Displaying the HTML Web Page

Whenever the ESP32 web server receives a request from a web client, the sendHTML() function generates a web page. It simply concatenates HTML code into a long string and returns to the server.send() function we discussed earlier. The function uses the status of the LEDs as a parameter to generate HTML content dynamically.

The first text you should always send is the <!DOCTYPE> declaration, which indicates that we�re sending HTML code.

String SendHTML(uint8_t led1stat,uint8_t led2stat){
String ptr = "<!DOCTYPE html> <html>\n";

The <meta> viewport element makes the web page responsive, ensuring that it looks good on all devices. The title tag determines the page�s title.

ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
ptr +="<title>LED Control</title>\n";

Styling the Web Page

Following that, we have some CSS to style the buttons and the overall appearance of the web page. We selected the Helvetica font and defined the content to be displayed as an inline-block, center-aligned.

ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";

The code that follows then sets the color, font, and margin around the body, H1, H3, and p tags.

ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";

The buttons are also styled with properties such as color, size, margin, and so on. The :active selector changes the look of buttons while they are being clicked.

ptr +=".button {display: block;width: 80px;background-color: #3498db;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
ptr +=".button-on {background-color: #3498db;}\n";
ptr +=".button-on:active {background-color: #2980b9;}\n";
ptr +=".button-off {background-color: #34495e;}\n";
ptr +=".button-off:active {background-color: #2c3e50;}\n";

Setting the Web Page Heading

Next, the heading of the web page is set. You can change this text to anything that fits your application.

ptr +="<h1>ESP32 Web Server</h1>\n";
ptr +="<h3>Using Access Point(AP) Mode</h3>\n";

Displaying the Buttons and Corresponding State

The if statement is used to dynamically update the status of the buttons and LEDs.

if(led1stat)
 {ptr +="<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n";}
else
 {ptr +="<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";}

if(led2stat)
 {ptr +="<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n";}
else
 {ptr +="<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";}

Example 2 � Configuring the ESP32 Web Server in WiFi Station (STA) mode

Let�s move on to the next example, which shows how to configure the ESP32 web server in Station (STA) mode and serve web pages to any connected client on the existing network.

Before you proceed with uploading the sketch, you must make some changes to ensure that it works for you. To connect ESP32 to an existing network, you must modify the following two variables with your network credentials.

Change SSID & Password before trying STA mode web server sketch

Once you�re done, go ahead and try out the sketch.

#include <WiFi.h>
#include <WebServer.h>

/*Put your SSID & Password*/
const char* ssid = " YourNetworkName";  // Enter SSID here
const char* password = " YourPassword";  //Enter Password here

WebServer server(80);

uint8_t LED1pin = 4;
bool LED1status = LOW;

uint8_t LED2pin = 5;
bool LED2status = LOW;

void setup() {
  Serial.begin(115200);
  delay(100);
  pinMode(LED1pin, OUTPUT);
  pinMode(LED2pin, OUTPUT);

  Serial.println("Connecting to ");
  Serial.println(ssid);

  //connect to your local wi-fi network
  WiFi.begin(ssid, password);

  //check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

  server.on("/", handle_OnConnect);
  server.on("/led1on", handle_led1on);
  server.on("/led1off", handle_led1off);
  server.on("/led2on", handle_led2on);
  server.on("/led2off", handle_led2off);
  server.onNotFound(handle_NotFound);

  server.begin();
  Serial.println("HTTP server started");
}
void loop() {
  server.handleClient();
  if(LED1status)
  {digitalWrite(LED1pin, HIGH);}
  else
  {digitalWrite(LED1pin, LOW);}
  
  if(LED2status)
  {digitalWrite(LED2pin, HIGH);}
  else
  {digitalWrite(LED2pin, LOW);}
}

void handle_OnConnect() {
  LED1status = LOW;
  LED2status = LOW;
  Serial.println("GPIO4 Status: OFF | GPIO5 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,LED2status)); 
}

void handle_led1on() {
  LED1status = HIGH;
  Serial.println("GPIO4 Status: ON");
  server.send(200, "text/html", SendHTML(true,LED2status)); 
}

void handle_led1off() {
  LED1status = LOW;
  Serial.println("GPIO4 Status: OFF");
  server.send(200, "text/html", SendHTML(false,LED2status)); 
}

void handle_led2on() {
  LED2status = HIGH;
  Serial.println("GPIO5 Status: ON");
  server.send(200, "text/html", SendHTML(LED1status,true)); 
}

void handle_led2off() {
  LED2status = LOW;
  Serial.println("GPIO5 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,false)); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

String SendHTML(uint8_t led1stat,uint8_t led2stat){
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr +="<title>LED Control</title>\n";
  ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
  ptr +=".button {display: block;width: 80px;background-color: #3498db;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
  ptr +=".button-on {background-color: #3498db;}\n";
  ptr +=".button-on:active {background-color: #2980b9;}\n";
  ptr +=".button-off {background-color: #34495e;}\n";
  ptr +=".button-off:active {background-color: #2c3e50;}\n";
  ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr +="</style>\n";
  ptr +="</head>\n";
  ptr +="<body>\n";
  ptr +="<h1>ESP32 Web Server</h1>\n";
    ptr +="<h3>Using Station(STA) Mode</h3>\n";
  
   if(led1stat)
  {ptr +="<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n";}
  else
  {ptr +="<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";}

  if(led2stat)
  {ptr +="<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n";}
  else
  {ptr +="<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";}

  ptr +="</body>\n";
  ptr +="</html>\n";
  return ptr;
}

Accessing the Web Server in STA mode

After uploading the sketch, open the Serial Monitor at 115200 baud and press the RESET button on the ESP32. If everything is fine, it will display the dynamic IP address obtained from your router as well as the �HTTP server started� message.

ESP32 Web Server Station Mode Serial Monitor Output - Server Started

Next, launch a browser and navigate to the IP address displayed on the serial monitor. The ESP32 should display a web page with the current status of the LEDs and two buttons for controlling them. At the same time, you can check the serial monitor to see the status of the ESP32�s GPIO pins.

ESP32 Web Server Station Mode - Web Page
ESP32 Web Server Station Mode Serial Monitor Output - Webpage Accessed

Now, while keeping an eye on the URL, click the button to turn LED1 ON. Once you click the button, the ESP32 receives a request for the /led1on URL. It then turns on LED1 and serves a web page with the LED status updated. It also prints the GPIO pin status on the serial monitor.

ESP32 Web Server Station Mode Web Page - LED Control
ESP32 Web Server Station Mode Serial Monitor Output - LED Control

You can test the LED2 button to see if it works similarly.

Let�s take a closer look at the code to see how it works so you can modify it to suit your needs.

Code Explanation

The only difference between this code and the previous code is that we are not creating our own WiFi network but rather joining an existing network using the WiFi.begin() function.

//connect to your local wi-fi network
WiFi.begin(ssid, password);

While the ESP32 attempts to connect to the network, we can use the WiFi.status() function to check the connectivity status.

//check wi-fi is connected to wi-fi network
while (WiFi.status() != WL_CONNECTED) 
{
delay(1000);
Serial.print(".");
}

For your information, this function returns the following statuses:

  • WL_CONNECTED: when connected to a Wi-Fi network
  • WL_NO_SHIELD: when no Wi-Fi shield is present
  • WL_IDLE_STATUS: a temporary status assigned when WiFi.begin() is called and remains active until the number of attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED)
  • WL_NO_SSID_AVAIL: when no SSID are available
  • WL_SCAN_COMPLETED: when the scan networks is completed
  • WL_CONNECT_FAILED: when the connection fails for all the attempts
  • WL_CONNECTION_LOST: when the connection is lost
  • WL_DISCONNECTED: when disconnected from a network

Once connected to the network, the WiFi.localIP() function is used to print the ESP32�s IP address.

Serial.println("");
Serial.println("WiFi connected..!");
Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

The only difference between AP and STA mode is that one creates its own network while the other joins an existing one. So, the rest of the code for handling HTTP requests and serving web pages in STA mode is the same as explained above for AP mode. This includes the following:

  • Declaring ESP32�s GPIO pins to which LEDs are connected
  • Defining multiple server.on() methods to handle incoming HTTP requests
  • Defining server.onNotFound() method to handle HTTP 404 error
  • Creating custom functions that are executed when specific URL is hit
  • Creating HTML page
  • Styling the web page
  • Creating buttons and displaying their status

Have you ever wished you could use your mobile, tablet, or PC to monitor the temperature and humidity in your home, walk-in cooler, or wine cellar at any time? Then this IoT project could be a great place to start!

This project employs the ESP32 as the control device, which connects to an existing WiFi network and creates a Web Server. When a device connects to this web server, the ESP32 will read the temperature and relative humidity from the DHT11/DHT22 sensor and send it to the device�s web browser with a nice interface. Excited? Let�s get started!

There are a few concepts you should be familiar with before continuing with this tutorial. If any of the tutorials below are unfamiliar to you, consider reading through them first:

The ESP32 has a built-in temperature sensor, so why not use that?

The ESP32 does, in fact, include a temperature sensor with a temperature range of -40�C to 125�C. This temperature sensor produces a voltage proportional to temperature, which is then converted to digital form by an internal analog-to-digital converter.

According to the ESP32 datasheet, the issue with this temperature sensor is that the offset varies from chip to chip due to process variation. Temperature measurements are also affected by the heat generated by the Wi-Fi circuitry. As a result, the internal temperature sensor is only appropriate for applications that detect temperature changes rather than absolute temperatures.

Wiring a DHT11/DHT22 sensor to an ESP32

Connecting a DHT11/DHT22 sensor to an ESP32 is pretty easy. Begin by placing the ESP32 on your breadboard, making sure that each side of the board is on a different side of the breadboard.

Place the sensor on your breadboard next to the ESP32. Connect the sensor�s VCC pin to the ESP32�s 3.3V pin and ground to ground. Connect the sensor�s Data pin to the ESP32�s D4 pin. Finally, add a 10K? pull-up resistor between VCC and the data line to keep it HIGH for proper communication between the sensor and the ESP32.

The image below shows the wiring.

Wiring Fritzing Connecting DHT22 Temperature Humidity Sensor with ESP32
Wiring DHT22 Temperature Humidity Sensor with ESP32
Wiring Fritzing Connecting DHT11 Temperature Humidity Sensor with ESP32
Wiring DHT11 Temperature Humidity Sensor with ESP32

Installing DHT Sensor Library

To begin reading sensor data, you will need to install the DHT sensor library. It is available from the Arduino library manager.

To install the library, navigate to Sketch > Include Library > Manage Libraries� Wait for the Library Manager to download the libraries index and update the list of installed libraries.

Arduino Library Installation - Selecting Manage Libraries in Arduino IDE

Filter your search by entering �DHT sensor�. Look for DHT sensor library by Adafruit. Click on that entry and then choose Install.

Adafruit DHT library Installation

The DHT sensor library makes use of the Adafruit Sensor support backend. So, look for Adafruit Unified Sensor in the library manager and install it as well.

Adafruit Unified Sensor library Installation

Creating an ESP32 Web Server using WiFi Station (STA) mode

Let�s move on to the interesting stuff now!

As the title implies, we will configure an ESP32 web server in Station (STA) mode to serve web pages to any connected client on the existing network.

Before you begin uploading the sketch, you must replace the following two variables with your network credentials so that the ESP32 can connect to an existing network.

const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

When you�re finished, try out the sketch, and then we�ll go over it in detail.

#include <WiFi.h>
#include <WebServer.h>
#include "DHT.h"

// Uncomment one of the lines below for whatever DHT sensor type you're using!
//#define DHTTYPE DHT11   // DHT 11
//#define DHTTYPE DHT21   // DHT 21 (AM2301)
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321

/*Put your SSID & Password*/
const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

WebServer server(80);

// DHT Sensor
uint8_t DHTPin = 4; 
               
// Initialize DHT sensor.
DHT dht(DHTPin, DHTTYPE);                

float Temperature;
float Humidity;
 
void setup() {
  Serial.begin(115200);
  delay(100);
  
  pinMode(DHTPin, INPUT);

  dht.begin();              

  Serial.println("Connecting to ");
  Serial.println(ssid);

  //connect to your local wi-fi network
  WiFi.begin(ssid, password);

  //check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

  server.on("/", handle_OnConnect);
  server.onNotFound(handle_NotFound);

  server.begin();
  Serial.println("HTTP server started");

}
void loop() {
  
  server.handleClient();
  
}

void handle_OnConnect() {

 Temperature = dht.readTemperature(); // Gets the values of the temperature
  Humidity = dht.readHumidity(); // Gets the values of the humidity 
  server.send(200, "text/html", SendHTML(Temperature,Humidity)); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

String SendHTML(float Temperaturestat,float Humiditystat){
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr +="<title>ESP32 Weather Report</title>\n";
  ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
  ptr +="p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n";
  ptr +="</style>\n";
  ptr +="</head>\n";
  ptr +="<body>\n";
  ptr +="<div id=\"webpage\">\n";
  ptr +="<h1>ESP32 Weather Report</h1>\n";
  
  ptr +="<p>Temperature: ";
  ptr +=(int)Temperaturestat;
  ptr +="�C</p>";
  ptr +="<p>Humidity: ";
  ptr +=(int)Humiditystat;
  ptr +="%</p>";
  
  ptr +="</div>\n";
  ptr +="</body>\n";
  ptr +="</html>\n";
  return ptr;
}

Accessing the Web Server

After uploading the sketch, open the Serial Monitor at 115200 baud and press the RESET button on the ESP32. If everything is fine, it will display the dynamic IP address obtained from your router as well as the �HTTP server started� message.

ESP32 Web Server Station Mode Serial Monitor Output - Server Started

Next, launch a browser and navigate to the IP address displayed on the serial monitor. The ESP32 should serve a web page with the current temperature and relative humidity.

Display DHT11 DHT22 AM2302 Temperature Humidity on ESP32 Web Server - Without CSS

Detailed Code Explanation

The sketch begins by including the WiFi.h library. This library contains ESP32-specific methods that we use to connect to the network. Following that, we include the WebServer.h library, which contains some methods that will assist us in configuring a server and handling incoming HTTP requests without having to worry about low-level implementation details. Finally, we include the DHT.h library.

#include <WiFi.h>
#include <WebServer.h>
#include "DHT.h"

Next, we specify the type of DHT sensor we are using. Uncomment the appropriate line below!

//#define DHTTYPE DHT11   // DHT 11
//#define DHTTYPE DHT21   // DHT 21 (AM2301)
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321

Because we are configuring the ESP32 web server in Station (STA) mode, it will join the existing WiFi network. So, we need to specify the SSID and password.

/*Put your SSID & Password*/
const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

Following that, we create an object of the WebServer library so that we can access its functions. The constructor of this object accepts as a parameter the port to which the server will be listening. Since HTTP uses port 80 by default, we�ll use this value. This allows us to connect to the server without specifying the port in the URL.

// declare an object of WebServer library
WebServer server(80);

Next, we define the GPIO pin number on the ESP32 to which our sensor�s Data pin is connected and create a DHT object. So, we can access DHT library-specific functions.

// DHT Sensor
uint8_t DHTPin = 4;
 
// Initialize DHT sensor.
DHT dht(DHTPin, DHTTYPE);

There are two float variables set up: Temperature and Humidity.

float Temperature;
float Humidity;

Inside Setup() Function

In the setup function, we configure our web server. First, we establish a serial connection for debugging purposes and configure the GPIO pin to behave as an INPUT. We also initialize the DHT object.

Serial.begin(115200);
delay(100);
  
pinMode(DHTPin, INPUT);

dht.begin();

Then, we use the WiFi.begin() function to connect to the WiFi network. The function accepts SSID (Network Name) and password as parameters.

Serial.println("Connecting to ");
Serial.println(ssid);

//connect to your local wi-fi network
WiFi.begin(ssid, password);

While the ESP32 attempts to connect to the network, we can use the WiFi.status() function to check the connectivity status.

//check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }

Once connected to the network, the WiFi.localIP() function is used to print the IP address assigned to the ESP32.

Serial.println("");
  Serial.println("WiFi connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

To handle incoming HTTP requests, we must specify which code should be executed when a specific URL is accessed. For this, we use the .on() method. This method accepts two parameters: a relative URL path and the name of the function to be executed when that URL is visited.

The code below indicates that when a server receives an HTTP request on the root (/) path, it will call the handle_OnConnect() function. It is important to note that the URL specified is a relative path.

server.on("/", handle_OnConnect);

We haven�t specified what the server should serve if the client requests a URL that isn�t specified with server.on() . It should give a 404 error (Page Not Found) as a response. To accomplish this, we use the server.onNotFound() method.

server.onNotFound(handle_NotFound);

Now, to start the server, we call the server object�s begin() method.

server.begin();
Serial.println("HTTP server started");

Inside Loop() Function

Actual incoming HTTP requests are handled in the loop function. For this, we use the server object�s handleClient() method.

server.handleClient();

Now we must write the handle_OnConnect() function, which we previously attached to the root (/) URL with server.on. We begin this function by reading temperature and humidity values from the sensor.

We use the send method to respond to an HTTP request. Although the method can be called with a number of different arguments, the simplest form requires the HTTP response code, the content type, and the content.

The first parameter we pass to the send method is the code 200 (one of the HTTP status codes), which corresponds to the OK response. Then we specify the content type as �text/html,� and finally we pass the SendHTML() custom function, which generates a dynamic HTML page with the temperature and humidity readings.

void handle_OnConnect() 
{
  Temperature = dht.readTemperature(); // Gets the values of the temperature
  Humidity = dht.readHumidity(); // Gets the values of the humidity 
  server.send(200, "text/html", SendHTML(Temperature,Humidity)); 
}

Similarly, we write a function to handle the 404 Error page.

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

Displaying the HTML Web Page

Whenever the ESP32 web server receives a request from a web client, the sendHTML() function generates a web page. It simply concatenates HTML code into a long string and returns to the server.send() function we discussed earlier. The function uses temperature and humidity readings as parameters to generate HTML content dynamically.

The first text you should always send is the <!DOCTYPE> declaration, which indicates that we�re sending HTML code.

String SendHTML(float Temperaturestat,float Humiditystat){
String ptr = "<!DOCTYPE html> <html>\n";

The <meta> viewport element makes the web page responsive, ensuring that it looks good on all devices. The title tag determines the page�s title.

ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
ptr +="<title>ESP32 Weather Report</title>\n";

Styling the Web Page

Following that, we have some CSS to style the overall appearance of the web page. We select the Helvetica font and define the content to be displayed as an inline-block, center-aligned.

ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";

The code that follows then sets the color, font, and margin around the body, H1, and p tags.

ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
ptr +="p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n";
ptr +="</style>\n";
ptr +="</head>\n";
ptr +="<body>\n";

Setting the Web Page Heading

Next, the heading of the web page is set. You can change this text to anything that works for your application.

ptr +="<div id=\"webpage\">\n";
ptr +="<h1>ESP32 Weather Report</h1>\n";

Displaying Temperature and Humidity on Web Page

We used the paragraph tag to display temperature and humidity values. Note that these values ??are converted to integers using type casting.

  ptr +="<p>Temperature: ";
  ptr +=(int)Temperaturestat;
  ptr +="�C</p>";
  ptr +="<p>Humidity: ";
  ptr +=(int)Humiditystat;
  ptr +="%</p>";
  
  ptr +="</div>\n";
  ptr +="</body>\n";
  ptr +="</html>\n";
  return ptr;
}

Styling Web Page to Look More Professional

Programmers have a tendency to overlook aesthetics, but with a little effort, our webpage can appear more attractive. The screenshot below will give you an idea of what we�re going to do.

Display DHT11 DHT22 AM2302 Temperature Humidity on ESP32 Web Server - Professional Look

Isn�t it incredible? Let�s add some style to our previous HTML page now. To begin, replace the SendHTML() function in the sketch above with the code below. Try out the new sketch, and then we�ll go over it in detail.

String SendHTML(float TempCstat,float TempFstat,float Humiditystat){
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr +="<link href=\"https://fonts.googleapis.com/css?family=Open+Sans:300,400,600\" rel=\"stylesheet\">\n";
  ptr +="<title>ESP32 Weather Report</title>\n";
  ptr +="<style>html { font-family: 'Open Sans', sans-serif; display: block; margin: 0px auto; text-align: center;color: #333333;}\n";
  ptr +="body{margin-top: 50px;}\n";
  ptr +="h1 {margin: 50px auto 30px;}\n";
  ptr +=".side-by-side{display: inline-block;vertical-align: middle;position: relative;}\n";
  ptr +=".humidity-icon{background-color: #3498db;width: 30px;height: 30px;border-radius: 50%;line-height: 36px;}\n";
  ptr +=".humidity-text{font-weight: 600;padding-left: 15px;font-size: 19px;width: 160px;text-align: left;}\n";
  ptr +=".humidity{font-weight: 300;font-size: 60px;color: #3498db;}\n";
  ptr +=".temperature-icon{background-color: #f39c12;width: 30px;height: 30px;border-radius: 50%;line-height: 40px;}\n";
  ptr +=".temperature-text{font-weight: 600;padding-left: 15px;font-size: 19px;width: 160px;text-align: left;}\n";
  ptr +=".temperature{font-weight: 300;font-size: 60px;color: #f39c12;}\n";
  ptr +=".superscript{font-size: 17px;font-weight: 600;position: absolute;right: -20px;top: 15px;}\n";
  ptr +=".data{padding: 10px;}\n";
  ptr +="</style>\n";
  ptr +="</head>\n";
  ptr +="<body>\n";
  
   ptr +="<div id=\"webpage\">\n";
   
   ptr +="<h1>ESP32 Weather Report</h1>\n";
   ptr +="<div class=\"data\">\n";
   ptr +="<div class=\"side-by-side temperature-icon\">\n";
   ptr +="<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n";
   ptr +="width=\"9.915px\" height=\"22px\" viewBox=\"0 0 9.915 22\" enable-background=\"new 0 0 9.915 22\" xml:space=\"preserve\">\n";
   ptr +="<path fill=\"#FFFFFF\" d=\"M3.498,0.53c0.377-0.331,0.877-0.501,1.374-0.527C5.697-0.04,6.522,0.421,6.924,1.142\n";
   ptr +="c0.237,0.399,0.315,0.871,0.311,1.33C7.229,5.856,7.245,9.24,7.227,12.625c1.019,0.539,1.855,1.424,2.301,2.491\n";
   ptr +="c0.491,1.163,0.518,2.514,0.062,3.693c-0.414,1.102-1.24,2.038-2.276,2.594c-1.056,0.583-2.331,0.743-3.501,0.463\n";
   ptr +="c-1.417-0.323-2.659-1.314-3.3-2.617C0.014,18.26-0.115,17.104,0.1,16.022c0.296-1.443,1.274-2.717,2.58-3.394\n";
   ptr +="c0.013-3.44,0-6.881,0.007-10.322C2.674,1.634,2.974,0.955,3.498,0.53z\"/>\n";
   ptr +="</svg>\n";
   ptr +="</div>\n";
   ptr +="<div class=\"side-by-side temperature-text\">Temperature</div>\n";
   ptr +="<div class=\"side-by-side temperature\">";
   ptr +=(int)TempCstat;
   ptr +="<span class=\"superscript\">�C</span></div>\n";
   ptr +="</div>\n";
   ptr +="<div class=\"data\">\n";
   ptr +="<div class=\"side-by-side humidity-icon\">\n";
   ptr +="<svg version=\"1.1\" id=\"Layer_2\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n\"; width=\"12px\" height=\"17.955px\" viewBox=\"0 0 13 17.955\" enable-background=\"new 0 0 13 17.955\" xml:space=\"preserve\">\n";
   ptr +="<path fill=\"#FFFFFF\" d=\"M1.819,6.217C3.139,4.064,6.5,0,6.5,0s3.363,4.064,4.681,6.217c1.793,2.926,2.133,5.05,1.571,7.057\n";
   ptr +="c-0.438,1.574-2.264,4.681-6.252,4.681c-3.988,0-5.813-3.107-6.252-4.681C-0.313,11.267,0.026,9.143,1.819,6.217\"></path>\n";
   ptr +="</svg>\n";
   ptr +="</div>\n";
   ptr +="<div class=\"side-by-side humidity-text\">Humidity</div>\n";
   ptr +="<div class=\"side-by-side humidity\">";
   ptr +=(int)Humiditystat;
   ptr +="<span class=\"superscript\">%</span></div>\n";
   ptr +="</div>\n";

  ptr +="</div>\n";
  ptr +="</body>\n";
  ptr +="</html>\n";
  return ptr;
}

Code Explanation

We already know that the <!DOCTYPE> declaration informs the browser that we are sending HTML code, and the <meta> viewport element makes the web page responsive. The only difference here is that we�ll be using Google Fonts. Google offers hundreds of free web fonts for commercial and personal use.

For our website, we will use the Google-commissioned Open Sans web font. The Google font is embedded in your HTML document�s head using the <link> tag.

For our page, we�ve chosen font weights of 300 (Light), 400 (Regular), and 600 (Bold). You can choose as many as you want, but keep in mind that excessive font weights slow down page load time.

You can also add italic style by simply appending an i character to the end of the font weight, for example, 400i will embed regular-italic-style-font.

It is important to note that google fonts are loaded dynamically; you will not be able to see the Google font unless you have an active internet connection on the device from which you are accessing this page.

String SendHTML(float TempCstat,float TempFstat,float Humiditystat){
String ptr = "<!DOCTYPE html> <html>\n";
ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
ptr +="<link href=\"https://fonts.googleapis.com/css?family=Open+Sans:300,400,600\" rel=\"stylesheet\">\n";

Next, we will apply the Open Sans font to the entire HTML document. We must also specify sans-serif as our fallback font. If the first specified font fails to load, the browser will attempt to load the fallback font.

ptr +="<title>ESP32 Weather Report</title>\n";
ptr +="<style>html { font-family: 'Open Sans', sans-serif; display: block; margin: 0px auto; text-align: center;color: #333333;}\n";
ptr +="body{margin-top: 50px;}\n";
ptr +="h1 {margin: 50px auto 30px;}\n";

Next, we apply CSS for Humidity & Temperature � icons, titles, and values. All three of these elements are inline and vertically aligned. The icon background is made circular using a border radius of 50% and 30px height and width.

ptr +=".side-by-side{display: inline-block;vertical-align: middle;position: relative;}\n";
ptr +=".humidity-icon{background-color: #3498db;width: 30px;height: 30px;border-radius: 50%;line-height: 36px;}\n";
ptr +=".humidity-text{font-weight: 600;padding-left: 15px;font-size: 19px;width: 160px;text-align: left;}\n";
ptr +=".humidity{font-weight: 300;font-size: 60px;color: #3498db;}\n";
ptr +=".temperature-icon{background-color: #f39c12;width: 30px;height: 30px;border-radius: 50%;line-height: 40px;}\n";
ptr +=".temperature-text{font-weight: 600;padding-left: 15px;font-size: 19px;width: 160px;text-align: left;}\n";
ptr +=".temperature{font-weight: 300;font-size: 60px;color: #f39c12;}\n";
ptr +=".superscript{font-size: 17px;font-weight: 600;position: absolute;right: -20px;top: 15px;}\n";
ptr +=".data{padding: 10px;}\n";
ptr +="</style>\n";
ptr +="</head>\n";
ptr +="<body>\n";
Following that, we display temperature readings with this nice little icon.

The temperature icon is a Scalable Vector Graphics (SVG) image defined in the <svg> tag. Creating an SVG requires no special programming skills. You can create graphics for your page using Google SVG Editor. Following the icon, we display the actual temperature reading from the sensor.

ptr +="<div id=\"webpage\">\n";
ptr +="<h1>ESP32 Weather Report</h1>\n";
ptr +="<div class=\"data\">\n";

ptr +="<div class=\"side-by-side temperature-icon\">\n";
ptr +="<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n";
ptr +="width=\"9.915px\" height=\"22px\" viewBox=\"0 0 9.915 22\" enable-background=\"new 0 0 9.915 22\" xml:space=\"preserve\">\n";
ptr +="<path fill=\"#FFFFFF\" d=\"M3.498,0.53c0.377-0.331,0.877-0.501,1.374-0.527C5.697-0.04,6.522,0.421,6.924,1.142\n";
ptr +="c0.237,0.399,0.315,0.871,0.311,1.33C7.229,5.856,7.245,9.24,7.227,12.625c1.019,0.539,1.855,1.424,2.301,2.491\n";
ptr +="c0.491,1.163,0.518,2.514,0.062,3.693c-0.414,1.102-1.24,2.038-2.276,2.594c-1.056,0.583-2.331,0.743-3.501,0.463\n";
ptr +="c-1.417-0.323-2.659-1.314-3.3-2.617C0.014,18.26-0.115,17.104,0.1,16.022c0.296-1.443,1.274-2.717,2.58-3.394\n";
ptr +="c0.013-3.44,0-6.881,0.007-10.322C2.674,1.634,2.974,0.955,3.498,0.53z\"/>\n";
ptr +="</svg>\n";
ptr +="</div>\n";

ptr +="<div class=\"side-by-side temperature-text\">Temperature</div>\n";
ptr +="<div class=\"side-by-side temperature\">";
ptr +=(int)TempCstat;
ptr +="<span class=\"superscript\">�C</span></div>\n";
ptr +="</div>\n";
With this icon, we display humidity readings.

It�s another SVG. After printing the humidity values, we close all open tags such as body and html.

ptr +="<div class=\"data\">\n";
ptr +="<div class=\"side-by-side humidity-icon\">\n";
ptr +="<svg version=\"1.1\" id=\"Layer_2\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n\"; width=\"12px\" height=\"17.955px\" viewBox=\"0 0 13 17.955\" enable-background=\"new 0 0 13 17.955\" xml:space=\"preserve\">\n";
ptr +="<path fill=\"#FFFFFF\" d=\"M1.819,6.217C3.139,4.064,6.5,0,6.5,0s3.363,4.064,4.681,6.217c1.793,2.926,2.133,5.05,1.571,7.057\n";
ptr +="c-0.438,1.574-2.264,4.681-6.252,4.681c-3.988,0-5.813-3.107-6.252-4.681C-0.313,11.267,0.026,9.143,1.819,6.217\"></path>\n";
ptr +="</svg>\n";
ptr +="</div>\n";
ptr +="<div class=\"side-by-side humidity-text\">Humidity</div>\n";
ptr +="<div class=\"side-by-side humidity\">";
ptr +=(int)Humiditystat;
ptr +="<span class=\"superscript\">%</span></div>\n";
ptr +="</div>\n";

ptr +="</div>\n";
ptr +="</body>\n";
ptr +="</html>\n";
return ptr;
}

Improvement 1 � Auto Page Refresh

One of the improvements you can make is to automatically refresh the page in order to update the sensor value.

You can instruct the browser to automatically reload the page at a specified interval by inserting a single meta tag into your HTML document.

<meta http-equiv="refresh" content="2" >

Insert this code into your document�s <head> tag; this meta tag will instruct the browser to refresh every two seconds.

Improvement 2 � Dynamically load Sensor Data with AJAX

Refreshing a web page is impractical if the web page is large. Asynchronous Javascript And Xml (AJAX) is a better method because it allows us to request data from the server asynchronously (in the background) without refreshing the page.

The XMLHttpRequest object within JavaScript is commonly used to execute AJAX on webpages. It sends a silent GET request to the server and updates the page element. AJAX is not a new technology, nor is it a different programming language; rather, it is simply an existing technology that is used in a different way. It allows you to:

  • Request data from a server after the page has loaded
  • Receive data from a server after the page has loaded
  • Send data to a server in the background

This is the AJAX script we�ll be using. Place this script before closing the <head> tag.

ptr +="<script>\n";
ptr +="setInterval(loadDoc,200);\n";
ptr +="function loadDoc() {\n";
ptr +="var xhttp = new XMLHttpRequest();\n";
ptr +="xhttp.onreadystatechange = function() {\n";
ptr +="if (this.readyState == 4 && this.status == 200) {\n";
ptr +="document.getElementById(\"webpage\").innerHTML =this.responseText}\n";
ptr +="};\n";
ptr +="xhttp.open(\"GET\", \"/\", true);\n";
ptr +="xhttp.send();\n";
ptr +="}\n";
ptr +="</script>\n";

Code Explanation

Because the AJAX script is just javascript, it must be enclosed in the <script> tag. We�ll use the javascript setInterval() function to make this function call itself repeatedly. It requires two parameters: the function to be executed and the time interval (in milliseconds).

ptr +="<script>\n";
ptr +="setInterval(loadDoc,200);\n";

A loadDoc() function is the most important part of this script. In this function, an object of XMLHttpRequest() is created. This object is used to query a web server for data.

ptr +="function loadDoc() {\n";
ptr +="var xhttp = new XMLHttpRequest();\n";

The xhttp.onreadystatechange() method is called whenever the readyState changes. The readyState property represents the XMLHttpRequest�s status. It has one of the values listed below.

  • 0: request not initialized
  • 1: server connection established
  • 2: request received
  • 3: processing request
  • 4: request finished and response is ready

The status property holds the XMLHttpRequest object�s status. It has one of the values listed below.

  • 200: �OK�
  • 403: �Forbidden�
  • 404: �Page not found�

When readyState is 4 and status is 200, the response is complete, and the content of the element with id �webpage� (div containing temperature and humidity values) is updated.

ptr +="xhttp.onreadystatechange = function() {\n";
ptr +="if (this.readyState == 4 && this.status == 200) {\n";
ptr +="document.getElementById(\"webpage\").innerHTML =this.responseText}\n";
ptr +="};\n";

The open() and send() functions are then used to initiate the HTTP request.

ptr +="xhttp.open(\"GET\", \"/\", true);\n";
ptr +="xhttp.send();\n";
ptr +="}\n";

Don�t rely solely on smartphone weather apps or commercial weather stations that provide data from stations located miles away from you. Instead, with this IoT project, you can become your own weather forecaster!

This project uses the ESP32 as the control device, which easily connects to any existing WiFi network and creates a web server. When any connected device accesses this web server, the ESP32 retrieves temperature, humidity, barometric pressure, and altitude data from the BME280 sensor and displays it on the device�s web browser with a user-friendly interface. Excited? Let�s dive in!

BME280 Temperature, Humidity and Pressure Sensor

First, let�s briefly discuss the BME280 module.

The BME280 is a next-generation digital temperature, humidity, and pressure sensor, manufactured by Bosch. It serves as a successor to sensors such as the BMP180, BMP085, and BMP183.

BME280 Temperature Humidity Pressure Altitude Sensor Specifications

The BME280 module operates within a voltage range of 3.3V to 5V, making it ideal for interfacing with 3.3V microcontrollers like the ESP32.

The module features a simple two-wire I2C interface for communication. The default I2C address for the BME280 module is 0x76, but it can be easily modified to 0x77 following a simple procedure.

Wiring BME280 Sensor to ESP32

Connections are fairly simple. Begin by connecting the VIN pin on the BME280 module to the 3.3V output on the ESP32, and the GND pin to the ground.

Next, link the SCL pin on the BME280 module to the D22 pin (I2C clock) on the ESP32, and the SDA pin to the D21 pin (I2C data).

The following table lists the pin connections:

BME280 SensorESP32
VCC3.3V
GNDGND
SCLD22
SDAD21

The following diagram shows you how to wire everything.

Fritzing Wiring ESP32 BME280 Temperature Humidity Pressure Sensor

Preparing the Arduino IDE

There�s an add-on available for the Arduino IDE that enables you to program the ESP32 within the Arduino IDE. If you haven�t set it up yet, follow the tutorial below to prepare your Arduino IDE to work with the ESP32.

Installing Library For BME280

Communicating with a BME280 module can be a bit complex, but fortunately, the Adafruit BME280 Library was created to abstract away these complexities, allowing us to issue simple commands to read temperature, relative humidity, and barometric pressure data.

To install this library, go to Arduino IDE > Sketch > Include Library > Manage Libraries� Wait for the Library Manager to download the library index and update the list of installed libraries.

manage libraries

To find the library, filter your search by typing �bme280�. You should see a couple of entries. Look for Adafruit BME280 Library by Adafruit and click on that entry, then select Install.

Installing BME280 Library In Arduino IDE

It�s worth noting that the BME280 sensor library relies on the Adafruit Sensor support backend. In the library manager, search for Adafruit Unified Sensor and install it as well.

Adafruit Unified Sensor Library Installation

Displaying Temperature, Humidity, Pressure & Altitude On ESP32 Web Server

Now, let�s configure the ESP32 to operate in Station (STA) mode (connect to the existing WiFi network) and create a web server. This server will deliver web pages to any client connected to the existing network.

If you�re interested in learning how to create a web server with ESP32 in AP/STA mode, refer to this tutorial.

Before uploading the sketch, there is one essential modification you need to make. Update the following two variables with your network credentials. This ensures that the ESP32 can connect to the existing network.

const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

After making these changes, go ahead and try out the sketch.

#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#define SEALEVELPRESSURE_HPA (1013.25)

Adafruit_BME280 bme;

float temperature, humidity, pressure, altitude;

/*Put your SSID & Password*/
const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

WebServer server(80);             
 
void setup() {
  Serial.begin(115200);
  delay(100);
  
  bme.begin(0x76);   

  Serial.println("Connecting to ");
  Serial.println(ssid);

  //connect to your local wi-fi network
  WiFi.begin(ssid, password);

  //check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

  server.on("/", handle_OnConnect);
  server.onNotFound(handle_NotFound);

  server.begin();
  Serial.println("HTTP server started");

}
void loop() {
  server.handleClient();
}

void handle_OnConnect() {
  temperature = bme.readTemperature();
  humidity = bme.readHumidity();
  pressure = bme.readPressure() / 100.0F;
  altitude = bme.readAltitude(SEALEVELPRESSURE_HPA);
  server.send(200, "text/html", SendHTML(temperature,humidity,pressure,altitude)); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

String SendHTML(float temperature,float humidity,float pressure,float altitude){
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr +="<title>ESP32 Weather Station</title>\n";
  ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
  ptr +="p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n";
  ptr +="</style>\n";
  ptr +="</head>\n";
  ptr +="<body>\n";
  ptr +="<div id=\"webpage\">\n";
  ptr +="<h1>ESP32 Weather Station</h1>\n";
  ptr +="<p>Temperature: ";
  ptr +=temperature;
  ptr +="&deg;C</p>";
  ptr +="<p>Humidity: ";
  ptr +=humidity;
  ptr +="%</p>";
  ptr +="<p>Pressure: ";
  ptr +=pressure;
  ptr +="hPa</p>";
  ptr +="<p>Altitude: ";
  ptr +=altitude;
  ptr +="m</p>";
  ptr +="</div>\n";
  ptr +="</body>\n";
  ptr +="</html>\n";
  return ptr;
}

Accessing the Web Server

After uploading the sketch, open the Serial Monitor and set the baud rate to 115200. Then, press the EN button on the ESP32. It will display the dynamic IP address obtained from your router along with the �HTTP server started� message.

ESP32 Station Mode Web Server IP Address On Serial Monitor

Next, open a browser and navigate to the IP address displayed on the Serial Monitor. The ESP32 should serve a web page displaying temperature, humidity, pressure, and altitude data from the BME280 sensor.

BME280 Readings on ESP32 Web Server

Detailed Code Explanation

The sketch begins by including the following libraries:

  • WiFi.h: offers ESP32-specific WiFi methods we use to connect to the network.
  • WebServer.h: offers methods for setting up a server and handling incoming HTTP requests.
  • Wire.h: allows communication with any I2C device, not just the BME280.
  • Adafruit_BME280.h and Adafruit_Sensor.h: are hardware-specific libraries that handle lower-level functions.
#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

Next, an object of the sensor is created along with variables to store temperature, humidity, pressure, and altitude data.

#define SEALEVELPRESSURE_HPA (1013.25)

Adafruit_BME280 bme;

float temperature, humidity, pressure, altitude;

Since we are configuring the ESP32 in Station (STA) mode, it will connect to the existing WiFi network. Therefore, we need to specify the network�s SSID and password. Then, we start the web server on port 80.

/*Put your SSID & Password*/
const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

WebServer server(80);

Inside Setup() Function

In the setup() function, we configure our HTTP server.

First of all, we initialize serial communication with the PC and initialize the BME object using the begin() function. This function initializes the I2C interface with the given I2C address (0x76) and verifies the chip ID. It then resets the chip via a soft reset and waits for sensor calibration after wake-up.

Serial.begin(115200);
delay(100);

bme.begin(0x76);

The ESP32 then attempts to join the WiFi network using the WiFi.begin() function, which accepts the SSID (Network Name) and password as arguments.

Serial.println("Connecting to ");
Serial.println(ssid);

//connect to your local wi-fi network
WiFi.begin(ssid, password);

While the ESP32 attempts to connect to the network, we can check the connectivity status using the WiFi.status() function.

//check wi-fi is connected to wi-fi network
while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
}

Once the ESP32 is successfully connected to the network, the sketch prints the ESP32�s IP address to the Serial Monitor using the WiFi.localIP() method.

Serial.println("");
Serial.println("WiFi connected..!");
Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

To handle incoming HTTP requests, we need to specify which code to execute when a URL is accessed. We use the server.on() method for this purpose. This method requires two parameters: the URL path and the name of the function to execute when that URL is accessed.

The code below indicates that when the server receives an HTTP request on the root (/) path, it triggers the handle_OnConnect function. Note that the specified URL is a relative path.

server.on("/", handle_OnConnect);

In case the server receives a request for a URL that wasn�t specified with server.on(), it should respond with an HTTP status 404 (Not Found) and a message for the user. For this purpose, we use server.onNotFound() method.

server.onNotFound(handle_NotFound);

Finally, to start our server, we call the begin() method on the server object.

server.begin();
Serial.println("HTTP server started");

Inside Loop() Function

To handle the actual incoming HTTP requests, we must invoke the handleClient() method on the server object.

server.handleClient();

Next, we need to create a function that we�ve associated with the root (/) URL using server.on.

At the beginning of this function, we retrieve the temperature, humidity, pressure, and altitude readings from the sensor. To respond to the HTTP request, we use the send() method. While this method can accept various arguments, its most basic form includes the HTTP response code, the content type, and the content itself.

In our case, we are sending the code 200 (one of the HTTP status codes), which corresponds to the OK response. We then specify the content type as �text/html.� Finally, we call the custom SendHTML() function, which generates a dynamic HTML page that contains the temperature, humidity, pressure, and altitude data.

void handle_OnConnect() {
  temperature = bme.readTemperature();
  humidity = bme.readHumidity();
  pressure = bme.readPressure() / 100.0F;
  altitude = bme.readAltitude(SEALEVELPRESSURE_HPA);
  server.send(200, "text/html", SendHTML(temperature,humidity,pressure,altitude)); 
}

Similarly, we need to create a function for handling the 404 Error page.

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

Displaying the HTML Web Page

The SendHTML() function is responsible for generating a web page whenever the ESP32 web server receives a request from a web client. This function accepts temperature, humidity, pressure, and altitude readings as parameters and returns HTML content in the form of a long string to the server.send() function discussed earlier.

Every HTML document begins with the <!DOCTYPE> declaration.

String SendHTML(float temperature,float humidity,float pressure,float altitude){
  String ptr = "<!DOCTYPE html> <html>\n";

Next, the <meta> viewport element ensures that the web page is responsive across various browsers, while the title tag sets the page�s title.

ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
ptr +="<title>ESP32 Weather Station</title>\n";

Styling the Web Page

Next, we use CSS to style the appearance of the web page. We select the Helvetica font and specify that the content be displayed as an inline-block, center-aligned.

ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";

The code below changes the color, font, and margin around the body, H1, and p tags.

ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
ptr +="p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n";
ptr +="</style>\n";
ptr +="</head>\n";
ptr +="<body>\n";

Setting the Web Page Heading

Following this, we set the page header; you can change this text to whatever suits your application.

ptr +="<div id=\"webpage\">\n";
ptr +="<h1>ESP32 Weather Station</h1>\n";

Displaying Readings on Web Page

To dynamically display temperature, humidity, pressure, and altitude readings, we place these values within paragraph tags. To display the degree symbol, we use the HTML entity &deg;.

ptr +="<p>Temperature: ";
ptr +=temperature;
ptr +="&deg;C</p>";
ptr +="<p>Humidity: ";
ptr +=humidity;
ptr +="%</p>";
ptr +="<p>Pressure: ";
ptr +=pressure;
ptr +="hPa</p>";
ptr +="<p>Altitude: ";
ptr +=altitude;
ptr +="m</p>";
ptr +="</div>\n";
ptr +="</body>\n";
ptr +="</html>\n";
return ptr;
}

Styling Web Page to Look More Professional

Makers, like us, often don�t pay much attention to design, but with a bit of effort, we can make our web pages look more attractive and professional. The screenshot below provides a glimpse of what we aim to achieve.

BME280 Readings on ESP32 Web Server Professional Look

Isn�t it amazing? Without further delay, let�s add some style to our previous HTML page. Just replace the SendHTML() function in the previous sketch with the code provided below.

String SendHTML(float temperature,float humidity,float pressure,float altitude){
  String ptr = "<!DOCTYPE html>";
  ptr +="<html>";
  ptr +="<head>";
  ptr +="<title>ESP32 Weather Station</title>";
  ptr +="<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
  ptr +="<link href='https://fonts.googleapis.com/css?family=Open+Sans:300,400,600' rel='stylesheet'>";
  ptr +="<style>";
  ptr +="html { font-family: 'Open Sans', sans-serif; display: block; margin: 0px auto; text-align: center;color: #444444;}";
  ptr +="body{margin: 0px;} ";
  ptr +="h1 {margin: 50px auto 30px;} ";
  ptr +=".side-by-side{display: table-cell;vertical-align: middle;position: relative;}";
  ptr +=".text{font-weight: 600;font-size: 19px;width: 200px;}";
  ptr +=".reading{font-weight: 300;font-size: 50px;padding-right: 25px;}";
  ptr +=".temperature .reading{color: #F29C1F;}";
  ptr +=".humidity .reading{color: #3B97D3;}";
  ptr +=".pressure .reading{color: #26B99A;}";
  ptr +=".altitude .reading{color: #955BA5;}";
  ptr +=".superscript{font-size: 17px;font-weight: 600;position: absolute;top: 10px;}";
  ptr +=".data{padding: 10px;}";
  ptr +=".container{display: table;margin: 0 auto;}";
  ptr +=".icon{width:65px}";
  ptr +="</style>";
  ptr +="</head>";
  ptr +="<body>";
  ptr +="<h1>ESP32 Weather Station</h1>";
  ptr +="<div class='container'>";
  ptr +="<div class='data temperature'>";
  ptr +="<div class='side-by-side icon'>";
  ptr +="<svg enable-background='new 0 0 19.438 54.003'height=54.003px id=Layer_1 version=1.1 viewBox='0 0 19.438 54.003'width=19.438px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><g><path d='M11.976,8.82v-2h4.084V6.063C16.06,2.715,13.345,0,9.996,0H9.313C5.965,0,3.252,2.715,3.252,6.063v30.982";
  ptr +="C1.261,38.825,0,41.403,0,44.286c0,5.367,4.351,9.718,9.719,9.718c5.368,0,9.719-4.351,9.719-9.718";
  ptr +="c0-2.943-1.312-5.574-3.378-7.355V18.436h-3.914v-2h3.914v-2.808h-4.084v-2h4.084V8.82H11.976z M15.302,44.833";
  ptr +="c0,3.083-2.5,5.583-5.583,5.583s-5.583-2.5-5.583-5.583c0-2.279,1.368-4.236,3.326-5.104V24.257C7.462,23.01,8.472,22,9.719,22";
  ptr +="s2.257,1.01,2.257,2.257V39.73C13.934,40.597,15.302,42.554,15.302,44.833z'fill=#F29C21 /></g></svg>";
  ptr +="</div>";
  ptr +="<div class='side-by-side text'>Temperature</div>";
  ptr +="<div class='side-by-side reading'>";
  ptr +=(int)temperature;
  ptr +="<span class='superscript'>&deg;C</span></div>";
  ptr +="</div>";
  ptr +="<div class='data humidity'>";
  ptr +="<div class='side-by-side icon'>";
  ptr +="<svg enable-background='new 0 0 29.235 40.64'height=40.64px id=Layer_1 version=1.1 viewBox='0 0 29.235 40.64'width=29.235px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><path d='M14.618,0C14.618,0,0,17.95,0,26.022C0,34.096,6.544,40.64,14.618,40.64s14.617-6.544,14.617-14.617";
  ptr +="C29.235,17.95,14.618,0,14.618,0z M13.667,37.135c-5.604,0-10.162-4.56-10.162-10.162c0-0.787,0.638-1.426,1.426-1.426";
  ptr +="c0.787,0,1.425,0.639,1.425,1.426c0,4.031,3.28,7.312,7.311,7.312c0.787,0,1.425,0.638,1.425,1.425";
  ptr +="C15.093,36.497,14.455,37.135,13.667,37.135z'fill=#3C97D3 /></svg>";
  ptr +="</div>";
  ptr +="<div class='side-by-side text'>Humidity</div>";
  ptr +="<div class='side-by-side reading'>";
  ptr +=(int)humidity;
  ptr +="<span class='superscript'>%</span></div>";
  ptr +="</div>";
  ptr +="<div class='data pressure'>";
  ptr +="<div class='side-by-side icon'>";
  ptr +="<svg enable-background='new 0 0 40.542 40.541'height=40.541px id=Layer_1 version=1.1 viewBox='0 0 40.542 40.541'width=40.542px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><g><path d='M34.313,20.271c0-0.552,0.447-1,1-1h5.178c-0.236-4.841-2.163-9.228-5.214-12.593l-3.425,3.424";
  ptr +="c-0.195,0.195-0.451,0.293-0.707,0.293s-0.512-0.098-0.707-0.293c-0.391-0.391-0.391-1.023,0-1.414l3.425-3.424";
  ptr +="c-3.375-3.059-7.776-4.987-12.634-5.215c0.015,0.067,0.041,0.13,0.041,0.202v4.687c0,0.552-0.447,1-1,1s-1-0.448-1-1V0.25";
  ptr +="c0-0.071,0.026-0.134,0.041-0.202C14.39,0.279,9.936,2.256,6.544,5.385l3.576,3.577c0.391,0.391,0.391,1.024,0,1.414";
  ptr +="c-0.195,0.195-0.451,0.293-0.707,0.293s-0.512-0.098-0.707-0.293L5.142,6.812c-2.98,3.348-4.858,7.682-5.092,12.459h4.804";
  ptr +="c0.552,0,1,0.448,1,1s-0.448,1-1,1H0.05c0.525,10.728,9.362,19.271,20.22,19.271c10.857,0,19.696-8.543,20.22-19.271h-5.178";
  ptr +="C34.76,21.271,34.313,20.823,34.313,20.271z M23.084,22.037c-0.559,1.561-2.274,2.372-3.833,1.814";
  ptr +="c-1.561-0.557-2.373-2.272-1.815-3.833c0.372-1.041,1.263-1.737,2.277-1.928L25.2,7.202L22.497,19.05";
  ptr +="C23.196,19.843,23.464,20.973,23.084,22.037z'fill=#26B999 /></g></svg>";
  ptr +="</div>";
  ptr +="<div class='side-by-side text'>Pressure</div>";
  ptr +="<div class='side-by-side reading'>";
  ptr +=(int)pressure;
  ptr +="<span class='superscript'>hPa</span></div>";
  ptr +="</div>";
  ptr +="<div class='data altitude'>";
  ptr +="<div class='side-by-side icon'>";
  ptr +="<svg enable-background='new 0 0 58.422 40.639'height=40.639px id=Layer_1 version=1.1 viewBox='0 0 58.422 40.639'width=58.422px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><g><path d='M58.203,37.754l0.007-0.004L42.09,9.935l-0.001,0.001c-0.356-0.543-0.969-0.902-1.667-0.902";
  ptr +="c-0.655,0-1.231,0.32-1.595,0.808l-0.011-0.007l-0.039,0.067c-0.021,0.03-0.035,0.063-0.054,0.094L22.78,37.692l0.008,0.004";
  ptr +="c-0.149,0.28-0.242,0.594-0.242,0.934c0,1.102,0.894,1.995,1.994,1.995v0.015h31.888c1.101,0,1.994-0.893,1.994-1.994";
  ptr +="C58.422,38.323,58.339,38.024,58.203,37.754z'fill=#955BA5 /><path d='M19.704,38.674l-0.013-0.004l13.544-23.522L25.13,1.156l-0.002,0.001C24.671,0.459,23.885,0,22.985,0";
  ptr +="c-0.84,0-1.582,0.41-2.051,1.038l-0.016-0.01L20.87,1.114c-0.025,0.039-0.046,0.082-0.068,0.124L0.299,36.851l0.013,0.004";
  ptr +="C0.117,37.215,0,37.62,0,38.059c0,1.412,1.147,2.565,2.565,2.565v0.015h16.989c-0.091-0.256-0.149-0.526-0.149-0.813";
  ptr +="C19.405,39.407,19.518,39.019,19.704,38.674z'fill=#955BA5 /></g></svg>";
  ptr +="</div>";
  ptr +="<div class='side-by-side text'>Altitude</div>";
  ptr +="<div class='side-by-side reading'>";
  ptr +=(int)altitude;
  ptr +="<span class='superscript'>m</span></div>";
  ptr +="</div>";
  ptr +="</div>";
  ptr +="</body>";
  ptr +="</html>";
  return ptr;
}

If you compare this function to the previous one, you will notice that they are similar except for these differences.

  • We�ve used the Google-commissioned Open Sans web font for our page. Note that without an active internet connection on the device, you won�t see the Google font as it�s loaded dynamically.
ptr +="<link href='https://fonts.googleapis.com/css?family=Open+Sans:300,400,600' rel='stylesheet'>";
  • The icons used to represent temperature, humidity, pressure, and altitude readings are Scalable Vector Graphics (SVG) defined within <svg> tags. Creating SVG graphics doesn�t require any special programming skills. You can use the Canva SVG Editor to create graphics for your page. We have used these SVG icons.
SVG Icons1

Improvement to the Code � Auto Page Refresh

One improvement for our code is to refresh the page automatically to update sensor values. By adding a single meta tag to your HTML document, you can instruct the browser to reload the page at specified intervals.

<meta http-equiv="refresh" content="2" >

Place this code within the <head> tag of your document. This meta tag will instruct the browser to refresh the page every two seconds. Quite handy!

Dynamically load Sensor Data with AJAX

Refreshing a web page frequently may not be practical for heavy web pages. A more efficient approach is to use Asynchronous JavaScript and XML (AJAX) to request data from the server without the need to refresh the entire page.

The XMLHttpRequest object in JavaScript is commonly employed for AJAX requests on web pages. It performs silent GET requests to the server and updates elements on the page. AJAX is not a new technology or a separate language, just existing technologies used in new ways. Additionally, AJAX enables you to:

  • Request data from a server after the page has loaded
  • Receive data from a server after the page has loaded
  • Send data to a server in the background

Here�s the AJAX script we�ll be using. Place this script just before closing the <head> tag.

ptr +="<script>\n";
ptr +="setInterval(loadDoc,1000);\n";
ptr +="function loadDoc() {\n";
ptr +="var xhttp = new XMLHttpRequest();\n";
ptr +="xhttp.onreadystatechange = function() {\n";
ptr +="if (this.readyState == 4 && this.status == 200) {\n";
ptr +="document.body.innerHTML =this.responseText}\n";
ptr +="};\n";
ptr +="xhttp.open(\"GET\", \"/\", true);\n";
ptr +="xhttp.send();\n";
ptr +="}\n";
ptr +="</script>\n";

The script begins with the <script> tag, as the AJAX script is essentially JavaScript and needs to be enclosed in <script> tags. To repeatedly call a function, we�ll use the JavaScript setInterval() function, which requires two parameters: the function to execute and the time interval (in milliseconds) for its execution.

ptr +="<script>\n";
ptr +="setInterval(loadDoc,1000);\n";

The core of this script is the loadDoc() function, inside which an XMLHttpRequest() object is created. This object is used to request data from the web server.

ptr +="function loadDoc() {\n";
ptr +="var xhttp = new XMLHttpRequest();\n";

The xhttp.onreadystatechange() function is called every time the readyState changes. The readyState property holds the status of the XMLHttpRequest, with values from 0 to 4 indicating various stages of the request process.

  • 0: request not initialized
  • 1: server connection established
  • 2: request received
  • 3: processing request
  • 4: request finished and response is ready

The status property, on the other hand, indicates the status of the XMLHttpRequest object, taking one of these values:

  • 200: �OK�
  • 403: �Forbidden�
  • 404: �Page not found�

When readyState is 4 and the status is 200, it signifies that the response is ready. At this point, the content of the <body> is updated.

ptr +="xhttp.onreadystatechange = function() {\n";
ptr +="if (this.readyState == 4 && this.status == 200) {\n";
ptr +="document.body.innerHTML =this.responseText}\n";
ptr +="};\n";
The HTTP request is then initiated via the open() and send() functions.
ptr +="xhttp.open(\"GET\", \"/\", true);\n";
ptr +="xhttp.send();\n";
ptr +="}\n";

Have you ever wanted to have sensors scattered all around your house and garden reporting their temperature regularly to a central server? Then, this IoT project might be the solid launching point for you!

This project uses ESP32 as the control device that easily connects to existing WiFi network & creates a Web Server. When any connected device accesses this web server, ESP32 reads in temperature from multiple DS18B20 Temperature sensors & sends it to the web browser of that device with a nice interface. Excited? Let�s get started!

Multiple DS18B20s On Single Bus

One of the biggest features of DS18B20 is that multiple DS18B20s can coexist on the same 1-Wire bus. As each DS18B20 has a unique 64-bit serial code burned in at the factory, it�s easier to differentiate them from one another.

This feature can be a huge advantage when you want to control many DS18B20s distributed over a large area. In this tutorial we are going to do the same.

Wiring Multiple DS18B20 Sensors to ESP32

Connecting DS18B20 sensors to ESP32 is fairly simple.

DS18B20 Pinout

Start by connecting all the DS18B20s in parallel i.e. common all the VDD pins, GND pins & signal pins. Then connect VDD to the 3.3V out on ESP32, GND to ground and connect signal pin to digital pin 15 on ESP32.

Next, you�ll need to add one 4.7k pull-up resistor for whole bus between the signal and power pin to keep the data transfer stable.

Wiring Multiple DS18B20 Temperature Sensors to ESP32
Wiring Multiple DS18B20 Temperature Sensors to ESP32

Preparing the Arduino IDE

There�s an add-on for the Arduino IDE that allows you to program the ESP32 using the Arduino IDE. Follow below tutorial to prepare your Arduino IDE to work with the ESP32, if you haven�t already.

Installing Library For DS18B20

The Dallas 1-Wire protocol is somewhat complex, and requires a bunch of code to parse out the communication. To hide away this unnecessary complexity we will install DallasTemperature.h library so that we can issue simple commands to get temperature readings from the sensor.

To install the library navigate to the Arduino IDE > Sketch > Include Library > Manage Libraries� Wait for Library Manager to download libraries index and update list of installed libraries.

Filter your search by typing �ds18b20�. There should be a couple entries. Look for DallasTemperature by Miles Burton. Click on that entry, and then select Install.

Installing Dallas Temperature Library In Arduino IDE

This Dallas Temperature library is a hardware-specific library which handles lower-level functions. It needs to be paired with One Wire Library to communicate with any one-wire device not just DS18B20. Install this library as well.

Installing OneWire Library In Arduino IDE

Finding Addresses Of DS18B20s On Bus

We know that each DS18B20 has a unique 64-bit address assigned to it to differentiate them from one another. First, we�ll find that address to label each sensor accordingly. The address can then be used to read each sensor individually.

The following sketch detects all the DS18B20s present on the bus and prints their one-wire address on the serial monitor.

You can wire just one sensor at a time to find its address (or successively add a new sensor) so that you�re able to identify each one by its address. Then, you can label each sensor.

#include <OneWire.h>
#include <DallasTemperature.h>

// Data wire is plugged into port 15 on the ESP32
#define ONE_WIRE_BUS 15

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

// variable to hold device addresses
DeviceAddress Thermometer;

int deviceCount = 0;

void setup(void)
{
  // start serial port
  Serial.begin(115200);

  // Start up the library
  sensors.begin();

  // locate devices on the bus
  Serial.println("Locating devices...");
  Serial.print("Found ");
  deviceCount = sensors.getDeviceCount();
  Serial.print(deviceCount, DEC);
  Serial.println(" devices.");
  Serial.println("");
  
  Serial.println("Printing addresses...");
  for (int i = 0;  i < deviceCount;  i++)
  {
    Serial.print("Sensor ");
    Serial.print(i+1);
    Serial.print(" : ");
    sensors.getAddress(Thermometer, i);
    printAddress(Thermometer);
  }
}

void loop(void)
{ }

void printAddress(DeviceAddress deviceAddress)
{ 
  for (uint8_t i = 0; i < 8; i++)
  {
    Serial.print("0x");
    if (deviceAddress[i] < 0x10) Serial.print("0");
    Serial.print(deviceAddress[i], HEX);
    if (i < 7) Serial.print(", ");
  }
  Serial.println("");
}

Now, open the serial monitor. You should get something as follows.

Finding One-Wire Address Of All DS18B20 On the Bus

Copy all the addresses as we need them in out next sketch.

Create ESP32 Web Server using WiFi Station (STA) mode

Now, we are going to configure our ESP32 into Station (STA) mode, and create a web server to serve up web pages to any connected client under existing network.

If you want to learn about creating a web server with ESP32 in AP/STA mode, check this tutorial out.

Before you head for uploading the sketch, you need to make some changes to make it work for you.

  • You need to modify the following two variables with your network credentials, so that ESP32 can establish a connection with existing network.
    const char* ssid = "YourNetworkName";  // Enter SSID here
    const char* password = "YourPassword";  //Enter Password here
  • Before serving up a web page ESP32 reads the temperature from each DS18B20 by its address so, you need to change the addresses of DS18B20s with the one you�ve found in previous sketch.
    uint8_t sensor1[8] = { 0x28, 0xEE, 0xD5, 0x64, 0x1A, 0x16, 0x02, 0xEC };
    uint8_t sensor2[8] = { 0x28, 0x61, 0x64, 0x12, 0x3C, 0x7C, 0x2F, 0x27 };
    uint8_t sensor3[8] = { 0x28, 0x61, 0x64, 0x12, 0x3F, 0xFD, 0x80, 0xC6 };

Once you are done, go ahead and try the sketch out.

#include <WiFi.h>
#include <WebServer.h>
#include <OneWire.h>
#include <DallasTemperature.h>

// Data wire is plugged into port 15 on the ESP32
#define ONE_WIRE_BUS 15

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

uint8_t sensor1[8] = { 0x28, 0xEE, 0xD5, 0x64, 0x1A, 0x16, 0x02, 0xEC  };
uint8_t sensor2[8] = { 0x28, 0x61, 0x64, 0x12, 0x3C, 0x7C, 0x2F, 0x27  };
uint8_t sensor3[8] = { 0x28, 0x61, 0x64, 0x12, 0x3F, 0xFD, 0x80, 0xC6  };

/*Put your SSID & Password*/
const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

WebServer server(80);             

float tempSensor1, tempSensor2, tempSensor3;
 
void setup() {
  Serial.begin(115200);
  delay(100);
  
  sensors.begin();              

  Serial.println("Connecting to ");
  Serial.println(ssid);

  //connect to your local wi-fi network
  WiFi.begin(ssid, password);

  //check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

  server.on("/", handle_OnConnect);
  server.onNotFound(handle_NotFound);

  server.begin();
  Serial.println("HTTP server started");

}
void loop() {
  server.handleClient();
}

void handle_OnConnect() {
  sensors.requestTemperatures();
  tempSensor1 = sensors.getTempC(sensor1); // Gets the values of the temperature
  tempSensor2 = sensors.getTempC(sensor2); // Gets the values of the temperature
  tempSensor3 = sensors.getTempC(sensor3); // Gets the values of the temperature
  server.send(200, "text/html", SendHTML(tempSensor1,tempSensor2,tempSensor3)); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

String SendHTML(float tempSensor1,float tempSensor2,float tempSensor3){
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr +="<title>ESP32 Temperature Monitor</title>\n";
  ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
  ptr +="p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n";
  ptr +="</style>\n";
  ptr +="</head>\n";
  ptr +="<body>\n";
  ptr +="<div id=\"webpage\">\n";
  ptr +="<h1>ESP32 Temperature Monitor</h1>\n";
  ptr +="<p>Living Room: ";
  ptr +=tempSensor1;
  ptr +="&deg;C</p>";
  ptr +="<p>Bedroom: ";
  ptr +=tempSensor2;
  ptr +="&deg;C</p>";
  ptr +="<p>Kitchen: ";
  ptr +=tempSensor3;
  ptr +="&deg;C</p>";
  ptr +="</div>\n";
  ptr +="</body>\n";
  ptr +="</html>\n";
  return ptr;
}

Accessing the Web Server

After uploading the sketch, open the Serial Monitor at a baud rate of 115200. And press the EN button on ESP32. If everything is OK, it will output the dynamic IP address obtained from your router and show HTTP server started message.

ESP32 Station Mode Web Server IP Address On Serial Monitor

Next, load up a browser and point it to the IP address shown on the serial monitor. The ESP32 should serve up a web page showing temperatures from all the DS18B20s.

Multiple DS18B20 Readings on ESP32 Web Server - Without CSS

Detailed Code Explanation

The sketch starts by including following libraries.

  • WiFi.h library provides ESP32 specific WiFi methods we are calling to connect to network.
  • WebServer.h library has some methods available that will help us setting up a server and handle incoming HTTP requests without needing to worry about low level implementation details.
  • DallasTemperature.h library is a hardware-specific library which handles lower-level functions. It needs to be paired with One Wire Library, in order to make it work.
  • OneWire.h library communicates with any one-wire device not just DS18B20.
#include <WiFi.h>
#include <WebServer.h>
#include <OneWire.h>
#include <DallasTemperature.h>

Next we create the instances needed for the temperature sensor and variables to store temperature readings. The temperature sensor is connected to GPIO15.

// Data wire is plugged into port 15 on the ESP32
#define ONE_WIRE_BUS 15

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

float tempSensor1, tempSensor2, tempSensor3;

Next, we enter the addresses that are found previously for each temperature sensor. In our case, we have the following.

uint8_t sensor1[8] = { 0x28, 0xEE, 0xD5, 0x64, 0x1A, 0x16, 0x02, 0xEC  };
uint8_t sensor2[8] = { 0x28, 0x61, 0x64, 0x12, 0x3C, 0x7C, 0x2F, 0x27  };
uint8_t sensor3[8] = { 0x28, 0x61, 0x64, 0x12, 0x3F, 0xFD, 0x80, 0xC6  };

As we are configuring ESP32 in Station (STA) mode, it will join existing WiFi network. Hence, we need to provide it with your network�s SSID & Password. Next we start web server at port 80.

/*Put your SSID & Password*/
const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

WebServer server(80);

Inside Setup() Function

Inside Setup() Function we configure our HTTP server before actually running it. First of all, we initialize serial communication with PC and initialize DallasTemperature object using begin() function. It initializes the bus and detects all the DS18B20s present on it. Each sensor is then assigned with an index and set bit resolution to 12-bit.

Serial.begin(115200);
delay(100);
  
sensors.begin(); 

Now, we need to join the WiFi network using WiFi.begin() function. The function takes SSID (Network Name) and password as a parameter.

Serial.println("Connecting to ");
Serial.println(ssid);

//connect to your local wi-fi network
WiFi.begin(ssid, password);

While the ESP32 tries to connect to the network, we can check the connectivity status with WiFi.status() function.

//check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }

Once the ESP32 is connected to the network, the sketch prints the IP address assigned to ESP32 by displaying WiFi.localIP() value on serial monitor.

Serial.println("");
  Serial.println("WiFi connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

In order to handle incoming HTTP requests, we need to specify which code to execute when a URL is hit. To do so, we use on method. This method takes two parameters. First one is a URL path and second one is the name of function which we want to execute when that URL is hit.

The code below indicates that when a server receives an HTTP request on the root (/) path, it will trigger the handle_OnConnect function. Note that the URL specified is a relative path.

server.on("/", handle_OnConnect);

We haven�t specified what the server should do if the client requests any URL other than specified with server.on. It should respond with an HTTP status 404 (Not Found) and a message for the user. We put this in a function as well, and use server.onNotFound to tell it that it should execute it when it receives a request for a URL that wasn�t specified with server.on

server.onNotFound(handle_NotFound);

Now, to start our server, we call the begin method on the server object.

server.begin();
Serial.println("HTTP server started");

Inside Loop() Function

To handle the actual incoming HTTP requests, we need to call the handleClient() method on the server object.

server.handleClient();

Next, we need to create a function we attached to root (/) URL with server.on Remember?

At the start of this function, we get the temperature reading from each sensor. In order to respond to the HTTP request, we use the send method. Although the method can be called with a different set of arguments, its simplest form consists of the HTTP response code, the content type and the content.

In our case, we are sending the code 200 (one of the HTTP status codes), which corresponds to the OK response. Then, we are specifying the content type as �text/html�, and finally we are calling SendHTML() custom function which creates a dynamic HTML page containing temperature readings.

void handle_OnConnect() {
  sensors.requestTemperatures();
  tempSensor1 = sensors.getTempC(sensor1);
  tempSensor2 = sensors.getTempC(sensor2);
  tempSensor3 = sensors.getTempC(sensor3);
  server.send(200, "text/html", SendHTML(tempSensor1,tempSensor2,tempSensor3)); 
}

Likewise, we need to create a function to handle 404 Error page.

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

Displaying the HTML Web Page

SendHTML() function is responsible for generating a web page whenever the ESP32 web server gets a request from a web client. It merely concatenates HTML code into a big string and returns to the server.send() function we discussed earlier. The function takes temperature readings as a parameter to dynamically generate the HTML content.

The first text you should always send is the <!DOCTYPE> declaration that indicates that we�re sending HTML code.

String SendHTML(float tempSensor1,float tempSensor2,float tempSensor3){
  String ptr = "<!DOCTYPE html> <html>\n";

Next, the <meta> viewport element makes the web page responsive in any web browser, while title tag sets the title of the page.

ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
ptr +="<title>ESP32 Temperature Monitor</title>\n";

Styling the Web Page

Next, we have some CSS to style the web page appearance. We choose the Helvetica font, define the content to be displayed as an inline-block and aligned at the center.

ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";

Following code then sets color, font and margin around the body, H1 and p tags.

ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
ptr +="p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n";
ptr +="</style>\n";
ptr +="</head>\n";
ptr +="<body>\n";

Setting the Web Page Heading

Next, heading of the web page is set; you can change this text to anything that suits your application.

ptr +="<div id=\"webpage\">\n";
ptr +="<h1>ESP32 Temperature Monitor</h1>\n";

Displaying Temperature readings on Web Page

To dynamically display temperature readings, we put those values in paragraph tag. To display degree symbol, we use HTML entity &deg;

ptr +="<p>Living Room: ";
ptr +=tempSensor1;
ptr +="&deg;C</p>";
ptr +="<p>Bedroom: ";
ptr +=tempSensor2;
ptr +="&deg;C</p>";
ptr +="<p>Kitchen: ";
ptr +=tempSensor3;
ptr +="&deg;C</p>";
ptr +="</div>\n";
ptr +="</body>\n";
ptr +="</html>\n";
return ptr;
}

Styling Web Page to Look More Professional

Programmers like us are often intimidated by design � but a little effort can make your web page look more attractive and professional. Below screenshot will give you a basic idea of what we are going to do.

Multiple DS18B20 Readings on ESP32 Web Server - With CSS

Pretty amazing, Right? Without further ado, let�s apply some style to our previous HTML page. To start with, copy-paste below code to replace SendHTML() function from the sketch above.

String SendHTML(float tempSensor1,float tempSensor2,float tempSensor3){
  String ptr = "<!DOCTYPE html>";
  ptr +="<html>";
  ptr +="<head>";
  ptr +="<title>ESP32 Temperature Monitor</title>";
  ptr +="<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
  ptr +="<link href='https://fonts.googleapis.com/css?family=Open+Sans:300,400,600' rel='stylesheet'>";
  ptr +="<style>";
  ptr +="html { font-family: 'Open Sans', sans-serif; display: block; margin: 0px auto; text-align: center;color: #444444;}";
  ptr +="body{margin-top: 50px;} ";
  ptr +="h1 {margin: 50px auto 30px;} ";
  ptr +=".side-by-side{display: table-cell;vertical-align: middle;position: relative;}";
  ptr +=".text{font-weight: 600;font-size: 19px;width: 200px;}";
  ptr +=".temperature{font-weight: 300;font-size: 50px;padding-right: 15px;}";
  ptr +=".living-room .temperature{color: #3B97D3;}";
  ptr +=".bedroom .temperature{color: #F29C1F;}";
  ptr +=".kitchen .temperature{color: #26B99A;}";
  ptr +=".superscript{font-size: 17px;font-weight: 600;position: absolute;right: -5px;top: 15px;}";
  ptr +=".data{padding: 10px;}";
  ptr +=".container{display: table;margin: 0 auto;}";
  ptr +=".icon{width:82px}";
  ptr +="</style>";
  ptr +="</head>";
  ptr +="<body>";
  ptr +="<h1>ESP32 Temperature Monitor</h1>";
  ptr +="<div class='container'>";
  ptr +="<div class='data living-room'>";
  ptr +="<div class='side-by-side icon'>";
  ptr +="<svg enable-background='new 0 0 65.178 45.699'height=45.699px id=Layer_1 version=1.1 viewBox='0 0 65.178 45.699'width=65.178px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><polygon fill=#3B97D3 points='8.969,44.261 8.969,16.469 7.469,16.469 7.469,44.261 1.469,44.261 1.469,45.699 14.906,45.699 ";
  ptr +="14.906,44.261 '/><polygon fill=#3B97D3 points='13.438,0 3,0 0,14.938 16.438,14.938 '/><polygon fill=#3B97D3 points='29.927,45.699 26.261,45.699 26.261,41.156 32.927,41.156 '/><polygon fill=#3B97D3 points='58.572,45.699 62.239,45.699 62.239,41.156 55.572,41.156 '/><path d='M61.521,17.344c-2.021,0-3.656,1.637-3.656,3.656v14.199H30.594V21c0-2.02-1.638-3.656-3.656-3.656";
  ptr +="c-2.02,0-3.657,1.636-3.657,3.656v14.938c0,2.021,1.637,3.655,3.656,3.655H61.52c2.02,0,3.655-1.637,3.655-3.655V21";
  ptr +="C65.177,18.98,63.54,17.344,61.521,17.344z'fill=#3B97D3 /><g><path d='M32.052,30.042c0,2.02,1.637,3.656,3.656,3.656h16.688c2.019,0,3.656-1.638,3.656-3.656v-3.844h-24";
  ptr +="L32.052,30.042L32.052,30.042z'fill=#3B97D3 /><path d='M52.396,6.781H35.709c-2.02,0-3.656,1.637-3.656,3.656v14.344h24V10.438";
  ptr +="C56.053,8.418,54.415,6.781,52.396,6.781z'fill=#3B97D3 /></g></svg>";
  ptr +="</div>";
  ptr +="<div class='side-by-side text'>Living Room</div>";
  ptr +="<div class='side-by-side temperature'>";
  ptr +=(int)tempSensor1;
  ptr +="<span class='superscript'>�C</span></div>";
  ptr +="</div>";
  ptr +="<div class='data bedroom'>";
  ptr +="<div class='side-by-side icon'>";
  ptr +="<svg enable-background='new 0 0 43.438 35.75'height=35.75px id=Layer_1 version=1.1 viewBox='0 0 43.438 35.75'width=43.438px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><g><path d='M25.489,14.909H17.95C13.007,14.908,0,15.245,0,20.188v3.688h43.438v-3.688";
  ptr +="C43.438,15.245,30.431,14.909,25.489,14.909z'fill=#F29C1F /><polygon fill=#F29C1F points='0,31.25 0,35.75 2.5,35.75 4.5,31.25 38.938,31.25 40.938,35.75 43.438,35.75 43.438,31.25 ";
  ptr +="43.438,25.375 0,25.375 	'/><path d='M13.584,11.694c-3.332,0-6.033,0.973-6.033,2.175c0,0.134,0.041,0.264,0.105,0.391";
  ptr +="c3.745-0.631,7.974-0.709,10.341-0.709h1.538C19.105,12.501,16.613,11.694,13.584,11.694z'fill=#F29C1F /><path d='M30.009,11.694c-3.03,0-5.522,0.807-5.951,1.856h1.425V13.55c2.389,0,6.674,0.081,10.444,0.728";
  ptr +="c0.069-0.132,0.114-0.268,0.114-0.408C36.041,12.668,33.34,11.694,30.009,11.694z'fill=#F29C1F /><path d='M6.042,14.088c0-2.224,3.376-4.025,7.542-4.025c3.825,0,6.976,1.519,7.468,3.488h1.488";
  ptr +="c0.49-1.97,3.644-3.489,7.469-3.489c4.166,0,7.542,1.801,7.542,4.025c0,0.17-0.029,0.337-0.067,0.502";
  ptr +="c1.08,0.247,2.088,0.549,2.945,0.926V3.481C40.429,1.559,38.871,0,36.948,0H6.49C4.568,0,3.009,1.559,3.009,3.481v12.054";
  ptr +="c0.895-0.398,1.956-0.713,3.095-0.968C6.069,14.41,6.042,14.251,6.042,14.088z'fill=#F29C1F /></g></svg>";
  ptr +="</div>";
  ptr +="<div class='side-by-side text'>Bedroom</div>";
  ptr +="<div class='side-by-side temperature'>";
  ptr +=(int)tempSensor2;
  ptr +="<span class='superscript'>�C</span></div>";
  ptr +="</div>";
  ptr +="<div class='data kitchen'>";
  ptr +="<div class='side-by-side icon'>";
  ptr +="<svg enable-background='new 0 0 48 31.5'height=31.5px id=Layer_1 version=1.1 viewBox='0 0 48 31.5'width=48px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><circle cx=24.916 cy=15.75 fill=#26B99A r=15.75 /><path d='M14.917,15.75c0-5.522,4.478-10,10-10c2.92,0,5.541,1.26,7.369,3.257l1.088-1.031";
  ptr +="c-2.103-2.285-5.106-3.726-8.457-3.726c-6.351,0-11.5,5.149-11.5,11.5c0,3.127,1.252,5.958,3.277,8.031l1.088-1.031";
  ptr +="C16.011,20.945,14.917,18.477,14.917,15.75z'fill=#FFFFFF /><path d='M45.766,2.906c-1.232,0-2.232,1-2.232,2.234v11.203c0,0,2.76,0,3,0v12H48v-12V2.906";
  ptr +="C48,2.906,46.035,2.906,45.766,2.906z'fill=#26B99A /><path d='M6.005,2.917v5.184c0,0.975-0.638,1.792-1.516,2.083V2.917H3.021v7.267c-0.878-0.29-1.516-1.107-1.516-2.083";
  ptr +="V2.917H0v5.458c0,1.802,1.306,3.291,3.021,3.592v16.376H4.49v-16.38c1.695-0.318,2.979-1.8,2.979-3.588V2.917H6.005z'fill=#26B99A /></svg>";
  ptr +="</div>";
  ptr +="<div class='side-by-side text'>Kitchen</div>";
  ptr +="<div class='side-by-side temperature'>";
  ptr +=(int)tempSensor3;
  ptr +="<span class='superscript'>�C</span></div>";
  ptr +="</div>";
  ptr +="</div>";
  ptr +="</body>";
  ptr +="</html>";
  return ptr;
}

If you try to compare this function with the previous one, you�ll come to know that they are similar except these changes.

  • We have used Google commissioned Open Sans web font for our web page. Note that you cannot see Google font, without active internet connection on the device. Google fonts are loaded on the fly.
    ptr +="<link href='https://fonts.googleapis.com/css?family=Open+Sans:300,400,600' rel='stylesheet'>";
  • The icons used to display temperature readings are actually a Scalable Vector Graphics (SVG) defined in <svg> tag. Creating SVG doesn�t require any special programming skills. You can use Google SVG Editor for creating graphics for your page. We have used these SVG icons.
    SVG Icons

Improvement to the Code � Auto Page Refresh

One of the improvements you can do with our code is refreshing the page automatically in order to update the sensor value.

With the addition of a single meta tag into your HTML document, you can instruct the browser to automatically reload the page at a provided interval.

<meta http-equiv="refresh" content="2" >

Place this code in the the <head> tag of your document, this meta tag will instruct the browser to refresh every two seconds. Pretty nifty!

Dynamically load Sensor Data with AJAX

Refreshing a web page isn�t too practical if you have a heavy web page. A better method is to use Asynchronous Javascript And Xml (AJAX) so that we can request data from the server asynchronously (in the background) without refreshing the page.

The XMLHttpRequest object within JavaScript is commonly used to execute AJAX on webpages. It performs the silent GET request on the server and updates the element on the page. AJAX is not a new technology, or different language, just existing technologies used in new ways. Besides this, AJAX also makes it possible to

  • Request data from a server after the page has loaded
  • Receive data from a server after the page has loaded
  • Send data to a server in the background

Here is the AJAX script that we�ll be using. Place this script just before you close </head> tag.

ptr +="<script>\n";
ptr +="setInterval(loadDoc,1000);\n";
ptr +="function loadDoc() {\n";
ptr +="var xhttp = new XMLHttpRequest();\n";
ptr +="xhttp.onreadystatechange = function() {\n";
ptr +="if (this.readyState == 4 && this.status == 200) {\n";
ptr +="document.body.innerHTML =this.responseText}\n";
ptr +="};\n";
ptr +="xhttp.open(\"GET\", \"/\", true);\n";
ptr +="xhttp.send();\n";
ptr +="}\n";
ptr +="</script>\n";

The script starts with <script> tag. As AJAX script is nothing but a javascript, we need to write it in <script> tag. In order for this function to be repeatedly called, we will be using the javascript setInterval() function. It takes two parameters � a function to be executed and time interval (in milliseconds) on how often to execute the function.

ptr +="<script>\n";
ptr +="setInterval(loadDoc,1000);\n";

The heart of this script is a loadDoc() function. Inside this function, an XMLHttpRequest() object is created. This object is used to request data from a web server.

ptr +="function loadDoc() {\n";
ptr +="var xhttp = new XMLHttpRequest();\n";

The xhttp.onreadystatechange() function is called every time the readyState changes. The readyState property holds the status of the XMLHttpRequest. It has one of the following values.

  • 0: request not initialized
  • 1: server connection established
  • 2: request received
  • 3: processing request
  • 4: request finished and response is ready

The status property holds the status of the XMLHttpRequest object. It has one of the following values.

  • 200: �OK�
  • 403: �Forbidden�
  • 404: �Page not found�

When readyState is 4 and status is 200, the response is ready. Now, the content of body (holding temperature readings) is updated.

ptr +="xhttp.onreadystatechange = function() {\n";
ptr +="if (this.readyState == 4 && this.status == 200) {\n";
ptr +="document.body.innerHTML =this.responseText}\n";
ptr +="};\n";

The HTTP request is then initiated via the open() and send() functions.

ptr +="xhttp.open(\"GET\", \"/\", true);\n";
ptr +="xhttp.send();\n";
ptr +="}\n";

If you were asked a few years ago how much a digital camera with WiFi would cost, you probably wouldn�t have said $10. But that is no longer the case.

The ESP32-CAM, a board that hit the market in early 2019, has changed the game. Amazingly, for less than $10, you get an ESP32 with support for a camera and an SD card.

These modules are seriously nifty. Whether you need to detect motion in your Halloween project, detect faces, decode license plates, or perhaps merely a security camera, it�s worth having one in your DIY toolbox.

ESP32-CAM Hardware Overview

The heart of the ESP32-CAM is an ESP32-S System-on-Chip (SoC) from Ai-Thinker. Being an SoC, the ESP32-S chip contains an entire computer�the microprocessor, RAM, storage, and peripherals�on a single chip. While the chip�s capabilities are quite impressive, the ESP32-CAM development board adds even more features to the mix. Let�s take a look at each component one by one.

The ESP32-S Processor

The ESP32-CAM equips the ESP32-S surface-mount printed circuit board module from Ai-Thinker. It is equivalent to Espressif�s ESP-WROOM-32 module (same form factor and general specifications).

esp32 cam esp32s module

The ESP32-S contains a Tensilica Xtensa� LX6 microprocessor with two 32-bit cores operating at a staggering 240 MHz! This is what makes the ESP32-S suitable for intensive tasks like video processing, facial recognition, and even artificial intelligence.

The Memory

Memory is paramount for complex tasks, so the ESP32-S has a full 520 kilobytes of internal RAM, which resides on the same die as the rest of the chip�s components.

esp32 cam ram flash memory psram

It may be inadequate for RAM-intensive tasks, so ESP32-CAM includes 4 MB of external PSRAM (Pseudo-Static RAM) to expand the memory capacity. This is plenty of RAM, especially for intensive audio or graphics processing.

All these features amount to nothing if you don�t have enough storage for your programs and data. The ESP32-S chip shines here as well, as it contains 4 MB of on-chip flash memory.

The Camera

The OV2640 camera sensor on the ESP32-CAM is what sets it apart from other ESP32 development boards and makes it ideal for use in video projects like a video doorbell or nanny cam.

esp32 cam ov2640 camera sensor

The OV2640 camera has a resolution of 2 megapixels, which translates to a maximum of 1600�1200 pixels, which is sufficient for many surveillance applications.

The ESP32-CAM is compatible with a wide variety of camera sensors, as listed on github.

The Storage

The addition of a microSD card slot on the ESP32-CAM is a nice bonus. This allows for limitless expansion, making it a great little board for data loggers or image capture.

esp32 cam microsd card slot

The Antenna

The ESP32-CAM comes with an on-board PCB trace antenna as well as a u.FL connector for connecting an external antenna. An Antenna Selection jumper (zero-ohm resistor) allows you to choose between the two options.

esp32 cam pcb antenna ufl connector selection jumper

If you want to switch from the onboard antenna, refer to connecting an external antenna to ESP32-CAM.

LEDs

The ESP32-CAM has a white square LED. It is intended to be used as a camera flash, but it can also be used for general illumination.

esp32 cam leds and gpio pin numbers

There is a small red LED on the back that can be used as a status indicator. It is user-programmable and connected to GPIO33.

Technical Specifications

To summarize, the ESP32-CAM has the following specifications.

  • Processors:
    • CPU: Xtensa dual-core 32-bit LX6 microprocessor, operating at 240 MHz and performing at up to 600 DMIPS
    • Ultra low power (ULP) co-processor
  • Memory:
    • 520 KB SRAM
    • 4MB External PSRAM
    • 4MB internal flash memory
  • Wireless connectivity:
    • Wi-Fi: 802.11 b/g/n
    • Bluetooth: v4.2 BR/EDR and BLE (shares the radio with Wi-Fi)
  • Camera:
    • 2 Megapixel OV2640 sensor
    • Array size UXGA 1622�1200
    • Output formats include YUV422, YUV420, RGB565, RGB555 and 8-bit compressed data
    • Image transfer rate of 15 to 60 fps
    • Built-in flash LED
    • Support many camera sensors
  • Supports microSD card
  • Security:
    • IEEE 802.11 standard security features all supported, including WFA, WPA/WPA2 and WAPI
    • Secure boot
    • Flash encryption
    • 1024-bit OTP, up to 768-bit for customers
    • Cryptographic hardware acceleration: AES, SHA-2, RSA, elliptic curve cryptography (ECC), random number generator (RNG)
  • Power management:
    • Internal low-dropout regulator
    • Individual power domain for RTC
    • 5?A deep sleep current
    • Wake up from GPIO interrupt, timer, ADC measurements, capacitive touch sensor interrupt

Schematic and Datasheets

For more information on ESP32-CAM, please refer to:

ESP32-CAM Power Consumption

The power consumption of the ESP32-CAM varies depending on what you�re using it for.

It ranges from 80 mAh when not streaming video to around 100~160 mAh when streaming video; with the flash on, it can reach 270 mAh.

Operation modePower Consumption
Stand by80 mAh
In streaming100~160 mAh
In streaming with flash270 mAh

ESP32-CAM Pinout

The ESP32-CAM has 16 pins in total. For convenience, pins with similar functionality are grouped together. The pinout is as follows:

esp32 cam pinout

Power Pins There are two power pins: 5V and 3V3. The ESP32-CAM can be powered via the 3.3V or 5V pins. Since many users have reported problems when powering the device with 3.3V, it is advised that the ESP32-CAM always be powered via the 5V pin. The VCC pin normally outputs 3.3V from the on-board voltage regulator. It can, however, be configured to output 5V by using the Zero-ohm link near the VCC pin.

GND is the ground pin.

GPIO Pins The ESP32-S chip has 32 GPIO pins in total, but because many of them are used internally for the camera and the PSRAM, the ESP32-CAM only has 10 GPIO pins available. These pins can be assigned a variety of peripheral duties, such as UART, SPI, ADC, and Touch.

UART Pins The ESP32-S chip actually has two UART interfaces, UART0 and UART2. However, only the RX pin (GPIO 16) of UART2 is broken out, making UART0 the only usable UART on the ESP32-CAM (GPIO 1 and GPIO 3). Also, because the ESP32-CAM lacks a USB port, these pins must be used for flashing as well as connecting to UART-devices such as GPS, fingerprint sensors, distance sensors, and so on.

MicroSD Card Pins are used for interfacing the microSD card. If you aren�t using a microSD card, you can use these pins as regular inputs and outputs.

ADC Pins On the ESP32-CAM, only ADC2 pins are broken out. However, because ADC2 pins are used internally by the WiFi driver, they cannot be used when Wi-Fi is enabled.

Touch Pins The ESP32-CAM has 7 capacitive touch-sensing GPIOs. When a capacitive load (such as a human finger) is in close proximity to the GPIO, the ESP32 detects the change in capacitance.

SPI Pins The ESP32-CAM features only one SPI (VSPI) in slave and master modes.

PWM Pins The ESP32-CAM has 10 channels (all GPIO pins) of PWM pins controlled by a PWM controller. The PWM output can be used for driving digital motors and LEDs.

For more information, refer to our comprehensive ESP32-CAM pinout reference guide. This guide also explains which ESP32-CAM GPIO pins are safe to use and which pins should be used with caution.

Programming the ESP32-CAM

Programming the ESP32-CAM can be a bit of a pain as it lacks a built-in USB port. Because of that design decision, users require additional hardware in order to upload programs from the Arduino IDE. None of that is terribly complex, but it is inconvenient.

To program this device, you�ll need either a USB-to-serial adapter (an FTDI adapter) or an ESP32-CAM-MB programmer adapter.

Using the FTDI Adapter

If you�ve decided to use the FTDI adapter, here�s how you connect it to the ESP32-CAM module.

esp32 cam ftdi adapter connections

Many FTDI programmers have a jumper that lets you choose between 3.3V and 5V. As we are powering the ESP32-CAM with 5V, make sure the jumper is set to 5V.

Please note that the GPIO 0 pin is connected to Ground. This connection is only necessary while programming the ESP32-CAM. Once you have finished programming the module, you must disconnect this connection.

Remember! You�ll have to make this connection every time you want to upload a new sketch.

Using the FTDI Adapter to program the ESP32-CAM is a bit of a hassle. This is why many vendors now sell the ESP32-CAM board along with a small add-on daughterboard called the ESP32-CAM-MB.

You stack the ESP32-CAM on the daughterboard, attach a micro USB cable, and click the Upload button to program your board. It�s that simple.

esp32 cam mb programmer hardware overview

The highlight of this board is the CH340G USB-to-Serial converter. That�s what translates data between your computer and the ESP32-CAM. There�s also a RESET button, a BOOT button, a power indicator LED, and a voltage regulator to supply the ESP32-CAM with plenty of power.

Setting Up the Arduino IDE

Installing the ESP32 Board

To use the ESP32-CAM, or any ESP32, with the Arduino IDE, you must first install the ESP32 board (also known as the ESP32 Arduino Core) via the Arduino Board Manager.

If you haven�t already, follow this tutorial to install the ESP32 board:

Selecting the Board and Port

After installing the ESP32 Arduino Core, restart your Arduino IDE and navigate to Tools > Board > ESP32 Arduino and select AI-Thinker ESP32-CAM.

selecting esp32cam board arduino ide boards manager

Now connect the ESP32-CAM to your computer using a USB cable. Then, navigate to Tools > Port and choose the COM port to which the ESP32-CAM is connected.

esp32 cam port selection

That�s it; the Arduino IDE is now set up for the ESP32-CAM!

Once you�ve finished the preceding steps, you are ready to test your first program with your ESP32-CAM! Launch the Arduino IDE. If you disconnected your board, plug it back in.

Let�s upload the most basic sketch of all � Blink!

This sketch uses the on-board Camera Flash LED. This LED is connected to GPIO 4.

int flashPin = 4;

void setup() {
    pinMode(flashPin, OUTPUT);
}

void loop() {
    digitalWrite(flashPin, HIGH);
    delay(1000);
    digitalWrite(flashPin, LOW);
    delay(1000);
}

Now, press the upload button. If you are using the FTDI adapter, disconnect GPIO 0 from GND after uploading the code. To execute the sketch, you may need to press the Reset button on your ESP32-CAM.

If everything worked, the on-board Flash LED on your ESP32-CAM should now be blinking!

Congratulations! You have just programmed your first ESP32-CAM!

More ESP32-CAM Examples

The ESP32 Arduino core includes several examples that demonstrate everything from scanning for nearby networks to building a web server. To access the example sketches, navigate to File > Examples > ESP32.

You will see a selection of example sketches. You can choose any of them to load the sketch into your IDE and start experimenting with it.

arduino ide esp32 cam examples

ESP32-CAM Example 2 : Live Video Streaming Server

Let�s try running the CameraWebServer sketch. This sketch transforms the ESP32-CAM into a full-featured webcam, complete with features like face detection and a ton of customization options. This is a very impressive example of what the ESP32-CAM can do!

You can find this example under File > Examples > ESP32 > Camera > CameraWebServer.

esp32 cam camerawebserver example

We�ll need to make a few changes to get this to work with your ESP32-CAM.

You must choose the appropriate camera model. Because we�re using the AI-THINKER model here, uncomment it and comment all the other models.

// ===================
// Select camera model
// ===================
//#define CAMERA_MODEL_WROVER_KIT // Has PSRAM
//#define CAMERA_MODEL_ESP_EYE // Has PSRAM
//#define CAMERA_MODEL_ESP32S3_EYE // Has PSRAM
//#define CAMERA_MODEL_M5STACK_PSRAM // Has PSRAM
//#define CAMERA_MODEL_M5STACK_V2_PSRAM // M5Camera version B Has PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE // Has PSRAM
//#define CAMERA_MODEL_M5STACK_ESP32CAM // No PSRAM
//#define CAMERA_MODEL_M5STACK_UNITCAM // No PSRAM
#define CAMERA_MODEL_AI_THINKER // Has PSRAM
//#define CAMERA_MODEL_TTGO_T_JOURNAL // No PSRAM
// ** Espressif Internal Boards **
//#define CAMERA_MODEL_ESP32_CAM_BOARD
//#define CAMERA_MODEL_ESP32S2_CAM_BOARD
//#define CAMERA_MODEL_ESP32S3_CAM_LCD

Next, you need to tell the ESP32-CAM about your wireless network. Fill in the following variables with your network credentials:

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

The code that needs to be modified is highlighted in yellow.

esp32 cam camerawebserver changes highlighted

The code is now ready to be uploaded to the ESP32-CAM.

Accessing the Video Streaming Server

Once you have uploaded the sketch, open the serial monitor at baud rate 115200 and press the Reset button on the ESP32-CAM. You should see the IP address in the Serial Monitor.

esp32 cam ip address

Launch a browser and enter the IP address shown on the serial monitor. Ensure that the web browser is on the same network that the ESP32-CAM is connected to.

The ESP32-CAM should display a web page. To begin video streaming, press the Start Stream button.

esp32 cam live video streaming

You can play around with various camera settings in the left pane. For instance, you can alter the video�s resolution and frame rate, as well as its brightness, contrast, saturation, and the like.

esp32 camerawebserver controls

Simply hit the Get Still button to take a picture. Note that images are downloaded to the computer rather than being saved to the microSD card.

Connecting an External Antenna to ESP32-CAM

The ESP32-CAM comes with an on-board PCB trace antenna as well as a u.FL connector for connecting an external antenna. An Antenna Selection jumper (zero-ohm resistor) allows you to choose between the two options.

The PCB antenna is an excellent way to begin experimenting with the ESP32-CAM. It works well if you are close to your router (AI-Thinker claims the PCB antenna has a gain of 2.1dBi).

If you are too far away from your router, you may experience slow video streaming and other connectivity issues, in which case you should use a 2.4GHz external antenna with an IPX connector. For this, you need to change the antenna jumpers around to enable the u.FL connector.

esp32 cam antenna selection setting

A trio of solder pads are located next to the u.FL connector and between the on-board antenna and the ESP32-S�s metal case. A zero-ohm resistor connects the top two pads. Simply remove this resistor and place it between the bottom pads.

Without a doubt, the ESP32-CAM is a powerful device with built-in camera and WiFi support. Unfortunately, the ESP32-CAM has fewer I/O pins, some of which are shared with the SD card and thus cannot be used when the card is present, making it difficult to design a project around it.

There are a few things you should be aware of, so please read the pinout carefully.

Note:

Please note that the following pinout reference is for the popular ESP32-CAM board from AI-Thinker.

ESP32-CAM Pinout

The ESP32-CAM has 16 pins in total. For convenience, pins with similar functionality are grouped together. The pinout is as follows:

esp32 cam pinout

Delving a little further into which pins do what�

ESP32-CAM GPIO Pins

The ESP32-S chip has 32 GPIO pins in total, but because many of them are used internally for the camera and the PSRAM, the ESP32-CAM only has 10 GPIO pins available. These pins can be assigned a variety of peripheral duties, such as UART, SPI, ADC, and Touch, by programming the appropriate registers.

esp32 cam gpio pins

Which ESP32-CAM GPIOs are safe to use?

Although the ESP32-CAM has 10 GPIO pins with various functions, some of them may not be suitable for your projects. The table below shows which pins are safe to use and which pins should be used with caution.

  • � Your top priority pins. They are perfectly safe to use.
  • � Pay close attention because their behavior, particularly during boot, can be unpredictable. Also, some GPIOs are shared with the microSD card. So use them with caution.
  • � It is recommended that you avoid using these pins.
��Label����GPIO����Safe�to�use?��Reason
D00must be HIGH during boot and LOW for flashing
TX01Tx pin, used for flashing and debugging
D22must be LOW during boot, cannot be used when microSD card is present
RX03Rx pin, used for flashing and debugging
D44Connected to the on-board Flash LED, cannot be used when microSD card is present
D1212must be LOW during boot, cannot be used when microSD card is present
D1313cannot be used when microSD card is present
D1414cannot be used when microSD card is present
D1515must be HIGH during boot, prevents startup log if pulled LOW, cannot be used when microSD card is present
RX216

The image below shows which GPIO pins can be used safely.

esp32 cam gpio pins that are safe to use

GPIO 0 Pin

GPIO 0 is the most important pin. It determines whether or not the ESP32 is in flashing mode.

This GPIO must be HIGH during boot and LOW during flashing, so it is internally pulled up by a 10K resistor. When you connect GPIO 0 to GND, the ESP32 enters flash mode, and you can upload code to the board. Once you have finished programming the board, you must disconnect this connection.

Remember! You�ll have to make this connection every time you want to upload a new code.

GPIO 33 � Built-in Red LED

There is a small red LED on the back that can be used as a status indicator. It is user-programmable and connected to GPIO33.

This LED works with inverted logic, so to turn it on, you send a LOW signal and to turn it off, you send a HIGH signal.

GPIO 4 � Camera FLASH

The ESP32-CAM has a very bright white LED. It is intended to be used as a camera flash, but it can also be used for general illumination. This LED is internally connected to GPIO 4.

ESP32-CAM MicroSD Card Pins

The following pins are used for interfacing the microSD card. If you aren�t using a microSD card, you can use these pins as regular inputs and outputs.

esp32 cam microsd card pins

ESP32-CAM ADC Pins

On the ESP32-CAM, only ADC2 pins are broken out. However, because ADC2 pins are used internally by the WiFi driver, they cannot be used when Wi-Fi is enabled.

esp32 cam adc pins

ESP32-CAM Touch Pins

The ESP32-CAM has 7 capacitive touch-sensing GPIOs. When a capacitive load (such as a human finger) is in close proximity to the GPIO, the ESP32 detects the change in capacitance.

esp32 cam touch pins

You can make a touch pad by attaching any conductive object to these pins, such as aluminum foil, conductive cloth, conductive paint, and so on. Because of the low-noise design and high sensitivity of the circuit, relatively small pads can be made.

Additionally, these capacitive touch pins can be used to wake the ESP32 from deep sleep.

ESP32-CAM SPI Pins

The ESP32-CAM features only one SPI (VSPI) in slave and master modes. It also supports the general-purpose SPI features listed below:

  • 4 timing modes of the SPI format transfer
  • Up to 80 MHz and the divided clocks of 80 MHz
  • Up to 64-Byte FIFO
esp32 cam spi pins

ESP32-CAM UART Pins

The ESP32-S chip actually has two UART interfaces, UART0 and UART2.

esp32 cam uart pins

However, only the RX pin (GPIO 16) of UART2 is broken out, making UART0 the only usable UART on the ESP32-CAM (GPIO 1 and GPIO 3). Also, because the ESP32-CAM lacks a USB port, these pins must be used for flashing as well as connecting to UART-devices such as GPS, fingerprint sensors, distance sensors, and so on.

ESP32-CAM PWM Pins

The board has 10 channels (all GPIO pins) of PWM pins controlled by a PWM controller. The PWM output can be used for driving digital motors and LEDs.

esp32 cam pwm pins

The PWM controller consists of PWM timers, the PWM operator and a dedicated capture sub-module. Each timer provides timing in synchronous or independent form, and each PWM operator generates a waveform for one PWM channel. The dedicated capture sub-module can accurately capture events with external timing.

ESP32-CAM RTC GPIO Pins

The GPIOs that are routed to the RTC low-power subsystem are referred to as RTC GPIOs. These pins are used to wake the ESP32 from deep sleep when the Ultra Low Power (ULP) co-processor is running. The GPIOs highlighted below can be used as external wake up sources.

esp32 cam rtc gpio pins

ESP32-CAM Power Pins

There are two power pins: 5V and 3V3. The ESP32-CAM can be powered via the 3.3V or 5V pins. Since many users have reported problems when powering the device with 3.3V, it is advised that the ESP32-CAM always be powered via the 5V pin.

The VCC pin normally outputs 3.3V from the on-board voltage regulator. It can, however, be configured to output 5V by using the Zero-ohm link near the VCC pin.

GND is the ground pin.

esp32 cam power pins

Whether you call them individually addressable RGB LEDs, WS2812B, or NeoPixels, there�s no denying that they are extremely popular and a must-have for any glowy and blinky project.

Writing code to control addressable LEDs isn�t difficult, but what if you just want to add some ambient lighting to your living room or office and manage it all from your smartphone? Currently the best option, hands down, is WLED�A free, feature-rich, open-source mobile app that gives us complete command over a wide variety of RGB LEDs.

The WLED app makes controlling individually addressable LEDs much simpler, convenient, and�most importantly�fun. This app is just too cool not to try.

In this tutorial, we will learn how to install WLED on an ESP32 board and use it to control a string of addressable LEDs.

Installing WLED on an ESP32 Board

WLED has made installing this custom firmware on an ESP32 Board a breeze. We simply plug in the ESP32 and click a few buttons, and the installer does the rest.

1. Connect your ESP32 board to your computer using a USB cable. Make sure the USB cable you�re using supports data transfer.

esp32 connected to computer using a usb cable

2. Launch a web browser and navigate to install.wled.me. This URL will take you to the website shown below.

wled installation page

Make sure the browser supports Web Serial. As of this writing, that means �desktop� browsers such as Google Chrome, Microsoft Edge, or Opera. Other browsers (Safari, Firefox, Explorer, and mobile) will not work.

3. Click Install.

4. Select the COM port to which your ESP32 is connected and click Connect. WLED uses Web Serial API to open serial ports on your computer.

If nothing appears, you may need to update your USB-to-serial drivers, or you may not be using a USB cable that supports data transfer.

wled select com port

5. Click �Install WLED� to begin the process.

wled install button

6. Confirm the installation to flash the firmware to the board. This additional step serves as your final warning, after which all data on the device will be erased.

It is important to note that some ESP32 boards require you to press the BOOT button before selecting the final install button.

wled final installation confirmation

7. The installation process should now begin. You can now release the BOOT button as it was able to connect. Installation should only take a few minutes.

wled installing

8. Click Next to complete the installation.

wled installation complete

9. Enter your Wi-Fi credentials and click Connect. This will connect your ESP32 to your Wi-Fi network and save the details for later use.

wled configure wifi

The WiFi network must be 2.4 GHz; the ESP32 does not support 5 GHz networks.

10. The message �Device connected to the network!� should appear. Click Visit Device to access the WLED User Interface (UI).

wled visit device

11. Make a note of the URL as you will need it to access the WLED UI.

wled web interface url

A Quick Tour of the WLED UI

WLED�s UI is simple, but may appear overwhelming at first. The UI can be divided into five sections.

wled ui overview pc mode
WLED Web Interface
wled app ui overview
WLED App

1. Configuration: Here you can find buttons such as Power (to turn the lights on or off), Timer (to organize the lights to come on and off at certain points during the day), Sync (to sync multiple WLED devices across your network), Peek (to see an animation preview of your lights), Config (to configure the number of LEDs and the GPIO port), and a Brightness slider (to control the overall brightness).

2. Color Picker: This section allows you to change the color of the LEDs, whether they are static or animated. If you scroll down this section, you can access various color palettes for use in effects.

3. Effects/Animation: Here you�ll find a library of ready-made animations for your lights. While each effect has its own color scheme, you can easily customize it in the Color Picker section.

4. Segment: If you have a large array or matrix of LEDs, you can split them into segments and assign a different color, animation, or color scheme to each segment.

5. Presets: Here, you can create presets for your custom light show, as well as a playlist to cycle through the many available animations.

Configuring the WLED

Changing the URL

Once the setup is complete it�s a good idea to head to WiFi Setup screen and change the address to something you can remember so you can easily access this interface again.

1. Click on Config.

wled config

2. Select WiFi Setup.

wled wifi settings

3. Scroll down to mDNS address and set the address to your liking. I called mine http://mylights.local/ � from now on, this is what I�ll be typing into a web browser on my WiFi network to connect to my lights.

wled mdns address

4. Scroll to the top or bottom of the page and click Save & Connect.

wled wifi setup save and connect

5. The message �WiFi settings saved.� should appear.

wled wifi settings saved

6. Connect to the new IP address now.

wled accessed through new address

Configuring LED lights

1. Click on Config and select LED Preferences.

wled led preferences

2. Scroll down to Hardware Setup and select the type of led strip you have.

wled led type select

4. Set the �Length� to match the number of LEDs. We have a total of 12 LEDs, so I changed the length to 12.

wled number of leds select

5. Make a note of the GPIO pin number. This pin will be used to send data to the LEDs. By default, GPIO16 is used.

wled esp32 gpio pin select

6. Scroll down to the bottom and click Save.

wled save led preferences

7. Click Back to return to the main screen.

wled return to main page

Connecting a WS2812x Addressable LED Strip to an ESP32

Once you�ve finished configuring the WLED, unplug the ESP32 from the USB port. Let�s wire up an addressable LED strip to the ESP32.

The wiring is relatively simple. There are only three wires to connect: two for power and one for data transmission.

Connect the Red wire (+5V/VCC) of the addressable LED strip to the ESP32�s VIN pin and the White/Yellow wire (GND) to the ESP32�s GND pin.

Finally, connect the Green wire (DIN) of the LED strip to the ESP32�s GPIO16 (RX2), via a 330 Ohm resistor. This in-line resistor is there to protect the data pin. A resistor between 220 and 470 Ohm should do nicely. Try to place the resistor as close to your addressable LEDs as possible.

If you have few LEDs you can plug the ESP32 into your computer using a USB cable and power the strip directly through the board.

connecting ws2812b addressable led strip to esp32 usb powered

If you have a larger project that requires more LEDs, USB power won�t be enough. Instead you should inject power into the strip from an external source. Keep in mind that each RGB LED consumes about 60mA (20mA per color channel), when the brightness is set to full. That means that for every 30 LEDs, your LED strip could be drawing up to 1.8 Amps.

connecting ws2812b addressable led strip to esp32 5v external power

Once wiring is complete, LEDs should come to life and emit a soft yellow light. If this isn�t the case, double-check your wiring before proceeding.

From here everything can now be done via the WLED App.

Using the WLED Mobile App

1. Download the WLED app from the Google Play Store or the Apple App Store on your smartphone or tablet.

wled app on google play store

2. Open the app and click on the plus icon in the upper right corner to open up the discovery page.

wled app add devices

3. Hit Discover Lights. This will search through your WIFI for all connected boards running WLED software.

wled app discover lights

4. Once it says Found WLED! click the Tick icon in the upper-right corner. This will bring you back to the home page where you will have a list of all the WLED devices on your network.

wled app add discovered wled devices

5. Click on the newly discovered device to open the control panel.

wled app list of wled devices

6. Select a color using the color wheel. And voil�, you now have fully operational, remotely activated addressable LEDs!

wled app interface

If the color of the LEDs does not match the color you selected in the app, go to Config > LED Preferences and adjust the Color Order setting under Hardware Setup until it does.

wled color order setting

7. If you have multiple WLED devices, you may want to change the name displayed on the app to differentiate them. Go to Config > User Interface and name it whatever you want, then hit Save.

wled device name change

Changing Effects

WLED offers over 180 different effects. This is where the real fun begins.

1. Navigate to the Effects tab and select an effect. The LEDs will respond immediately.

wled app effect select

2. You can change the speed and intensity of the effect. The further you move the slider, the faster and more intense the effect.

wled app effect speed intensity

3. Each effect has its own color scheme, which you can easily change in the Color Picker section. This will keep the animation effect but replace the colors.

wled app effect color scheme

Tips and Recommendations

There are a few tips and recommendations to keep in mind when designing your setup with ESP32:

  • ESP32 can handle up to ten strips at a time.
  • While most strip types have yet to be tested, the following are known to work: WS281x, SK6812 RGBW, PWM white
  • As soon as installation is complete, select the LEDs type, pin numbers, length, and color order of your LED strips in the LED settings page.
  • Framerate can be increased significantly by selecting the appropriate power supply for the setup and turning off the WLED brightness limiter setting.
  • There are no pin restrictions; feel free to use any available pin.
  • The performance is determined by how many LEDs you drive with the ESP32 and how many ESP32 output pins you use.
    • For excellent performance, it is recommended to use 512 LEDs/pin with 4 outputs for a total of 2048 LEDs.
    • For optimal performance, it is recommended to use 800 LEDs/pin with 4 outputs for a total of 3200 LEDs.
    • For good performance, you can use 1000 LEDs/pin with 4 outputs for a total of 4000 LEDs.
    • For marginal performance, you can use 1000 LEDs/pin with 5 outputs for a total of 5000 LEDs.
    • For marginal performance, you can use 800 LEDs/pin with 6 outputs for a total of 4800 LEDs.
  • ESP32 is capable of calculating approximately 65k-85k LEDs per second (that�s 1000 LEDs @~70fps, 2000 LEDs @~35fps, 4000 LEDs @~18fps)

Wildly blinking LEDs may not be the ideal lighting, but they�ll surely spice up any party. And since a party without music is boring, having both synced up is a great way to set the mood.

When it comes to sound-reactive lighting, WLED is without a doubt the best option. It�s a free, feature-rich, open-source mobile app that gives us complete command over a wide variety of NeoPixel LEDs. With the help of a MAX4466-amplified microphone connected to an ESP32, this app allows you to sync an LED strip with music. Additionally, you can select from more than 30 distinct sound-reactive effects and more than 70 color palettes to make the party even more fun.

In this tutorial, you will learn how to connect a strip of WS2812B LEDs and a MAX4466 microphone amplifier module to an ESP32 board and install WLED-SR (the sound-reactive version of WLED).

Things You Will Need

For this project, you will need the following items:

  • An ESP32 Development board
  • A WS2812B addressable LED strip
  • A MAX4466 microphone amplifier module
  • A 5V power supply (with a rating of 3A or higher)

Installing WLED-SR on an ESP32 Board

WLED has made installing this custom firmware on an ESP32 Board a breeze. We simply plug in the ESP32 and click a few buttons, and the installer does the rest.

1. Connect your ESP32 board to your computer using a USB cable. Make sure the USB cable you�re using supports data transfer.

esp32 connected to computer using a usb cable

2. Launch a web browser and navigate to install.wled.me. This URL will take you to the website shown below.

wled installation page 2

3. From the dropdown menu, you will need to select the �sound-reactive� version of WLED, which can be found as the bottom option.

select sound reactive version of wled

Make sure the browser supports Web Serial. As of this writing, that means �desktop� browsers such as Google Chrome, Microsoft Edge, or Opera. Other browsers (Safari, Firefox, Explorer, and mobile) will not work.

4. Click Install.

click install wled

5. Select the COM port to which your ESP32 is connected and click Connect. WLED uses Web Serial API to open serial ports on your computer.

wled select com port

If nothing appears, you may need to update your USB-to-serial drivers, or you may not be using a USB cable that supports data transfer.

If your ESP32 isn�t connecting, you might need to press and hold the BOOT button while connecting.

6. Click �Install WLED SR� to begin the process.

wled sr install button

7. Confirm the installation to flash the firmware to the board. This additional step serves as your final warning, after which all data on the device will be erased.

wled sr final installation confirmation

It is important to note that some ESP32 boards require you to press the BOOT button before selecting the final install button.

8. The installation process should now begin. You can now release the BOOT button as it was able to connect. Installation should only take a few minutes.

wled sr installing

9. Click Next to complete the installation.

wled sr installation complete

10. Select the Network icon on the taskbar. You should notice a new wireless access point named WLED-AP.

wled ap network

11. Join that and then enter wled1234 if prompted for a password.

wled ap network joined

12. When you connect to the network, your default browser will launch automatically and load the WLED home page. If it doesn�t launch for some reason, open a new browser and navigate to http://4.3.2.1, which should take you to the same location.

wled sr access point home page

13. Select WIFI Settings. This will bring up the WIFI Setup section.

wled ap mode wifi settings

14. Change the Network Name and Network Password to the name and password for your WiFi network. Scroll down to mDNS address and set the address to your liking. I called mine http://mylights.local/ � from now on, this is what I�ll be typing into a web browser on my WiFi network to connect to my lights.

wled wifi settings updated

The WiFi network must be 2.4 GHz; the ESP32 does not support 5 GHz networks.

15. Click Save and Connect. Your ESP32 will reboot and connect to your WiFi network. However, it is a good idea to reset your ESP32 by pressing the EN button.

wled wifi settings save and connect

16. Finally, make sure that you reconnect to your home network.

17. Now navigate to your mDNS address (in my case it�s http://mylights.local/) to access the WLED User Interface (UI).

wled accessed through new address

A Quick Tour of the WLED UI

WLED�s UI is simple, but may appear overwhelming at first. The UI can be divided into five sections.

wled ui overview pc mode
WLED Web Interface
wled app ui overview
WLED App

1. Configuration: Here you can find buttons such as Power (to turn the lights on or off), Timer (to organize the lights to come on and off at certain points during the day), Sync (to sync multiple WLED devices across your network), Peek (to see an animation preview of your lights), Config (to configure the number of LEDs and the GPIO port), and a Brightness slider (to control the overall brightness).

2. Color Picker: This section allows you to change the color of the LEDs, whether they are static or animated. If you scroll down this section, you can access various color palettes for use in effects.

3. Effects/Animation: Here you�ll find a library of ready-made animations for your lights. While each effect has its own color scheme, you can easily customize it in the Color Picker section.

4. Segment: If you have a large array or matrix of LEDs, you can split them into segments and assign a different color, animation, or color scheme to each segment.

5. Presets: Here, you can create presets for your custom light show, as well as a playlist to cycle through the many available animations.

Configuring the WLED

Once the setup is complete it�s a good idea to head to the configuration section.

Configuring Sound Input

1. Click on �Config� and then select �Sound Settings.�

wled sr sound settings

2. Scroll down to �Sound Input Pin Manager� and change the �Analog input pin� number to 35. This pin will be used to connect to the output pin of the MAX4466 microphone amplifier module.

wled sr analog input pin selection

3. Click �Save.�

wled sr save sound settings

4. Finally, click �Back� to return to the main screen.

wled sr return to main page

Configuring LED lights

1. Click on Config and select �LED Preferences�.

wled sr led preferences

2. Scroll down to �Hardware Setup� and select the type of led strip you have.

wled led type select

4. Set the �Length� to match the number of LEDs. We have a total of 12 LEDs, so I changed the length to 12.

wled number of leds select

5. Make a note of the GPIO pin number. This pin will be used to send data to the LEDs. By default, GPIO16 is used.

wled esp32 gpio pin select

6. Scroll up to the top and click �Save�.

wled save led preferences

7. Click �Back� to return to the main screen.

wled return to main page

Connecting the Hardware

Once you�ve finished configuring the WLED, unplug the ESP32 from the USB port. Now, let�s proceed to connect the addressable LED strip and the MAX4466 module to the ESP32.

The wiring for the addressable LED strip is relatively straightforward. You�ll need to connect just three wires: two for power and one for data transmission.

Connect the Red wire (+5V/VCC) of the addressable LED strip to the ESP32�s VIN pin and the White/Yellow wire (GND) to the ESP32�s GND pin.

Finally, connect the Green wire (DIN) of the LED strip to the ESP32�s GPIO16 (RX2), via a 330 Ohm resistor. This in-line resistor is there to protect the data pin. A resistor between 220 and 470 Ohm should do nicely. Try to place the resistor as close to your addressable LEDs as possible.

Now, let�s connect the MAX4466 module to the ESP32. The VCC of the MAX4466 module can range between 2.4-5VDC. For optimal performance, it�s recommended to use the 3.3v pin as it provides the �quietest� supply on the ESP32. Therefore, connect the module�s VCC pin to the ESP32�s 3.3V pin and the GND pin to the ground. Lastly, connect the OUT pin of the module to the ESP32�s ADC pin, GPIO35.

If you have few LEDs, you can connect the ESP32 to your computer (or a wall charger) with a USB cable and power the strip directly from the board.

connecting ws2812b led strip and max4466 to esp32 usb powered

If you have a larger project that requires more LEDs, USB power won�t be enough. Instead you should inject power into the strip from an external source. Keep in mind that each RGB LED consumes about 60mA (20mA per color channel), when the brightness is set to full. That means that for every 30 LEDs, your LED strip could be drawing up to 1.8 Amps.

connecting ws2812b led strip and max4466 to esp32 5v external power

Once wiring is complete, LEDs should come to life and emit a soft yellow light. If this isn�t the case, double-check your wiring before proceeding.

From here everything can now be done via the WLED App.

Using the WLED Mobile App

1. Download the WLED app from the Google Play Store or the Apple App Store on your smartphone or tablet.

wled app on google play store

2. Open the app and click on the plus icon in the upper right corner to open up the discovery page.

wled app add devices

3. Hit Discover Lights. This will search through your WIFI for all connected boards running WLED software.

wled app discover lights

4. Once it says Found WLED! click the Tick icon in the upper-right corner. This will bring you back to the home page where you will have a list of all the WLED devices on your network.

wled app add discovered wled devices

5. Click on the newly discovered device to open the control panel.

wled app list of wled devices

6. Select a color using the color wheel. And voil�, you now have fully operational, remotely activated addressable LEDs!

wled app interface

If the color of the LEDs does not match the color you selected in the app, go to Config > LED Preferences and adjust the Color Order setting under Hardware Setup until it does.

wled color order setting

7. If you have multiple WLED devices, you may want to change the name displayed on the app to differentiate them. Go to Config > User Interface and name it whatever you want, then hit Save.

wled device name change

Changing Effects

WLED offers over 30 distinct sound-reactive effects. This is where the real fun begins.

1. Navigate to the Effects tab and choose an effect that displays a musical-note symbol before its name. The LEDs will respond immediately.

wled app sound reactive effect selection

2. You can change the Rate of fall, sensitivity and LED brightness to customize the effects.

wled app effect sensitivity brightness rate of fall

3. Each effect has its own color scheme, which you can easily change in the Color Picker section. This will keep the animation effect but replace the colors.

wled app effect color scheme

Adjusting the Sensitivity

Depending on the source of the sound, you may need to adjust the gain of the MAX4466 module. The gain basically determines how much the module amplifies the sound signal, and thus the sensitivity to sound.

max4466 module gain trimmer

To adjust the gain, locate the small trimmer potentiometer on the back of the MAX4466 module and use a small straight-bladed screwdriver to make the necessary adjustments. The gain range can be set anywhere from 25x to 125x. Turning the potentiometer counterclockwise (CCW) increases the gain, while turning it clockwise (CW) decreases the gain.

Make all gain adjustments gently. If you feel resistance, stop immediately. The tiny trim pot is delicate and can be easily damaged if turned beyond its limits.

You can also adjust the sound sensitivity through WLED settings to suit your requirements. To adjust the sound sensitivity, navigate to Config > Sound Settings.

wled sound squelch gain setting

Here, you�ll find two settings:

  • Squelch: This represents the minimum threshold at which the lights will display any color or effect. The higher the value, the louder the sound required to trigger the LEDs.
  • Gain: This setting controls the sound sensitivity. The higher the value, the higher the sensitivity.

Feel free to play with these settings until you achieve the optimal experience for your setup.

Each ESP32 has a unique MAC address that is hardcoded into the chip and plays an important role in facilitating effective communication between the devices. However, there are times when you may need to assign a new MAC address to your ESP32. For example, you might want to do this for network security purposes, such as implementing a MAC address whitelist that allows only selected devices to connect to a Wi-Fi network.

In such instances, the following tutorial will be invaluable. It provides step-by-step instructions on how to find the current MAC address of your ESP32 and how to set a new one.

What�s a MAC Address?

A MAC (short for �media access control�) address is a series of numbers and letters that serves as a unique identifier for a network device. MAC addresses consist of 12 hexadecimal characters grouped into six pairs, such as AA:BB:CC:DD:EE:FF.

Each device that connects to a network is equipped with a network interface controller (NIC), which incorporates the electronic circuitry necessary for communication via a specific network protocol, such as Wi-Fi, Bluetooth, or Ethernet. Manufacturer assigns a unique MAC address to each NIC (if there are multiple) during the production process, and this address is permanently stored in the device�s hardware.

We need these addresses to be able to send or receive data over the network. While IP addresses identify a network connection, MAC addresses identify hardware. However, unlike IP addresses, which change frequently, MAC addresses are static because they are only used within the local network.

Although the MAC address is usually burned into the device�s hardware, many systems, including the ESP32, allow overriding it through software. But keep in mind that the custom MAC address is temporary and will reset to the manufacturer default upon chip reset, unless you program the ESP32 to set it on every boot.

MAC Address on ESP32

The ESP32 has several MAC addresses, one for each network interface it supports, including the following:

  • Wi-Fi Station (STA)
  • Wi-Fi Access Point (AP)
  • Bluetooth Interface (Classic and BLE)
  • Ethernet

Only the first one, known as the Base MAC Address, is saved in eFuse or external storage, and it is used to generate MAC addresses for other interfaces.

The Wi-Fi Station (STA) uses the base MAC address. In contrast, the Wi-Fi Access Point (AP) MAC address is derived by incrementing the last octet of the base MAC address by one. Similarly, the Bluetooth MAC address is derived by adding two to the last octet of the base address, and the Ethernet MAC address by adding three to the last octet of the base address.

InterfaceMAC AddressExample
Wi-Fi Stationbase_mac80:7D:3A:CB:12:5C
Wi-Fi SoftAPbase_mac +1 to the last octet80:7D:3A:CB:12:5D
Bluetoothbase_mac +2 to the last octet80:7D:3A:CB:12:5E
Ethernetbase_mac +3 to the last octet80:7D:3A:CB:12:5F

For more information, please refer to the Espressif documentation.

Finding the MAC Address

To find the MAC address on an ESP32, you can use the Espressif IoT Development Framework (ESP-IDF) or the Arduino IDE. The example provided here will be for the Arduino IDE.

First, ensure you have the ESP32 board support installed in your Arduino IDE:

Here�s a simple sketch to print out the MAC address of the device:

#include <WiFi.h>

void setup(){
  Serial.begin(115200);
  
  // Variable to store the MAC address
  uint8_t baseMac[6];
  
  // Get MAC address of the WiFi station interface
  esp_read_mac(baseMac, ESP_MAC_WIFI_STA);
  Serial.print("Station MAC: ");
  for (int i = 0; i < 5; i++) {
    Serial.printf("%02X:", baseMac[i]);
  }
  Serial.printf("%02X\n", baseMac[5]);
  
  // Get the MAC address of the Wi-Fi AP interface
  esp_read_mac(baseMac, ESP_MAC_WIFI_SOFTAP);
  Serial.print("SoftAP MAC: ");
  for (int i = 0; i < 5; i++) {
    Serial.printf("%02X:", baseMac[i]);
  }
  Serial.printf("%02X\n", baseMac[5]);
  
  // Get the MAC address of the Bluetooth interface
  esp_read_mac(baseMac, ESP_MAC_BT);
  Serial.print("Bluetooth MAC: ");
  for (int i = 0; i < 5; i++) {
    Serial.printf("%02X:", baseMac[i]);
  }
  Serial.printf("%02X\n", baseMac[5]);

  // Get the MAC address of the Ethernet interface
  esp_read_mac(baseMac, ESP_MAC_ETH);
  Serial.print("Ethernet MAC: ");
  for (int i = 0; i < 5; i++) {
    Serial.printf("%02X:", baseMac[i]);
  }
  Serial.printf("%02X\n", baseMac[5]);
}
 
void loop(){
}

After uploading the code, open the Serial Monitor and set the baud rate to 115200. Then, press the EN button on the ESP32. You should see the MAC addresses for each network interface being printed to the Serial Monitor.

esp32 mac addresses output

Code Explanation:

The code begins by including the WiFi library which provides the necessary functions for handling Wi-Fi operations on the ESP32.

#include "WiFi.h"

Inside the setup() function, we begin with initiating the Serial communication at a baud rate of 115200 bps.

Serial.begin(115200);

A variable named baseMac is declared to store the MAC address. This variable is an array of six uint8_t elements, which are unsigned 8-bit integers, perfect for holding MAC address bytes which range from 00 to FF (Hex).

uint8_t baseMac[6];

The ESP32�s built-in function esp_read_mac() is then called with baseMac and ESP_MAC_WIFI_STA as arguments to read the Wi-Fi Station interface�s MAC address and store it in the baseMac array.

esp_read_mac(baseMac, ESP_MAC_WIFI_STA);

The Serial Monitor then prints �Station MAC: �, followed by the MAC address. A for loop runs through the first five elements of the baseMac array, formatting each byte into two hexadecimal digits followed by a colon. After the loop, the sixth element of the baseMac array is printed without a trailing colon, marking the end of the MAC address.

Serial.print("Station MAC: ");
for (int i = 0; i < 5; i++) {
  Serial.printf("%02X:", baseMac[i]);
}
Serial.printf("%02X\n", baseMac[5]);

The process is repeated for the Wi-Fi AP MAC address, this time with the ESP_MAC_WIFI_SOFTAP argument to read the MAC address for the Wi-Fi Access Point interface.

esp_read_mac(baseMac, ESP_MAC_WIFI_SOFTAP);
Serial.print("SoftAP MAC: ");
for (int i = 0; i < 5; i++) {
  Serial.printf("%02X:", baseMac[i]);
}
Serial.printf("%02X\n", baseMac[5]);

Next, the Bluetooth interface MAC is obtained by calling esp_read_mac() with ESP_MAC_BT.

esp_read_mac(baseMac, ESP_MAC_BT);
Serial.print("Bluetooth MAC: ");
for (int i = 0; i < 5; i++) {
  Serial.printf("%02X:", baseMac[i]);
}
Serial.printf("%02X\n", baseMac[5]);

Similarly, the Ethernet interface MAC is read with ESP_MAC_ETH.

esp_read_mac(baseMac, ESP_MAC_ETH);
Serial.print("Ethernet MAC: ");
for (int i = 0; i < 5; i++) {
  Serial.printf("%02X:", baseMac[i]);
}
Serial.printf("%02X\n", baseMac[5]);

The loop() function is empty because the MAC addresses are printed only once when the ESP32 is reset.

void loop(){
}

Changing the MAC Address

The following code changes the base MAC address of an ESP32 and then displays the updated MAC addresses for each network interface.

It�s important to note that you only need to change the base MAC address on an ESP32, as all other MAC addresses for different network interfaces (Wi-Fi AP, Bluetooth, and Ethernet) are automatically updated since they are derived from the base address with specific offsets.

Before uploading the code, it�s essential to make one modification. You must update the newMAC variable with your custom MAC address:

uint8_t newMAC[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};

After making the change, go ahead and upload the code.

#include <WiFi.h>
#include <esp_wifi.h>

// Define your new MAC address
uint8_t newMAC[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};

void setup(){
  Serial.begin(115200);
  
  // Disable WiFi
  WiFi.mode(WIFI_OFF);
  
  Serial.println("[OLD]---");
  FindMACAddress();
  
  // Set the new MAC address
  if (esp_base_mac_addr_set(newMAC) == ESP_OK) {
    Serial.println("MAC address set successfully");
  } else {
    Serial.println("Failed to set MAC address");
  }
  
  Serial.println();
  Serial.println("[NEW]---");
  FindMACAddress();
}
 
void loop(){
}

void FindMACAddress(){
  // Get the MAC address of the Wi-Fi station interface
  uint8_t baseMac[6];
  
  // Get MAC address for WiFi station
  esp_read_mac(baseMac, ESP_MAC_WIFI_STA);
  Serial.print("Station MAC: ");
  for (int i = 0; i < 5; i++) {
    Serial.printf("%02X:", baseMac[i]);
  }
  Serial.printf("%02X\n", baseMac[5]);
  
  // Get the MAC address of the Wi-Fi AP interface
  esp_read_mac(baseMac, ESP_MAC_WIFI_SOFTAP);
  Serial.print("SoftAP MAC: ");
  for (int i = 0; i < 5; i++) {
    Serial.printf("%02X:", baseMac[i]);
  }
  Serial.printf("%02X\n", baseMac[5]);
  
  // Get the MAC address of the Bluetooth interface
  esp_read_mac(baseMac, ESP_MAC_BT);
  Serial.print("Bluetooth MAC: ");
  for (int i = 0; i < 5; i++) {
    Serial.printf("%02X:", baseMac[i]);
  }
  Serial.printf("%02X\n", baseMac[5]);

  // Get the MAC address of the Ethernet interface
  esp_read_mac(baseMac, ESP_MAC_ETH);
  Serial.print("Ethernet MAC: ");
  for (int i = 0; i < 5; i++) {
    Serial.printf("%02X:", baseMac[i]);
  }
  Serial.printf("%02X\n", baseMac[5]);
  
  Serial.println();
}

After uploading the code, open the Serial Monitor and set the baud rate to 115200. Then, press the EN button on the ESP32. You should see both the old and new MAC addresses for the Wi-Fi station, Wi-Fi access point, Bluetooth, and Ethernet interfaces.

esp32 mac addresses changed

Please keep in mind that, as explained previously, the modifications made do not permanently overwrite the MAC address set by the manufacturer. Thus, each time you reset the board or upload new code, it will revert to its default MAC address, unless you program the ESP32 to set it on every boot.

Code Explanation:

The code begins by including the WiFi.h and esp_wifi.h libraries, which provide functions necessary for Wi-Fi operations and specific ESP32 Wi-Fi features.

#include <WiFi.h>
#include <esp_wifi.h>

A new MAC address is then defined as an array of six hexadecimal bytes.

uint8_t newMAC[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};

In setup(), the Serial Monitor is initialized and Wi-Fi is turned off to ensure that the new MAC address is set before any Wi-Fi activity begins.

WiFi.mode(WIFI_OFF);

Next, the function FindMACAddress() is called to print the current MAC addresses for various interfaces. The FindMACAddress() function is basically the previous code segment that retrieves and prints the MAC addresses, wrapped in a function for modularity and reusability.

FindMACAddress();

The esp_base_mac_addr_set() function is then called with the new MAC address. If successful, a confirmation message is printed. If it fails, an error message is displayed.

if (esp_base_mac_addr_set(newMAC) == ESP_OK) {
  Serial.println("MAC address set successfully");
} else {
  Serial.println("Failed to set MAC address");
}

After setting the new MAC address, the FindMACAddress() function is called again to display the new MAC addresses.

FindMACAddress();

Finally, the loop() function is left empty because the code only needs to run once at startup.

void loop(){
}

So, you�re trying to upload a new sketch to your ESP32, but getting the error message �A fatal error occurred: Failed to connect to ESP32: Timed out waiting for packet header�, huh? Sorry to hear that! But don�t worry. Here�s a quick guide on why and when this error occurs and how to fix it.

Understanding the Error

Some ESP32 boards enter flashing/uploading mode automatically, and the sketch is uploaded successfully, while others do not, and you may receive the following error:

Failed to connect to ESP32 Error while Uploading Sketch in Arduino IDE

This error message typically occurs during the initial boot sequence when the computer attempts but fails to establish a communication link with the ESP32.

The �packet header� refers to the initial part of the data packet that the computer expects to receive from the ESP32, which contains critical information for the data transfer process. A timeout indicates that the computer did not receive this packet within a specified time frame.

When Does This Error Occur?

There could be several reasons why you�re getting this error:

  • Driver Issues: Outdated USB drivers can cause communication failures.
  • Connection Issues: A faulty USB cable or loose connection can prevent the computer from communicating with the ESP32.
  • PCB Design Flaws: Some poorly designed development boards can cause this issue.
  • Failure to Enter Flashing Mode: To upload a new sketch, the ESP32 must be in flashing/uploading mode. If it isn�t, the device won�t respond correctly.

How to Fix the Error

1. Update USB Drivers

When the ESP32 is connected to the computer to upload a sketch, it communicates over the USB port using the USB-to-Serial chip. If the drivers for this chip are outdated, the computer may not be able to communicate with the ESP32, resulting in the error.

So identify the USB-to-Serial chip used on your ESP32 board (such as the CH340, CP210x, or FTDI) and make sure the latest version of the drivers is installed. Sometimes, simply uninstalling and reinstalling the drivers can resolve this issue.

2. Check the USB Cable

While uploading code to the ESP32, a constant and stable connection is required. If the connection is interrupted due to a faulty cable or a loose connection, the computer may fail to receive the necessary data packets from the ESP32 in time, resulting in the error.

So always use a high-quality USB cable and make sure it is securely plugged into both the computer and the ESP32.

Using USB hubs or extenders can sometimes introduce connection problems. Connect the ESP32 directly to the computer�s USB port, if possible.

Also, make sure the cable supports data transfer. Some USB cables, particularly those that come with certain gadgets, are only meant for charging.

3. Disconnect Peripherals

One possible reason for the error could be that peripherals are connected to the strapping pins.

esp32 strapping pins

There are five strapping pins on the ESP32: GPIO0, GPIO2, GPIO5, GPIO12, and GPIO15. The state of these pins determines whether the ESP32 enters BOOT mode (to run the program stored in flash memory) or FLASH mode (to upload a program to flash memory) upon power-up. If peripherals are connected to these pins, they may prevent the ESP32 from entering the correct mode, resulting in the error.

So if possible, disconnect peripherals connected to strapping pins during the flashing process.

4. Hold the Boot Button

If you have confirmed that peripherals are not connected to the strapping pins, ensured that the USB drivers are up-to-date, are using a high-quality USB cable capable of data transfer, and have purchased the ESP32 development board from a reputable source, but the problem still persists, you can manually force the ESP32 into flashing/uploading mode while uploading a new sketch.

One of the simplest methods to achieve this is by holding the boot button. Here�s how you do it:

  1. Press the Upload button in the Arduino IDE to upload a new sketch.
  2. When you see the �Connecting�� message or just before, press and hold the BOOT button on the ESP32.
  3. Once the �Writing at�� message appears, indicating that the uploading has begun, release the BOOT button.
esp32 boot button

Note that you�ll have to repeat that button sequence every time you want to upload a new sketch. Want a more permanent solution? Read on.

5. Solder a 10 uF Electrolytic Capacitor

A more permanent solution to ensure the ESP32 automatically enters flashing/uploading mode involves soldering a 10 uF electrolytic capacitor between the EN (enable) pin and the GND (ground) pin.

The EN pin on the ESP32 module controls the reset function. When the pin is pulled low momentarily, the module resets, and when it is pulled high, the module works normally. The addition of a capacitor provides a delay to the voltage change, ensuring a stable reset and giving the computer enough time to establish a connection before the ESP32 exits bootloader mode.

Here are the steps:

  1. Disconnect the ESP32 from your computer.
  2. Take a 10 uF electrolytic capacitor, and put it in the breadboard as shown in the figure below. Note the polarity�the longer leg is the positive, and there�s usually a stripe on the side marking the negative leg.
  3. Wire the positive leg to the EN pin and the negative leg to a GND pin on the ESP32.
  4. After wiring, reconnect the ESP32 to your computer.
  5. Try uploading a sketch again to see if the error is resolved.
wiring 10uf capacitor to en pin on esp32

If this method works, you can solder the 10 uF capacitor directly onto the board. Since the EN and GND pins are pretty far apart, you can simply connect the capacitor between the EN and the first pin (GND pin) of the ESP32-WROOM-32 module, as shown in the diagram below. Just be careful to avoid shorting adjacent pins.

soldering 10uf capacitor to en pin on esp32

Did You Upload the Sketch Successfully?

The �Failed to Connect to ESP32� error can be frustrating, but it�s usually fixable with some troubleshooting. Hopefully, one of these fixes worked for you, and you were able to upload the sketch successfully. If you�re still having trouble uploading, don�t gloom; all you need to do is spend some more time troubleshooting.

If you have a single ESP32 on your network, you can connect to it using its IP address (192.168.1.128, for example). But imagine having several ESP32s scattered around your house. You�ll suddenly find yourself needing to remember multiple IP addresses. Terribly time consuming and no fun. Enter mDNS.

mDNS allows you to access the web server running on your ESP32 using a user-friendly hostname, such as �esp32.local�, rather than fiddling with an IP address. And here�s the best part�even if the IP address of your ESP32 changes, mDNS automatically resolves the new IP address to the same hostname. This means you can keep using the same hostname without worrying about tracking IP address changes.

Pretty amazing, right? In this tutorial, you will learn step-by-step how to set up mDNS on an ESP32. But first, let�s talk about what mDNS is.

What is mDNS?

Thirty years ago, when the Internet was still in its infancy, if you wanted to visit a website, you had to know its IP address. That�s because computers are and were only able to communicate using numbers.

Consider an IP address like 127.33.54.200: it�s lengthy, hard to remember, and certainly not user-friendly. We needed a way to translate these machine-readable IP addresses into something more understandable.

In the early 1980s, Paul Mockapetris introduced a revolutionary system that mapped IP addresses to more memorable domain names�and the Domain Name System (DNS) was born. This system remains the foundation of today�s internet.

When you type a domain name, such as google.com, into your browser, it asks the nameservers if they have the DNS records for that domain. A DNS record is just a file that says �this domain� maps to �this IP address,� and a nameserver is a specialized server that stores such DNS records. For instance, the domain name google.com might be linked to an IP address like 142.250.189.174.

The Domain Name System is one of the foundations of how the internet works; without it, we wouldn�t be able to browse the internet with ease.

But what about a local home network? In such an environment, where devices frequently switch on and off, join and leave the network, and often have dynamic IP addresses, this system becomes impractical, because deploying a dedicated server just to resolve hostnames to IP addresses is not a feasible solution. Is there a simpler way? Yes, indeed�it�s called Multicast DNS, abbreviated as mDNS.

mDNS is a decentralized, peer-to-peer protocol specifically designed for small networks. It enables devices to discover each other�s IP addresses. It works by allowing each device to broadcast its name and IP address to every other device on the network. Obviously, this approach wouldn�t be great for the internet at large, but local networks are small enough that the overhead isn�t a problem.

Key Features and Benefits of mDNS:

  • User-Friendly Hostnames: Devices on the network can be accessed using easily remembered hostnames, such as �esp32.local�, instead of IP addresses.
  • Dynamic IP Handling: Even if a device�s IP address changes, mDNS takes care of resolving the new IP address with the same hostname.
  • Zero Configuration: There�s no need for additional DNS setup, as mDNS operates in a standalone fashion on local networks.
  • Platform Independence: mDNS can be used across various devices and operating systems, making integration seamless.

Let�s go over how to set up mDNS on an ESP32 step by step.

Step 1 � Set Up the Arduino IDE

We will be using the Arduino IDE to program the ESP32, so please ensure you have the ESP32 add-on installed before you proceed:

Step 2 � Connect the ESP32 to your Computer

Use a micro-USB cable to connect your ESP32 board to your computer.

esp32 connected to computer using a usb cable

Step 3 � Define Network Credentials

Before uploading the sketch below, there is one essential modification you need to make. Update the following two variables with your actual WiFi SSID and password.

const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

Step 4 � Upload the Code

Here�s a simple sketch that demonstrates setting up mDNS on an ESP32 board. This code connects to a Wi-Fi network, starts an mDNS service, and creates an HTTP server to serve a simple web page. Once uploaded, you should be able to access the web server by navigating to http://esp32.local/ in a web browser.

Go ahead and try out the sketch.

#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>

/*Put your SSID & Password*/
const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

WebServer server(80);

void setup() {
  Serial.begin(115200);
  delay(100);

  Serial.println("Connecting to ");
  Serial.println(ssid);

  //connect to your local wi-fi network
  WiFi.begin(ssid, password);

  //check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected..!");
  
  // Initialize mDNS
  if (!MDNS.begin("esp32")) {   // Set the hostname to "esp32.local"
    Serial.println("Error setting up MDNS responder!");
    while(1) {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");
  
  server.on("/", handle_OnConnect);
  server.onNotFound(handle_NotFound);

  server.begin();
  Serial.println("HTTP server started");
}
void loop() {
  server.handleClient();
}

void handle_OnConnect() {
  server.send(200, "text/html", "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\"></head><body><h1>Hey there!</h1></body></html>"); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

Step 5 � Testing the code

After uploading the code, open the Serial Monitor and set the baud rate to 115200. Then, press the EN button on the ESP32. It may take a few moments to connect to your network, after which it will display the messages �mDNS responder started� and �HTTP server started.�

mdns serial monitor output

Next, launch a web browser and navigate to http://esp32.local/. The ESP32 should display a web page with a greeting message.

esp32 mdns web server desktop screenshot

Code Explanation

The sketch begins by including the necessary libraries: WiFi.h for connecting to a WiFi network, WebServer.h for setting up the HTTP server, and ESPmDNS.h for mDNS services.

#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>

Next, two constants, ssid and password, are defined. These are placeholders for your Wi-Fi network�s name (SSID) and password.

const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

The next line creates a web server object that listens on port 80 (default HTTP port).

WebServer server(80);

In the setup() function, we first initialize serial communication with the PC.

Serial.begin(115200);

The ESP32 then attempts to join the WiFi network using the WiFi.begin() function, which accepts the SSID (Network Name) and password as arguments.

WiFi.begin(ssid, password);

While the ESP32 attempts to connect to the network, we can check the connectivity status using the WiFi.status() function.

while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
}

Next, we simply call the begin() method on an extern variable named MDNS to start the mDNS service. This MDNS variable is an instance of the MDNSResponder class, which provides all of the address resolution functionality.

The call to the previously mentioned begin() method is shown below. As an argument to the method, we pass the desired hostname (which will be used in the URL). It is important to note that the hostname should not be longer than 63 characters.

if (!MDNS.begin("esp32")) {   // Set the hostname to "esp32.local"
  Serial.println("Error setting up MDNS responder!");
  while(1) {
    delay(1000);
  }
}

The following block of code simply sets up a handler for the root URL. When someone accesses the root URL, handle_OnConnect() is called. It also sets up a handler that is called when a requested URL is not found on the server.

server.on("/", handle_OnConnect);
server.onNotFound(handle_NotFound);

Finally, to start the server, we call the begin() method on the server object.

server.begin();

The loop() function continuously checks for incoming client requests and handles them by invoking the handleClient() method on the server object.

void loop() {
  server.handleClient();
}

The handle_OnConnect() is a function that sends an HTML response back to the client when the root URL (�/�) is accessed. It sends a simple HTML page with a greeting message using the server.send() method.

The server.send() method accepts as arguments the HTTP response code, the content type, and the content itself. In our case, we are sending the code 200 (one of the HTTP status codes), which corresponds to the OK response (indicating that the request has been successfully processed). We then specify the content type as �text/html.� Finally, we have a long string that is nothing more than HTML code that creates a simple web page that will display the message �Hey there!�.

void handle_OnConnect() {
  server.send(200, "text/html", "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\"></head><body><h1>Hey there!</h1></body></html>"); 
}

If the server receives a request for a URL that does not exist on the server, handle_NotFound() sends a 404 Not Found response.

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

If you�ve ever tried running a web server on an ESP32, you�ve likely noticed something annoying�every time you restart your ESP32, the IP address can change, based on what the router decides to assign at the moment. This means you always have to check the serial monitor to find out this new IP address. It�s quite a hassle, right?

This is where static IP addresses come into play. By setting a static IP address, you can access the web server using the same IP address, even after restarting the ESP32. This is also useful to avoid confusion when you have multiple ESP32s connected to your network.

Fortunately, once you know how, it�s a fairly simple and quick process to set a static IP address. We�ll guide you step-by-step on how to set a static IP address on your ESP32. Let�s dive in!

Step 1 � Set Up the Arduino IDE

We will be using the Arduino IDE to program the ESP32, so please ensure you have the ESP32 add-on installed before you proceed:

Step 2 � Connect the ESP32 to your Computer

Use a micro-USB cable to connect your ESP32 board to your computer.

esp32 connected to computer using a usb cable

Step 3 � Obtain Current Network Settings

Before setting a static IP, it�s a good practice to check the current network settings (IP, Gateway, Subnet, and DNS) assigned by your router. This information can help avoid IP conflicts. To find out the ESP32�s current IP address and other network settings, you need to upload the following sketch to your ESP32.

Before that, there is one essential modification you need to make. Update the following two variables with your actual WiFi SSID and password.

const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

After making these changes, go ahead and upload the code.

#include <WiFi.h>

// Replace with your network credentials
const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

void setup() {
  Serial.begin(115200);
  
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  
  Serial.println("");
  Serial.println("Connected..!");
 
  Serial.print("Current ESP32 IP: ");
  Serial.println(WiFi.localIP());
  Serial.print("Gateway (router) IP: ");
  Serial.println(WiFi.gatewayIP());
  Serial.print("Subnet Mask: " );
  Serial.println(WiFi.subnetMask());
  Serial.print("Primary DNS: ");
  Serial.println(WiFi.dnsIP(0));
  Serial.print("Secondary DNS: ");
  Serial.println(WiFi.dnsIP(1));
}

void loop() {
}

After uploading the code, open the Serial Monitor and set the baud rate to 115200. Then, press the EN button on the ESP32. It may take a few moments to connect to your network, after which it will print the current network settings of the ESP32 to the serial monitor. Take note of these.

esp32 current network settings serial monitor output

Step 4 � Set a Static IP Address

After obtaining the current network settings, you can now set a static IP address.

Modify the sketch below to include the static IP configuration. You will need to specify the static IP address, Gateway, Subnet Mask, and DNS settings (optional).

IPAddress staticIP(192, 168, 1, 100); // ESP32 static IP
IPAddress gateway(192, 168, 1, 1);    // IP Address of your network gateway (router)
IPAddress subnet(255, 255, 255, 0);   // Subnet mask
IPAddress primaryDNS(192, 168, 1, 1); // Primary DNS (optional)
IPAddress secondaryDNS(0, 0, 0, 0);   // Secondary DNS (optional)

Based on the current network settings, select an appropriate static IP address for your ESP32. This address should be within the same subnet as your current network but outside the range your router typically assigns (to avoid IP conflicts).

Once you modify the sketch, upload it to the ESP32.

#include <WiFi.h>

// Replace with your network credentials
const char* ssid = "Your_SSID";
const char* password = "Your_PASSWORD";

// Static IP configuration
IPAddress staticIP(192, 168, 1, 100); // ESP32 static IP
IPAddress gateway(192, 168, 1, 1);    // IP Address of your network gateway (router)
IPAddress subnet(255, 255, 255, 0);   // Subnet mask
IPAddress primaryDNS(192, 168, 1, 1); // Primary DNS (optional)
IPAddress secondaryDNS(0, 0, 0, 0);   // Secondary DNS (optional)

void setup() {
  Serial.begin(115200);

  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }

  // Configuring static IP
  if(!WiFi.config(staticIP, gateway, subnet, primaryDNS, secondaryDNS)) {
    Serial.println("Failed to configure Static IP");
  } else {
    Serial.println("Static IP configured!");
  }
  
  Serial.print("ESP32 IP Address: ");
  Serial.println(WiFi.localIP());  // Print the ESP32 IP address to Serial Monitor
}

void loop() {
  // Nothing to do here
}

Step 6 � Testing

After uploading the code to your ESP32, use the Serial Monitor to confirm that the ESP32 is now using the static IP address you set.

esp32 static ip configured

Static IP vs mDNS

While setting a static IP for your ESP32 is effective, there�s another approach worth considering: mDNS (Multicast DNS). This method offers significant advantages over static IP.

mDNS allows you to access the web server running on your ESP32 using a user-friendly hostname, such as �esp32.local�, rather than fiddling with an IP address. Moreover, even if the IP address of your ESP32 changes, mDNS automatically resolves the new IP address to the same hostname. This means you can keep using the same hostname without worrying about tracking IP address changes.

To learn more about how to implement mDNS on your ESP32, check out our detailed guide here:.

Detailed Code Explanation

The sketch begins by including the WiFi.h library. This library provides the functions needed to connect the ESP32 to a Wi-Fi network and set its IP configuration.

#include <WiFi.h>

Next, constants for the Wi-Fi SSID (ssid) and password (password) are defined. You�ll need to replace these with your actual Wi-Fi network�s name and password.

const char* ssid = "Your_SSID";
const char* password = "Your_PASSWORD";

Then, IPAddress objects are created for the static IP, gateway, subnet mask, and optionally, primary and secondary DNS servers. These objects will store the respective addresses and are used later to configure the ESP32�s network settings.

// Static IP configuration
IPAddress staticIP(192, 168, 1, 100); // ESP32 static IP
IPAddress gateway(192, 168, 1, 1);    // IP Address of your network gateway (router)
IPAddress subnet(255, 255, 255, 0);   // Subnet mask
IPAddress primaryDNS(192, 168, 1, 1); // Primary DNS (optional)
IPAddress secondaryDNS(0, 0, 0, 0);   // Secondary DNS (optional)

Inside the setup() function, serial communication is initiated with the computer.

Serial.begin(115200);

The ESP32 attempts to connect to the Wi-Fi network using WiFi.begin(ssid, password). A while loop ensures the ESP32 keeps trying to connect until it succeeds.

WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
}

Once connected to Wi-Fi, the ESP32 is configured to use the previously defined static IP, gateway, subnet mask, and DNS servers using the WiFi.config() function. If there�s an issue configuring the static IP, the message �Failed to configure Static IP� is printed to the Serial Monitor.

if(!WiFi.config(staticIP, gateway, subnet, primaryDNS, secondaryDNS)) {
  Serial.println("Failed to configure Static IP");
} else {
  Serial.println("Static IP configured!");
}

After setting the IP configuration, the ESP32�s current IP address is printed to the Serial Monitor using WiFi.localIP(). If everything goes as planned, this should display the static IP address that was set.

Serial.print("ESP32 IP Address: ");
Serial.println(WiFi.localIP());

The IP is set in the setup() function, and since there�s nothing left to do, the loop() function is kept empty.

void loop() {
}

One of the best things about ESP32 is that its firmware can be updated wirelessly. This kind of programming is called �Over-The-Air� (OTA).

What is OTA programming in ESP32?

OTA programming lets you update/upload a new program to the ESP32 over Wi-Fi without having to connect the ESP32 to the computer via USB.

The OTA functionality comes in handy when there is no physical access to the ESP module. In addition, it reduces the time required to update each ESP module during maintenance.

One key advantage of OTA is that a single central location can send an update to multiple ESPs on the same network.

The only disadvantage is that you must include an OTA code with each sketch you upload in order to use OTA in the next update.

Ways To Implement OTA In ESP32

There are two ways to implement OTA functionality in the ESP32.

  • Basic OTA � updates are delivered using the Arduino IDE.
  • Web Updater OTA � updates are delivered via a web browser.

Each one has its own benefits, so you can use whichever one works best for your project.

This tutorial will walk you through the process of implementing Basic OTA. If you want to learn more about web updater OTA, please visit this tutorial.

3 Simple Steps for Using Basic OTA with the ESP32

  1. Installing Python 2.7.x series: The first step is to install the Python 2.7.x series on your computer.
  2. Uploading Basic OTA Firmware Serially: Upload the sketch containing the OTA firmware serially. This is a required step in order to perform the subsequent updates over-the-air.
  3. Uploading New Sketch Over-The-Air: You can now upload new sketches to the ESP32 from the Arduino IDE over-the-air.

Step 1: Install Python 2.7.x series

To use OTA functionality, you must first install Python 2.7.x, if it is not already installed on your machine.Download Python 2.7.x for Windows (MSI installer) from the official Python website.

Download Python 2.7.x Series

Launch the installer and proceed through the installation wizard.

Install Python 2.7.x Series On PC

Make sure the �Add python.exe to Path� option is enabled in the Customize Python 2.7.X section.

Enable Add Python.exe to Path While Python Installation

Step 2: Upload Basic OTA Firmware Serially

Because the ESP32�s factory image lacks OTA Upgrade capability, you must first load the OTA firmware on the ESP32 via serial interface.

It is required to first update the firmware in order to perform subsequent updates over-the-air.

The ESP32 add-on for the Arduino IDE includes an OTA library as well as a BasicOTA example. Simply navigate to File > Examples > ArduinoOTA > BasicOTA.

Open ESP32 BasicOTA Sketch In Arduino IDE

Before you begin uploading the sketch, you must modify the following two variables with your network credentials so that the ESP32 can connect to an existing network.

const char* ssid = "..........";
const char* password = "..........";

When you�re finished, go ahead and upload the sketch.

#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

const char* ssid = "..........";
const char* password = "..........";

void setup() {
  Serial.begin(115200);
  Serial.println("Booting");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }

  // Port defaults to 3232
  // ArduinoOTA.setPort(3232);

  // Hostname defaults to esp3232-[MAC]
  // ArduinoOTA.setHostname("myesp32");

  // No authentication by default
  // ArduinoOTA.setPassword("admin");

  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");

  ArduinoOTA
    .onStart([]() {
      String type;
      if (ArduinoOTA.getCommand() == U_FLASH)
        type = "sketch";
      else // U_SPIFFS
        type = "filesystem";

      // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
      Serial.println("Start updating " + type);
    })
    .onEnd([]() {
      Serial.println("\nEnd");
    })
    .onProgress([](unsigned int progress, unsigned int total) {
      Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
    })
    .onError([](ota_error_t error) {
      Serial.printf("Error[%u]: ", error);
      if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
      else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
      else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
      else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
      else if (error == OTA_END_ERROR) Serial.println("End Failed");
    });

  ArduinoOTA.begin();

  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  ArduinoOTA.handle();
}

Now, open the Serial Monitor at 115200 baud rate and press the EN button on the ESP32. If everything is fine, you should see the dynamic IP address assigned by your router. Make a note of it.

Note Down IP Address Allotted to ESP32

Step 3: Upload New Sketch Over-The-Air

Now, let�s upload a new sketch over-the-air.

Remember that you must include the OTA code in every sketch you upload. Otherwise, you will lose OTA capability and will be unable to perform the next over-the-air upload. Therefore, it is recommended that you modify the preceding code to include your new code.

As an example, we will include a simple Blink sketch in the Basic OTA code. Remember to modify the SSID and password variables with your network credentials.

Changes in the Basic OTA program are highlighted in Blue.

#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

const char* ssid = "..........";
const char* password = "..........";

//variabls for blinking an LED with Millis
const int led = 2; // ESP32 Pin to which onboard LED is connected
unsigned long previousMillis = 0;  // will store last time LED was updated
const long interval = 1000;  // interval at which to blink (milliseconds)
int ledState = LOW;  // ledState used to set the LED
void setup() {

pinMode(led, OUTPUT);  
  Serial.begin(115200);
  Serial.println("Booting");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }

  // Port defaults to 3232
  // ArduinoOTA.setPort(3232);

  // Hostname defaults to esp3232-[MAC]
  // ArduinoOTA.setHostname("myesp32");

  // No authentication by default
  // ArduinoOTA.setPassword("admin");

  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");

  ArduinoOTA
    .onStart([]() {
      String type;
      if (ArduinoOTA.getCommand() == U_FLASH)
        type = "sketch";
      else // U_SPIFFS
        type = "filesystem";

      // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
      Serial.println("Start updating " + type);
    })
    .onEnd([]() {
      Serial.println("\nEnd");
    })
    .onProgress([](unsigned int progress, unsigned int total) {
      Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
    })
    .onError([](ota_error_t error) {
      Serial.printf("Error[%u]: ", error);
      if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
      else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
      else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
      else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
      else if (error == OTA_END_ERROR) Serial.println("End Failed");
    });

  ArduinoOTA.begin();

  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  ArduinoOTA.handle();  
  //loop to blink without delay
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
  // save the last time you blinked the LED
  previousMillis = currentMillis;
  // if the LED is off turn it on and vice-versa:
  ledState = not(ledState);
  // set the LED with the ledState of the variable:
  digitalWrite(led,  ledState);
  }
}

Notice that we haven�t used the delay() function to make the LED blink. This is because the delay() function pauses the program. If the next OTA request is generated while the ESP32 is paused waiting for the delay() to complete, your program will miss that request.

After copying the above sketch to your Arduino IDE, navigate to Tools > Port option. Look for something like: esp32-xxxxxx at your_esp_ip_address. If you are unable to locate it, you may need to restart your IDE.

Select OTA Port in Arduino IDE

Choose the port and press the Upload button. The new sketch will be uploaded in a matter of seconds. The on-board LED should start blinking.

ESP32 Onboard LED Blinking

One of the best things about the ESP32 is that its firmware can be updated wirelessly. This kind of programming is called �Over-The-Air� (OTA).

What is OTA programming in ESP32?

OTA programming lets you update/upload a new program to the ESP32 over Wi-Fi without having to connect the ESP32 to the computer via USB.

The OTA functionality comes in handy when there is no physical access to the ESP module. In addition, it reduces the time required to update each ESP module during maintenance.

One key advantage of OTA is that a single central location can send an update to multiple ESPs on the same network.

The only disadvantage is that you must include an OTA code with each sketch you upload in order to use OTA in the next update.

Ways To Implement OTA In ESP32

There are two ways to implement OTA functionality in the ESP32.

  • Basic OTA � updates are delivered using the Arduino IDE.
  • Web Updater OTA � updates are delivered via a web browser.

Each one has its own benefits, so you can use whichever one works best for your project.

This tutorial will walk you through the process of implementing web updater OTA. If you are interested in learning more about Basic OTA, please visit the tutorial below.

3 Simple Steps for Using Web Updater OTA with the ESP32

  1. Upload OTA Routine Serially: The first step is to serially upload the sketch containing OTA firmware. This is a required step in order to perform the subsequent updates over-the-air.
  2. Access Web Server: The OTA sketch creates a web server in STA mode that can be accessed via a web browser. Once you log in to the web server, you can upload new sketches.
  3. Upload New Sketch Over-The-Air: You can now upload new sketches to the ESP32 by generating and uploading a compiled .bin file via a web server.
ESP32 Over The Air OTA Web Updater Working

Step 1: Upload OTA Routine Serially

Because the ESP32�s factory image lacks OTA upgrade capability, you must first load the OTA firmware on the ESP32 via serial interface.

It is required to first update the firmware in order to perform subsequent updates over-the-air.

The ESP32 add-on for the Arduino IDE includes an OTA library as well as an OTAWebUpdater example. Simply navigate to File > Examples > ArduinoOTA > OTAWebUpdater.

The user interface of the OTA Web Updater is extremely unappealing. So we tweaked the code to make it look better. To begin, connect your ESP32 to your computer and upload the sketch below.

Before you begin uploading the sketch, you must modify the following two variables with your network credentials so that the ESP32 can connect to an existing network.

const char* ssid = "---";
const char* password = "----";

When you�re finished, go ahead and upload the sketch.

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>

const char* host = "esp32";
const char* ssid = "---";
const char* password = "----";

WebServer server(80);

/* Style */
String style =
"<style>#file-input,input{width:100%;height:44px;border-radius:4px;margin:10px auto;font-size:15px}"
"input{background:#f1f1f1;border:0;padding:0 15px}body{background:#3498db;font-family:sans-serif;font-size:14px;color:#777}"
"#file-input{padding:0;border:1px solid #ddd;line-height:44px;text-align:left;display:block;cursor:pointer}"
"#bar,#prgbar{background-color:#f1f1f1;border-radius:10px}#bar{background-color:#3498db;width:0%;height:10px}"
"form{background:#fff;max-width:258px;margin:75px auto;padding:30px;border-radius:5px;text-align:center}"
".btn{background:#3498db;color:#fff;cursor:pointer}</style>";

/* Login page */
String loginIndex = 
"<form name=loginForm>"
"<h1>ESP32 Login</h1>"
"<input name=userid placeholder='User ID'> "
"<input name=pwd placeholder=Password type=Password> "
"<input type=submit onclick=check(this.form) class=btn value=Login></form>"
"<script>"
"function check(form) {"
"if(form.userid.value=='admin' && form.pwd.value=='admin')"
"{window.open('/serverIndex')}"
"else"
"{alert('Error Password or Username')}"
"}"
"</script>" + style;
 
/* Server Index Page */
String serverIndex = 
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
"<input type='file' name='update' id='file' onchange='sub(this)' style=display:none>"
"<label id='file-input' for='file'>   Choose file...</label>"
"<input type='submit' class=btn value='Update'>"
"<br><br>"
"<div id='prg'></div>"
"<br><div id='prgbar'><div id='bar'></div></div><br></form>"
"<script>"
"function sub(obj){"
"var fileName = obj.value.split('\\\\');"
"document.getElementById('file-input').innerHTML = '   '+ fileName[fileName.length-1];"
"};"
"$('form').submit(function(e){"
"e.preventDefault();"
"var form = $('#upload_form')[0];"
"var data = new FormData(form);"
"$.ajax({"
"url: '/update',"
"type: 'POST',"
"data: data,"
"contentType: false,"
"processData:false,"
"xhr: function() {"
"var xhr = new window.XMLHttpRequest();"
"xhr.upload.addEventListener('progress', function(evt) {"
"if (evt.lengthComputable) {"
"var per = evt.loaded / evt.total;"
"$('#prg').html('progress: ' + Math.round(per*100) + '%');"
"$('#bar').css('width',Math.round(per*100) + '%');"
"}"
"}, false);"
"return xhr;"
"},"
"success:function(d, s) {"
"console.log('success!') "
"},"
"error: function (a, b, c) {"
"}"
"});"
"});"
"</script>" + style;

/* setup function */
void setup(void) {
  Serial.begin(115200);

  // Connect to WiFi network
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  /*use mdns for host name resolution*/
  if (!MDNS.begin(host)) { //http://esp32.local
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");
  /*return index page which is stored in serverIndex */
  server.on("/", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", loginIndex);
  });
  server.on("/serverIndex", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverIndex);
  });
  /*handling uploading firmware file */
  server.on("/update", HTTP_POST, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
    ESP.restart();
  }, []() {
    HTTPUpload& upload = server.upload();
    if (upload.status == UPLOAD_FILE_START) {
      Serial.printf("Update: %s\n", upload.filename.c_str());
      if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_WRITE) {
      /* flashing firmware to ESP*/
      if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_END) {
      if (Update.end(true)) { //true to set the size to the current progress
        Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
      } else {
        Update.printError(Serial);
      }
    }
  });
  server.begin();
}

void loop(void) {
  server.handleClient();
  delay(1);
}

Step 2: Access Web Server

The OTA Web Updater sketch creates a web server in STA mode that can be accessed via a web browser and used to upload new sketches to your ESP32 over-the-air.

To access the web server, open the Serial Monitor at 115200 baud and press the EN button on the ESP32. If everything is fine, it will display the dynamic IP address obtained from your router.

Note Down IP Address Allotted to ESP32

Next, launch a browser and navigate to the IP address displayed on the serial monitor. The ESP32 should display a web page requesting login information.

Access ESP32 OTA Web Server

Enter the following User ID and Password:

User ID: admin
Password: admin

If you want to change the User ID and Password, edit the following code in your sketch.

"if(form.userid.value=='admin' && form.pwd.value=='admin')"

After logging in, you�ll be redirected to the /serverIndex page. This page allows you to upload new sketches to your ESP32 over-the-air.

Please note that the new sketch you wish to upload must be in the .bin (compiled binary) format. You will learn how to generate the .bin file of your sketch in the next step.

Warning:

The /serverIndex page is not protected by the login functionality. This flaw could allow users to access the system without logging in.

Step 3: Upload New Sketch Over-The-Air

Now, let�s upload a new sketch over-the-air.

Remember that you must include the OTA code in every sketch you upload. Otherwise, you will lose OTA capability and will be unable to perform the next over-the-air upload. Therefore, it is recommended that you modify the preceding code to include your new code.

As an example, we will include a simple Blink sketch in the OTA web updater code. Remember to modify the SSID and password variables with your network credentials.

Changes in the Web Updater OTA program are highlighted in Blue.

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>

const char* host = "esp32";
const char* ssid = "---";
const char* password = "----";

//variabls for blinking an LED with Millis
const int led = 2; // ESP32 Pin to which onboard LED is connected
unsigned long previousMillis = 0;  // will store last time LED was updated
const long interval = 1000;  // interval at which to blink (milliseconds)
int ledState = LOW;  // ledState used to set the LED
WebServer server(80);

/* Style */
String style =
"<style>#file-input,input{width:100%;height:44px;border-radius:4px;margin:10px auto;font-size:15px}"
"input{background:#f1f1f1;border:0;padding:0 15px}body{background:#3498db;font-family:sans-serif;font-size:14px;color:#777}"
"#file-input{padding:0;border:1px solid #ddd;line-height:44px;text-align:left;display:block;cursor:pointer}"
"#bar,#prgbar{background-color:#f1f1f1;border-radius:10px}#bar{background-color:#3498db;width:0%;height:10px}"
"form{background:#fff;max-width:258px;margin:75px auto;padding:30px;border-radius:5px;text-align:center}"
".btn{background:#3498db;color:#fff;cursor:pointer}</style>";

/* Login page */
String loginIndex = 
"<form name=loginForm>"
"<h1>ESP32 Login</h1>"
"<input name=userid placeholder='User ID'> "
"<input name=pwd placeholder=Password type=Password> "
"<input type=submit onclick=check(this.form) class=btn value=Login></form>"
"<script>"
"function check(form) {"
"if(form.userid.value=='admin' && form.pwd.value=='admin')"
"{window.open('/serverIndex')}"
"else"
"{alert('Error Password or Username')}"
"}"
"</script>" + style;
 
/* Server Index Page */
String serverIndex = 
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
"<input type='file' name='update' id='file' onchange='sub(this)' style=display:none>"
"<label id='file-input' for='file'>   Choose file...</label>"
"<input type='submit' class=btn value='Update'>"
"<br><br>"
"<div id='prg'></div>"
"<br><div id='prgbar'><div id='bar'></div></div><br></form>"
"<script>"
"function sub(obj){"
"var fileName = obj.value.split('\\\\');"
"document.getElementById('file-input').innerHTML = '   '+ fileName[fileName.length-1];"
"};"
"$('form').submit(function(e){"
"e.preventDefault();"
"var form = $('#upload_form')[0];"
"var data = new FormData(form);"
"$.ajax({"
"url: '/update',"
"type: 'POST',"
"data: data,"
"contentType: false,"
"processData:false,"
"xhr: function() {"
"var xhr = new window.XMLHttpRequest();"
"xhr.upload.addEventListener('progress', function(evt) {"
"if (evt.lengthComputable) {"
"var per = evt.loaded / evt.total;"
"$('#prg').html('progress: ' + Math.round(per*100) + '%');"
"$('#bar').css('width',Math.round(per*100) + '%');"
"}"
"}, false);"
"return xhr;"
"},"
"success:function(d, s) {"
"console.log('success!') "
"},"
"error: function (a, b, c) {"
"}"
"});"
"});"
"</script>" + style;

/* setup function */
void setup(void) {

pinMode(led,  OUTPUT);
  Serial.begin(115200);

  // Connect to WiFi network
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  /*use mdns for host name resolution*/
  if (!MDNS.begin(host)) { //http://esp32.local
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");
  /*return index page which is stored in serverIndex */
  server.on("/", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", loginIndex);
  });
  server.on("/serverIndex", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverIndex);
  });
  /*handling uploading firmware file */
  server.on("/update", HTTP_POST, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
    ESP.restart();
  }, []() {
    HTTPUpload& upload = server.upload();
    if (upload.status == UPLOAD_FILE_START) {
      Serial.printf("Update: %s\n", upload.filename.c_str());
      if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_WRITE) {
      /* flashing firmware to ESP*/
      if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_END) {
      if (Update.end(true)) { //true to set the size to the current progress
        Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
      } else {
        Update.printError(Serial);
      }
    }
  });
  server.begin();
}

void loop(void) {
  server.handleClient();
  delay(1);

//loop to blink without delay
 unsigned long currentMillis = millis();
 if (currentMillis - previousMillis >= interval) {
 // save the last time you blinked the LED
 previousMillis = currentMillis;
 // if the LED is off turn it on and vice-versa:
 ledState = not(ledState);
 // set the LED with the ledState of the variable:
 digitalWrite(led,  ledState);
 }
}

Notice that we haven�t used the delay() function to make the LED blink. This is because the delay() function pauses the program. If the next OTA request is generated while the ESP32 is paused waiting for the delay() to complete, your program will miss that request.

Generate a .bin file in Arduino IDE

To upload a new sketch to the ESP32, you must first generate a compiled binary .bin file of your sketch.

To do so, navigate to Sketch > Export compiled Binary.

Exporting Compiled Binary of a Program In Arduino IDE

After a successful compilation of the sketch, the .bin file is generated in the Sketch Folder. To open the Sketch folder, select Sketch > Show Sketch Folder.

Open Sketch Folder From Arduino IDE

Upload new sketch over-the-air to the ESP32

After generating the .bin file, the new sketch can be uploaded over-the-air to the ESP32.

Launch your browser and navigate to the /serverIndex page. Click Choose File� Choose the newly generated .bin file and hit Update.

Access ServerIndex Page To Upload OTA Updates

The new sketch will be uploaded in a matter of seconds.

Upload OTA Binary

And the on-board LED should begin to blink.

ESP32 Onboard LED Blinking

Every now and then, you�ll come across an idea where keeping time is a top priority. For example, consider a relay that must be activated at a specific time or a data logger that must store values at precise intervals.

The first thing that comes to mind is to use an RTC (Real Time Clock) chip. However, because these chips are not perfectly accurate, you must perform manual adjustments on a regular basis to keep them synchronized.

Instead, it is preferable to employ the Network Time Protocol (NTP). If your ESP32 project has Internet access, you can obtain date and time (with a precision of a few milliseconds of UTC) for�FREE. Also, you don�t need any additional hardware.

What is an NTP?

NTP is an abbreviation for�Network Time Protocol. It is a standard Internet Protocol (IP) for synchronizing computer clocks over a network.

This protocol synchronizes all networked devices to�Coordinated Universal Time�(UTC) within a few milliseconds ( 50 milliseconds over the public Internet and under 5 milliseconds in a LAN environment).

Coordinated Universal Time (UTC) is a global time standard that is similar to GMT (Greenwich Mean Time). UTC does not change; it is the same all over the world.

The idea here is to use NTP to set the computer clocks to UTC and then apply any local time zone offset or daylight saving time offset. This allows us to synchronize our computer clocks regardless of location or time zone differences.

NTP Architecture

NTP employs a hierarchical architecture. Each level in the hierarchy is known as a�stratum.

NTP Hierarchical Architecture With Stratums

At the very top are high-precision timekeeping devices, such as atomic clocks, GPS or radio clocks, known as stratum 0 hardware clocks.

Stratum 1 servers have a direct connection to a stratum 0 hardware clock and therefore provide the most accurate time.

Each stratum in the hierarchy synchronizes with the stratum above and acts as a server for computers in lower stratums.

How NTP Works?

NTP can operate in a number of ways. The most common configuration is to operate in client-server mode.

The fundamental operating principle is as follows:

  1. The client device, such as the ESP32, connects to the NTP server via the User Datagram Protocol (UDP) on port 123.
  2. The client then sends a request packet to the NTP server.
  3. In response to this request, the NTP server sends a time stamp packet. A time stamp packet contains a variety of data, such as a UNIX timestamp, accuracy, delay, or timezone.
  4. A client can then extract the current date and time from it.
NTP Server Working - Request & Timestamp Packet Transfer

Preparing the Arduino IDE

You should have the ESP32 add-on installed in your Arduino IDE before proceeding with this tutorial. If you haven�t installed it yet, follow the tutorial below.

Getting Date and Time from NTP Server

The sketch below will show you exactly how to get the date and time from the NTP Server.

#include <WiFi.h>
#include "time.h"

const char* ssid       = "YOUR_SSID";
const char* password   = "YOUR_PASS";

const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 3600;
const int   daylightOffset_sec = 3600;

void printLocalTime()
{
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    return;
  }
  Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
}

void setup()
{
  Serial.begin(115200);
  
  //connect to WiFi
  Serial.printf("Connecting to %s ", ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
  }
  Serial.println(" CONNECTED");
  
  //init and get the time
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  printLocalTime();

  //disconnect WiFi as it's no longer needed
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);
}

void loop()
{
  delay(1000);
  printLocalTime();
}

Before you start uploading the sketch, you�ll need to make a few changes to make sure it�ll work for you.

  • Modify the following two variables with your network credentials so that the ESP32 can connect to an existing network.
    const char* ssid       = "YOUR_SSID";
    const char* password   = "YOUR_PASS";
  • Adjust the UTC offset for your timezone (in seconds). Refer to the�list of UTC time offsets. Here are some examples for various time zones:
    • For UTC -5.00 : -5 * 60 * 60 : -18000
    • For UTC +1.00 : 1 * 60 * 60 : 3600
    • For UTC +0.00 : 0 * 60 * 60 : 0
    const long  gmtOffset_sec = 3600;
  • Change the Daylight offset (in seconds). Set it to 3600 if your country observes�Daylight saving time; otherwise, set it to 0.
    const int   daylightOffset_sec = 3600;

After uploading the sketch, press the EN button on your ESP32. The serial monitor should display the date and time every second.

ESP32 Reads Date & Time From NTP Server Output On Serial monitor

Code Explanation

Let�s take a quick look at the code to see how it works. To begin, we include the libraries required for this project.

  • WiFi.h�is a library containing the ESP32-specific WiFi methods we will use to connect to a network.
  • time.h�is the ESP32 native time library that handles NTP server synchronization gracefully.
#include <WiFi.h>
#include "time.h"

A few constants are then defined, such as the SSID, WiFi password, UTC offset, and daylight offset.

const char* ssid       = "YOUR_SSID";
const char* password   = "YOUR_PASS";

const long  gmtOffset_sec = 3600;
const int   daylightOffset_sec = 3600;

In addition, the address of the NTP Server is specified. pool.ntp.org is a great open NTP project for this kind of thing.

const char* ntpServer = "pool.ntp.org";

pool.ntp.org automatically selects time servers that are physically close to you. However, if you want to select a specific server, use one of the pool.ntp.org sub-zones.

AreaHostName
Worldwidepool.ntp.org
Asiaasia.pool.ntp.org
Europeeurope.pool.ntp.org
North Americanorth-america.pool.ntp.org
Oceaniaoceania.pool.ntp.org
South Americasouth-america.pool.ntp.org

In the setup section, we first establish serial communication with the PC and then connect to the WiFi network by calling the WiFi.begin() function.

Serial.begin(115200);

//connect to WiFi
Serial.printf("Connecting to %s ", ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
}
Serial.println(" CONNECTED");

Once the ESP32 is connected to the network, we use the configTime() function to initialize the NTP client and obtain the date and time from the NTP server.

//init and get the time
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

Finally, we use the custom function printLocalTime() to print the current date and time.

The printLocalTime() function calls the getLocalTime() function internally. The getLocalTime() function sends a request packet to an NTP server, parses the time stamp packet received, and stores the date and time information in a time structure called timeinfo.

void printLocalTime()
{
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    return;
  }
  Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
}

In the table below, you can see how each member of this time structure relates to a certain piece of information.

%Areturns day of week
%Breturns month of year
%dreturns day of month
%Yreturns year
%Hreturns hour
%Mreturns minutes
%Sreturns seconds

Login
ADS CODE