docs/en/guides/adding_new_soc.rst
################ Adding a New SoC ################
This guide describes every file that must be created or modified to add support for a new Espressif
SoC to the Arduino ESP32 core and the esp32-arduino-lib-builder.
.. contents:: Table of Contents :local: :depth: 2
{soc} -- lowercase IDF target name (e.g. esp32c5, esp32p4).{SOC} -- uppercase variant used in C preprocessor macros (e.g. ESP32C5, ESP32P4).esp32-arduino-lib-builder/esp-idf/). Always verify against that copy, not a local
installation on a different branch.There are two levels of SoC support. Full support is a strict superset of component support -- you cannot add core support without first completing component support.
This is the minimum level of component support. The SoC can be used when Arduino is included as an ESP-IDF component
(idf.py build with Arduino in the component list). It involves:
#error guards in core source files so the code compiles for the new target.idf_component.yml."skip": 1 in builds.json (libraries build in CI
for validation but are not deployed).ALL_SOCS, SKIP_LIB_BUILD_SOCS, and the appropriate
IDF_V*_TARGETS arrays in socs_config.sh.This level includes everything from component support, plus:
SKIP_LIB_BUILD_SOCS and added to CORE_VARIANTS.Throughout this guide, each step is marked [component] (required for both levels) or [core] (only required for full support).
Before starting, ensure you have access to:
components/soc/{soc}/).Espressif DevKits page <https://docs.espressif.com/projects/esp-dev-kits/>_).esptool supports the new chip (check
esptool releases <https://github.com/espressif/esptool/releases>_ for the ROM class at
esptool/targets/{soc}.py).openocd-esp32 package includes a debug config (board/{soc}-builtin.cfg) if
debugging support is needed at launch.Collect hardware parameters from the lib-builder's bundled ESP-IDF. The table below lists what
you need and where to find it. All paths use the convention components/soc/{soc}/... unless
stated otherwise.
.. list-table:: :widths: 25 45 30 :header-rows: 1
components/soc/{soc}/include/soc/soc_caps.hSOC_WIFI_SUPPORTED, SOC_USB_OTG_SUPPORTED, SOC_BT_SUPPORTED,
SOC_TOUCH_SENSOR_SUPPORTED, SOC_GPIO_PIN_COUNT, SOC_IEEE802154_SUPPORTED, etc.components/soc/{soc}/include/soc/clk_tree_defs.hSOC_CLK_XTAL_FREQ_*, CPU clock source enums, max CPU frequency.components/soc/{soc}/include/soc/uart_pins.hU0TXD_GPIO_NUM, U0RXD_GPIO_NUM, U1TXD_GPIO_NUM, etc.components/soc/{soc}/include/soc/boot_mode.h and DevKit schematicboot_mode.h. Cross-reference with the DevKit schematic to find
the BOOT button GPIO.components/bootloader/Kconfig.projbuildCONFIG_BOOTLOADER_OFFSET_IN_FLASH: per-SoC defaults.components/spi_flash/{soc}/Kconfig.flash_freqESPTOOLPY_FLASHFREQ_* choices and defaults. Verify against the lib-builder's IDF.components/esptool_py/Kconfig.projbuildesptool/targets/{soc}.py for the byte encoding dictionary.components/soc/{soc}/register/soc/efuse_reg.hEFUSE_RD_MAC_SPI_SYS_*_REG,
EFUSE_RD_MAC_SYS0_REG).components/soc/{soc}/register/soc/efuse_reg.hEFUSE_PKG_VERSION field and its containing register.components/soc/{soc}/register/soc/gpio_struct.hpin[n].int_type or pinn[n].pinn_int_type
(newer SoCs use the pinn variant).components/soc/{soc}/include/soc/reg_base.hDR_REG_SPI2_BASE or DR_REG_GPSPI2_BASE (newer chips use GPSPI naming).components/soc/{soc}/register/soc/usb_serial_jtag_struct.hSOC_USB_SERIAL_JTAG_SUPPORTED. Check the IDF HAL layer
(usb_serial_jtag_ll.h) for abstracted register access.components/soc/{soc}/include/soc/usb_pins.hSOC_USB_OTG_SUPPORTED).components/soc/{soc}/include/soc/adc_channel.hADC1_CHANNEL_0_GPIO_NUM, etc.components/soc/{soc}/include/soc/touch_sensor_channel.hSOC_TOUCH_SENSOR_SUPPORTED.components/esp_hal_gpspi/{soc}/include/soc/spi_pins.hSPI2_IOMUX_PIN_NUM_CLK, SPI2_IOMUX_PIN_NUM_MOSI, etc.components/esp_hw_support/include/esp_chip_info.hesp_chip_model_t enum.openocd-esp32 packageboard/{soc}-builtin.cfg.Espressif DevKits page <https://docs.espressif.com/projects/esp-dev-kits/>_.. note::
For SoCs with hardware versioning (e.g. ESP32-P4 with hw_ver1/hw_ver3), register
headers may be under components/soc/{soc}/register/hw_ver*/soc/ instead of the usual
components/soc/{soc}/register/soc/.
The esp32-arduino-lib-builder builds the precompiled static libraries that ship with the
Arduino core. These changes should be done first because the Arduino core depends on the
libraries the builder produces.
File: configs/builds.json
Add a new object to the targets array. For component-only support, include "skip": 1
so the target is excluded from production deploys but still built in CI for validation.
Each value in features, idf_libs, bootloaders, and mem_variants corresponds
to a defconfig fragment filename under configs/ (e.g. "80m" means
configs/defconfig.80m).
.. code-block:: json
{ "target": "{soc}", "skip": 1, "features": [], "idf_libs": ["qio", "80m"], "bootloaders": [ ["qio", "80m"], ["dio", "80m"], ["qio", "40m"], ["dio", "40m"] ], "mem_variants": [ ["dio", "80m"] ] }
.. list-table:: :widths: 20 80 :header-rows: 1
targetidf.py -DIDF_TARGET=).chip_varianttarget (e.g. esp32p4_es
for an early-silicon variant). Defaults to target.skip1 for component-only support. Remove when promoting to full support.featuresconfigs/defconfig.<name>.idf_libsbootloaders[mode, freq] tuples. Each produces a bootloader ELF. Include all
combinations users can select in boards.txt.mem_variants[mode, freq] tuples for alternative memory configs (e.g. DIO fallback).Also add the new SoC to the mem_variants_files section at the top of the file -- at minimum
to the libspi_flash.a entry's targets array.
To promote to full support [core]: Remove "skip": 1 from the target entry.
File: configs/defconfig.{soc}
Create a per-SoC Kconfig fragment. The build system chains configs as:
defconfig.common + defconfig.{soc} + feature/mode/freq fragments.
Use the closest existing SoC as a template. Common options to set:
CONFIG_BT_NIMBLE_ENABLED, CONFIG_BT_BLUEDROID_ENABLED)CONFIG_SPIRAM=y) if supportedSOC_IEEE802154_SUPPORTEDSOC_WIFI_SUPPORTEDCONFIG_ZB_ENABLED=n if prebuilt Zigbee libraries are not available yetSOC_USB_OTG_SUPPORTEDFiles: configs/defconfig.{freq} and main/Kconfig.projbuild
If the SoC introduces flash frequencies not already covered by existing fragments, create
new files (e.g. configs/defconfig.48m):
.. code-block:: text
CONFIG_ESPTOOLPY_FLASHFREQ_{FREQ}M=y
And add the mapping in main/Kconfig.projbuild under LIB_BUILDER_FLASHFREQ:
.. code-block:: text
default "{freq}m" if ESPTOOLPY_FLASHFREQ_{FREQ}M
The frequency values in builds.json must match an available ESPTOOLPY_FLASHFREQ_*
Kconfig choice in the lib-builder's bundled IDF. If they don't match, the option is silently
ignored and the build produces files with unexpected names.
Files: components/arduino_tinyusb/CMakeLists.txt,
components/arduino_tinyusb/Kconfig.projbuild
Only if SOC_USB_OTG_SUPPORTED:
IDF_TARGET branch with the correct OPT_MCU_ESP32* define.depends on IDF_TARGET_{SOC} to the Kconfig.During the lib-builder build, the Arduino core is cloned/linked into
components/arduino/ (this directory is gitignored). The CMakeLists.txt used is the
same file as the Arduino core's root CMakeLists.txt (see Step 3.8). No separate
file exists in the lib-builder repo -- just ensure the core's CMakeLists.txt has correct
IDF_TARGET conditionals before building.
File: main/idf_component.yml
Update dependency rules: to include or exclude the new SoC for components like
esp32-camera, esp-tflite-micro, esp-sr, esp_matter, etc., based on the
SoC's capabilities.
File: tools/copy-libs.sh
If the new SoC depends on headers from another SoC's Bluetooth or other component at build time (e.g. ESP32-C61 shares BT headers with ESP32-C6), add the appropriate cross-SoC copy logic.
File: tools/get_projbuild_gitconfig.py
This file has a hardcoded if-chain for architecture detection. If the new SoC uses RISC-V,
add it to the condition that sets arch_target = "riscv32-esp". If Xtensa, add it to the
Xtensa branch.
File: .github/workflows/push.yml
Add the new SoC (using chip_variant if applicable) to the matrix.target array.
This ensures the target is built in CI for every push, even with "skip": 1.
File: .github/workflows/cron.yml
Add the SoC to the targets: string in the active matrix entry. This enables automatic
deployment of prebuilt libraries.
File: configs/pioarduino_start.txt
If the SoC supports Zigbee (SOC_IEEE802154_SUPPORTED), add it to the appropriate
build_mcu in [...] list for native 802.15.4 Zigbee library selection.
File: .github/scripts/socs_config.sh
This is the central SoC registry. Add the new SoC to:
ALL_SOCS: always.SKIP_LIB_BUILD_SOCS: for component-only support.IDF_V*_TARGETS: add to every IDF version array that supports this chip.If the SoC uses Xtensa, add it to the explicit case in get_arch(). RISC-V SoCs are
handled by the default case.
To promote to full support [core]:
SKIP_LIB_BUILD_SOCS.CORE_VARIANTS.HW_TEST_TARGETS when hardware CI runners are available.WOKWI_TEST_TARGETS when Wokwi simulation support exists.QEMU_TEST_TARGETS when QEMU support exists.File: boards.txt
Add a Dev Module entry block. Use an existing similar SoC as a template (e.g. a RISC-V chip
with similar peripherals). The board entry is required even for component-only support because
the IDF component build uses ARDUINO_VARIANT to locate pin definitions.
Key properties to set (source of truth noted for each):
.. list-table:: :widths: 30 70 :header-rows: 1
build.tarchriscv32 or xtensa (from SoC architecture)build.mcubuild.variantvariants/build.f_cpuclk_tree_defs.h)build.bootloader_addrCONFIG_BOOTLOADER_OFFSET_IN_FLASH in IDF's Kconfig.projbuildbuild.flash_freqspi_flash/{soc}/Kconfig.flash_freq)build.img_freqflash_freq for SoCs with
non-40 MHz XTALs (check esptool_py/Kconfig.projbuild for mapping)build.usb_mode0 for USB-OTG (TinyUSB), 1 for HW CDC/JTAG. Only if USB supported.upload.maximum_sizeupload.maximum_data_sizesoc_caps.h)Menu entries to add as applicable (based on soc_caps.h):
CPUFreq, FlashFreq, FlashMode, FlashSize, PartitionSchemeUploadSpeed, DebugLevel, EraseFlash, JTAGAdapterUSBMode (if SOC_USB_OTG_SUPPORTED)CDCOnBoot (if SOC_USB_SERIAL_JTAG_SUPPORTED or SOC_USB_OTG_SUPPORTED)ZigbeeMode (if SOC_IEEE802154_SUPPORTED)PSRAM (if SOC_SPIRAM_SUPPORTED)For component-only support, the board entry still exists but is not usable from the Arduino IDE without prebuilt libraries.
Directory: variants/{soc}/
Create variants/{soc}/pins_arduino.h with default pin definitions:
TX / RX: Default UART0 pins (from uart_pins.h)SDA / SCL: Default I2C pins (from DevKit schematic)SS / MOSI / MISO / SCK: Default SPI pins (from spi_pins.h)LED_BUILTIN: Onboard LED GPIO (from DevKit schematic)A0, A1, ...): ADC GPIO mappings (from adc_channel.h)File: platform.txt
Add:
Build extra flags (in the target-dependent section):
.. code-block:: text
build.extra_flags.{soc}=...
The flags depend on USB capabilities:
-DARDUINO_USB_CDC_ON_BOOT=0-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT={build.cdc_on_boot}-DARDUINO_USB_MODE={build.usb_mode} -DARDUINO_USB_CDC_ON_BOOT={build.cdc_on_boot} -DARDUINO_USB_MSC_ON_BOOT={build.msc_on_boot} -DARDUINO_USB_DFU_ON_BOOT={build.dfu_on_boot}Debug configuration (in the debugger section):
.. code-block:: text
debug_script.{soc}={soc}-builtin.cfg debug_config.{soc}=
Newer SoCs use this minimal form. Older SoCs (ESP32, S2, S3, C3) have full
cortex-debug.custom.* blocks -- use those as a reference only if the OpenOCD config
requires special attach/restart commands.
These files contain #error directives and will not compile without adding a branch
for the new SoC:
.. list-table:: :widths: 30 70 :header-rows: 1
cores/esp32/esp32-hal.hBOOT_PIN definition (from DevKit schematic / boot_mode.h)cores/esp32/esp32-hal-cpu.ccores/esp32/esp32-hal-misc.ccores/esp32/esp32-hal-matrix.ccores/esp32/esp32-hal-spi.cDR_REG_SPI2_BASE vs DR_REG_GPSPI2_BASE)cores/esp32/esp32-hal-psram.cSOC_SPIRAM_SUPPORTED; the #error fires for
SoCs that support PSRAM but are not yet handled.cores/esp32/Esp.cppESP_FLASH_IMAGE_BASE (same as build.bootloader_addr) and chip model stringThese files may compile without changes but can produce incorrect behavior if the new SoC has different peripheral layouts:
.. list-table:: :widths: 30 70 :header-rows: 1
cores/esp32/HardwareSerial.hsoc_caps.h, uart_pins.h)cores/esp32/esp32-hal-gpio.cgpio_dev_t pin access pattern: pin[n].int_type vs pinn[n].pinn_int_type
(from gpio_struct.h)cores/esp32/esp32-hal-uart.ccores/esp32/esp32-hal-i2c-slave.ccores/esp32/esp32-hal-ledc.ccores/esp32/esp32-hal-cpu.hcores/esp32/chip-debug-report.cppefuse_reg.h)cores/esp32/esp32-hal-tinyusb.cSOC_USB_OTG_SUPPORTED.cores/esp32/HWCDC.cppSOC_USB_SERIAL_JTAG_SUPPORTED.
The IDF HAL layer (usb_serial_jtag_ll_*) handles most abstraction, but the
PHY init section accesses registers directly.cores/esp32/esp32-hal-adc.ccores/esp32/esp32-hal-touch.c / esp32-hal-touch-ng.cSOC_TOUCH_SENSOR_SUPPORTEDcores/esp32/USB.cppSOC_USB_OTG_SUPPORTEDlibraries/SPI/src/SPI.cpp: Almost always needs updating for SPI pin/bus
configuration differences.ci.yml files: Update target lists or requirement conditions
in example directories to include or exclude the new SoC based on capabilities.File: CMakeLists.txt (repository root)
Review and update IDF_TARGET conditionals:
requires openthread condition if
SOC_IEEE802154_SUPPORTED.requires esp_driver_touch_sens condition if
SOC_TOUCH_SENSOR_SUPPORTED.IDF_TARGET MATCHES condition for arduino_tinyusb if
SOC_USB_OTG_SUPPORTED.File: idf_component.yml
targets: list.variants/{soc}/**/* to the files.include section.rules: conditions (e.g. esp_matter, esp32-camera, esp-sr)
to include or exclude the new SoC based on capabilities.File: tools/pioarduino-build.py
Add the new SoC to the correct bootloader offset group. The offset comes from
CONFIG_BOOTLOADER_OFFSET_IN_FLASH in ESP-IDF's components/bootloader/Kconfig.projbuild.
.. note::
There is also a per-SoC pioarduino-build.py inside
tools/esp32-arduino-libs/{soc}/. That file is auto-generated by the lib-builder's
tools/copy-libs.sh from configs/pioarduino_start.txt + build output +
configs/pioarduino_end.txt. No manual action is needed for that file.
File: tools/ide-debug/svd/{soc}.svd
Add the SVD (System View Description) file if available from Espressif. This enables register-level debugging in IDEs. Not all SoCs have SVD files available at launch.
File: package/package_esp32_index.template.json
Add a board entry (e.g. "name": "ESP32-XX Dev Board"). Also verify that the pinned
esptool_py and openocd-esp32 versions support the new SoC:
esptool/targets/{soc}.py). If the
pinned version is too old, update the version and all per-platform URLs/checksums.board/{soc}-builtin.cfg.gen_tools_json.py in the lib-builder from IDF's
tools/tools.json -- typically no manual changes needed.File: .github/scripts/sketch_utils.sh
Add default FQBN options for the new SoC in the default_fqbn_for_target() function.
Define a {soc}_opts variable with appropriate defaults (e.g. PSRAM enabled, USB mode).
Examples that don't work on the new SoC (e.g. Wi-Fi examples on a chip without Wi-Fi) need to be
excluded. The modern approach uses ci.yml files in example directories
with explicit targets or requires fields.
Check existing examples and add exclusions where the SoC lacks required capabilities.
File: tests/validation/adc_pwm/pins_config.h (and similar test files)
Add a #elif CONFIG_IDF_TARGET_{SOC} block with appropriate test GPIO assignments.
Files with #error "add pins for this target" guards will indicate where this is needed.
[component]:
.github/workflows/build_component.yml: Ensure the IDF version matrix covers the new
target (driven by socs_config.sh's IDF_V*_TARGETS).[core]:
tests.yml, tests_hw_wokwi.yml): Driven by socs_config.sh
test target arrays..github/workflows/release.yml): May reference target lists for
packaging.Several documentation files reference SoC-specific information and should be updated:
docs/en/boards/boards.rst: Add the new SoC to the family list with its key
features (connectivity, architecture). For component-only SoCs, add a footnote indicating
that rebuilding static libraries or using Arduino as an IDF component is required.
docs/en/libraries.rst: Update the peripheral support matrix table. Add a column
for the new SoC with |yes|, |no|, or |n/a| for each feature based on
soc_caps.h.
docs/en/contributing.rst: Update the default FQBN list if the SoC has full
support (this list mirrors what sketch_utils.sh defines).
.github/CI_README.md:
socs_config.sh). Verify it is still accurate after your changes.Board-specific RST (optional): If a dedicated documentation page is needed for the
DevKit, add a file under docs/en/boards/ and include it in the boards.rst toctree.
The tests/validation/ directory contains hardware validation tests. For full support,
ensure the new SoC can run the relevant validation tests:
pins_config.h files that have #error guards must have pin assignments
added (see Step 3.15).ci.yml files that specify targets: lists should include the new SoC
where applicable.gpio, uart, spi, i2c_master, adc_pwm, timer, periman.ble, wifi, touch, psram,
openthread, zigbee, ethernet, as applicable... code-block:: text
[ ] configs/builds.json has the target entry (with "skip": 1) [ ] configs/defconfig.{soc} exists with appropriate options [ ] ./build.sh -t {soc} completes successfully in the lib-builder [ ] All #error guards in cores/esp32/ are resolved [ ] idf_component.yml includes the SoC in targets list [ ] CMakeLists.txt conditionals are updated [ ] boards.txt has a Dev Module entry [ ] variants/{soc}/pins_arduino.h exists [ ] platform.txt has build.extra_flags.{soc} and debug_script.{soc} [ ] socs_config.sh has the SoC in ALL_SOCS, SKIP_LIB_BUILD_SOCS, and IDF_V*_TARGETS [ ] Lib-builder push.yml includes the target in CI matrix [ ] Arduino build_component.yml covers the SoC [ ] docs/en/boards/boards.rst updated with new SoC family entry [ ] docs/en/libraries.rst support matrix updated [ ] A basic sketch (Blink) compiles when using Arduino as IDF component [ ] CI passes on both repositories
.. code-block:: text
[ ] builds.json: "skip" removed [ ] cron.yml: target added to deploy matrix [ ] socs_config.sh: SoC in CORE_VARIANTS, removed from SKIP_LIB_BUILD_SOCS [ ] sketch_utils.sh: default FQBN options defined [ ] pioarduino-build.py: bootloader offset group updated [ ] package_esp32_index.template.json: board entry added, tool versions verified [ ] Bootloader ELF files generated with correct names [ ] Libraries built in <lib-builder>/out/tools/esp32-arduino-libs/{soc}/ and copied to <arduino-repo>/tools/esp32-arduino-libs/{soc}/ [ ] .github/CI_README.md: DevKit GPIO reference table added [ ] Sketch compiles and uploads via Arduino IDE/CLI [ ] USB Serial/JTAG (if applicable) works for upload and monitor [ ] Validation tests pass: gpio, uart, spi, i2c_master, adc_pwm, timer, periman [ ] Capability-specific tests pass (ble, wifi, touch, psram, etc. as applicable) [ ] CI passes on both repositories
Flash frequency configuration is error-prone due to indirection between Kconfig names, esptool encoding values, and actual flash speeds.
For SoCs with a 40 MHz XTAL, the Kconfig option names match the actual flash speeds
and flash_freq / img_freq / boot_freq are all the same (e.g. 80m everywhere).
For SoCs with a 32 MHz XTAL (or other non-40 MHz crystals), there is additional indirection:
Kconfig option name (e.g. ESPTOOLPY_FLASHFREQ_64M): The label defined in
spi_flash/{soc}/Kconfig.flash_freq.
esptool string (e.g. 48m): The value of CONFIG_ESPTOOLPY_FLASHFREQ after
Kconfig resolution (defined in esptool_py/Kconfig.projbuild). Passed to esptool's
--flash-freq argument.
esptool encoding (e.g. 0xF): The byte value written to the flash image header.
Defined in esptool/targets/{soc}.py in the FLASH_FREQUENCY dictionary.
Bootloader filename: bootloader_{boot}_{boot_freq}.elf where boot_freq
defaults to flash_freq (from platform.txt).
Lib-builder filename: Determined by LIB_BUILDER_FLASHFREQ in
main/Kconfig.projbuild, which maps the Kconfig choice to the output directory string.
The critical rule: the frequency names in builds.json must match an available
ESPTOOLPY_FLASHFREQ_* Kconfig choice in the lib-builder's bundled IDF. If they don't,
the defconfig option is silently ignored, the build falls through to the Kconfig default,
and bootloader files end up with unexpected names.
.. list-table:: :widths: 50 15 35 :header-rows: 1
.github/scripts/socs_config.sh.github/scripts/sketch_utils.shboards.txtplatform.txtvariants/{soc}/pins_arduino.hCMakeLists.txtidf_component.ymlcores/esp32/esp32-hal.hBOOT_PIN definitioncores/esp32/esp32-hal-cpu.ccores/esp32/esp32-hal-misc.ccores/esp32/esp32-hal-matrix.ccores/esp32/esp32-hal-spi.ccores/esp32/esp32-hal-psram.ccores/esp32/Esp.cppcores/esp32/HardwareSerial.hcores/esp32/esp32-hal-gpio.ccores/esp32/esp32-hal-i2c-slave.ccores/esp32/chip-debug-report.cppcores/esp32/esp32-hal-tinyusb.ccores/esp32/HWCDC.cpplibraries/SPI/src/SPI.cpptools/pioarduino-build.pytools/ide-debug/svd/{soc}.svdpackage/package_esp32_index.template.jsontests/validation/*/pins_config.hdocs/en/boards/boards.rstdocs/en/libraries.rstdocs/en/contributing.rst.github/CI_README.md.. list-table:: :widths: 50 15 35 :header-rows: 1
configs/builds.jsonconfigs/defconfig.{soc}configs/defconfig.commonconfigs/defconfig.{freq}main/Kconfig.projbuildmain/idf_component.ymlcomponents/arduino_tinyusb/tools/copy-libs.shtools/get_projbuild_gitconfig.pyconfigs/pioarduino_start.txt.github/workflows/push.yml.github/workflows/cron.yml