Migrating a Custom Board to ESP-IDF 6: Lessons from the Upstream Migration Commit

Migrating a Custom Board to ESP-IDF 6: Lessons from the Upstream Migration Commit

October 23, 2026

The upstream firmware moved to ESP-IDF 6.0 in v2.4.0 through 3e78cd7, and v2.5.0 removed ESP-IDF 5.x support. If you maintain your own board directory, the typical symptom after upgrading is a build error: a header cannot be found, a driver component is missing, or a struct initializer no longer compiles.

Upstream has no dedicated migration document. This article does one thing: it lines up Espressif’s official 6.0 migration guide with the real changes in the upstream migration commit and lists the places a custom board should check first. Every item can be traced to one of those two sources. Only you know which peripherals your board uses, so treat this as a checklist, not a replacement for the compiler’s errors.

Commit 3e78cd7 changes 119 files (1,379 insertions, 654 deletions) across many boards. The examples below come from a few of them; directory names are the paths at the time of that commit. In v2.5.2, main/boards is grouped by vendor, for example espressif/esp-hi and alientek/atk-dnesp32s3-box2-wifi.

1. Get the environment right first

  1. Install ESP-IDF v6.0.1 or later, preferably the release/v6.1 branch. Avoid the v6.1 release tag (Wi-Fi initialization failures on ESP32-C3 have been reported, see issue #2344).
  2. After switching SDKs, run git submodule update --init --recursive, reload export.sh, and run idf.py fullclean.
  3. Confirm with idf.py --version that the terminal loaded the new version.

The full steps are in ESP-IDF Setup. With the wrong environment every other error is noise, so rule this out first.

2. The checklist

1. Declare driver component dependencies explicitly

The official guide says the legacy driver component no longer carries public dependencies on driver components such as esp_driver_i2s, esp_driver_spi, esp_driver_uart and esp_driver_ledc. It recommends removing the dependency on driver and adding the matching esp_driver_xxx components.

Upstream main/CMakeLists.txt lists esp_driver_gpio, esp_driver_uart, esp_driver_spi, esp_driver_i2c, esp_driver_i2s, esp_driver_ledc and others in PRIV_REQUIRES. Components that only some boards need go into MAIN_PRIV_REQUIRES_EXTRA; for example the ESP32-P4 Function EV Board adds esp_driver_sdmmc and sdmmc.

If your custom board pulls in a new peripheral driver, follow the same pattern and add the dependency to the project.

2. Include FreeRTOS headers yourself

Per the official guide, since 6.0 public driver headers no longer include FreeRTOS headers, so code that relied on the implicit include must add them. The migration commit added these in 3 places:

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

3. Legacy driver headers are removed or end-of-life

The official guide lists these legacy driver headers as completely removed in 6.0: driver/adc.h, driver/dac.h, driver/i2s.h, driver/timer.h, driver/pcnt.h, driver/rmt.h, driver/mcpwm.h and driver/sigmadelta.h. The legacy I2C driver driver/i2c.h is marked end-of-life in 6.0 and scheduled for removal in 7.0; the guide recommends driver/i2c_master.h.

What the migration commit did:

OldNew
#include <driver/adc.h> (removed in two places)#include <esp_adc/adc_oneshot.h> (replaced in one place, already present in the other)
#include <driver/i2c.h>#include <driver/i2c_master.h>

Use this command to find legacy headers in your board directory:

# Run from the firmware root; every hit is a migration candidate
rg -n 'driver/(adc|dac|i2s|timer|pcnt|mcpwm|rmt|sigmadelta|temp_sensor)\.h|driver/i2c\.h' main/boards/<your-board>

4. LCD driver GPIO type and initialization

The official guide says the GPIO number type in the LCD driver changed from int to the type-safe gpio_num_t. The matching change in the migration commit replaces -1 for the reset pin with GPIO_NUM_NC:

// Before
panel_config.reset_gpio_num = -1;
// After
panel_config.reset_gpio_num = GPIO_NUM_NC;

In the SSD1306 initialization of bread-compact-wifi the same commit also made two changes you can compare with your own I2C display code:

  • the panel IO creation call changed from esp_lcd_new_panel_io_i2c_v2(...) to esp_lcd_new_panel_io_i2c(...);
  • in esp_lcd_panel_io_i2c_config_t, .scl_speed_hz moved up to follow .dev_addr, and .on_color_trans_done and .user_ctx moved to follow .lcd_param_bits. C++ designated initializers must follow the declaration order of the struct, otherwise the compiler reports an error.

In addition, atoms3-echo-base changed rgb_endian = LCD_RGB_ENDIAN_BGR to rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR in its panel config.

5. Sleep wake-up causes use the bitmask API

In main/boards/common/sleep_timer.cc the migration commit replaced esp_sleep_get_wakeup_cause() with esp_sleep_get_wakeup_causes(). The new call returns a bitmask that can represent several wake-up sources at once. Testing for the timer therefore becomes a bit test, and 32-bit values are printed with PRIx32 (include <inttypes.h>):

auto wakeup_causes = esp_sleep_get_wakeup_causes();
ESP_LOGI(TAG, "Wake up from light sleep, wakeup_causes: 0x%" PRIx32, wakeup_causes);
if (!(wakeup_causes & (1UL << ESP_SLEEP_WAKEUP_TIMER))) {
    break;
}

If your board implements its own sleep logic, compare it against this.

6. Continuous ADC: start and stop in the same task

In adc_pdm_audio_codec.cc of esp-hi and esp-sensairshuttle, the migration commit removed EnableInput(true) from Start() and added a comment: input is opened lazily by AudioService::AudioInputTask because ESP-IDF’s ADC continuous driver requires start and stop to run in the same task. Custom ADC/PDM audio codecs should respect this constraint.

7. Component versions move forward

In main/idf_component.yml the migration commit raised several components to a new major version, for example:

ComponentOldNew
espressif/esp_lcd_ili9341==1.2.0^2.0.2
espressif/esp_lcd_st77916^1.0.1^2.0.2
espressif/esp_lcd_axs15231b^1.0.0^2.1.0
espressif/esp_lcd_st7701^1.1.4^2.0.2
espressif/esp_video==1.3.1^2.0.1

If your board depends on these display drivers, their APIs may change with the major version, so check them as well.

8. Audio clock settings vary by board

During the migration a few audio boards also had their clock settings adjusted. For example, atk-dnesp32s3-box2 changed its audio sample rate from 16000 Hz to 24000 Hz and added AUDIO_CODEC_USE_MCLK true. A later commit, c0fe42a, standardized the I2S clock configuration across audio codecs. If your board produces noise or no sound after the upgrade, compare its config.h and codec implementation with a similar board.

9. The C++ standard is pinned

A comment in upstream main/CMakeLists.txt explains that ESP-IDF 6 may default to a newer C++ dialect, so the project pins its own C++ to -std=gnu++23 so that main/ does not depend on the IDF default. Your board code is built with that standard too.

3. A suggested order

  1. Fix the environment and run idf.py fullclean.
  2. Run the rg command above and deal with legacy headers.
  3. Add FreeRTOS headers and esp_driver_xxx dependencies.
  4. Review the LCD, sleep and ADC code against sections 4 to 6.
  5. Build with python scripts/build.py <your-board-directory> and fix errors one by one.
  6. Once it builds, flash it and verify display, audio and wake word.

For the board directory layout, config.json and build.py, see the Custom Board Guide. For the overall changes, see the v2.5 overview.

Sources

  • Espressif documentation: Migration from 5.5 to 6.0, the Peripherals section.
  • Upstream commit 3e78cd7 (v2.4.0), c0fe42a, and main/CMakeLists.txt, main/idf_component.yml, main/boards/common/sleep_timer.cc at the v2.5.2 tag.
  • Checked on 2026-10-23. Send corrections to [email protected].