Перевод статьи ESP-IDF: ESP32 I2C Scanner – Finding the Address of I2C DevicesRui Santos & Sara Santos, Random Nerd Tutorials.

Это краткое руководство показывает, как создать на ESP-IDF I2C-сканер, который находит адреса всех I2C-устройств, подключённых к пинам SDA и SCL ESP32.

Что понадобится заранее#

Прежде чем следовать этому руководству, установите расширение ESP-IDF в VS Code. Если ещё не сделали:

Для проверки I2C-сканера мы используем датчик BME280. Что понадобится:

  • ESP32;
  • модуль датчика BME280;
  • макетная плата;
  • соединительные провода.

Примечание: можно взять любое другое I2C-устройство — просто подключите его к правильным пинам I2C своей платы.

Схема — ESP32 с BME280 по I2C#

Для проверки I2C мы используем модуль BME280. Подключите пин SDA датчика к GPIO 21, а SCL — к GPIO 22 (сверьтесь с распиновкой ESP32).

Схема подключения BME280 к ESP32 по I2C: SDA GPIO 21, SCL GPIO 22

ESP32 с BME280 по I2C: SDA → GPIO 21, SCL → GPIO 22.

Для плат ESP32-S3 пины I2C другие: SDA подключается к GPIO 8, а SCL — к GPIO 9.

Схема подключения BME280 к ESP32-S3 по I2C: SDA GPIO 8, SCL GPIO 9

ESP32-S3 с BME280 по I2C: SDA → GPIO 8, SCL → GPIO 9.

Создание проекта ESP-IDF из шаблона#

Расширение ESP-IDF позволяет легко создать проект с нуля — все нужные файлы и настройки генерируются автоматически. Чтобы создать новый проект в VS Code:

  1. Откройте расширение ESP-IDF (Espressif) и разверните меню Advanced.
  2. Нажмите New Project Wizard.
  3. Выберите Use ESP-IDF v6.X, чтобы задать версию фреймворка.
Мастер нового проекта ESP-IDF в VS Code: выбор версии

Мастер нового проекта: выбор версии ESP-IDF.

В меню выберите пример ESP-IDF Templates и нажмите Create project using template sample project.

Выбор шаблонного проекта ESP-IDF Templates

Выбор шаблонного проекта из ESP-IDF Templates.

Откроется окно, где нужно заполнить поля:

  • Project Name — имя проекта;
  • Enter Project Directory — папка для файлов проекта. Не используйте папки Google Drive / OneDrive / Dropbox: при сборке создаётся множество файлов, и в облачной папке это может быть крайне медленно;
  • ESP-IDF Target — целевой чип (в примере — esp32s3);
  • ESP-IDF Board — для esp32s3 нужно выбрать конфигурацию: ESP32-S chip (via builtin USB-JTAG);
  • Serial Port — COM-порт, к которому подключён ESP32;
  • Choose Template — синяя кнопка создания проекта из шаблона.
Форма мастера нового проекта ESP-IDF: имя, папка, чип, плата, порт, шаблон

Форма мастера: имя проекта, папка, чип, плата, порт и шаблон.

Открытие проекта в VS Code#

Через несколько секунд в VS Code появится уведомление. Нажмите Open Project, чтобы открыть только что созданный проект.

Уведомление для открытия созданного проекта ESP-IDF

Уведомление для открытия нового проекта.

Если уведомление не появилось, откройте проект вручную: File → Open Folder…, найдите папку проекта и нажмите Select Folder.

ESP-IDF создаёт много файлов и папок. Для этого руководства оставьте все файлы по умолчанию без изменений — мы будем менять только файл main.c. Чтобы открыть его: откройте проводник проекта (первая иконка на левой панели), разверните папку проекта, затем папку main и кликните по файлу main.c.

Проводник проекта VS Code: переход к файлу main.c

Открытие файла main.c в проводнике проекта.

Код — I2C-сканер на ESP-IDF#

Чтобы найти I2C-адрес конкретного датчика, дисплея или другой периферии, подключите её к пинам I2C ESP32 и запустите сканер. Скопируйте следующий код в файл main.c проекта и сохраните.

