Перевод статьи ESP-IDF: Read and Set ESP32 MAC Address — Rui Santos & Sara Santos, Random Nerd Tutorials.

У ESP32 не один MAC-адрес, а сразу несколько: заводской базовый, записанный в одноразовую память eFuse, и вычисляемые из него адреса Wi-Fi-станции, точки доступа и Bluetooth. В этом руководстве мы выведем все четыре в монитор последовательного порта, а заодно научимся подменять базовый MAC-адрес собственным.

Зачем это нужно на практике? Чтобы настроить фильтрацию по MAC на роутере, найти плату в таблице ARP, различать несколько одинаковых ESP32 в сети или отлаживать сетевые проблемы. Работать будем в VS Code с официальным расширением ESP-IDF.

Что такое MAC-адрес#

MAC-адрес (Media Access Control) — уникальный 48-битный (6 байт) идентификатор сетевого интерфейса. Записывается он шестью парами шестнадцатеричных цифр, например 24:0A:C4:11:22:33. Адрес закрепляется за интерфейсом производителем и служит для адресации на канальном уровне — в Wi-Fi и Ethernet.

У чипа ESP32 MAC-адресов несколько: свой есть у Wi-Fi-станции (Station), у точки доступа (SoftAP) и у Bluetooth. Заводской базовый адрес (base MAC) хранится в eFuse — одноразовой памяти конфигурации чипа, — а остальные адреса ESP-IDF вычисляет из него с фиксированными смещениями. Поэтому на практике они идут почти подряд и различаются последними байтами.

Что нужно знать заранее#

Понадобится настроенная среда ESP-IDF. Установить её можно тремя способами:

  • расширением ESP-IDF для VS Code — рекомендуемый вариант, его же мы используем ниже;
  • установщиком ESP-IDF для Windows;
  • плагином Espressif IoT Development Framework для Eclipse.

Подробно установку и настройку мы разбирали в руководстве «Программирование ESP32 на ESP-IDF в VS Code». А если вы пришли в ESP-IDF из Arduino, полезно сначала прочитать про ключевые концепции фреймворка — они сильно экономят время при переходе.

Создаём проект из шаблона#

Расширение ESP-IDF умеет генерировать готовый каркас проекта. Нажмите F1, наберите ESP-IDF: Open ESP-IDF Extension Settings и убедитесь, что выбрана нужная версия фреймворка — у нас это v6.0.

Настройки расширения ESP-IDF в VS Code: выбор версии фреймворка

Проверяем версию ESP-IDF в настройках расширения.

Затем снова нажмите F1 и выполните команду ESP-IDF: New Project Wizard.

Мастер создания нового проекта ESP-IDF

Мастер New Project Wizard.

В окне мастера заполните поля:

  • Project name — имя проекта;
  • Project directory — каталог, в котором он будет создан;
  • Board — ваша плата (мы взяли ESP32 DevKitS);
  • Template — стартовый шаблон кода; выберите sample project.
Поля мастера: имя проекта, каталог, плата и шаблон

Имя проекта, каталог, плата и шаблон sample project.

Нажмите Create project using template E — и расширение создаст каркас проекта с минимальным примером.

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

Нажмите F1, наберите ESP-IDF: Open VS Code Folder и нажмите Enter. То же самое можно сделать через меню File > Open Folder.

Команда открытия папки проекта ESP-IDF в VS Code

Открываем каталог со свежесозданным проектом.

Пункт меню File > Open Folder в VS Code

Или через меню File > Open Folder.

Укажите каталог проекта и нажмите Select Folder (или Open):

Выбор каталога проекта в диалоге VS Code

Выбираем папку проекта.

Если VS Code спросит, доверяете ли вы авторам файлов, нажмите Yes, I trust the authors.

Код приложения лежит в файле main/main.c — откройте его в проводнике слева:

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

Нас интересует main/main.c.

Код: чтение MAC-адреса ESP32#

Сотрите содержимое main.c и вставьте следующее:

/*  
  Rui Santos & Sara Santos - Random Nerd Tutorials
  https://RandomNerdTutorials.com/esp-idf-esp32-mac-address/
*/
#include <stdio.h>
#include "esp_mac.h"
#include "esp_log.h"

static const char *TAG = "MAC";

void app_main(void)
{
    uint8_t mac[6];
    esp_err_t ret;

    // Base MAC Address (factory default)
    ret = esp_read_mac(mac, ESP_MAC_EFUSE_FACTORY);
    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "Base MAC Address (Factory): %02X:%02X:%02X:%02X:%02X:%02X",
                 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    }

    // Wi-Fi Station MAC Address
    ret = esp_read_mac(mac, ESP_MAC_WIFI_STA);
    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "Wi-Fi STA MAC Address: %02X:%02X:%02X:%02X:%02X:%02X",
                 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    }

    // Wi-Fi SoftAP MAC Address
    ret = esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP);
    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "Wi-Fi SoftAP MAC Address: %02X:%02X:%02X:%02X:%02X:%02X",
                 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    }

    // Bluetooth MAC Address
    ret = esp_read_mac(mac, ESP_MAC_BT);
    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "Bluetooth MAC Address: %02X:%02X:%02X:%02X:%02X:%02X",
                 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    }
}

