This is a quick guide that shows how to create an ESP-IDF I2C scanner that finds the address of all I2C devices connected to the ESP32 SDA and SCL pins.

Related Guide: ESP32 I2C Communication: Set Pins, Multiple Bus Interfaces and Peripherals (Arduino IDE).
Prerequisites
Before following this guide, you need to install the ESP-IDF extension on VS Code IDE (Microsoft Visual Studio Code). Follow the next guide to install it, if you haven’t already:
To test the I2C scanner, we’ll be using a BME280 sensor. Here’s a list of the parts you need to build the circuit:
Note: you can use any other I2C device, you just need to wire it to the correct I2C pins of your device.
Schematic – ESP32 with BME280 using I2C
To test the I2C communication, we’ll be using the BME280 sensor module. Wire the sensor SDA pin to GPIO 21 and SCL to GPIO 22, as shown in the following schematic diagram (check ESP32 Pinout Reference).

In case of the ESP32-S3 boards, the I2C pins are SDA connected to GPIO 8 and SCL connected to GPIO 9, as shown in the following schematic diagram (check ESP32-S3 GPIO Reference Guide).

Creating an ESP-IDF Template App Project for the ESP32
The ESP-IDF extension provides an easy way to create a project from scratch with all the required files and configurations generated automatically.
To create a new ESP-IDF project on VS Code, follow these steps:
- Open the ESP-IDF Espressif extension and expand the “Advanced” menu
- Click the “New Project Wizard” option
- Choose the “Use ESP-IDF v6.X” to select the ESP-IDF framework version

In the menu, select the “ESP-IDF Templates” sample project and press the “Create project using template sample project” button.

A new window opens, you need to fill in these fields:
- Project Name: type the desired project name;
- Enter Project Directory: click the folder icon and select the target folder to save all your project files. You can use any directory. Note: do NOT use a Google Drive / One Drive / Dropbox folder, because it will write/create many files during the building process—if it’s on a cloud folder, this process might be extremely slow;
- ESP-IDF Target: select the target device chip, I’m using an ESP32 with the esp32s3 chip;
- ESP-IDF Board: for the esp32s3 chip, I also need to select the configuration: ESP32-S chip (via builtin USB-JTAG);
- Serial Port: while having your ESP32 board connected to your computer, select the correct COM port number that refers to your ESP32;
- Choose Template: click the blue button to create a new project using a template.

Opening the ESP-IDF Project on VS Code
After a few seconds, a notification will appear on a new window on VS Code. You can click “Open Project” to open the newly created ESP-IDF sample project template.

IMPORTANT: if you didn’t see the notification that allows you to automatically open the ESP-IDF project on VS Code, you can easily do it by following these instructions:
Go to File > Open Folder…

Browse on your computer for the esp-idf-project folder (your project folder name that you’ve previously defined) and “Select Folder“.

That’s it! Your new ESP-IDF project template has been successfully created and opened.
ESP-IDF generates many files, folders, and subfolders for your project. For this guide, I recommend keeping all the default files unchanged; we will only modify the main.c file.
The example codes will be written in the main.c file. To open it, follow these instructions:
- Open the project explorer by clicking the first icon on the left sidebar.
- Select your project folder name, in my case it’s “ESP-IDF-PROJECT“.
- Expand the “main” folder.
- Click the “main.c” file.
- The default main.c template file loads in the code window.

