Skip to main content

Working with ESP-IDF

This chapter includes the following sections. Please read as needed:

ESP-IDF Getting Started​

New to ESP32 ESP-IDF development and looking to get started quickly? We have prepared a general Getting Started Tutorial for you.

Please Note: This tutorial uses the ESP32-S3-Zero as a teaching example, and all hardware code is based on its pinout. Before you start, it is recommended that you check the pinout of your development board to ensure the pin configuration is correct.

Set Up the Development Environment​

info

This document corresponds to the examples/esp-idf/ directory of the current example package. The stable versions configured for the project are ESP-IDF v5.5.5 / v6.1, and the component requirement is >=5.5.5,<6.3. The initial setup steps below give the tool dependencies for these two versions respectively. Please choose one environment to use. The repository also has a fixed 6.2 development-version compatibility channel, which is not used as the default environment for this starter tutorial.

note

The following guide uses Windows as an example, demonstrating development using VS Code + the ESP-IDF extension. macOS and Linux users should refer to the official documentation.

Version Selection

The screenshots in this section use ESP-IDF V5.5.2 as an example. When installing, please select the ESP-IDF version that matches your board's example.

Install the ESP-IDF Development Environment​

  1. Download the installation manager from the ESP-IDF Installation Manager page. This is Espressif's latest cross-platform installer. The following steps demonstrate how to use its offline installation feature.

    Click the Offline Installer tab on the page, then select Windows as the operating system and the ESP-IDF version you need (the version shown in the screenshot is for reference only — choose the version that fits your actual needs).

    Download EIM and offline package

    After confirming your selection, click the download button. The browser will automatically download two files: the ESP-IDF Offline Package (.zst) and the ESP-IDF Installer (.exe).

    Download EIM and offline package 2

    Please wait for both files to finish downloading.

  2. Once the download is complete, double-click to run the ESP-IDF Installer (eim-gui-windows-x64.exe).

    The installer will automatically detect if the offline package exists in the same directory. Click Install from archive.

    Auto-detect offline package

    Next, select the installation path. We recommend using the default path. If you need to customize it, ensure the path does not contain Chinese characters or spaces. Click Start installation to proceed.

    Select installation path
  3. When you see the following screen, the ESP-IDF installation is successful.

    Installation successful
  4. We recommend installing the drivers as well. Click Finish installation, then select Install driver.

    Install drivers via ESP-IDF Installation Manager

Install Visual Studio Code and the ESP-IDF Extension​

  1. Download and install Visual Studio Code.

  2. During installation, it is recommended to check Add "Open with Code" action to Windows Explorer file context menu to facilitate opening project folders quickly.

  3. In VS Code, click the Extensions icon Extensions Icon in the Activity Bar on the side (or use the shortcut Ctrl + Shift + X) to open the Extensions view.

  4. Enter ESP-IDF in the search box, locate the ESP-IDF extension, and click Install.

    Search and install ESP-IDF extension in VS Code

  5. For ESP-IDF extension versions ≥ 2.0, the extension will automatically detect and recognize the ESP-IDF environment installed in the previous steps, requiring no manual configuration.

1. Prepare the Complete Example Package​

Obtain the example package from Resources and Documents and extract it completely, keeping the relative positions of directories such as examples/ and config/. Each example has its own project entry point, but depends on repository-level shared configuration, so you cannot copy just one example folder.

2. Install the Board Manager Extension​

First activate the selected ESP-IDF environment, or use VS Code's "ESP-IDF: Open ESP-IDF Terminal" to open a terminal for that environment. bmgr is an extension command, and it must be installed separately for each ESP-IDF Python environment.

Windows PowerShell or Ubuntu Bash, ESP-IDF v5.5.5 environment:

python -m pip install idf-component-manager==2.5.0 esp-bmgr-assist==0.8.3

Windows PowerShell or Ubuntu Bash, ESP-IDF v6.1 environment:

python -m pip install idf-component-manager==3.0.3 esp-bmgr-assist==0.8.3

3. Select the Board Definition and Build​

The following uses a freshly extracted 01_i2c_scan as an example. Run from the root directory of the example package; when using other examples, replace the directory name.

Windows PowerShell or Ubuntu Bash (with the ESP-IDF environment activated):

cd examples/esp-idf/01_i2c_scan
idf.py set-target esp32s3
idf.py reconfigure
idf.py bmgr -l
idf.py bmgr -b esp32_s3_lr1121_oled_1_54_xf
idf.py build

reconfigure resolves and downloads dependencies; bmgr generates the required code based on the board definition in the project. After the selection is complete, you can open that example directory in VS Code to continue compiling and flashing. The board definition name is a project identifier, and the actual RF version and operating frequency band still need to be selected according to the hardware in use.

Existing Project Configuration