Исходник на GitHub

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

Подключаемые заголовки#

stdio.h нужна для стандартного вывода, esp_mac.h — для функций работы с MAC-адресами, esp_log.h — для журналирования в монитор порта.

#include <stdio.h>
#include "esp_mac.h"
#include "esp_log.h"

Тег для логирования#

Тег TAG будет предварять каждую строку наших сообщений в мониторе — так их легко отличать от остального вывода.

static const char *TAG = "MAC";

app_main()#

В ESP-IDF точка входа — не setup()/loop(), а функция app_main().

void app_main(void)

Внутри неё мы заводим буфер mac из шести байт (как раз размер MAC-адреса) и переменную ret для кодов возврата.

    uint8_t mac[6];
    esp_err_t ret;

Базовый (заводской) MAC-адрес#

Первым делом читаем заводской адрес из eFuse — тип ESP_MAC_EFUSE_FACTORY.

    // Base MAC Address (factory default)
    ret = esp_read_mac(mac, ESP_MAC_EFUSE_FACTORY);
    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "Base MAC Address (Factory): %02X:%02X:%02X:%02X:%02X:%02X",
                 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    }

Функция esp_read_mac() принимает указатель на буфер и тип интерфейса, а кладёт в буфер шесть байт адреса. Выводим их макросом ESP_LOGI (уровень Info), форматируя каждый байт как пару шестнадцатеричных цифр — %02X. Именно из этого адреса ESP32 производит остальные.

MAC-адрес Wi-Fi-станции#

Тип ESP_MAC_WIFI_STA — адрес, под которым плата представляется роутеру в режиме клиента Wi-Fi.

    // Wi-Fi Station MAC Address
    ret = esp_read_mac(mac, ESP_MAC_WIFI_STA);
    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "Wi-Fi STA MAC Address: %02X:%02X:%02X:%02X:%02X:%02X",
                 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    }

MAC-адрес точки доступа (SoftAP)#

Тип ESP_MAC_WIFI_SOFTAP — адрес, который ESP32 использует, когда сам раздаёт Wi-Fi в режиме точки доступа.

    // Wi-Fi SoftAP MAC Address
    ret = esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP);
    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "Wi-Fi SoftAP MAC Address: %02X:%02X:%02X:%02X:%02X:%02X",
                 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    }

MAC-адрес Bluetooth#

Наконец, ESP_MAC_BT — адрес Bluetooth-интерфейса.

    // Bluetooth MAC Address
    ret = esp_read_mac(mac, ESP_MAC_BT);
    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "Bluetooth MAC Address: %02X:%02X:%02X:%02X:%02X:%02X",
                 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    }

Сборка и прошивка#

Перед сборкой проверьте настройки в строке состояния внизу окна VS Code: метод прошивки, COM-порт и целевое устройство.

Строка состояния VS Code с настройками прошивки ESP32

Метод прошивки, порт и целевая плата — в строке состояния.

Кликните по полю метода прошивки и выберите UART.

Выбор метода прошивки UART в VS Code

Прошиваем через UART.

Затем укажите COM-порт, к которому подключена плата.

Выбор COM-порта платы ESP32

Выбираем правильный COM-порт.

И целевое устройство: для классического ESP32 — пункт ESP32, для плат на ESP32-S3 — соответствующая позиция.

Выбор целевого устройства ESP32 или ESP32-S3

Указываем целевой чип.

Плата на ESP32-S3 с прошивкой через встроенный USB-JTAG выбирается отдельным пунктом.

Выбор ESP32-S3 через встроенный USB-JTAG

Вариант для S3 со встроенным USB-JTAG.

Теперь нажмите значок сборки (Build).

Значок сборки проекта в строке состояния VS Code

Собираем проект.

После успешной сборки появится сообщение о том, что образ для платы создан.

Сообщение об успешной сборке проекта ESP-IDF

Сборка прошла успешно.

Подключите ESP32 по USB и нажмите значок прошивки (Flash) — расширение запишет бинарник в плату.

Значок прошивки ESP32 в VS Code

Прошиваем плату.

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

Прошивка завершена.

Наконец, откройте монитор последовательного порта — значок в той же строке состояния. Именно в него наш код выводит MAC-адреса.

Значок монитора последовательного порта в VS Code

Открываем монитор порта.

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

В мониторе появятся четыре строки: заводской адрес из eFuse и три адреса, которые ESP-IDF производит от базового.

