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.
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:
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:
The ESP32 is equipped with a range of peripheral interfaces that allow for communication with external devices:
The ESP32 is optimized for low-power consumption. It offers several power-saving modes, including:
| Board | CPU Speed | RAM | Flash Memory | Peripherals | Operating Voltage | Other Specifications |
|---|---|---|---|---|---|---|
| ESP32-WROOM-32 | 240 MHz (Dual-core Xtensa LX6) | 520 KB SRAM | 4 MB | 34 GPIO, UART, SPI, I2C, ADC, DAC, PWM, Wi-Fi, Bluetooth | 3.3V | Standard ESP32 module, widely used in IoT applications |
| ESP32-WROVER | 240 MHz (Dual-core Xtensa LX6) | 520 KB SRAM, 4 MB PSRAM | 4 MB | 34 GPIO, UART, SPI, I2C, ADC, DAC, PWM, Wi-Fi, Bluetooth | 3.3V | Includes extra PSRAM, ideal for applications needing more RAM |
| ESP32-WROVER-B | 240 MHz (Dual-core Xtensa LX6) | 520 KB SRAM, 8 MB PSRAM | 4 MB | 34 GPIO, UART, SPI, I2C, ADC, DAC, PWM, Wi-Fi, Bluetooth | 3.3V | More PSRAM compared to WROVER, great for AI/ML applications |
| ESP32-S2 | 240 MHz (Single-core Xtensa LX7) | 320 KB SRAM | 4 MB | 43 GPIO, USB, UART, SPI, I2C, ADC, DAC, PWM, Wi-Fi | 3.3V | Lower power, secure IoT-focused, lacks Bluetooth |
| ESP32-S3 | 240 MHz (Dual-core Xtensa LX7) | 512 KB SRAM | 4-8 MB | 43 GPIO, USB, UART, SPI, I2C, ADC, DAC, PWM, Wi-Fi, Bluetooth | 3.3V | Enhanced AI acceleration, better for vision/ML applications |
| ESP32-C3 | 160 MHz (Single-core RISC-V) | 400 KB SRAM | 4 MB | 22 GPIO, UART, SPI, I2C, ADC, PWM, Wi-Fi, Bluetooth 5.0 | 3.3V | RISC-V architecture, lower power consumption, compact size |
| ESP32-C6 | 160 MHz (Single-core RISC-V) | 400 KB SRAM | 4 MB | 22 GPIO, UART, SPI, I2C, ADC, PWM, Wi-Fi 6, Bluetooth 5.0 | 3.3V | Wi-Fi 6 support, optimized for low-latency applications |
| ESP32-PICO-D4 | 240 MHz (Dual-core Xtensa LX6) | 520 KB SRAM | 4 MB | 34 GPIO, UART, SPI, I2C, ADC, DAC, PWM, Wi-Fi, Bluetooth | 3.3V | Compact, integrated flash and crystal, ideal for space-constrained designs |
| ESP32-MINI-1 | 240 MHz (Dual-core Xtensa LX6) | 520 KB SRAM | 4 MB | 34 GPIO, UART, SPI, I2C, ADC, DAC, PWM, Wi-Fi, Bluetooth | 3.3V | Small form factor, great for embedded projects |
| ESP32-DevKitC | 240 MHz (Dual-core Xtensa LX6) | 520 KB SRAM | 4 MB | 34 GPIO, UART, SPI, I2C, ADC, DAC, PWM, Wi-Fi, Bluetooth | 3.3V | General-purpose development board, USB interface |
The ESP32 features two 12-bit ADC modules, which allow for analog-to-digital conversions, enabling voltage measurements from sensors like temperature sensors, potentiometers, light sensors, and more. Each module has a 12-bit resolution, converting the voltage into 4096 discrete levels.
The ESP32 ADC channels are divided into ADC1 (GPIO 32-39) and ADC2 (GPIO 0, 2, 4, 12-15, 25-27).
Here is a code example showing how to use ADC1 to read a voltage signal:
// ADC Example to Read Voltage
const int analogPin = 34; // ADC1 Channel 6 (GPIO 34)
int adcValue = 0; // Variable to store ADC value
float voltage = 0.0; // Variable to store calculated voltage
void setup() {
Serial.begin(115200); // Initialize serial communication
analogReadResolution(12); // Set ADC resolution to 12 bits
}
void loop() {
adcValue = analogRead(analogPin); // Read the analog value from the pin
voltage = (adcValue / 4095.0) * 3.3; // Calculate the voltage
Serial.print("ADC Value: ");
Serial.print(adcValue);
Serial.print(" | Voltage: ");
Serial.println(voltage);
delay(1000); // Wait for 1 second before reading again
}
To read voltages higher than 1.1V, you can configure attenuation, as shown below:
// ADC with Attenuation to read higher voltages
const int analogPin = 33; // ADC1 Channel 5 (GPIO 33)
int adcValue = 0;
float voltage = 0.0;
void setup() {
Serial.begin(115200);
analogReadResolution(12); // Set ADC to 12-bit resolution
analogSetPinAttenuation(analogPin, ADC_11db); // Set attenuation to 11 dB
}
void loop() {
adcValue = analogRead(analogPin); // Read the analog value
voltage = (adcValue / 4095.0) * 3.9; // Calculate voltage
Serial.print("ADC Value: ");
Serial.print(adcValue);
Serial.print(" | Voltage: ");
Serial.println(voltage);
delay(1000); // Wait 1 second before reading again
}
The ADC (Analog-to-Digital Converter) on the ESP32 can be used to read analog signals and convert them into digital values. When dealing with high-speed or high-resolution data acquisition, using DMA (Direct Memory Access) with the ADC can significantly improve performance by offloading data transfer tasks from the CPU to the DMA controller.
This example demonstrates how to set up the ESP32 ADC with DMA to continuously read analog data and store it in a buffer.
// Include necessary libraries
#include "driver/adc.h"
#include "driver/dma.h"
// Define ADC properties
#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
// DMA buffer for ADC data
uint16_t adc_buffer[DMA_BUFFER_SIZE];
// DMA configuration
void setup_adc_dma() {
// Configure ADC width and attenuation
adc1_config_width(ADC_WIDTH);
adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN);
// Initialize DMA
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);
// Start ADC conversion
adc1_start();
}
void setup() {
// Initialize serial communication
Serial.begin(115200);
// Set up ADC with DMA
setup_adc_dma();
}
void loop() {
// Process ADC data
for (int i = 0; i < DMA_BUFFER_SIZE; i++) {
Serial.println(adc_buffer[i]);
}
// Wait before the next read
delay(1000);
}
The ESP32 supports the I2C communication protocol, which allows it to communicate with multiple devices using just two lines: SDA (data line) and SCL (clock line). I2C is commonly used in sensors, memory devices, and communication between microcontrollers.
In this example, the ESP32 acts as an I2C master device, sending and receiving data to/from a slave device at address 0x27.
#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);
}
In this example, the ESP32 is configured as an I2C slave device with an address of 0x27, ready to respond to communication from a master device.
#include <Wire.h>
void receiveEvent(int bytes); // Function to handle data reception
void requestEvent(); // Function to send data when requested
void setup() {
Wire.begin(0x27); // Join I2C bus with address 0x27 as a slave
Wire.onReceive(receiveEvent); // Register the receive event handler
Wire.onRequest(requestEvent); // Register the request event handler
Serial.begin(115200);
}
void loop() {
delay(100); // Main loop does nothing, just waits for events
}
// Function to handle data reception from the master
void receiveEvent(int bytes) {
while (Wire.available()) {
char c = Wire.read(); // Read the received byte
Serial.print(c); // Print received data
}
}
// Function to handle data request from the master
void requestEvent() {
Wire.write("ACK"); // Send back an acknowledgment
}
The I2S (Inter-IC Sound) interface is a serial bus interface standard used to connect digital audio devices. It is widely used for audio applications due to its ability to transmit data in a synchronous manner. I2S can be used to interface with various audio components such as ADCs, DACs, and audio processors.
The ESP32 microcontroller supports the I2S interface, which is used for handling digital audio signals. The ESP32 I2S peripheral can be configured as either an I2S master or an I2S slave. This flexibility allows the ESP32 to communicate with various audio components, either generating or receiving audio data.
The ESP32 I2S interface is configured using several parameters. Here’s an overview of the main configuration options:
The ESP32 can operate as both an I2S master and an I2S slave. Here’s a brief overview of the differences:
// Include the I2S driver library
#include <driver/i2s>
// Define I2S configuration parameters
#define I2S_NUM I2S_NUM_0 // I2S peripheral number
#define I2S_BCK_IO 26 // I2S Bit Clock (BCK) pin
#define I2S_WS_IO 25 // I2S Word Select (WS) pin
#define I2S_DATA_IO 22 // I2S Data pin
#define SAMPLE_RATE 44100 // Audio sample rate
#define I2S_BUFFER_SIZE 1024 // Buffer size for I2S data
void setup() {
// Configure the I2S driver
i2s_config_t i2s_config = {
mode: I2S_MODE_MASTER | I2S_MODE_TX, // I2S master and transmitter mode
sample_rate: SAMPLE_RATE, // Sample rate in Hz
bits_per_sample: I2S_BITS_PER_SAMPLE_16BIT, // 16-bit audio samples
channel_format: I2S_CHANNEL_FMT_RIGHT_LEFT, // Stereo audio format
communication_format: I2S_COMM_FORMAT_I2S_MSB, // I2S format
dma_buf_count: 2, // Number of DMA buffers
dma_buf_len: I2S_BUFFER_SIZE, // Length of each DMA buffer
intr_alloc_flags: ESP_INTR_FLAG_LEVEL1 // Interrupt priority
};
// Set up the I2S pins
i2s_pin_config_t pin_config = {
bck_io_num: I2S_BCK_IO, // Bit Clock pin
ws_io_num: I2S_WS_IO, // Word Select pin
data_out_num: I2S_DATA_IO, // Data output pin
data_in_num: I2S_PIN_NO_CHANGE // No data input pin used
};
// Install the I2S driver
i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
// Set the I2S pin configuration
i2s_set_pin(I2S_NUM, &pin_config);
// Prepare data to send
uint8_t data_to_send[I2S_BUFFER_SIZE] = {0}; // Initialize buffer with zeroes
// Start sending data
while (true) {
i2s_write_bytes(I2S_NUM, (const char *)data_to_send, I2S_BUFFER_SIZE, portMAX_DELAY);
}
}
void loop() {
// Nothing to do in the loop
}
// Include the I2S driver library
#include <driver/i2s>
// Define I2S configuration parameters
#define I2S_NUM I2S_NUM_0 // I2S peripheral number
#define I2S_BCK_IO 26 // I2S Bit Clock (BCK) pin
#define I2S_WS_IO 25 // I2S Word Select (WS) pin
#define I2S_DATA_IO 22 // I2S Data pin
#define SAMPLE_RATE 44100 // Audio sample rate
#define I2S_BUFFER_SIZE 1024 // Buffer size for I2S data
void setup() {
// Configure the I2S driver
i2s_config_t i2s_config = {
mode: I2S_MODE_SLAVE | I2S_MODE_RX, // I2S slave and receiver mode
sample_rate: SAMPLE_RATE, // Sample rate in Hz
bits_per_sample: I2S_BITS_PER_SAMPLE_16BIT, // 16-bit audio samples
channel_format: I2S_CHANNEL_FMT_RIGHT_LEFT, // Stereo audio format
communication_format: I2S_COMM_FORMAT_I2S_MSB, // I2S format
dma_buf_count: 2, // Number of DMA buffers
dma_buf_len: I2S_BUFFER_SIZE, // Length of each DMA buffer
intr_alloc_flags: ESP_INTR_FLAG_LEVEL1 // Interrupt priority
};
// Set up the I2S pins
i2s_pin_config_t pin_config = {
bck_io_num: I2S_BCK_IO, // Bit Clock pin
ws_io_num: I2S_WS_IO, // Word Select pin
data_out_num: I2S_PIN_NO_CHANGE, // No data output pin used
data_in_num: I2S_DATA_IO // Data input pin
};
// Install the I2S driver
i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
// Set the I2S pin configuration
i2s_set_pin(I2S_NUM, &pin_config);
// Buffer to hold received data
uint8_t received_data[I2S_BUFFER_SIZE];
// Start receiving data
while (true) {
i2s_read_bytes(I2S_NUM, (char *)received_data, I2S_BUFFER_SIZE, portMAX_DELAY);
// Process received data here
}
}
void loop() {
// Nothing to do in the loop
}
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, meaning that data can be transmitted and received simultaneously.
The ESP32 microcontroller supports two SPI hardware peripherals: HSPI and VSPI. These can be configured to work as either SPI master or SPI slave. The ESP32 SPI interface is highly flexible and can be used for a variety of applications including communication with sensors, displays, and other peripherals.
The ESP32 SPI interface is configured using several parameters. Here’s an overview of the main configuration options:
The ESP32 can operate as both an SPI master and an SPI slave. Here’s a brief overview of the differences:
// Include the SPI master driver library
#include <driver/spi_master>
// Define GPIO pins for SPI interface
#define SPI_HOST HSPI_HOST // Use the HSPI host for communication
#define PIN_MISO 19 // GPIO pin for Master In Slave Out
#define PIN_MOSI 23 // GPIO pin for Master Out Slave In
#define PIN_SCK 18 // GPIO pin for Clock
#define PIN_CS 5 // GPIO pin for Chip Select
void setup() {
// Initialize the SPI bus
spi_bus_config_t bus_config = {
mosi_io_num: PIN_MOSI, // Set MOSI pin
miso_io_num: PIN_MISO, // Set MISO pin
sclk_io_num: PIN_SCK, // Set Clock pin
quadwp_io_num: -1, // Not used
quadhd_io_num: -1 // Not used
};
// Initialize the SPI bus with the given configuration
spi_bus_initialize(SPI_HOST, &bus_config, 1);
// Configure the SPI device
spi_device_interface_config_t dev_config = {
command_bits: 0, // No command bits used
address_bits: 0, // No address bits used
dummy_bits: 0, // No dummy bits used
mode: 0, // SPI mode 0
clock_speed_hz: 1 * 1000 * 1000, // Set SPI clock speed to 1 MHz
spics_io_num: PIN_CS, // Set Chip Select pin
queue_size: 7 // Number of transactions that can be queued
};
spi_device_handle_t spi;
// Add the SPI device to the bus
spi_bus_add_device(SPI_HOST, &dev_config, &spi);
// Prepare data to send
uint8_t data_to_send[1] = {0xAA}; // Data to be transmitted (e.g., 0xAA)
// Create and configure the SPI transaction
spi_transaction_t transaction = {
length: 8, // Data length in bits
tx_buffer: data_to_send, // Pointer to data to send
rx_buffer: NULL // No data expected to be received
};
// Perform SPI transaction
spi_device_transmit(spi, &transaction); // Transmit the data
}
void loop() {
// Nothing to do in the loop
}
// Include the SPI slave driver library
#include <driver/spi_slave>
// Define GPIO pins for SPI interface
#define SPI_HOST HSPI_HOST // Use the HSPI host for communication
#define PIN_MISO 19 // GPIO pin for Master In Slave Out
#define PIN_MOSI 23 // GPIO pin for Master Out Slave In
#define PIN_SCK 18 // GPIO pin for Clock
#define PIN_CS 5 // GPIO pin for Chip Select
void setup() {
// Initialize the SPI bus
spi_bus_config_t bus_config = {
mosi_io_num: PIN_MOSI, // Set MOSI pin
miso_io_num: PIN_MISO, // Set MISO pin
sclk_io_num: PIN_SCK, // Set Clock pin
quadwp_io_num: -1, // Not used
quadhd_io_num: -1 // Not used
};
// Initialize the SPI bus with the given configuration
spi_bus_initialize(SPI_HOST, &bus_config, 1);
// Configure the SPI slave
spi_slave_interface_config_t dev_config = {
mode: 0, // SPI mode 0
spics_io_num: PIN_CS, // Set Chip Select pin
queue_size: 3, // Number of transactions that can be queued
flags: SPI_SLAVE_KEEP_CS_LOW // Keep CS line low during transactions
};
// Initialize the SPI slave with the given configuration
spi_slave_initialize(SPI_HOST, &bus_config, &dev_config, 1);
// Buffer to hold received data
uint8_t received_data[1];
// Create and configure the SPI transaction
spi_transaction_t transaction = {
length: 8, // Data length in bits
tx_buffer: NULL, // No data to send
rx_buffer: received_data // Pointer to buffer for received data
};
// Wait for SPI transaction
spi_slave_transmit(SPI_HOST, &transaction, portMAX_DELAY); // Receive data
}
void loop() {
// Process received data here
}
The ESP32 microcontroller includes multiple hardware timers that can be used for a variety of 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.
The ESP32 has four hardware timers, each of which can be configured independently. These timers can be used for tasks like generating PWM signals, periodic interrupts, and delay functions. They are highly versatile and can be used in both one-shot and periodic modes.
The ESP32 timers can be configured using several parameters. Here’s an overview of the main configuration options:
In addition to the general-purpose timers, the ESP32 also includes watchdog timers that are used to reset the system if it becomes unresponsive. The watchdog timers are critical for ensuring the reliability of the system in various scenarios.
// Include the timer library
#include "driver/timer.h"
// Define timer configurations
#define TIMER_DIVIDER 16 // Timer clock divider
#define TIMER_SCALE (TIMER_BASE_CLK / TIMER_DIVIDER) // Timer scale for microseconds
#define TIMER_INTERVAL_SEC (1) // Timer interval in seconds
// Timer interrupt handler
void IRAM_ATTR timer_group0_isr(void* arg) {
// Clear the interrupt
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);
// Your interrupt handling code here
printf("Timer Interrupt Triggered!\n");
}
void setup() {
// Configure the timer
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;
// Initialize timer
timer_init(TIMER_GROUP_0, TIMER_0, &config);
// Set the timer alarm value
timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, TIMER_SCALE * TIMER_INTERVAL_SEC);
// Enable timer interrupt
timer_enable_intr(TIMER_GROUP_0, TIMER_0);
timer_isr_register(TIMER_GROUP_0, TIMER_0, timer_group0_isr, NULL, 0, NULL);
// Start the timer
timer_start(TIMER_GROUP_0, TIMER_0);
}
void loop() {
// Main loop does nothing, timer ISR handles tasks
}
// Include the watchdog library
#include "esp_system.h"
#include "esp_task_wdt.h"
// Define watchdog timer timeout
#define WDT_TIMEOUT_S 10 // Watchdog timeout in seconds
void setup() {
// Initialize the watchdog timer
esp_task_wdt_init(WDT_TIMEOUT_S, true); // Enable panic on watchdog timeout
// Add the current task to the watchdog
esp_task_wdt_add(NULL); // NULL refers to the current task
// Start the watchdog timer
esp_task_wdt_reset();
}
void loop() {
// Main loop does nothing, watchdog timer needs periodic resets
while (true) {
delay(5000); // Delay for 5 seconds
esp_task_wdt_reset(); // Reset watchdog timer
}
}
| Timer Channel | Triggered Peripheral / DMA Channel |
|---|---|
| Timer 0 Channel 0 | DMA Channel 0 |
| Timer 0 Channel 1 | DMA Channel 1 |
| Timer 1 Channel 0 | DMA Channel 2 |
| Timer 1 Channel 1 | DMA Channel 3 |
| Timer 2 Channel 0 | DMA Channel 4 |
| Timer 2 Channel 1 | DMA Channel 5 |
| Timer 3 Channel 0 | DMA Channel 6 |
| Timer 3 Channel 1 | DMA Channel 7 |
| Trigger Source | Timers Triggered |
|---|---|
| External Signal (GPIO) | Timer 0, Timer 1, Timer 2, Timer 3 |
| Timer 0 | Timer 1, Timer 2 |
| Timer 1 | Timer 2, Timer 3 |
| Timer 2 | Timer 3 |
| PWM | All Timers |
| ADC | All Timers |
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.
// 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
}
// 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
}
// 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
}
// 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
}
// 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
}
// Include necessary libraries
#include "driver/adc.h"
// ISR for handling ADC interrupts
void IRAM_ATTR adc_isr_handler(void* arg) {
// Handle ADC conversion complete event
int adc_value = adc1_get_raw(ADC1_CHANNEL_0);
printf("ADC Value: %d\n", adc_value);
}
void setup() {
// Initialize ADC
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_0);
// ADC interrupt setup
adc1_add_sample(ADC1_CHANNEL_0);
}
void loop() {
// Main loop does nothing, ADC ISR handles interrupts
}
// Include necessary libraries
#include "driver/timer.h"
// Define timer properties
#define TIMER_DIVIDER 16 // Timer clock divider
#define TIMER_SCALE (TIMER_BASE_CLK / TIMER_DIVIDER) // Timer scale for microseconds
#define TIMER_INTERVAL_SEC (1) // Timer interval in seconds
// ISR for handling the timer interrupt
void IRAM_ATTR timer_group0_isr(void* arg) {
// Clear the interrupt
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);
// Your interrupt handling code here
printf("Timer Interrupt Triggered!\n");
}
void setup() {
// Configure the timer
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;
// Initialize timer
timer_init(TIMER_GROUP_0, TIMER_0, &config);
// Set the timer alarm value
timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, TIMER_SCALE * TIMER_INTERVAL_SEC);
// Enable timer interrupt
timer_enable_intr(TIMER_GROUP_0, TIMER_0);
timer_isr_register(TIMER_GROUP_0, TIMER_0, timer_group0_isr, NULL, 0, NULL);
// Start the timer
timer_start(TIMER_GROUP_0, TIMER_0);
}
void loop() {
// Main loop does nothing, timer ISR handles tasks
}
The **RTC interrupt** is triggered by the Real-Time Clock to perform tasks at specific time intervals. This example configures an RTC alarm interrupt.
#include "driver/rtc_io.h"
#include "esp_sleep.h"
void setup() {
Serial.begin(115200);
esp_sleep_enable_timer_wakeup(10 * 1000000); // Set wakeup timer for 10 seconds
Serial.println("Going to sleep now...");
esp_deep_sleep_start(); // Enter deep sleep mode
}
void loop() {
// Code will only run after waking up from deep sleep
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TIMER) {
Serial.println("Woke up from RTC Timer interrupt!");
}
delay(1000);
}
In this example, we use the ESP32’s **deep sleep** mode and set up an RTC wake-up timer to wake the ESP32 every 10 seconds. The function `esp_sleep_enable_timer_wakeup()` configures the interrupt to trigger after the specified time.
The ESP32 can trigger interrupts based on Bluetooth events, such as device connections. Here’s an example to configure a Bluetooth interrupt using the **BluetoothSerial** library.
#include "BluetoothSerial.h"
BluetoothSerial SerialBT;
void btCallback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) {
if (event == ESP_SPP_SRV_OPEN_EVT) {
Serial.println("Bluetooth Connected!");
}
}
void setup() {
Serial.begin(115200);
SerialBT.register_callback(btCallback);
SerialBT.begin("ESP32_Test"); // Device name
Serial.println("Waiting for Bluetooth connection...");
}
void loop() {
if (SerialBT.available()) {
Serial.println("Received data via Bluetooth.");
}
}
This example uses the **BluetoothSerial** library to handle Bluetooth connections. The callback function `btCallback()` is invoked when a Bluetooth device connects to the ESP32. The `ESP_SPP_SRV_OPEN_EVT` event indicates that the connection has been established.
The ESP32 can also trigger interrupts based on WiFi events, such as connection or disconnection. The following example demonstrates how to set up a WiFi interrupt.
#include <WiFi.h>
void WiFiEvent(WiFiEvent_t event) {
switch (event) {
case SYSTEM_EVENT_STA_GOT_IP:
Serial.println("WiFi Connected, IP Address Obtained");
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
Serial.println("WiFi Disconnected");
break;
}
}
void setup() {
Serial.begin(115200);
WiFi.onEvent(WiFiEvent); // Register WiFi event handler
WiFi.begin("YourSSID", "YourPassword");
Serial.println("Connecting to WiFi...");
}
void loop() {
// Regular loop code
}
In this example, we register a WiFi event handler using `WiFi.onEvent()`. The `WiFiEvent()` function is called when specific WiFi events occur, such as when the device connects to or disconnects from a WiFi network. The **SYSTEM_EVENT_STA_GOT_IP** event indicates that the ESP32 has successfully connected and obtained an IP address, while **SYSTEM_EVENT_STA_DISCONNECTED** is triggered when the WiFi disconnects.
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.
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:
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.
// 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
}
This example shows how to read an analog sensor using the ULP and wake up the ESP32 if a threshold is crossed.
#include "esp32/ulp.h"
#include "esp_sleep.h"
#include "driver/adc.h"
#include "esp_log.h"
// Threshold for sensor value to wake up ESP32
#define THRESHOLD 1000
// ULP program declaration
extern const uint8_t ulp_bin_start[] asm("_binary_ulp_program_bin_start");
extern const uint8_t ulp_bin_end[] asm("_binary_ulp_program_bin_end");
void setup() {
// Initialize serial monitor
Serial.begin(115200);
delay(1000);
// Initialize ULP and load ULP program
initULP();
// Go to deep sleep with ULP wakeup enabled
esp_sleep_enable_ulp_wakeup();
esp_deep_sleep_start();
}
void loop() {
// The loop should never run, as the ESP32 is in deep sleep.
}
void initULP() {
// Load ULP binary program into ULP memory
size_t ulp_size = ulp_bin_end - ulp_bin_start;
ulp_load_binary(0, ulp_bin_start, ulp_size);
// Initialize ADC channel (example: 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 (1000 ms)
ulp_set_wakeup_period(0, 1000 * 1000); // 1 second
// Start the ULP program
ulp_run((&ulp_entry - RTC_SLOW_MEM) / sizeof(uint32_t));
}
Direct Memory Access (DMA) is a feature used in microcontrollers and other computing systems to transfer data directly between peripherals and memory, bypassing the CPU. By offloading these data transfers to the DMA controller, the CPU can perform other tasks, significantly improving system performance, especially in real-time applications.
In a typical embedded system without DMA, the CPU is responsible for reading data from a peripheral (e.g., an ADC, UART, or SPI) and writing it to memory, or vice versa. This process consumes significant CPU cycles, especially for high-frequency or large-volume data transfers. DMA automates these transfers by allowing peripherals to communicate directly with memory, reducing the CPU's involvement.
The DMA controller operates as a separate entity within the microcontroller, managing the data transfer between memory and peripherals. The following sequence outlines the basic operation of a DMA transfer:
DMA supports several modes of operation, depending on the needs of the application:
DMA transfers can be triggered by a variety of sources, including:
DMA provides several advantages over CPU-driven data transfers:
Despite its benefits, DMA has some limitations:
DMA is an essential feature in embedded systems, offering improved data transfer efficiency, reduced CPU load, and enhanced real-time performance. By offloading data transfer tasks from the CPU to the DMA controller, system resources are optimized, allowing for more complex and responsive applications. Understanding how to configure and utilize DMA effectively is a key skill in embedded systems development.
| DMA Channel | Triggered Peripheral |
|---|---|
| DMA Channel 0 | UART0, SPI0 |
| DMA Channel 1 | UART1, SPI1 |
| DMA Channel 2 | UART2, SPI2 |
| DMA Channel 3 | I2C0, I2S0 |
| DMA Channel 4 | I2C1, I2S1 |
| DMA Channel 5 | Timer 0, Timer 1 |
| DMA Channel 6 | Timer 2, Timer 3 |
| DMA Channel 7 | Ethernet, Wi-Fi |
| Peripheral | Valid DMA Channels |
|---|---|
| UART0 | DMA Channel 0 |
| UART1 | DMA Channel 1 |
| UART2 | DMA Channel 2 |
| SPI0 | DMA Channel 0 |
| SPI1 | DMA Channel 1 |
| SPI2 | DMA Channel 2 |
| I2C0 | DMA Channel 3 |
| I2C1 | DMA Channel 4 |
| Timer 0 | DMA Channel 5 |
| Timer 1 | DMA Channel 5 |
| Timer 2 | DMA Channel 6 |
| Timer 3 | DMA Channel 6 |
| Ethernet | DMA Channel 7 |
| Wi-Fi | DMA Channel 7 |
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.
The ESPConfig library by Peter Dunne simplifies the configuration management of ESP8266 and ESP32-based projects. It allows for easy reading and writing of configuration parameters, making your projects more modular and maintainable.
You can find the source code for the ESPConfig library on GitHub: ESPConfig GitHub Repository
In this example, we will create a simple configuration for an ESP32 device, including settings for Wi-Fi SSID and password.
/* 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, provides for servicing of the OTA & Web server issues among others, it also handles the blinking of the status LED
*/
#include "sysconfig32.h";
void setup() {
Serial.begin(115200);
sysConfig.init();
Serial.println("Ready.");
}
void loop() {
sysConfig.run();
}
In **Client mode**, the ESP32 connects to a WiFi network to access the internet or communicate with other network devices. This mode is commonly used to allow the ESP32 to act as an IoT device.
#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
}
This code demonstrates how to connect the ESP32 to a WiFi network in **Client mode**. The `WiFi.begin()` function takes the SSID and password of the WiFi network and attempts to connect. The `WiFi.status()` function checks the connection status, and once connected, the ESP32 is ready for internet communication.
In **Station mode**, the ESP32 creates its own WiFi network, allowing other devices to connect to it. This is useful when the ESP32 needs to act as a server for IoT applications or act as a hotspot.
#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
}
In **Station mode**, the ESP32 acts as an **Access Point** (AP), allowing other WiFi-enabled devices to connect to it. The `WiFi.softAP()` function sets up the ESP32's WiFi as an AP, and the device becomes discoverable with the SSID "ESP32_AP" and password "12345678".
The ESP32 is a versatile microcontroller with powerful WiFi capabilities. Whether you need to connect it to an existing network (Client mode), create an access point (Station mode), or extend your network (Relay mode), the ESP32 offers flexible solutions. The **ESPconfig** library by Peter Dunne provides additional tools to make WiFi management even easier.
This example demonstrates how to use the dual-core capabilities of the ESP32 microcontroller in Arduino. The ESP32 has two processing cores:
In this example, we are running two different tasks on the two cores:
We use the FreeRTOS operating system (which is built into the ESP32) to create and manage the tasks. By specifying which core each task should run on, we can take advantage of the ESP32’s dual-core architecture.
#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
}
In the Serial Monitor, you should see the following messages:
Task1 running on core 0 Task2 running on core 1 Task2 is running Task2 is running ...
Additionally, the LED connected to GPIO pin 2 will blink every 500 milliseconds.
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.
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.
// 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
}