Code – ESP-IDF: ESP32 I2C Scanner
If you want to find the I2C address of a specific sensor, display, or any other I2C peripheral, connect it to the ESP32 I2C pins and then run the I2C scanner provided.
Copy the following code to the ESP-IDF project main.c file and save it.
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
https://RandomNerdTutorials.com/esp-idf-esp32-i2c-scanner/
*/
#include <stdio.h>
#include "esp_log.h"
#include "driver/i2c_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define I2C_SDA_PIN 8 // Change to your SDA pin
#define I2C_SCL_PIN 9 // Change to your SCL pin
#define I2C_TIMEOUT_MS 50 // Timeout for probing each address (in milliseconds)
static const char *TAG = "I2C_SCANNER";
void i2c_scanner_task(void *pvParameters)
{
// Configure and initialize the I2C master bus
i2c_master_bus_config_t bus_cfg = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.sda_io_num = I2C_SDA_PIN,
.scl_io_num = I2C_SCL_PIN,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = false,
};
i2c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_cfg, &bus_handle));
while (1) {
ESP_LOGI(TAG, "Starting I2C scan...");
uint8_t found_addresses[128] = {0}; // To store found devices
int devices_found = 0;
// Optional: Print classic grid header
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\n");
for (int addr = 0x00; addr <= 0x7F; addr++) {
if (addr % 16 == 0) {
printf("%02x: ", addr);
}
// Skip general call (0x00) and reserved addresses
esp_err_t ret = i2c_master_probe(bus_handle, addr, I2C_TIMEOUT_MS);
if (ret == ESP_OK) {
printf("%02x ", addr);
if (addr >= 0x08 && addr <= 0x77) { // Find only count valid 7-bit addresses
found_addresses[devices_found++] = addr;
}
} else if (ret == ESP_ERR_NOT_FOUND) {
printf("-- ");
} else {
printf("UU "); // Timeout or other error
}
if ((addr + 1) % 16 == 0) {
printf("\n");
}
}
// Print summary
printf("\nScan complete: %d device(s) found.\n", devices_found);
for (int i = 0; i < devices_found; i++) {
printf("%d. Device 0x%02x address\n", i + 1, found_addresses[i]);
}
if (devices_found == 0) {
printf("\n"); // Extra newline for readability when none found
}
// Repeats scan every 10 seconds (optional)
vTaskDelay(pdMS_TO_TICKS(10000));
}
// Cleanup (never reached in this loop)
ESP_ERROR_CHECK(i2c_del_master_bus(bus_handle));
vTaskDelete(NULL);
}
void app_main(void)
{
ESP_LOGI(TAG, "I2C scanner starting on SDA=%d, SCL=%d", I2C_SDA_PIN, I2C_SCL_PIN);
xTaskCreate(i2c_scanner_task, "i2c_scanner", 4096, NULL, 5, NULL);
}
How the Code Works
In this section, we’ll take a look at the code to see how it works.
Libraries
We start by including the required libraries:
- stdio.h – the standard C library will be used for the printf function that prints the debugging information in the serial monitor;
- esp_log.h – offers a framework to format log messages in the serial monitor for debugging;
- i2c_master.h – the ESP-IDF I2C driver;
- FreeRTOS.h – provides the core FreeRTOS types and functions;
- task.h – allows to use task management.
#include <stdio.h>
#include "esp_log.h"
#include "driver/i2c_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
Setting SDA and SCL Pins
Make sure you have your I2C peripheral properly connected to your board on the right I2C pins. In my case, I’m using an ESP32-S3 (I2C_SDA_PIN -> GPIO 8 and I2C_SCL_PIN -> GPIO 9).
#define I2C_SDA_PIN 8
#define I2C_SCL_PIN 9
#define I2C_TIMEOUT_MS 50
Logging Tag
The “I2C_SCANNER” prefix will be used to log all the debugging messages.
static const char *TAG = "I2C_SCANNER";
i2c_scanner_task() function
The i2c_scanner_task() function is the FreeRTOS task that has everything required to run the I2C scanner to find the connected devices.
void i2c_scanner_task(void *pvParameters)
{
(...)
}
This structure creates the I2C master bus configuration with the correct I2C pins,
i2c_master_bus_config_t bus_cfg = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.sda_io_num = I2C_SDA_PIN,
.scl_io_num = I2C_SCL_PIN,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = false,
};
The i2c_new_master_bus initializes the I2C hardware on the ESP32 with the configuration variable to set the I2C pins.
i2c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_cfg, &bus_handle));
Start the I2C Scanner
The actual scanner is inside an infinite loop that runs continuously every 10 seconds:
while (1)
{
(...)
ESP_ERROR_CHECK(i2c_del_master_bus(bus_handle));
}
This code section loops through all 128 possible 7-bit addresses (from 0x00 to 0x7F). The key part of the scanner is the i2c_master_probe() function that sends an I2C acknowledgment message to see if any connected device acknowledges it. Devices that respond with ESP_OK are valid and their address is printed in the terminal.
uint8_t found_addresses[128] = {0};
int devices_found = 0;
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\n");
for (int addr = 0x00; addr <= 0x7F; addr++) {
if (addr % 16 == 0) {
printf("%02x: ", addr);
}
esp_err_t ret = i2c_master_probe(bus_handle, addr, I2C_TIMEOUT_MS);
if (ret == ESP_OK) {
printf("%02x ", addr);
if (addr >= 0x08 && addr <= 0x77) {
found_addresses[devices_found++] = addr;
}
} else if (ret == ESP_ERR_NOT_FOUND) {
printf("-- ");
} else {
printf("UU ");
}
if ((addr + 1) % 16 == 0) {
printf("\n");
}
}
Once the scan is complete, it prints a message saying how many devices has found and a numbered list with their corresponding I2C address.
printf("\nScan complete: %d device(s) found.\n", devices_found);
for (int i = 0; i < devices_found; i++) {
printf("%d. Device 0x%02x address\n", i + 1, found_addresses[i]);
}
if (devices_found == 0) {
printf("\n");
}
app_main(void)
When creating an ESP-IDF project, the app_main function will always be called to run. This function is where you need to write your code for any ESP-IDF applications; it is the equivalent of the setup() in Arduino programming. When the ESP32 boots, the ESP-IDF framework calls app_main.
In the app_main(void) function, you start by printing some debugging information. Finally, create a FreeRTOS task that runs the i2c_scanner_task() function.
void app_main(void)
{
ESP_LOGI(TAG, "I2C scanner starting on SDA=%d, SCL=%d", I2C_SDA_PIN, I2C_SCL_PIN);
xTaskCreate(i2c_scanner_task, "i2c_scanner", 4096, NULL, 5, NULL);
}
Build and Flash Code to the ESP32 Board
To build and flash ESP-IDF code to the ESP32, you always need to follow this procedure. You need to select the flash method (UART), the COM port number, the target device (ESP32), build the code, and finally, flash it to the board. All these commands are available in the bottom menu bar of VS Code.
Make sure all your options are correct (they may already be properly configured if you used the project wizard).

