ESP32 Detailed Overview
1. Introduction to ESP32

The ESP32 is a powerful, low-cost, low-power system on a chip (SoC) designed by Espressif Systems. It features both Wi-Fi and Bluetooth capabilities, making it an ideal choice for IoT (Internet of Things) applications, wearables, and smart home solutions. Its versatility, energy efficiency, and high processing power have made it one of the most widely used microcontrollers for connected devices.

2. Key Features of ESP32
ESP32 Doit dev-kit-1 pinout
3. Wi-Fi Connectivity in Detail

The ESP32’s Wi-Fi capability supports the 2.4 GHz frequency band and offers 802.11 b/g/n protocol support. It can act as a:

4. Bluetooth Capabilities

The ESP32’s dual-mode Bluetooth capabilities allow it to support both Bluetooth Classic and BLE (Bluetooth Low Energy). This makes it suitable for a range of applications that require short-range wireless communication, such as:

5. Peripheral Interfaces

The ESP32 is equipped with a range of peripheral interfaces that allow for communication with external devices:

6. Security Features
7. Low Power Modes

The ESP32 is optimized for low-power consumption. It offers several power-saving modes, including:

8. Applications of ESP32
List of ESP32 Variants
Board CPU Speed RAM Flash Memory Peripherals Operating Voltage Other Specifications
ESP32-WROOM-32240 MHz (Dual-core Xtensa LX6)520 KB SRAM4 MB34 GPIO, UART, SPI, I2C, ADC, DAC, PWM, Wi-Fi, Bluetooth3.3VStandard ESP32 module, widely used in IoT applications
ESP32-WROVER240 MHz (Dual-core Xtensa LX6)520 KB SRAM, 4 MB PSRAM4 MB34 GPIO, UART, SPI, I2C, ADC, DAC, PWM, Wi-Fi, Bluetooth3.3VIncludes extra PSRAM, ideal for applications needing more RAM
ESP32-WROVER-B240 MHz (Dual-core Xtensa LX6)520 KB SRAM, 8 MB PSRAM4 MB34 GPIO, UART, SPI, I2C, ADC, DAC, PWM, Wi-Fi, Bluetooth3.3VMore PSRAM compared to WROVER, great for AI/ML applications
ESP32-S2240 MHz (Single-core Xtensa LX7)320 KB SRAM4 MB43 GPIO, USB, UART, SPI, I2C, ADC, DAC, PWM, Wi-Fi3.3VLower power, secure IoT-focused, lacks Bluetooth
ESP32-S3240 MHz (Dual-core Xtensa LX7)512 KB SRAM4-8 MB43 GPIO, USB, UART, SPI, I2C, ADC, DAC, PWM, Wi-Fi, Bluetooth3.3VEnhanced AI acceleration, better for vision/ML applications
ESP32-C3160 MHz (Single-core RISC-V)400 KB SRAM4 MB22 GPIO, UART, SPI, I2C, ADC, PWM, Wi-Fi, Bluetooth 5.03.3VRISC-V architecture, lower power consumption, compact size
ESP32-C6160 MHz (Single-core RISC-V)400 KB SRAM4 MB22 GPIO, UART, SPI, I2C, ADC, PWM, Wi-Fi 6, Bluetooth 5.03.3VWi-Fi 6 support, optimized for low-latency applications
ESP32-PICO-D4240 MHz (Dual-core Xtensa LX6)520 KB SRAM4 MB34 GPIO, UART, SPI, I2C, ADC, DAC, PWM, Wi-Fi, Bluetooth3.3VCompact, integrated flash and crystal, ideal for space-constrained designs
ESP32-MINI-1240 MHz (Dual-core Xtensa LX6)520 KB SRAM4 MB34 GPIO, UART, SPI, I2C, ADC, DAC, PWM, Wi-Fi, Bluetooth3.3VSmall form factor, great for embedded projects
ESP32-DevKitC240 MHz (Dual-core Xtensa LX6)520 KB SRAM4 MB34 GPIO, UART, SPI, I2C, ADC, DAC, PWM, Wi-Fi, Bluetooth3.3VGeneral-purpose development board, USB interface
ESP32 ADC (Analog to Digital Converter) Detailed Overview

The ESP32 features two 12-bit ADC modules, enabling analog-to-digital conversions for sensors like temperature sensors, potentiometers, and light sensors. Each module converts voltage into 4096 discrete levels.

Key Features of ESP32 ADC
  • Two ADC Modules: ADC1 and ADC2, 18 input channels total.
  • 12-bit Resolution: Values 0–4095 for 0–3.3V inputs.
  • Voltage Range: Default 0–3.3V, adjustable via attenuation.
  • Attenuation: 0 dB, 2.5 dB, 6 dB, or 11 dB for higher voltage readings.
ADC Input Pins

ADC channels are divided into ADC1 (GPIO 32–39) and ADC2 (GPIO 0, 2, 4, 12–15, 25–27).

Example 1: Reading Voltage with the ADC
 
// ADC Example to Read Voltage
const int analogPin = 34;  
int adcValue = 0;          
float voltage = 0.0;       

void setup() {
    Serial.begin(115200);  
    analogReadResolution(12);  
}

void loop() {
    adcValue = analogRead(analogPin);   
    voltage = (adcValue / 4095.0) * 3.3;  
    Serial.print("ADC Value: ");
    Serial.print(adcValue);
    Serial.print(" | Voltage: ");
    Serial.println(voltage);
    delay(1000);  
}
        
Example 2: Using Attenuation for Higher Voltage Reading

// ADC with Attenuation
const int analogPin = 33;  
int adcValue = 0;
float voltage = 0.0;

void setup() {
    Serial.begin(115200);
    analogReadResolution(12);   
    analogSetPinAttenuation(analogPin, ADC_11db);  
}