Монитор порта: заводской MAC и адреса Wi-Fi STA, SoftAP и Bluetooth

Вывод программы: базовый MAC и три производных адреса.

Обратите внимание: SoftAP- и Bluetooth-адреса отличаются от адреса станции на фиксированные смещения — потому они и идут почти подряд.

Код: подмена MAC-адреса своим#

Второй пример — установим собственный базовый MAC-адрес. Сначала предупреждение из оригинала: два устройства с одинаковым MAC-адресом в одной сети — это конфликт, поэтому не задавайте адрес, который может оказаться занят.

Впишите в переменную custom_mac нужный адрес и замените содержимое main.c на следующий код:

/*  
  Rui Santos & Sara Santos - Random Nerd Tutorials
  https://RandomNerdTutorials.com/esp-idf-esp32-mac-address/
*/
#include <stdio.h>
#include "esp_mac.h"
#include "esp_log.h"
#include "nvs_flash.h"

static const char *TAG = "CUSTOM_MAC";

// REPLACE THE NEXT VARIABLE WITH YOUR CUSTOM MAC ADDRESS
uint8_t custom_mac[6] = {0x32, 0xA1, 0xB2, 0xC3, 0xD4, 0xE5}; 

void app_main(void)
{  
    // Set custom MAC Address
    esp_err_t err = esp_base_mac_addr_set(custom_mac);
    if (err == ESP_OK) {
        ESP_LOGI(TAG, "Custom MAC address set successfully!");
    } else {
        ESP_LOGE(TAG, "Failed to set custom MAC address: %s", esp_err_to_name(err));
    }

    uint8_t read_mac[6];
    // Read new custom Wi-Fi STA MAC Address
    err = esp_read_mac(read_mac, ESP_MAC_WIFI_STA);
    if (err == ESP_OK) {
        ESP_LOGI(TAG, "[NEW] Wi-Fi STA MAC Address: %02X:%02X:%02X:%02X:%02X:%02X",
                 read_mac[0], read_mac[1], read_mac[2],
                 read_mac[3], read_mac[4], read_mac[5]);
    }
}

Исходник на GitHub

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

Собственный адрес#

Массив из шести байт — это и есть новый MAC-адрес, записанный в привычном порядке: слева старший байт.

// REPLACE THE NEXT VARIABLE WITH YOUR CUSTOM MAC ADDRESS
uint8_t custom_mac[6] = {0x32, 0xA1, 0xB2, 0xC3, 0xD4, 0xE5}; 

Установка базового адреса#

Функция esp_base_mac_addr_set() задаёт новый базовый MAC-адрес. Все производные от него адреса — Wi-Fi-станции, SoftAP, Bluetooth — будут вычисляться уже от нового значения.

    // Set custom MAC Address
    esp_err_t err = esp_base_mac_addr_set(custom_mac);
    if (err == ESP_OK) {
        ESP_LOGI(TAG, "Custom MAC address set successfully!");
    } else {
        ESP_LOGE(TAG, "Failed to set custom MAC address: %s", esp_err_to_name(err));
    }

Вызвать её нужно как можно раньше — до инициализации Wi-Fi и Bluetooth, поэтому в примере это первая же инструкция в app_main(); иначе функция вернёт ошибку.

Важный момент: новый адрес живёт только в оперативной памяти и на время работы прошивки. Заводской MAC в eFuse не перезаписывается — после сброса питания плата вернётся к родному адресу.

Проверка результата#

Осталось прочитать адрес Wi-Fi-станции ещё раз и убедиться, что он сменился.

    uint8_t read_mac[6];
    // Read new custom Wi-Fi STA MAC Address
    err = esp_read_mac(read_mac, ESP_MAC_WIFI_STA);
    if (err == ESP_OK) {
        ESP_LOGI(TAG, "[NEW] Wi-Fi STA MAC Address: %02X:%02X:%02X:%02X:%02X:%02X",
                 read_mac[0], read_mac[1], read_mac[2],
                 read_mac[3], read_mac[4], read_mac[5]);
    }

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

Прошейте плату собранным проектом и откройте монитор порта.

Монитор порта: новая Wi-Fi STA MAC-строка ESP32 после установки собственного адреса

Станция теперь представляется заданным нами адресом.

Wi-Fi-станция показывает адрес 32:A1:B2:C3:D4:E5 — ровно тот, что мы записали в массив custom_mac.

Итоги#

В этом руководстве вы научились читать все MAC-адреса ESP32 в ESP-IDF: заводской базовый из eFuse (ESP_MAC_EFUSE_FACTORY), а также адреса Wi-Fi-станции, точки доступа и Bluetooth через esp_read_mac(). Разобрали и обратную задачу — подмену базового адреса функцией esp_base_mac_addr_set(), не забывая, что изменение живёт только до перезагрузки платы.

Что почитать дальше: