Introduction#
As we saw earlier, ESP-IDF contains several libraries, from FreeRTOS — the operating system which manages all tasks — to the peripheral drivers and protocol libraries. Including the libraries for every possible protocol, algorithm, or driver inside ESP-IDF is not possible: It’s size would increase dramatically. If you need a specific protocol, you can probably find it’s C implementation somewhere on Github. In this case, the challenge will be to port it to ESP-IDF, taking care of finding all dependencies and informing the build system about which files should be compiled and linked.
To solve these problem, Espressif developed a component system similar to a package system in GNU/Linux distributions. The components take care of the dependencies and the build system and you can simply include the header file and you’re ready to go!
Like in the case of Linux packages, there is also a component manager and a component registry, where you can find all the official packages by Espressif. Once, components are included, the idf.py
tool will download the component and set the stage for its use.
For additional information, we recommend that you watch the talk DevCon23 - Developing, Publishing, and Maintaining Components for ESP-IDF.
We will explore the differences in the use of the integrated libraries and the ones provided by the component registry. We will also see how to create a component, in order to make reusable code.
We will explore how to:
- Include and use the
gpio
and thei2c
libraries (included) - See how and use the
button
component (registry) - Create a new component
During the assignments, the goal will be to control the LED and the I2C sensor (SHTC3) on the board (see Fig. 1).

Fig.1 - GPIO connected to the LED
Included Libraries#
Let’s take a look at how to use the included libraries. This usually involves three main steps:
- Let the build system know about the library
(include the header file and update
CMakeLists.txt
) - Configure the library settings
- Use the library by calling its functions
GPIO#
A GPIO (General-Purpose Input/Output) peripheral is a digital interface on a microcontroller or processor that allows it to read input signals (like button presses) or control output devices (like LEDs) through programmable pins. These pins can be configured individually as either input or output and are commonly used for basic device interfacing and control.
On our board, we have an LED connected to the GPIO10 (see Fig. 1) and we will use this pin for the example.
Including the library#
To include the gpio
library, we first need to include the header file and tell the build system where to find it.
We need first to include
#include "driver/gpio.h"
and then add to CMakeList.txt
REQUIRES esp_driver_gpio
Note that the header file and the required path are different: When including a library, make sure you check the programming guide first. You need to:
- In the upper left corner, choose the core (ESP32-C3)
- Find the page for the peripheral (GPIO)
- Find the section API Reference
Configuration#
Peripherals have many settings (input/output, frequency, etc). You need to confiure them before using the peripherals.
In case of GPIO, a basic configuration is
//zero-initialize the config structure.
gpio_config_t io_conf = {};
//disable interrupt
io_conf.intr_type = GPIO_INTR_DISABLE;
//set as output mode
io_conf.mode = GPIO_MODE_OUTPUT;
//bit mask of the pins that you want to set,e.g.GPIO18/19
io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;
//disable pull-down mode
io_conf.pull_down_en = 0;
//disable pull-up mode
io_conf.pull_up_en = 0;
//configure GPIO with the given settings
gpio_config(&io_conf);
In this workshop, we will use GPIO for output. Due to this, we won’t talk about:
- Interrupts (trigger a function when the input changes)
- Pull-up and pull-down (set a default input value)
The only field that needs some explanation is the pin_bit_mask
.
The configuration refers to the whole GPIO peripheral. In order to apply the configuration only to certain pins (via gpio_config
), we need to specify the pins via a bit mask.
The pin_bit_mask
is set equal to GPIO_OUTPUT_PIN_SEL which is
#define GPIO_OUTPUT_LED 10
#define GPIO_OUTPUT_PIN_SEL (1ULL<<GPIO_OUTPUT_LED) // i.e. 0000000000000000000000000000010000000000
If you want to apply the configuration to more than one GPIO, you need to OR
them.
For example:
#define GPIO_OUTPUT_LED 10
#define GPIO_OUTPUT_EXAMPLE 12
#define GPIO_OUTPUT_PIN_SEL ((1ULL<<GPIO_OUTPUT_LED) | (GPIO_OUTPUT_EXAMPLE))// i.e. 0000000000000000000000000001010000000000
Usage#
Once the peripheral is configured, we can use the function gpio_set_level
to set the GPIO output to either 0
or 1
.
The header file:
gpio_set_level(GPIO_OUTPUT_LED, 1); // turns led on
gpio_set_level(GPIO_OUTPUT_LED, 0); // turns led off
I2C#
I2C (Inter-Integrated Circuit) is a communication protocol that uses only two wires—SDA (data line) and SCL (clock line)—to transmit data between devices. Usually, it is used to connect a microcontroller to an external sensor or actuator. It allows multiple peripherals to communicate with a microcontroller using unique addresses, enabling efficient and scalable device interconnection.
Including the library#
Consulting the corresponding programming guide section we get the header file
#include "driver/i2c_master.h"
and value for the CMakeList.txt
REQUIRES esp_driver_i2c
Configuration#
A configuration has the following form:
i2c_master_bus_config_t bus_config = {
.i2c_port = I2C_NUM_0,
.sda_io_num = I2C_MASTER_SDA_IO,
.scl_io_num = I2C_MASTER_SCL_IO,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
i2c_new_master_bus(&bus_config, bus_handle);
i2c_device_config_t dev_config = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = SHTC3_SENSOR_ADDR,
.scl_speed_hz = 400000,
};
i2c_master_bus_add_device(*bus_handle, &dev_config, dev_handle);
The values for our board are (see Fig. 1)
#define I2C_MASTER_SDA_IO 7
#define I2C_MASTER_SCL_IO 8
#define SHTC3_SENSOR_ADDR 0x70
The other macros are defined internally.
Component registry#
Use a component from the registry - button#
For our last external library (button), we will use the component manager and registry.
- Go to the component registry
- Search for the button component (espressif/button)
- Copy the instruction on the left (see Fig.2) -
idf.py add-dependency "espressif/button^4.1.3"
- In VSCode:
> ESP-IDF: Open ESP-IDF Terminal
and paste the instruction

