Board Directory Structure and File Responsibilities
A minimal board directory requires three files:
my_board/
board_info.yaml # Board metadata: board name, chip, version, description, manufacturer
board_peripherals.yaml # Low-level peripheral resource declarations
board_devices.yaml # Functional device declarations
BMGR uses the presence of all three files as the recognition criterion when scanning for available boards. The remaining files are optional:
my_board/
sdkconfig.defaults.board # Optional: board-level default sdkconfig entries
setup_device.c # Optional: board-level init logic that cannot be expressed in YAML alone
Kconfig.projbuild # Optional: board-level Kconfig symbol extensions
packages/ # Optional: board-level local components
The board name is determined by the directory name and must match the board field in board_info.yaml. Board names may only contain letters, digits, and underscores; hyphens and other special characters are not supported. Boards that do not meet these requirements are ignored during scanning.
board_info.yaml Board Metadata
board_info.yaml describes the static metadata of a board. It is written into gen_board_info.c during generation and can be printed via esp_board_manager_print_board_info(). All fields are informational metadata and do not affect device or peripheral initialization logic.
board: my_board
chip: esp32s3
version: "1.0.0"
description: "My custom board"
manufacturer: "MyCompany"
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Board name; must match the board directory name. Only lowercase letters, digits, and underscores are allowed; hyphens and other special characters are not supported. |
|
string |
Yes |
The main chip model of the board, for example |
|
string |
No |
Schema identifier for the YAML parsing contract. Currently |
|
string |
No |
Brief description of the board; free-form text. Printed at runtime via |
|
string |
No |
Board manufacturer name. Printed at runtime by |
board_peripherals.yaml
board_peripherals.yaml declares the low-level hardware resource instances on the board. Each entry corresponds to one low-level resource, such as an I2C bus, an SPI controller, an I2S interface, or a GPIO.
The basic structure of a peripheral entry:
peripherals:
- name: <peripheral_name>
type: <peripheral_type>
version: <version>
role: <role>
format: <format_string>
config: <configuration>
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Peripheral instance name. Must be prefixed with the type, for example |
|
string |
Yes |
Peripheral category; use a standard type name supported by BMGR, for example |
|
string |
No |
Schema identifier for this peripheral entry; same meaning as |
|
string |
Conditional |
Peripheral operating mode, for example |
|
string |
Conditional |
Data format declaration; required only for certain peripherals (currently only I2S, for example |
|
mapping |
Yes |
Peripheral-specific configuration; fields come from |
board_devices.yaml
board_devices.yaml declares the functional device instances on the board. Each entry corresponds to one functional device, such as an audio codec, an LCD display, or a button.
The basic structure of a device entry:
devices:
- name: <device_name>
type: <device_type>
chip: <chip_name>
version: <version>
sub_type: <sub_type>
init_skip: false
depends_on: other_device
power_ctrl_device: power_ctrl
config:
<configurations>
peripherals:
- name: <periph_name>
# Device-side exclusive parameters can be appended here
dependencies:
<component_name>:
require: <scope>
version: <version_spec>
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Device instance name; must be unique within the file. Only lowercase letters, digits, and underscores are allowed (cannot be purely numeric). Not required to be prefixed with the type; names should reflect functional semantics, for example |
|
string |
Yes |
Device category; use a standard type name supported by BMGR, for example |
|
string |
Conditional |
External chip model of the device (different from |
|
string |
No |
Schema identifier for this device entry; same meaning as |
|
string |
Conditional |
Sub-implementation path within the same type—for example, |
|
bool |
No |
Defaults to |
|
string / list |
No |
Declares the names of other device instances this device depends on. BMGR recursively initializes dependencies before initializing this device. See Runtime Lifecycle. |
|
string |
No |
References a device instance of type |
|
mapping |
Yes |
Device-specific configuration; fields come from |
|
list of mappings |
Conditional |
Declares the peripheral instances this device depends on at runtime (for example, a codec depends on |
|
mapping |
Conditional |
Declares additional ESP-IDF components required by the device at runtime. Written into |
Typical syntax for referencing a peripheral in peripherals with additional device-side parameters (for example, specifying the I2C address on the device side for an audio codec):
devices:
- name: audio_codec_0
type: audio_codec
peripherals:
- name: i2c_main
addr: 0x18 # Device-side exclusive parameter
sdkconfig.defaults.board and Kconfig.projbuild
Each board can optionally include the following two files in its directory to extend the project configuration system:
sdkconfig.defaults.board: Declares board-specific sdkconfig defaults (PSRAM, Flash, partition tables, application-levelCONFIG_*entries, etc.); merged by BMGR intocomponents/gen_bmgr_codes/board_manager.defaultsduring generation.Kconfig.projbuild: Additional Kconfig symbols the board needs to expose inmenuconfig(typically board-specific feature flags, sub-board enumerations, etc.); appended by BMGR tocomponents/gen_bmgr_codes/Kconfig.projbuild.
# Example sdkconfig.defaults.board
CONFIG_SPIRAM_MODE_OCT=y
CONFIG_SPIRAM_SPEED_80M=y
Generation Phase: Board and Amend Append and Override
When running idf.py bmgr -b <board> [-a <amend>], BMGR assembles the final board_manager.defaults and Kconfig.projbuild in the following order, with later entries overriding earlier ones:
BMGR auto-generated section:
CONFIG_IDF_TARGET,CONFIG_ESP_BOARD_<BOARD>=y,CONFIG_ESP_BOARD_NAME, and the capability symbols derived from YAML parsing:CONFIG_ESP_BOARD_PERIPH_*_SUPPORT,CONFIG_ESP_BOARD_DEV_*_SUPPORT, andCONFIG_ESP_BOARD_DEV_<DEV>_SUB_<SUB>_SUPPORT.The
sdkconfig.defaults.boardandKconfig.projbuildin the board directory (if present).The
sdkconfig.defaults.boardandKconfig.projbuildfragments listed underapply:in theboard_amend.yamlmanifest, appended strictly in the order they appear inapply:. To have one fragment override another amend fragment, place it later in theapply:list.
When duplicate CONFIG_* entries appear in board_manager.defaults, BMGR keeps the last occurrence and rewrites earlier duplicate lines as comments in the form # BMGR_CONFIG_OVERRIDE by <section>: <original line>, making override relationships easy to trace. Kconfig.projbuild is assembled by plain-text concatenation; a # --- <label>: <path> --- marker is inserted before each segment to indicate its source.
Note
The sdkconfig.defaults.board and Kconfig.projbuild files listed in the board_amend.yaml manifest must be explicitly listed under apply: to be included in the merge. Files placed in the amend directory but not listed are ignored and an INFO log is emitted. See Using -a/–amend for details.
Runtime: Integrating BMGR into the Project Build
During the build, ESP-IDF reads a set of SDKCONFIG_DEFAULTS (a semicolon-separated list of files declared by the SDKCONFIG_DEFAULTS environment variable or CMake variable). BMGR uses an idf.py global callback to append the generated components/gen_bmgr_codes/board_manager.defaults to the end of this chain, injecting the current board’s device and peripheral capability symbols, CONFIG_IDF_TARGET, and board-level sdkconfig defaults into the build configuration to drive subsequent conditional compilation.
Warning
Do not manually write BMGR device or peripheral capability symbols (such as CONFIG_ESP_BOARD_DEV_*_SUPPORT, CONFIG_ESP_BOARD_PERIPH_*_SUPPORT, or CONFIG_ESP_BOARD_<BOARD>=y) in the project’s sdkconfig.defaults. These symbols should come exclusively from the board_manager.defaults that BMGR automatically generates based on the current board’s YAML. Writing them manually in the project defaults can easily lead to inconsistencies with BMGR’s generated output, causing issues with dependency resolution, conditional compilation, or runtime initialization. Board-specific sdkconfig entries (PSRAM, Flash, partition tables, application-level switches, etc.) should be placed in sdkconfig.defaults.board under the board directory; BMGR will merge them uniformly.
When switching boards, idf.py bmgr -b <other_board> regenerates board_manager.defaults and Kconfig.projbuild, and backs up and cleans up the capability macros written by the previous board from the old sdkconfig.
setup_device.c and custom Devices
setup_device.c is for board-level initialization logic that cannot be fully expressed in YAML, such as specific LCD reset sequences or touch chip factory function registration. Place this file in the board directory; BMGR includes it in the gen_bmgr_codes component during generation.
To allow downstream projects to override the board’s default behavior via -a/--amend, the following two conventions are recommended:
Declare the factory or hook functions exposed to the outside (such as
lcd_panel_factory_entry_t,lcd_touch_factory_entry_t, andio_expander_factory_entry_t) as weak symbols with__attribute__((weak)). A strong symbol implementation with the same name in the amend directory will replace the board’s default implementation at link time, without modifying the original board source. See Using -a/–amend for the override mechanism.Wrap
#includedirectives for chip driver headers with__has_include, and apply the same guard around the weak symbol function body. This allows an amend to not only replace the function itself but also, by removing the corresponding component dependency (or substituting another chip component), cause the board’s default implementation to disappear automatically—avoiding the situation where an amend has taken over a factory entry while the old board implementation still fails to compile because its header is missing.
// setup_device.c: common weak symbol + __has_include combination; see esp32_s3_korvo_2_3/setup_device.c
#if __has_include(<esp_lcd_ili9341.h>)
#include "esp_lcd_ili9341.h"
__attribute__((weak)) esp_err_t lcd_panel_factory_entry_t(esp_lcd_panel_io_handle_t io,
const esp_lcd_panel_dev_config_t *cfg,
esp_lcd_panel_handle_t *ret_panel)
{
return esp_lcd_new_panel_ili9341(io, cfg, ret_panel);
}
#endif /* __has_include(<esp_lcd_ili9341.h>) */
custom type devices are suitable for hardware not yet built into BMGR, such as specific power management chips or sensors. Set type to custom in board_devices.yaml; during generation, BMGR expands the fields under config: into a dedicated configuration structure and writes it to components/gen_bmgr_codes/gen_board_device_custom.h. To provide custom initialization logic, implement the initialization and deinitialization functions in the board directory and register them with the CUSTOM_DEVICE_IMPLEMENT macro, for example:
CUSTOM_DEVICE_IMPLEMENT(axp2101_power_manager,
cores3_power_manager_init,
cores3_power_manager_deinit);
For the configuration struct naming convention, type inference table, macro usage, and application-side access methods, see Custom Device (custom); for a complete board-level example, refer to boards/m5stack_cores3/power_manager.c.