void loop() {
    adcValue = analogRead(analogPin);   
    voltage = (adcValue / 4095.0) * 3.9;  
    Serial.print("ADC Value: ");
    Serial.print(adcValue);
    Serial.print(" | Voltage: ");
    Serial.println(voltage);
    delay(1000);  
}
        
ESP32 ADC with DMA

Using DMA with the ADC allows high-speed, high-resolution data acquisition by transferring ADC data directly to memory without CPU intervention.

  • High Resolution: 12-bit accurate measurements.
  • DMA Support: Efficient transfers, reduced CPU load.
  • Multiple Channels: Can read from several ADC channels.
Example: Configuring ADC with DMA

// Include libraries
#include "driver/adc.h"
#include "driver/dma.h"

#define ADC_WIDTH         ADC_WIDTH_BIT_12
#define ADC_ATTEN         ADC_ATTEN_DB_0
#define ADC_CHANNEL       ADC1_CHANNEL_0
#define DMA_BUFFER_SIZE   1024

uint16_t adc_buffer[DMA_BUFFER_SIZE];

void setup_adc_dma() {
    adc1_config_width(ADC_WIDTH);
    adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN);

    dma_config_t dma_config = {
        .src_addr = (void*) ADC1_BASE,
        .dest_addr = adc_buffer,
        .size = DMA_BUFFER_SIZE * sizeof(uint16_t),
        .channel = 0
    };
    dma_init(&dma_config);

    adc1_start();
}

void setup() {
    Serial.begin(115200);
    setup_adc_dma();
}

void loop() {
    for (int i = 0; i < DMA_BUFFER_SIZE; i++) {
        Serial.println(adc_buffer[i]);
    }
    delay(1000);
}
        
ESP32 I2C Interface - Master and Slave Mode Examples

The ESP32 supports the I2C protocol, allowing communication with multiple devices using just two lines: SDA (data) and SCL (clock). I2C is commonly used with sensors, memory devices, and other microcontrollers.

Key Features of ESP32 I2C
  • Two I2C Controllers: I2C0 and I2C1.
  • Master and Slave Modes: Can operate as either master or slave.
  • Standard Speeds: Supports 100 kHz (standard), 400 kHz (fast), and 1 MHz (fast-plus).
Example 1: ESP32 as I2C Master

In this example, the ESP32 acts as an I2C master, sending data to a slave device at address 0x3C.

 
#include <Wire.h>

void setup() {
    Wire.begin();  // Join I2C bus as master
    Serial.begin(115200);
}

void loop() {
    Wire.beginTransmission(0x3C);  // Communicate with device at address 0x3C
    Wire.write(0x00);              // Send data
    Wire.endTransmission();
    delay(1000);
}
        
Example 2: ESP32 as I2C Slave

Here, the ESP32 is configured as an I2C slave with address 0x27, ready to respond to a master device.

 
#include <Wire.h>

void receiveEvent(int bytes);
void requestEvent();

void setup() {
    Wire.begin(0x27);             // Join I2C bus as slave
    Wire.onReceive(receiveEvent); // Register receive handler
    Wire.onRequest(requestEvent); // Register request handler
    Serial.begin(115200);
}

void loop() {
    delay(100); // Wait for events
}

void receiveEvent(int bytes) {
    while (Wire.available()) {
        char c = Wire.read();  
        Serial.print(c);       
    }
}

void requestEvent() {
    Wire.write("ACK");  // Send acknowledgment
}
        
ESP32 I2S Interface

The I2S (Inter-IC Sound) interface is a serial bus standard for connecting digital audio devices. It transmits audio data synchronously and can interface with ADCs, DACs, and audio processors.

ESP32 I2S Implementation

The ESP32 supports I2S as master or slave, allowing communication with various audio components for sending or receiving digital audio data.

  • Two I2S Peripherals: I2S0 and I2S1, each with configurable pins.
  • Flexible Data Formats: Supports I2S, PCM, and other formats.
  • Master/Slave Operation: Can act as I2S master or slave.
  • High-Quality Audio: Supports high sample rates and resolutions.
  • DMA Support: Efficient data transfer via Direct Memory Access.
Configuration Parameters
  • Data Format: I2S, PCM, etc.
  • Bit Depth: Number of bits per sample (16-bit, 24-bit).
  • Sample Rate: Audio sample rate (8 kHz – 192 kHz).
  • Channels: Mono or stereo.
  • Clock Configuration: Source, frequency, and divider settings.
I2S Master vs. I2S Slave
  • I2S Master: Generates clock, controls timing, initiates communication, and sends audio data.
  • I2S Slave: Receives clock from master and responds to data transfers, typically receiving audio data.
I2S Master Code

// Include the I2S driver
#include 

#define I2S_NUM I2S_NUM_0
#define I2S_BCK_IO 26
#define I2S_WS_IO 25
#define I2S_DATA_IO 22
#define SAMPLE_RATE 44100
#define I2S_BUFFER_SIZE 1024

void setup() {
    i2s_config_t i2s_config = {
        mode: I2S_MODE_MASTER | I2S_MODE_TX,
        sample_rate: SAMPLE_RATE,
        bits_per_sample: I2S_BITS_PER_SAMPLE_16BIT,
        channel_format: I2S_CHANNEL_FMT_RIGHT_LEFT,
        communication_format: I2S_COMM_FORMAT_I2S_MSB,
        dma_buf_count: 2,
        dma_buf_len: I2S_BUFFER_SIZE,
        intr_alloc_flags: ESP_INTR_FLAG_LEVEL1
    };

    i2s_pin_config_t pin_config = {
        bck_io_num: I2S_BCK_IO,
        ws_io_num: I2S_WS_IO,
        data_out_num: I2S_DATA_IO,
        data_in_num: I2S_PIN_NO_CHANGE
    };

    i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
    i2s_set_pin(I2S_NUM, &pin_config);

    uint8_t data_to_send[I2S_BUFFER_SIZE] = {0};
    while (true) {
        i2s_write_bytes(I2S_NUM, (const char *)data_to_send, I2S_BUFFER_SIZE, portMAX_DELAY);
    }
}