The above steps apply to the first build. If you already have a custom sdkconfig, back it up first; the first board definition selection may back up the old configuration to components/gen_bmgr_codes/sdkconfig.bmgr_board.old and remove the original configuration. When switching ESP-IDF environments, you should use a new build directory and handle the existing dependency lock file according to the example package's docs/board-manager.md.

4. Flash and View Logs​

Replace the port in the commands with the actual port on your machine.

Windows PowerShell:

idf.py -p COM5 flash monitor

Ubuntu Bash:

idf.py -p /dev/ttyACM0 flash monitor

Use Ctrl+] to exit the serial monitor. If you cannot enter download mode, refer to the FAQ.

Example​

The examples are located in examples/esp-idf/. Please complete the above dependency installation and board definition selection first, then compile and flash in the corresponding project directory. The board-level adaptation code and YAML configuration are located in each project's components/ws_board/.

ExampleDescription
01_i2c_scanI2C toolset, providing the i2cdetect, i2cget, i2cset, and i2cdump commands
02_sd_mmcInitialize the TF card and mount the FAT file system, read and write a test file
03_buttonRead the onboard button status and register click callback events
04_microphoneRead ES7210 microphone audio data via I2S
05_speakerRead an MP3 file from the TF card and play it through the ES8311 speaker
06_rtc_pcf85063Read and set the PCF85063A RTC time
07_bat_bq27220Read the BQ27220 battery voltage, current, and remaining capacity
08_oled_ssd1309Drive the SSD1309 OLED display and show an LVGL interface
09_lora_ping_pongLoRa transmit/receive test, with two devices performing Ping-Pong communication
10_walkie_talkieUse the onboard microphone, speaker, and LR1121 to implement half-duplex voice intercom
11_lorawan_starterLoRaWAN starter example, joining a network via OTAA and periodically reporting data

01_i2c_scan​

Provides an I2C toolset, supporting the i2cdetect, i2cget, i2cset, and i2cdump commands, and interacts with I2C devices through the serial command line.

Code Explanation​

  • ws_board_init() / bsp_i2c_get_handle(): Initialize board-level management and obtain the shared I2C Master bus handle, SDA=GPIO13, SCL=GPIO14.
  • esp_console_new_repl_usb_serial_jtag(): Start the serial REPL interactive environment, with the prompt i2c-tools>.
  • register_i2ctools(): Register console commands such as i2cdetect, i2cget, i2cset, and i2cdump.
  • i2cdetect: The current implementation traverses the 0x00-0x7F address range (including reserved addresses), determining whether a device exists by its ACK response.
  • i2cget / i2cset: Read or write the register value at the specified device address.
  • i2cdump: Continuously dump the register area of the specified device.

Expected Behavior

02_sd_mmc​

Initialize the TF card and mount the FAT file system, create directories and read/write test files on the TF card, and print the card information.

Code Explanation​

  • bsp_sdcard_mount(): Initialize the TF card through the board-level adaptation layer and mount the FAT file system to /sdcard.
  • The board definition configures CLK=GPIO12, CMD=GPIO10, D0=GPIO11, using 1-line SDMMC, with a default clock of 20 MHz. This board does not have the data lines required for 4-line mode connected.
  • If mounting fails, the TF card is not automatically formatted; please prepare a card using a supported FAT file system in advance.
  • fopen() / fputs() / fgets() / fclose(): Use standard C library functions to operate on files.
  • Example flow: write hello.txt → rename to foo.txt → read and verify → write nihao.txt → read and verify → unmount the TF card.
  • sdmmc_card_print_info(): Print information such as the TF card name, capacity, and speed class.

Expected Behavior

03_button​

Obtain the button devices through Board Manager, register single click and PTT double click callbacks, and output button events over the serial port.

Code Explanation​

  • ws_button_for_gpio(): Obtain the button object for the corresponding GPIO; the pin and active level are uniformly configured by the board definition.
  • iot_button_register_cb(): The current example registers a single click callback for 4 buttons and additionally registers a double click callback for PTT.
  • 4 buttons: GPIO0 (BOOT), GPIO5 (MENU), GPIO6 (PTT, supports double click), GPIO4 (PWR).
  • Callback function button_event_cb(): Obtain the event type via iot_button_get_event(), and output the event name and GPIO number over the serial port.
  • The callback function retains the handling branches for events such as long press duration and repeat count, but the current example does not register these events; when they need to be demonstrated, the corresponding callback registration should be added first.

Expected Behavior

04_microphone​

Read ES7210 microphone audio sampling data via the I2S interface and output the sample values over the serial port.

