This tutorial is a getting started guide to the key programming concepts you need to understand when programming the ESP32 with ESP-IDF.
At first, programming the ESP32 with ESP-IDF can seem overwhelming, especially if you’re coming from the Arduino Core/IDE. However, once you understand some of the key concepts, you’ll see that ESP-IDF isn’t as complicated as it may seem. In this guide, we’ll cover configuration structures, pointers, handles, error codes and logging, and event-driven programming.

Recommended reading: Programming ESP32 with ESP-IDF using VS Code – Getting Started Guide.
Table of Contents
If you’re transitioning from Arduino Core, these are the concepts that usually require the biggest adjustment and that will be covered in this guide:
- Structures (struct) and typedefs (_t)
- Handles
- Pointers (*) and the address operator (&)
- FreeRTOS tasks and synchronization
- Event-driven programming with callbacks
- Error Codes and Logging
Structures
Structures are used to group related information. In ESP-IDF, you’ll rarely see a function accepting a long list of parameters as you often do in the Arduino Core.
Instead, you pass a structure containing all the related configuration settings. There are many configuration structures in ESP-IDF that are used to configure peripherals such as GPIO, ADC, SPI, Wi-Fi, etc…
One example is the gpio_config_t structure, which stores all the settings needed to configure one or more GPIO pins. Instead of passing each setting separately to a function, you first fill in the structure and then pass it to the gpio_config() function.
You can learn more in this tutorial: ESP-IDF: ESP32 GPIO – Control Digital Outputs.
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << GPIO_NUM_2), // Select GPIO 2
.mode = GPIO_MODE_OUTPUT, // Set as output
.pull_up_en = GPIO_PULLUP_DISABLE, // Disable pull-up
.pull_down_en = GPIO_PULLDOWN_DISABLE, // Disable pull-down
.intr_type = GPIO_INTR_DISABLE // Disable interrupts
};
gpio_config(&io_conf);
In this example, the io_conf structure stores all the GPIO configuration options, including the pin number, pin mode, pull-up and pull-down resistors, and interrupt type. The gpio_config() function then reads these values and configures the GPIO accordingly.
Custom Types (typedef and _t)
In ESP-IDF, you’ll see many data types ending in _t. These are custom types created using the typedef keyword. A typedef creates a new name for an existing data type. For example, the gpio_config_t type stores the configuration settings for the GPIOs.
For example, instead of writing:
struct gpio_config config;
You can simply write:
gpio_config_t config;
While working with ESP-IDF, you’ll notice that most peripherals have one or more custom types that store configuration settings, like the wifi_config_t for the Wi-Fi settings.
Handles
Another thing you’ll see often in ESP-IDF code is handles. A handle is a variable that identifies a peripheral or resource created by ESP-IDF. Then, when you want to use that peripheral, you use its handle to refer to it.
For example, when creating an ADC unit:
adc_oneshot_unit_handle_t adc_handle;
adc_oneshot_read(adc_handle, channel, &value);
The adc_handle variable identifies the ADC unit. When calling adc_oneshot_read(), the handle tells ESP-IDF which ADC unit to read from.
Handles are also used for I2C bus, or SPI devices, and more…
Pointers and the Address Operator (* and &)
This concept usually creates a lot of confusion. Pointers are not used often in Arduino programming, so you may not be familiar with them. But you’ll see them used a lot with ESP-IDF.
After you understand this concept, it will make it much easier to understand many ESP-IDF programs and also other C programs.
Every variable is stored somewhere in the computer’s memory (in this case, in the ESP32 memory) and has a unique memory address. A pointer is a variable that stores the memory address of another variable.
For example:
int value = 25;
value stores the number 25 somewhere in ESP32 memory. Every location in memory has a unique address. A pointer stores that address instead of the number itself. In the following line, ptr is a variable that stores the address of the value variable:
int *ptr = &value;
The * in int *ptr means that ptr is a pointer to an integer.
Another example…
The address operator & is used to obtain the memory address of a variable. For example, still using the ADC as an example:
int value;
adc_oneshot_read(adc_handle, channel, &value);
In this example, &value means the memory address of value. Instead of returning the ADC reading directly, the adc_oneshot_read() function writes the result into the value variable using its memory address.
Logging
When programming the ESP32 using Arduino IDE, you often use the Serial.print() function to check what’s going on in the code for logging and also debugging.
In ESP-IDF, there is a logging system using:
- ESP_LOGI() – Information message
- ESP_LOGW() – Warning message
- ESP_LOGE() – Error message
The I, W, and E indicate the log level.
These functions accept two arguments: the tag and the message to be displayed. For example:
ESP_LOGI("MAIN", "Web Server Ready!")
These are like Serial.print(). They print text to the Serial Monitor but add useful information like the debug level, timestamp, and tag.
Error Handling
Many ESP-IDF functions return a value of type esp_err_t. This value indicates if the function was successfully executed, or, if it failed, what error occurred.
For example, when initializing a peripheral such as I2C, the function returns ESP_OK if the initialization was successful, or returns an error otherwise. For example:
esp_err_t err = i2c_new_master_bus(&bus_config, &bus_handle);
if (err == ESP_OK) {
   printf("I2C initialized successfully.\n");
} else {
   printf("Failed to initialize I2C.\n");
}
Alternatively, you can use the ESP_ERROR_CHECK() macro to check the returned value. If it is ESP_OK, the program continues normally. If an error is returned, the macro prints information about the error and stops the program.
This makes it much easier to debug and find problems in your code, and it’s more practical than writing lots of Serial.print() lines throughout your code.
There is a long list of possible returned values for ESP_ERRPR_CHECK(). These are the most common ones:
- ESP_OK: operation completed successfully
- ESP_FAIL: generic failure
- ESP_ERR_INVALID_ARG: invalid argument
- ESP_ERR_NO_MEM: out of memory
- ESP_ERR_TIMEOUT: operation timed out
You can find a reference for all possible error codes in the following link: Error Codes Reference.
Event-Driven Programming and Callbacks
When programming with the Arduino Core, your program runs continuously inside the loop() function and often polls for events. For example, it repeatedly checks whether a pushbutton has been pressed, if new data has been received, or if a sensor value has changed.
In ESP-IDF, most applications use event-driven programming. Instead of continuously checking whether an event has occurred, you register a callback function that is associated with a specific event. When that event occurs, ESP-IDF automatically calls the callback function.
FreeRTOS
Another difference is that ESP-IDF is built on FreeRTOS, a real-time operating system (RTOS) (however, you can still use FreeRTOS in Arduino Core/IDE by including the corresponding library/module).
Instead of placing all your code inside the loop(), with FreeRTOS your application is divided into multiple tasks. Each task is responsible for doing a specific action like reading sensors, connecting to Wi-Fi, displaying data on a display, etc…
If you’re new to FreeRTOS, we recommend checking out our FreeRTOS tutorials. Although the examples use the Arduino Core, the core concepts and the way FreeRTOS works are the same in ESP-IDF.
Wrapping Up
In this tutorial, we’ve compiled some key concepts related to programming the ESP32 using ESP-IDF. You can use it as a reference while learning ESP-IDF programming. We hope it makes everything easier to understand.
What other key concepts would you like to see covered in this guide? Let us know in the comments section below.
If you’d like to learn more about programming the ESP32 using ESP-IDF, check out our eBook:
We hope you found this tutorial useful.
Thanks for reading.