Fig.2 - espressif/button component
You should get a message
Executing action: add-dependency
NOTICE: Successfully added dependency "espressif/button": "^4.1.3" to component "main"
NOTICE: If you want to make additional changes to the manifest file at path <user_path>/blink/main/idf_component.yml manually, please refer to the documentation: https://docs.espressif.com/projects/idf-component-manager/en/latest/reference/manifest_file.html
A new file idf_component.yml
has been created in your project with the following content:
dependencies:
espressif/led_strip: ^2.4.1
espressif/button: ^4.1.3
You can add dependencies directly in this file, but it’s recommended to use idf.py add-dependency
utility.
To use the component, you have to include the appropriate header file and call the functions given in the component documentation and folder.
Create a component#
For detailed instructions on how to create a component using the CLI, you can refer to the article How to create an ESP-IDF component on the Espressif Developer Portal.
In VSCode, you can follow a similar flow:
- Create a new project
- Create a new component by calling
> ESP-IDF: Create New ESP-IDF Component
- Give the component a name (e.g.
led_toggle
)
The project will now contain a components folder and all the required files
.
└── project_folder/
├── components/
│ └── led_toggle/
│ ├── include/
│ │ └── led_toggle.h
│ ├── CMakeList.txt
│ └── led_toggle.c
├── main
└── build
Each time you create or download a component, you need to perform a project full cleal by calling:
> ESP-IDF: Full Clean Project
You can then include your component in the main file as led_toggle.h
.
Conclusion#
In this short lecture, we explored two main ways to include external libraries: directly through the CMakeLists.txt file and via the component registry. We covered how to include and use libraries with both methods and explained how to create a custom component from scratch using VSCode. Now it’s time to put these concepts into practice in the upcoming assignments.
Next step#
Next assignment → Assignment 3.1