Code Explanation​

  • bsp_extra_codec_init(): Initialize the ES7210 audio Codec, configuring the I2S interface and sample rate.
  • bsp_extra_codec_gain_set(30, NULL): Set the microphone gain to 30dB.
  • bsp_extra_i2s_read(): Read audio data from the I2S receive buffer, blocking until data is ready.
  • The audio frame is 4 channels, 16-bit, with 1024 sample points, and the array size is FRAME_SIZE * CHANNEL_COUNT.
  • In the loop, one frame of data is read each time, taking the left channel (channel 0) and right channel (channel 2) values of the first 16 sample points and formatting and outputting them.

Expected Behavior

05_speaker​

Mount the TF card, scan for MP3 files in the music directory, and play them on the onboard speaker through the ES8311 audio DAC and amplifier.

tip

Before running, you need to place an MP3 file in the music directory on the TF card.

Code Explanation​

  • bsp_sdcard_mount(): Mount the TF card to BSP_SD_MOUNT_POINT.
  • list_files(): Traverse the TF card music directory and collect the paths and file names of all .mp3 files into an array.
  • bsp_extra_codec_init() / bsp_extra_codec_volume_set(): Initialize the ES8311 audio Codec and set the default volume.
  • bsp_extra_player_init() / bsp_extra_player_play_file(): Initialize the MP3 player and play the specified file.
  • 4 button controls: GPIO0 (UP, next track), GPIO4 (DOWN, previous track); for the active level see Button Description, GPIO5 (RESUME, play/pause), GPIO6 (VOLUME, single click to increase volume / double click to decrease volume, step 5%).
  • speaker_callback(): Playback end callback, automatically switching to the next track and looping playback.

Expected Behavior

06_rtc_pcf85063​

Initialize the PCF85063A RTC, set the default time and alarm, and periodically read and output the current time.

Code Explanation​

  • bsp_rtc_init(): Initialize the PCF85063A RTC chip via I2C.
  • bsp_set_rtc_time_date(rtc_time): Set the initial time (year, month, day, weekday, hour, minute, second).
  • bsp_set_rtc_alarm_time(rtc_time): Set the alarm time, with the alarm seconds set to 29 seconds.
  • bsp_enable_rtc_alarm(): Enable the alarm function. When the alarm is triggered, the GPIO7 pin generates an interrupt.
  • GPIO7 is configured as a button input. When the alarm is triggered, output a notification through the BUTTON_PRESS_DOWN event callback.
  • The main loop reads the current time once per second, formatting it into a string via bsp_datetime_to_str() and outputting it over the serial port.

Expected Behavior

07_bat_bq27220​

Read and output information such as the voltage, current, remaining capacity, and charging status of the BQ27220 battery fuel gauge.

Code Explanation​

  • bsp_bat_init(1000): Initialize the BQ27220 fuel gauge, with the parameter being the battery design capacity 1000mAh. It should be adjusted according to the specifications of the actually installed battery; this value is not a statement of the product's standard battery capacity.
  • bsp_get_bat_info(&bat_info): Read the battery information into the bsp_bat_info_t struct.
  • bat_info.mv: Battery voltage (mV).
  • bat_info.ma: Battery current (mA), positive when charging and negative when discharging.
  • bat_info.soc: Remaining battery percentage (State of Charge %).
  • bat_info.mah_rem: Remaining capacity (mAh).
  • bat_info.mah_fcc: Full charge capacity (mAh, Full Charge Capacity).
  • bat_info.tc: Battery temperature (°C).
  • The main loop reads once per second and formats and outputs all parameters.

Expected Behavior

08_oled_ssd1309​

Start the BSP display driver, drive the SSD1309 OLED display, and show an LVGL graphical interface.

Code Explanation​

  • bsp_display_start(): Initialize the I2C OLED display driver and LVGL, returning the display device handle.
  • bsp_display_lock(-1): Acquire the LVGL display lock. -1 means wait indefinitely, ensuring multi-thread safety.
  • lv_display_get_screen_active(display): Get the current active screen object of the specified display device.
  • lv_label_create(): Create text labels, displaying the title "SSD1309 128x64" and the bottom text "1.54 inch OLED" respectively.
  • lv_image_create() + lv_image_set_src(): Create image widgets, displaying LVGL built-in icons (Wi-Fi, battery full).
  • lv_obj_align(): Set the alignment of the widgets (top center, middle left, middle right, bottom center).
  • bsp_display_unlock(): Release the display lock.

Expected Behavior

09_lora_ping_pong​

Use RadioLib to perform transmit/receive testing in LoRa packet mode, with two devices performing Ping-Pong communication.

tip

Requires two devices used together: the initiating end keeps #define INITIATING_NODE in main/main.cpp, and the other end comments out that line. Both ends alternate between transmitting and receiving. The example defaults to 868 MHz. Please confirm that the board's RF version, antenna, and operating frequency band match, and keep the parameters on both ends consistent.