However, if your setup is not correct, follow the next instructions to ensure everything is set up correctly. First, click the “Star” icon and select the flash method as UART.

While the ESP32 board is connected to your computer, click the COM Port (plug icon) and select the correct port number that refers to your ESP32.

You also need to select the target device. Click on the chip icon at the bottom bar. In my case, I have an ESP32 with the esp32s3 chip.

For this board, I also need to select the configuration: ESP32-S3 chip (via builtin USB-JTAG).

Finally, your command bar at the bottom of VS Code should have similar options selected.

Now, you can build the project by clicking the wrench icon (Build Project) as shown in the image below.

The first time you build a project, it usually takes a bit more time. Once completed, it should print a similar message in the Terminal menu and show a “Build Successfully” message.

This is the final step. You can now flash the ESP-IDF project to the ESP32 by clicking the “Flash Device” button (thunder icon).

Depending on your board, you might need to hold down the on-board BOOT button on your ESP32 to put it into flashing mode. Once the process is completed, it will pop-up a info message saying “Flash Done“.

Demonstration
If you followed all the steps, the example should be running successfully on your board. Open your Terminal window — click the “Monitor Device” tool that is illustrated with a screen icon.

Wait a couple of seconds, and the I2C address of your peripheral will be printed in the Serial Monitor. If you have more than one device connected to the same I2C bus, it will display the address of all devices.
Having the BME280 sensor attached to your ESP32:

The terminal should be printing a message saying that the scan has been completed and the devices that were found. In our case, we found “Device 0x76 address” which corresponds to the BME280 I2C address.

Wrapping Up
In this guide, you learned how to quickly find the address of I2C devices. You just need to connect your I2C peripheral to the ESP32 board and upload the I2C scanner code provided.
We hope you’ve found this guide useful. To learn more about I2C, but using Arduino IDE we recommend reading our article: ESP32 I2C Communication.
You might find it helpful to read other ESP-IDF guides:
- ESP-IDF: ESP32 Simple Web Server
- ESP-IDF: ESP32 GPIO PWM with LEDC (Control LED Brightness)
- ESP-IDF: ESP32 GPIO – Read Analog Input (ADC – Analog to Digital Converter)
- ESP-IDF: ESP32 Projects, Tutorials and Guides
Thanks for reading.




The project does not run on my Mac Mini M4.
1. The libraries were not found
2. Error: …/.espressif/tools/python/v6.0.2/venv/lib/python3.11/site-packages/pydantic_core/_pydantic_core.cpython-311-darwin.so’
(mach-o file, but is an incompatible architecture (have ‘arm64’, need
‘x86_64’))
Are you running this on ESP-IDF?