void loop() {}
        
I2S Slave Code

// Include the I2S driver
#include 

#define I2S_NUM I2S_NUM_0
#define I2S_BCK_IO 26
#define I2S_WS_IO 25
#define I2S_DATA_IO 22
#define SAMPLE_RATE 44100
#define I2S_BUFFER_SIZE 1024

void setup() {
    i2s_config_t i2s_config = {
        mode: I2S_MODE_SLAVE | I2S_MODE_RX,
        sample_rate: SAMPLE_RATE,
        bits_per_sample: I2S_BITS_PER_SAMPLE_16BIT,
        channel_format: I2S_CHANNEL_FMT_RIGHT_LEFT,
        communication_format: I2S_COMM_FORMAT_I2S_MSB,
        dma_buf_count: 2,
        dma_buf_len: I2S_BUFFER_SIZE,
        intr_alloc_flags: ESP_INTR_FLAG_LEVEL1
    };

    i2s_pin_config_t pin_config = {
        bck_io_num: I2S_BCK_IO,
        ws_io_num: I2S_WS_IO,
        data_out_num: I2S_PIN_NO_CHANGE,
        data_in_num: I2S_DATA_IO
    };

    i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
    i2s_set_pin(I2S_NUM, &pin_config);

    uint8_t received_data[I2S_BUFFER_SIZE];
    while (true) {
        i2s_read_bytes(I2S_NUM, (char *)received_data, I2S_BUFFER_SIZE, portMAX_DELAY);
        // Process received data here
    }
}

void loop() {}
        
ESP32 SPI Interface

The Serial Peripheral Interface (SPI) is a synchronous serial communication protocol used for high-speed data transfer between devices. It operates in full-duplex mode, allowing simultaneous transmission and reception of data.

ESP32 SPI Implementation

The ESP32 supports two SPI hardware peripherals: HSPI and VSPI. They can be configured as SPI master or SPI slave and are used for communication with sensors, displays, and other peripherals.

  • Two SPI Hosts: HSPI and VSPI, each with configurable pins.
  • Configurable Clock Speed: Adjustable to match peripheral requirements.
  • Full-Duplex Communication: Simultaneous data transmission and reception.
  • Multiple Modes: Supports SPI modes 0–3.
  • Chip Select Management: Handles multiple slaves using a single SPI host.
Configuration Parameters
  • Clock Speed: Configurable from a few Hz to tens of MHz.
  • SPI Mode:
    • Mode 0: Clock idle low, data sampled on rising edge.
    • Mode 1: Clock idle low, data sampled on falling edge.
    • Mode 2: Clock idle high, data sampled on falling edge.
    • Mode 3: Clock idle high, data sampled on rising edge.
  • Bits Per Word: Number of bits per SPI transaction.
  • Data Order: MSB or LSB first.
  • Chip Select (CS): Managed automatically or manually.
SPI Master vs. SPI Slave
  • SPI Master: Initiates and controls communication, generates the clock signal, and manages data transfer.
  • SPI Slave: Responds to the SPI master, receives the clock signal, and communicates data accordingly.
SPI Master Code

// Include the SPI master driver library
#include 

// Define SPI pins
#define SPI_HOST HSPI_HOST
#define PIN_MISO 19
#define PIN_MOSI 23
#define PIN_SCK  18
#define PIN_CS   5

void setup() {
    spi_bus_config_t bus_config = {
        mosi_io_num: PIN_MOSI,
        miso_io_num: PIN_MISO,
        sclk_io_num: PIN_SCK,
        quadwp_io_num: -1,
        quadhd_io_num: -1
    };
    spi_bus_initialize(SPI_HOST, &bus_config, 1);

    spi_device_interface_config_t dev_config = {
        command_bits: 0,
        address_bits: 0,
        dummy_bits: 0,
        mode: 0,
        clock_speed_hz: 1000000,
        spics_io_num: PIN_CS,
        queue_size: 7
    };
    spi_device_handle_t spi;
    spi_bus_add_device(SPI_HOST, &dev_config, &spi);

    uint8_t data_to_send[1] = {0xAA};
    spi_transaction_t transaction = {
        length: 8,
        tx_buffer: data_to_send,
        rx_buffer: NULL
    };
    spi_device_transmit(spi, &transaction);
}

void loop() {}
        
SPI Slave Code

// Include the SPI slave driver library
#include 

#define SPI_HOST HSPI_HOST
#define PIN_MISO 19
#define PIN_MOSI 23
#define PIN_SCK  18
#define PIN_CS   5

void setup() {
    spi_bus_config_t bus_config = {
        mosi_io_num: PIN_MOSI,
        miso_io_num: PIN_MISO,
        sclk_io_num: PIN_SCK,
        quadwp_io_num: -1,
        quadhd_io_num: -1
    };
    spi_bus_initialize(SPI_HOST, &bus_config, 1);

    spi_slave_interface_config_t dev_config = {
        mode: 0,
        spics_io_num: PIN_CS,
        queue_size: 3,
        flags: SPI_SLAVE_KEEP_CS_LOW
    };
    spi_slave_initialize(SPI_HOST, &bus_config, &dev_config, 1);

    uint8_t received_data[1];
    spi_transaction_t transaction = {
        length: 8,
        tx_buffer: NULL,
        rx_buffer: received_data
    };
    spi_slave_transmit(SPI_HOST, &transaction, portMAX_DELAY);
}

void loop() {}
        
ESP32 Timers

The ESP32 microcontroller includes multiple hardware timers that can be used for tasks such as generating precise time delays, creating periodic events, and measuring time intervals. The ESP32 has two types of timers: hardware timers and watchdog timers.

Hardware Timers