Code Explanation​

  • LR1121 radio(&radio_module): Use RadioLib's LR1121 class to drive the RF chip, encapsulating the SPI interface through Module and EspHal.
  • radio.setRfSwitchTable(): Configure the RF switch table, controlling RX/TX path switching via DIO5/DIO6.
  • radio.setFrequency(868.0): Set the operating frequency to 868 MHz.
  • radio.setOutputPower(22): Set the transmit power to 22 dBm.
  • radio.setBandwidth(125.0) / radio.setSpreadingFactor(7) / radio.setCodingRate(5): Set the LoRa modulation parameters (125kHz bandwidth, SF7, CR4/5).
  • radio.setIrqAction(set_flag): Register the interrupt callback, setting the operation_done flag when transmission/reception is complete.
  • Initiating end (INITIATING_NODE macro definition): Send "Hello World!" first, then switch to receive mode and wait for a reply after sending is complete.
  • Receiving end: Start receiving. After receiving the data, output the content, RSSI, and SNR, then reply with the same data after a 1-second delay.

Expected Behavior

10_walkie_talkie​

Use the onboard microphone, speaker, OLED, and LR1121 to implement half-duplex voice intercom, using Codec2 3200 bps voice encoding. Hold the PTT button to transmit and release it to receive.

tip

Requires two devices used together. The example includes the ws_board board-level adaptation and the esp32_codec2 component, and the interface uses LVGL. The frequency band, frequency, modulation method, and related parameters on both ends should be consistent, and should match the actual board and antenna.

Code Explanation​

  • audio_pipeline: Audio pipeline module, responsible for microphone capture, Codec2 encoding, decoding, and speaker playback.
  • radio_device / radio_runtime: LR1121 RF driver and runtime management, handling LoRa packet transmission and reception.
  • ui: Draws the OLED interface based on LVGL, displaying information such as wireless parameters, PTT status, time, and battery level.
  • peripheral_guard: Provides a shared mutex to coordinate access to peripherals such as the wireless module, RTC, fuel gauge, and display-side I2C.
  • PTT button controls the working mode: press to enter transmit mode (I2S capture → Codec2 encoding → LR1121 transmission), release to enter receive mode (LR1121 reception → Codec2 decoding → I2S playback).
  • Supports low-power sleep: automatically enters Light Sleep after 30 seconds of no operation, waking up via button or LoRa interrupt. The PWR level and wake-up configuration need to be verified according to the Button Description.
  • nvs_flash_init(): Initialize NVS, used to save configuration parameters such as channel and volume.

Interface Illustration (the layout may differ across frameworks and versions; the actual running interface shall prevail)

Walkie-talkie interface illustration

11_lorawan_starter​

LoRaWAN starter example, using RadioLib's LoRaWANNode class to join a LoRaWAN network via OTAA, periodically sending uplink data and receiving downlink data, and using NVS to save the session state for reconnection after power loss.

tip

Before running, you need to fill in the LoRaWAN credentials (JoinEUI, DevEUI, AppKey, NwkKey) in main/config.h. Production credentials can be configured through the lorawan_secrets.h file and excluded from version control. You need to register the device on a network server first and use a gateway already connected to that server; the region configuration (default EU868) should be consistent with the board, antenna, and network. JoinEUI is called AppEUI on some platforms, and the key fields should be filled in according to the selected LoRaWAN version and server requirements.

Code Explanation​

  • EspHal: Encapsulates ESP-IDF's SPI/GPIO/timer interfaces, providing a hardware abstraction layer for RadioLib and configuring the SPI bus pins and clock rate.
  • LR1121 radio(&radio_module): Initialize the LR1121 RF module through Module and EspHal.
  • radio.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table): Configure the RF switch table, controlling RX/TX path switching via DIO5/DIO6.
  • LoRaWANNode node(&radio, &lorawan_region, lorawan_sub_band): Create a LoRaWAN node, configuring the region as EU868 and sub-band 0.
  • node.beginOTAA(join_eui, dev_eui, nwk_key, app_key): Initialize the node using OTAA, passing in JoinEUI, DevEUI, NwkKey, and AppKey.
  • node.activateOTAA(): Execute the OTAA join procedure. Returning RADIOLIB_LORAWAN_NEW_SESSION indicates a new session, and RADIOLIB_LORAWAN_SESSION_RESTORED indicates the session was restored from NVS.
  • restore_lorawan_state() / save_lorawan_state(): Save and restore the LoRaWAN session state (DevNonce and session context) via NVS, avoiding rejoining the network on every restart.
  • node.sendReceive(uplinkPayload, sizeof(uplinkPayload), 1, downlink, &downlinkLen): Send 3 bytes of uplink data (random value val1 + 2-byte val2) and wait for downlink data. A return value greater than 0 indicates that downlink data was received.
  • uplink_interval_seconds = 5 * 60: Uplink interval 5 minutes.
  • When downlink data is received, output the receive window number, data length, HEX dump, and ASCII content.