/*  
  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);
}

Как работает код#

Библиотеки. Подключаем нужные библиотеки:

  • stdio.h — стандартная библиотека C, нужна для printf, выводящей отладочную информацию в монитор порта;
  • esp_log.h — фреймворк для форматированных логов в мониторе порта;
  • driver/i2c_master.h — драйвер I2C из ESP-IDF;
  • freertos/FreeRTOS.h — базовые типы и функции FreeRTOS;
  • freertos/task.h — управление задачами.
#include <stdio.h>
#include "esp_log.h"
#include "driver/i2c_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

Пины SDA и SCL. Убедитесь, что периферия подключена к правильным пинам I2C. В примере — ESP32-S3 (SDA → GPIO 8, SCL → GPIO 9).

#define I2C_SDA_PIN    8
#define I2C_SCL_PIN    9
#define I2C_TIMEOUT_MS 50

Тег логирования. Префиксом I2C_SCANNER помечаются все отладочные сообщения.

static const char *TAG = "I2C_SCANNER";

Функция i2c_scanner_task(). Это задача FreeRTOS, в которой собрано всё нужное для работы сканера. Структура ниже создаёт конфигурацию мастера шины I2C с правильными пинами, а i2c_new_master_bus инициализирует I2C-железо ESP32 по этой конфигурации.

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));

Запуск сканера. Сам сканер работает в бесконечном цикле, повторяясь каждые 10 секунд. Код перебирает все 128 возможных 7-битных адресов (от 0x00 до 0x7F). Ключевая часть — функция i2c_master_probe(), которая отправляет по I2C сигнал подтверждения (ACK), чтобы проверить, откликнется ли подключённое устройство. Устройства, ответившие ESP_OK, считаются найденными, и их адрес выводится в терминал.

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");
    }
}

По завершении сканирования выводится, сколько устройств найдено, и нумерованный список их I2C-адресов.

app_main(void). В проекте ESP-IDF функция app_main всегда вызывается при запуске — это аналог setup() в Arduino. Здесь мы выводим отладочную информацию и создаём задачу FreeRTOS, выполняющую i2c_scanner_task().

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);
}

Сборка и прошивка кода на ESP32#

Чтобы собрать и прошить код ESP-IDF, нужно: выбрать метод прошивки (UART), номер COM-порта, целевой чип (ESP32), собрать код и прошить его на плату. Все эти команды доступны в нижней панели VS Code (если вы создавали проект мастером, они, возможно, уже настроены верно).

Если настройки неверны: сначала кликните по иконке «звезды» и выберите метод прошивки UART. Затем при подключённой плате кликните по иконке COM-порта (розетка) и выберите правильный порт. Также выберите целевой чип, кликнув по иконке чипа внизу (в примере — esp32s3), и для этой платы — конфигурацию ESP32-S3 chip (via builtin USB-JTAG).

Теперь соберите проект, кликнув по иконке гаечного ключа (Build Project). Первая сборка обычно занимает чуть больше времени; по завершении в терминале появится сообщение Build Successfully.

Терминал VS Code с сообщением об успешной сборке проекта ESP-IDF

Успешная сборка проекта.

Последний шаг — прошить проект на ESP32, кликнув Flash Device (иконка молнии). Возможно, потребуется зажать кнопку BOOT на плате, чтобы перевести её в режим прошивки. По завершении появится сообщение Flash Done.

Сообщение Flash Done после прошивки ESP32 в VS Code

Сообщение об успешной прошивке.

Демонстрация#

Если вы всё сделали правильно, пример уже работает на плате. Откройте окно терминала, кликнув по инструменту Monitor Device (иконка экрана).

Подключив датчик BME280 к ESP32:

ESP32 с подключённым по I2C датчиком BME280

ESP32 с BME280, подключённым по I2C.

Через пару секунд в мониторе порта появится I2C-адрес периферии. Если на одной шине несколько устройств — покажутся адреса всех. В нашем случае найдено Device 0x76 address, что соответствует I2C-адресу BME280.

Вывод I2C-сканера в мониторе порта: найден адрес 0x76 (BME280)

Вывод сканера: найдено устройство по адресу 0x76 (BME280).

Итог#

В этом руководстве вы научились быстро находить адреса I2C-устройств. Достаточно подключить I2C-периферию к ESP32 и загрузить приведённый код сканера.

Спасибо за чтение.