The ESP32 has four hardware timers, each configurable independently. These timers can generate PWM signals, periodic interrupts, and perform delay functions. They support both one-shot and periodic modes.

  • Four Independent Timers: Timers 0, 1, 2, and 3, each with 64-bit resolution.
  • Configurable Prescaler: Adjusts the timer frequency.
  • Interrupt Generation: Generates interrupts on overflow or compare match events.
  • One-Shot and Periodic Modes: Supports both single and repeating timer events.
Timer Configuration Parameters
  • Timer Number: Select one of the hardware timers (0–3).
  • Timer Mode: One-shot or periodic mode.
  • Counter Value: Initial timer counter value.
  • Prescaler: Divides timer clock to desired frequency.
  • Interrupts: Configures interrupt generation on events.
Watchdog Timers

Watchdog timers reset the system if it becomes unresponsive, ensuring reliability in various scenarios.

  • Hardware Watchdog: Resets the system on unresponsiveness.
  • Configurable Timeout: Sets the period before triggering a reset.
  • Independent Operation: Operates separately from general-purpose timers.
Hardware Timer Example

// Include the timer library
#include "driver/timer.h"

// Define timer configurations
#define TIMER_DIVIDER         16
#define TIMER_SCALE           (TIMER_BASE_CLK / TIMER_DIVIDER)
#define TIMER_INTERVAL_SEC    (1)

// Timer interrupt handler
void IRAM_ATTR timer_group0_isr(void* arg) {
    timer_spinlock_take(TIMER_GROUP_0);
    TIMERG0.int_clr_timers.t0 = 1;
    timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
    timer_spinlock_give(TIMER_GROUP_0);
    printf("Timer Interrupt Triggered!\n");
}

void setup() {
    timer_config_t config;
    config.alarm_en = TIMER_ALARM_EN;
    config.auto_reload = true;
    config.counter_dir = TIMER_COUNT_UP;
    config.intr_type = TIMER_INTR_LEVEL;
    config.timer_idx = TIMER_0;
    config.counter_en = TIMER_PAUSE;
    config.divider = TIMER_DIVIDER;

    timer_init(TIMER_GROUP_0, TIMER_0, &config);
    timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, TIMER_SCALE * TIMER_INTERVAL_SEC);
    timer_enable_intr(TIMER_GROUP_0, TIMER_0);
    timer_isr_register(TIMER_GROUP_0, TIMER_0, timer_group0_isr, NULL, 0, NULL);
    timer_start(TIMER_GROUP_0, TIMER_0);
}

void loop() {
    // Main loop does nothing
}
        
Watchdog Timer Example

// Include the watchdog library
#include "esp_system.h"
#include "esp_task_wdt.h"

#define WDT_TIMEOUT_S    10

void setup() {
    esp_task_wdt_init(WDT_TIMEOUT_S, true);
    esp_task_wdt_add(NULL);
    esp_task_wdt_reset();
}

void loop() {
    while (true) {
        delay(5000);
        esp_task_wdt_reset();
    }
}
        
Timers as Triggers
Timer Channel Triggered Peripheral / DMA Channel
Timer 0 Channel 0DMA Channel 0
Timer 0 Channel 1DMA Channel 1
Timer 1 Channel 0DMA Channel 2
Timer 1 Channel 1DMA Channel 3
Timer 2 Channel 0DMA Channel 4
Timer 2 Channel 1DMA Channel 5
Timer 3 Channel 0DMA Channel 6
Timer 3 Channel 1DMA Channel 7
Timer Triggers
Trigger Source Timers Triggered
External Signal (GPIO)Timer 0, Timer 1, Timer 2, Timer 3
Timer 0Timer 1, Timer 2
Timer 1Timer 2, Timer 3
Timer 2Timer 3
PWMAll Timers
ADCAll Timers
ESP32 Interrupts Examples

Interrupts are crucial for real-time applications as they allow the microcontroller to respond to events immediately. The ESP32 supports various types of interrupts including external interrupts, serial communication interrupts, I2C interrupts, SPI interrupts, I2S interrupts, ADC interrupts, and timer interrupts. This document provides detailed examples for each type of interrupt.

Types of Interrupts
  • External Interrupts: Triggered by changes on a GPIO pin.
  • Serial Interrupts: Triggered by data transmission or reception on a serial communication interface.
  • I2C Interrupts: Triggered by I2C events like data transmission or reception.
  • SPI Interrupts: Triggered by SPI events.
  • I2S Interrupts: Triggered by I2S data transmission or reception events.
  • ADC Interrupts: Triggered by ADC conversion completion.
  • Timer Interrupts: Triggered by hardware timers at regular intervals.
  • RTC Interrupts: Triggered by hardware real time clock.
  • Bluetooth Interrupts: Triggered by Bluetooth events.
  • WiFi Interrupts: Triggered by WiFi events.
External Interrupt Example

// Include necessary libraries
#include "driver/gpio.h"

// Define GPIO pin for external interrupt
#define INTERRUPT_PIN  4

// ISR for handling the interrupt
void IRAM_ATTR gpio_isr_handler(void* arg) {
    // Handle the interrupt (e.g., toggle an LED)
    gpio_set_level(GPIO_NUM_2, !gpio_get_level(GPIO_NUM_2));
}

void setup() {
    // Initialize the GPIO for the interrupt pin
    gpio_config_t io_conf;
    io_conf.intr_type = GPIO_INTR_NEGEDGE; // Trigger interrupt on falling edge
    io_conf.mode = GPIO_MODE_INPUT;
    io_conf.pin_bit_mask = (1ULL << INTERRUPT_PIN);
    io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
    io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
    gpio_config(&io_conf);

    // Initialize the GPIO for the LED (output)
    gpio_set_direction(GPIO_NUM_2, GPIO_MODE_OUTPUT);

    // Install ISR service
    gpio_install_isr_service(0);

    // Attach the ISR to the interrupt pin
    gpio_isr_handler_add(INTERRUPT_PIN, gpio_isr_handler, (void*) INTERRUPT_PIN);
}

