If the Arduino core hides the ESP32, ESP-IDF shows you everything. It is Espressif’s official SDK and the base everything else is built on. It has a steeper curve, yes, but it also gives you the control a product needs.
Let’s cover what matters without drowning in docs.
Project layout
my-project/
├── CMakeLists.txt # project (almost always the same)
├── sdkconfig # generated configuration
└── main/
├── CMakeLists.txt # registers main
└── main.c
The root CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(my-project)
And the one in main/:
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")The commands you will use
idf.py set-target esp32s3 # pick the chip
idf.py menuconfig # configure (Kconfig)
idf.py build # compile
idf.py flash monitor # flash and open the monitor
menuconfig is one of the best things in ESP-IDF: you change options (Wi-Fi,
logs, partitions, PSRAM) without touching code, and they are stored in
sdkconfig.
Hello world with logs
#include "esp_log.h"
static const char *TAG = "app";
void app_main(void) {
ESP_LOGI(TAG, "Starting…");
ESP_LOGW(TAG, "This is a warning");
ESP_LOGE(TAG, "And this an error");
}
No printf: use log levels. You can raise or lower the level per component
from menuconfig, and turn almost all of them off in production.
Tasks: the heart of ESP-IDF
Here is the big difference from Arduino. Instead of a loop(), you create
FreeRTOS tasks with their own priority and stack size:
void sensor_task(void *arg) {
while (1) {
read_sensor();
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main(void) {
xTaskCreate(sensor_task, "sensor", 4096, NULL, 5, NULL);
}
Real benefits:
- True parallelism: one task measures, another publishes over MQTT.
vTaskDelaydoes not block the rest of the system.- You control stack and priority, which is what prevents weird reboots.
Partitions and OTA
Flash is not a single block: it is partitioned (a partitions.csv table). By
default there are two application slots (app0 and app1) so you can do OTA
(Wi-Fi updates) with rollback if something fails.
# Name, Type, SubType, Offset, Size
nvs, data, nvs, 0x9000, 0x4000
factory, app, factory, 0x10000, 1M
An OTA-style partition table:
# Name, Type, SubType, Size
nvs, data, nvs, 0x4000
otadata, data, ota, 0x2000
phy_init, data, phy, 0x1000
factory, app, factory, 1M
ota_0, app, ota_0, 1M
ota_1, app, ota_1, 1MWiring: Arduino or ESP-IDF?
| Arduino core | ESP-IDF | |
|---|---|---|
| Curve | Low | High |
| Control | Medium | Total |
| Tasks | 1 loop | N tasks |
| Memory | Automatic | You decide |
| OTA | Easy | Complete (rollback) |
| Logs | Serial | Per-component levels |
When it is worth it
- Yes: products, battery, fine timing, serious OTA, many peripherals.
- No: if you just want to read a sensor and publish, Arduino gets you there in an afternoon and is more than enough.
Tip: you can mix them. The Arduino core is ESP-IDF underneath, so from a sketch you can call ESP-IDF APIs when you need them.
Summary
- ESP-IDF is the official SDK: CMake +
idf.py+menuconfig. - Use FreeRTOS tasks, not a
loop(). - Log levels with
ESP_LOGxinstead ofprintf. - Flash is partitioned; OTA with rollback comes almost for free.
- Current version: 6.1.
Next step: MicroPython and Lua for the opposite extreme: zero compiling. Or go back to the series hub.