void loop() {
    // Main loop does nothing, ISR handles the interrupt
}
        
Serial Interrupt Example

// Include necessary libraries
#include "driver/uart.h"

// Define UART properties
#define UART_NUM          UART_NUM_1
#define BUF_SIZE           1024
#define TXD_PIN            (UART_PIN_NO_CHANGE)
#define RXD_PIN            9

// ISR for handling UART interrupts
void IRAM_ATTR uart_isr_handler(void* arg) {
    uint16_t rx_fifo_len;
    uart_get_buffered_data_len(UART_NUM, &rx_fifo_len);

    // Handle UART data reception
    uint8_t data[BUF_SIZE];
    int length = uart_read_bytes(UART_NUM, data, rx_fifo_len, 100 / portTICK_RATE_MS);
    // Process received data
}

void setup() {
    // Initialize UART
    uart_config_t uart_config = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_BITS_8,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
    };
    uart_param_config(UART_NUM, &uart_config);
    uart_set_pin(UART_NUM, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    uart_driver_install(UART_NUM, BUF_SIZE * 2, 0, 0, NULL, 0);

    // Install UART ISR
    uart_isr_register(UART_NUM, uart_isr_handler, NULL, 0, NULL);
}

void loop() {
    // Main loop does nothing, UART ISR handles interrupts
}
        
I2C Interrupt Example

// Include necessary libraries
#include "driver/i2c.h"

// Define I2C properties
#define I2C_MASTER_NUM     I2C_NUM_0
#define I2C_SDA_IO         21
#define I2C_SCL_IO         22
#define I2C_FREQ_HZ        100000

// ISR for handling I2C interrupts
void IRAM_ATTR i2c_isr_handler(void* arg) {
    // Handle I2C events (not directly supported by I2C driver)
}

void setup() {
    // Initialize I2C
    i2c_config_t conf;
    conf.mode = I2C_MODE_MASTER;
    conf.sda_io_num = I2C_SDA_IO;
    conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
    conf.scl_io_num = I2C_SCL_IO;
    conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
    conf.master.clk_speed = I2C_FREQ_HZ;
    i2c_param_config(I2C_MASTER_NUM, &conf);
    i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0);
}

void loop() {
    // Main loop does nothing, I2C ISR would handle events if supported
}
        
SPI Interrupt Example

// Include necessary libraries
#include "driver/spi_master.h"

// Define SPI properties
#define SPI_HOST          HSPI_HOST
#define DMA_CHANNEL       1

// ISR for handling SPI interrupts
void IRAM_ATTR spi_isr_handler(void* arg) {
    // Handle SPI events (not directly supported by SPI driver)
}

void setup() {
    // Initialize SPI
    spi_bus_config_t buscfg = {
        .miso_io_num = -1,
        .mosi_io_num = 23,
        .sclk_io_num = 18,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1
    };
    spi_bus_initialize(SPI_HOST, &buscfg, DMA_CHANNEL);

    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 1000000,
        .mode = 0,
        .spics_io_num = 5,
        .queue_size = 1,
    };
    spi_device_handle_t spi;
    spi_bus_add_device(SPI_HOST, &devcfg, &spi);
}

void loop() {
    // Main loop does nothing, SPI ISR would handle events if supported
}
        
I2S Interrupt Example

// Include necessary libraries
#include "driver/i2s.h"

// Define I2S properties
#define I2S_NUM           I2S_NUM_0
#define I2S_BCK_IO        26
#define I2S_WS_IO         25
#define I2S_DO_IO         22

// ISR for handling I2S interrupts
void IRAM_ATTR i2s_isr_handler(void* arg) {
    // Handle I2S events (not directly supported by I2S driver)
}

void setup() {
    // Initialize I2S
    i2s_config_t i2s_config = {
        .mode = I2S_MODE_MASTER | I2S_MODE_TX,
        .sample_rate = 44100,
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
        .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
        .communication_format = I2S_COMM_FORMAT_I2S_MSB,
        .intr_alloc_flags = ESP_INTR_FLAG_IRAM,
        .dma_buf_count = 8,
        .dma_buf_len = 64
    };
    i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);

    i2s_pin_config_t pin_config = {
        .bck_io_num = I2S_BCK_IO,
        .ws_io_num = I2S_WS_IO,
        .data_out_num = I2S_DO_IO,
        .data_in_num = I2S_PIN_NO_CHANGE
    };
    i2s_set_pin(I2S_NUM, &pin_config);
}

void loop() {
    // Main loop does nothing, I2S ISR would handle events if supported
}
        
ESP32 RTC (Real-Time Clock) Detailed Description

The ESP32 microcontroller features a Real-Time Clock (RTC), a built-in component that allows the ESP32 to track time and manage power efficiently, even when the main CPU is powered down or in deep sleep mode. The RTC is especially useful for time-sensitive applications or when power efficiency is crucial.

Key Features of ESP32 RTC
  • Low-Power Operation: The RTC continues to operate when the ESP32 is in low-power modes (e.g., deep sleep).
  • Timekeeping: Keeps track of the current time, including seconds, minutes, hours, days, and even the date.
  • Alarm and Timer Functionality: Can trigger events or wake the ESP32 from sleep at a specified time or after a set period.
  • Watchdog Timer (WDT): Monitors system operations to reset the ESP32 in case of system failures.
  • Periodic Wake-up: The RTC can wake up the ESP32 at predefined intervals, such as every minute or hour.
  • Independent Power Source: The RTC can operate with a backup battery, allowing it to continue tracking time even if the ESP32 loses power.
How the ESP32 RTC Works

The ESP32 RTC has its own 32.768 kHz crystal oscillator, which helps in maintaining accurate timekeeping even in low-power modes. It can run autonomously when the main cores of the ESP32 are asleep, which enables significant power savings.

The RTC manages the following tasks:

  • Counting time and date
  • Maintaining timers and alarms
  • Monitoring the system using the watchdog timer
  • Handling wake-up events after deep sleep
ESP32 RTC Memory

The ESP32 provides special memory regions that are powered independently of the main CPU. This memory allows you to store small amounts of data even when the CPU is in deep sleep mode.

  • RTC Fast Memory: This memory is faster but consumes more power. It can store up to 8 KB of data.
  • RTC Slow Memory: This memory is slower but consumes less power. It can store up to 8 KB of data.
Example Code for Using ESP32 RTC
 
// Example code to configure ESP32 RTC

#include "esp_sleep.h"
#include "time.h"

void setup() {
  Serial.begin(115200);

  // Set time struct
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Failed to obtain time");
    return;
  }

  // Configure RTC to wake up from deep sleep
  esp_sleep_enable_timer_wakeup(10 * 1000000); // Wake up after 10 seconds
  Serial.println("Going to sleep now");
  esp_deep_sleep_start();
}

void loop() {
  // The loop won't be executed due to deep sleep
}
        
ESP32 ULP (Ultra Low Power) Coprocessor Overview

The ESP32 contains a small, ultra-low-power (ULP) coprocessor that can run independently of the main CPU. It is designed to perform simple tasks while the main cores are in deep sleep, enabling extremely low power consumption.

Typical uses of the ULP include monitoring ADC values, GPIO pins, or timers, and waking up the main processor only when a specific condition occurs. Programs for the ULP are written in a **special assembly language**, compiled into a binary, and loaded into ULP memory.

Key points about the ULP:

  • Runs while main cores are in deep sleep
  • Has access to some ADC channels, RTC memory, and GPIOs
  • Can wake the main processor using wakeup events
  • Programs are small, efficient, and deterministic
ESP32 ULP Coprocessor Example

This example demonstrates how to monitor an analog sensor using the ULP and wake up the ESP32 if a threshold is crossed.


// ULP binary symbols
extern const uint8_t ulp_program_bin_start[] asm("_binary_ulp_program_bin_start");
extern const uint8_t ulp_program_bin_end[]   asm("_binary_ulp_program_bin_end");

#define THRESHOLD 1000  // Sensor threshold

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

    // Initialize ULP
    initULP();

    // Enable ULP wakeup and enter deep sleep
    esp_sleep_enable_ulp_wakeup();
    esp_deep_sleep_start();
}

void loop() {
    // Loop never runs in deep sleep mode
}

void initULP() {
    size_t ulp_size = ulp_program_bin_end - ulp_program_bin_start;
    ulp_load_binary(0, ulp_program_bin_start, ulp_size);

    // Configure ADC1 channel 6 (GPIO34)
    adc1_config_width(ADC_WIDTH_BIT_12);
    adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);

    // Set ULP wakeup period to 1 second
    ulp_set_wakeup_period(0, 1000 * 1000);

    // Start ULP program
    ulp_run((&ulp_entry - RTC_SLOW_MEM) / sizeof(uint32_t));
}
        
Writing ULP Coprocessor Code

Programs for the ULP are written in assembly language with instructions for ADC, GPIO, memory, and basic arithmetic/logic operations.


// ulp_program.S
.global ulp_entry
ulp_entry:
    READ_ADC ADC1_CHANNEL_6, R0
    MOVI R1, 1000
    SUB R2, R0, R1
    BLTZ R2, sleep
    ST R0, RTC_MEM[0]
sleep:
    HALT
        
Compiling ULP Code to Binary

Assemble the ULP program into a binary file that can be loaded into ULP memory.


riscv32-esp-elf-as -o ulp_program.o ulp_program.S
riscv32-esp-elf-ld -o ulp_program.elf -T ulp_program.ld ulp_program.o
riscv32-esp-elf-objcopy -O binary ulp_program.elf ulp_program.bin
        

The resulting ulp_program.bin is included in your Arduino sketch.

Including ULP Binary in Arduino C Program

Link the compiled binary into your Arduino sketch and start it using ulp_load_binary and ulp_run.


// Already integrated into setup() and initULP() as shown above
        

With this, the ESP32 can remain in deep sleep while the ULP monitors sensors and wakes the main processor on events.

Understanding DMA (Direct Memory Access)

Direct Memory Access (DMA) enables peripherals to transfer data to and from memory without CPU involvement. Offloading this work improves throughput and reduces CPU load, especially in real-time systems.

How DMA Works

Without DMA, the CPU must move data between peripherals and memory manually, which consumes time and interrupts real-time responsiveness. DMA allows peripherals to read/write memory directly, eliminating CPU overhead during bulk transfers.

The DMA controller operates independently and manages transfer sequences automatically once configured.

Basic DMA Operation
  1. Initialization: CPU configures source, destination, transfer size, and transfer mode.
  2. Trigger: A peripheral event or software request triggers the DMA transfer.
  3. Transfer: DMA moves data autonomously between peripheral and memory or between memory regions.
  4. Completion: DMA can signal completion via interrupt to allow post-processing.
DMA Transfer Modes
  • Normal Mode: One transfer cycle, then stop.
  • Circular Mode: Continuous ring-buffer operation for real-time streaming.
  • Peripheral-to-Memory: e.g. ADC sampling to RAM.
  • Memory-to-Peripheral: e.g. buffered UART transmission.
  • Memory-to-Memory: High-speed RAM copying or rearrangement.
DMA Trigger Sources
  • Peripheral Requests: UART, SPI, ADC, etc.
  • Timer Events: Periodic triggering.
  • Software Commands: CPU-initiated transfers.
Benefits of Using DMA
  • Reduced CPU Load: CPU can continue executing main tasks.
  • Higher Throughput: Continuous high-speed transfers.
  • Low Latency: Immediate transfer on hardware request.
DMA Limitations
  • Peripheral Compatibility: Not all peripherals support DMA.
  • Memory Bus Contention: CPU and DMA can compete for RAM access.
Conclusion

DMA is a key performance feature in embedded systems. By offloading data movement, system responsiveness improves, CPU load decreases, and high-speed continuous data streams become manageable. Mastering DMA configuration is essential for advanced embedded work.

DMA Channels for Peripherals
DMA Channel Triggered Peripheral
DMA Channel 0UART0, SPI0
DMA Channel 1UART1, SPI1
DMA Channel 2UART2, SPI2
DMA Channel 3I2C0, I2S0
DMA Channel 4I2C1, I2S1
DMA Channel 5Timer 0, Timer 1
DMA Channel 6Timer 2, Timer 3
DMA Channel 7Ethernet, Wi-Fi
Peripherals Triggering DMA Channels
Peripheral Valid DMA Channels
UART0DMA Channel 0
UART1DMA Channel 1
UART2DMA Channel 2
SPI0DMA Channel 0
SPI1DMA Channel 1
SPI2DMA Channel 2
I2C0DMA Channel 3
I2C1DMA Channel 4
Timer 0DMA Channel 5
Timer 1DMA Channel 5
Timer 2DMA Channel 6
Timer 3DMA Channel 6
EthernetDMA Channel 7
Wi-FiDMA Channel 7
ESP32 WiFi Modes & ESPconfig Library

The ESP32 microcontroller comes with a highly integrated WiFi module capable of working in Client mode, Station mode, and Relay/Range Extender mode. These modes enable the ESP32 to connect to networks, act as a standalone WiFi access point, or extend WiFi range.

ESPConfig Example in Arduino C

The ESPConfig library by Peter Dunne simplifies the configuration management of ESP8266 and ESP32-based projects. It allows easy reading and writing of configuration parameters, making your projects more modular and maintainable.

  • Easy storage and retrieval of configuration parameters.
  • Supports multiple data types (strings, integers, booleans).
  • Automatic saving and loading from EEPROM or SPIFFS.
  • Modular design for easy integration into existing projects.
  • Flexible API for managing configuration data.

Source code available on GitHub:
https://github.com/peterjazenga/ESPconfig

Basic Setup

This example creates a simple configuration for an ESP32 device including Wi-Fi SSID and password settings.


 /*  author: Peter Dunne, peterjazenga@gmail.com
 *  Web interface to Configure WiFi interface
 *  Bluetooth (ESP32 only) control and Configuration interface to phones/tablets/etc.
 
 *  sysConfig.init(); is called after Serial.begin(); and it handles all WiFi, OTA, NAT,
 *  Time setting and Bluetooth configuration. WPS is also supported via sysConfig.
 *
 *  sysConfig.run(); keeps things updated and services OTA & Web server operations,
 *  also handles blinking status LED.
 */

#include "sysconfig32.h";

void setup() {
  Serial.begin(115200);
  sysConfig.init();
  Serial.println("Ready.");  
 }

void loop() {
  sysConfig.run();
}
        

ESP Config Icons are unicode glyphs, adding graphic appeal without overhead.

WiFi configuration supports multiple connection points, tried in order.

ESP 32 WiFi configuration page screenshot

The WiFi AP Page — AP is active by default on first start for easy configuration.

ESP Config AP Page screenshot

MQTT is included due to popularity, although ESPConfig does not use MQTT internally.

ESP 32 MQTT page screenshot

The information page gives an overview of the device and system details.

ESP 32 information page screenshot
WiFi Client Mode Example

In Client mode, the ESP32 connects to a WiFi network to access the internet or communicate with other network devices.


#include <Wifi.h>

const char* ssid = "your_SSID";
const char* password = "your_PASSWORD";

void setup() {
    Serial.begin(115200);
    WiFi.begin(ssid, password);
    
    while (WiFi.status() != WL_CONNECTED) {
        delay(1000);
        Serial.println("Connecting to WiFi...");
    }
    Serial.println("Connected to WiFi");
}

void loop() {
    // Code for internet communication here
}
        

The ESP32 attempts to connect using WiFi.begin(). Once connected, normal network communication can begin.

Station Mode Example (Access Point)

In Station mode, the ESP32 creates a WiFi network, allowing devices to connect directly to it.


#include <Wifi.h>

const char* ssid = "ESP32_AP";
const char* password = "12345678";

void setup() {
    Serial.begin(115200);
    WiFi.softAP(ssid, password);
    Serial.println("ESP32 Access Point is ready");
}

void loop() {
    // Code for server-side or data handling here
}
        

WiFi.softAP() enables the ESP32 to function as an access point with the SSID "ESP32_AP".

Conclusion

The ESP32 provides flexible WiFi features including Client, Station, and Relay modes. The ESPConfig library by Peter Dunne simplifies management and configuration of WiFi and device settings, improving integration for embedded projects.

ESP32 Dual Core Example in Arduino C

Why dual core matters in embedded computing

The ESP32 contains two independent processing cores. Core 0 executes Wi-Fi, Bluetooth, and system-level tasks but can still run user code. Core 1 is almost entirely dedicated to user tasks, allowing consistent timing and isolation from system interrupts.

ESP32 Dual Core Architecture
  • Core 0: System duties + user tasks (when explicitly pinned).
  • Core 1: User-only execution with minimal interference.

This allows deterministic task separation, smooth multitasking, and hardware utilization similar to embedded SMP systems.

Code Description

Two tasks are created using FreeRTOS. Each is explicitly pinned to a specific core:

  • Task1: Assigned to Core 0 — toggles an LED every 500 ms.
  • Task2: Assigned to Core 1 — prints a Serial message every second.

This demonstrates how independent workloads run predictably on separate cores.

Key Functions Used
  • xTaskCreatePinnedToCore: Creates a task and binds it to a specific core.
  • vTaskDelay: Non-blocking timing inside tasks.
  • xPortGetCoreID: Returns the active core (0 or 1).
Example Code

#include <Arduino.h>

// Pin for LED
#define LED_PIN 2

// Handles for tasks
TaskHandle_t Task1;
TaskHandle_t Task2;

// Function for Task1 (running on Core 0)
void Task1code( void * pvParameters ) {
    Serial.print("Task1 running on core ");
    Serial.println(xPortGetCoreID());

    for(;;) {
        digitalWrite(LED_PIN, HIGH);
        delay(500);
        digitalWrite(LED_PIN, LOW);
        delay(500);
    }
}

// Function for Task2 (running on Core 1)
void Task2code( void * pvParameters ) {
    Serial.print("Task2 running on core ");
    Serial.println(xPortGetCoreID());

    for(;;) {
        Serial.println("Task2 is running");
        delay(1000);
    }
}

void setup() {
    // Initialize Serial Monitor
    Serial.begin(115200);
    delay(1000);

    // Configure LED pin as output
    pinMode(LED_PIN, OUTPUT);

    // Create Task1 on Core 0
    xTaskCreatePinnedToCore(
        Task1code,   // Task function
        "Task1",     // Task name
        10000,       // Stack size
        NULL,        // Task parameters
        1,           // Task priority
        &Task1,      // Task handle
        0            // Core to run on (Core 0)
    );

    // Create Task2 on Core 1
    xTaskCreatePinnedToCore(
        Task2code,   // Task function
        "Task2",     // Task name
        10000,       // Stack size
        NULL,        // Task parameters
        1,           // Task priority
        &Task2,      // Task handle
        1            // Core to run on (Core 1)
    );
}

void loop() {
    // Nothing to do in loop, tasks are handled by FreeRTOS
}
        
Steps to Run the Code
  1. Copy the code into the Arduino IDE.
  2. Select an ESP32 board.
  3. Upload the program.
  4. Open the Serial Monitor at 115200 baud.
Expected Output

The Serial Monitor displays:

Task1 running on core 0
Task2 running on core 1
Task2 is running
Task2 is running
...
      

The LED on GPIO 2 blinks every 500 ms.

ESP32 Bluetooth Detailed Description

The ESP32 comes equipped with a powerful Bluetooth module that supports both Bluetooth Classic (BR/EDR) and Bluetooth Low Energy (BLE). This feature makes the ESP32 an ideal choice for wireless communication in a variety of IoT applications.

Key Features of ESP32 Bluetooth
  • Bluetooth Classic (BR/EDR): Suitable for applications requiring high throughput, such as audio streaming.
  • Bluetooth Low Energy (BLE): Designed for low-power applications, BLE is ideal for sensors, beacons, and other IoT devices that require infrequent data transmissions.
  • Dual Mode: The ESP32 can simultaneously operate in both Bluetooth Classic and BLE modes.
  • Range: Up to 100 meters, depending on the environment and conditions.
  • Speed: BLE can achieve a data rate of up to 1 Mbps, while Bluetooth Classic supports data rates of up to 3 Mbps.
  • Power Efficiency: BLE is optimized for minimal energy consumption, which is ideal for battery-powered devices.
  • Multiple Connections: ESP32 supports multiple simultaneous BLE connections, acting as a central or peripheral device.
How ESP32 Bluetooth Works

The Bluetooth module in the ESP32 allows it to communicate wirelessly with other Bluetooth-enabled devices, including smartphones, computers, or other IoT devices. The dual-mode capability means that you can use Bluetooth Classic for continuous high-speed communication or BLE for energy-efficient, infrequent data transfers.

Common Use Cases
  • Wearable Devices: Smartwatches, fitness trackers, and health monitors can use BLE for syncing data with smartphones.
  • Home Automation: Control smart devices like lights, locks, or sensors via Bluetooth connectivity.
  • Beacons: BLE beacons for indoor positioning systems or marketing applications.
  • Wireless Audio: Use Bluetooth Classic for streaming audio data in applications like wireless speakers or headphones.
Example Code for Using ESP32 Bluetooth (BLE)
 
// Example code for ESP32 BLE

#include <BLEDevice>
#include <BLEServer>
#include <BLEUtils>
#include <BLE2902>
            
BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
            
// UUIDs for BLE Service and Characteristic
#define SERVICE_UUID        "0000180A-0000-1000-8000-00805F9B34FB"
#define CHARACTERISTIC_UUID "00002A29-0000-1000-8000-00805F9B34FB"
            
void setup() {
  Serial.begin(115200);
  BLEDevice::init("ESP32_Bluetooth");
            
  // Create BLE Server
  pServer = BLEDevice::createServer();
            
  // Create BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);
            
  // Create BLE Characteristic
  pCharacteristic = pService->createCharacteristic(
          CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ |
                      BLECharacteristic::PROPERTY_WRITE
                    );
            
  // Start the service
 pService->start();
            
  // Start advertising
  pServer->getAdvertising()->start();
  Serial.println("Waiting for a client to connect...");
}
            
void loop() {
  // If a device connects
  if (pServer->getConnectedCount() > 0 && !deviceConnected) {
    deviceConnected = true;
    Serial.println("Device connected");
  }
            
  // If a device disconnects
  if (pServer->getConnectedCount() == 0 && deviceConnected) {
    deviceConnected = false;
    Serial.println("Device disconnected");
  }
            
  delay(1000); // Wait for 1 second
}
        
ESP (ESP32) – Legal & Documentation Notice

ESP32® and its variants (ESP32‑C, ESP32‑S2, S3, C3, ...) are trademarks of Espressif Systems. ESP32 / SoC documentation & datasheets index.

© 2025 Espressif Systems. All rights reserved.

ESP32 / ESP8266 Boards & Modules

Sources for ESP8266EX and various ESP32 modules (Classic, S2, C3, C6, S3). Ideal for IoT and WiFi/BLE projects.