This document explains how to add a new board and how to add build variants for an existing board.
Build system overview
Section titled “Build system overview”tools/build_scripts/build_mgr.pyis the central launcher (interactive when run without arguments, seedocs/guides/tools.md).- It discovers variants by reading
VARIANTSandFACTORY_VARIANTSfrom each board’sbuild_scripts/variants.py. A board is listed only if itsbuild_scripts/folder holds bothvariants.pyandbuild_one.py. - Each board provides three fixed scripts:
variants.py(variant definitions),common.py(shared helpers), andbuild_one.py(generic entry point). - No per-variant wrapper script is needed. Adding a variant means adding one entry in
variants.py. tools/build_scripts/validate_build_scripts.pyverifies option consistency betweenCMakeLists.txtand board variants.
Add a new board
Section titled “Add a new board”-
Declare the board in the root
CMakeLists.txt.- Add
OPTION(<BOARD_OPTION> "<short description>" OFF)in the Select one targeted hardware block. - Add
<BOARD_OPTION>toPLATFORMS_LIST(cmake/sanity_check.cmakerequires exactly one selected platform from this list). cmake/targets.cmakeincludes everyboards/*/board_config.cmake, so no other registration is needed.
- Add
-
Create
boards/<board>/board_config.cmake.- Wrap its whole content in
if (<BOARD_OPTION>)…endif(). - Set
TFT_TARGETfor the board. - Define hardware capabilities that are always present on this board.
- Use
set(<OPTION> "ON" CACHE BOOL "..." FORCE)for board capabilities. - Add board-specific components, SDKCONFIG defaults, or other target-specific settings.
- Wrap its whole content in
-
Create a board-specific
build_scripts/directory.boards/<board>/build_scripts/
-
Create a
variants.pyfile for the board.- Define
ROOT_CMAKE_OPTIONSif this board uses the shared root option table. - Use a
DEFAULT_OFF_CMAKE_ARGSstrategy so each variant only enables the options it needs. - Keep variant definitions small and readable.
- Define
-
Create
build_one.py— a single generic entry point for all variants.- It reads the variant name from
sys.argv[1]and looks it up inVARIANTSandFACTORY_VARIANTS. - Copy the pattern from an existing board’s
build_one.py. - No per-variant script file is needed.
- It reads the variant name from
-
If the board has a factory project, add a
Factory/build config as needed. -
Optional — if the board needs its own boot logo, icons, fonts or theme colors, create
boards/<board>/resources/mirroring the layout ofresources/. Overrides are automatically scoped to the board’s resolution and to the target firmware. See Board-level overrides indocs/ui_resources/development.md. Skip this step (and do not create an empty folder) if the shared assets are fine. -
Validate the new board.
- Run
python tools/build_scripts/validate_build_scripts.py - Run
python tools/build_scripts/build_mgr.py --list - Build at least one variant to confirm the board integration (
python tools/build_scripts/build_mgr.py --build_board <board>).
- Run
Add a new variant for an existing board
Section titled “Add a new variant for an existing board”-
Edit
boards/<board>/build_scripts/variants.py.- Add a new entry under
VARIANTS. - Use
make_variant_args(...)so the variant only specifies the flags that should be turned on. - Do not repeat all default
OFFflags in each variant.
- Add a new entry under
-
If you introduce a new root-level CMake option:
- Add the option to
CMakeLists.txt. - Add the option to
ROOT_CMAKE_OPTIONSin the boardvariants.pyfile. - Update
README.mdif the option should be documented.
- Add the option to
-
Validate the change.
python tools/build_scripts/validate_build_scripts.pypython tools/build_scripts/build_mgr.py --listpython tools/build_scripts/build_mgr.py --build_variant <board>/<new_variant>(the<board>/prefix is required when the variant name exists on several boards)
New board vs. new feature option
Section titled “New board vs. new feature option”- Board capabilities belong in
boards/<board>/board_config.cmake. - Feature options belong in
CMakeLists.txtand are managed by variant definitions.
Quick validation
Section titled “Quick validation”-
python tools/build_scripts/validate_build_scripts.pychecks both:- the
ROOT_CMAKE_OPTIONStable matchesCMakeLists.txt - build scripts only use supported options.
- the
-
python tools/build_scripts/build_mgr.py --listshows all available build targets.
Hardware capabilities
Section titled “Hardware capabilities”See also: features.md for the capability × SKU matrix, and pibot-cnc-pendant-hardware-documentation.md for the PiBot V1 reference board pinout and schematic.
Board capability vs. feature option
Section titled “Board capability vs. feature option”| Type | Where declared | Mechanism |
|---|---|---|
| Hardware capability | boards/<board>/board_config.cmake | set(FLAG "ON" CACHE BOOL "..." FORCE) — cannot be overridden by the user |
| Software feature option | CMakeLists.txt OPTION(...) | User-selectable per build variant |
Capabilities reflect what is physically present on the board (a buzzer is soldered or it is not). Feature options reflect what the user wants enabled for a given build (e.g. WiFi vs BT).
Five-file pattern for hardware
Section titled “Five-file pattern for hardware”Every hardware peripheral follows the same five-file contract:
| File | Role |
|---|---|
boards/<board>/board_config.cmake | Declares the capability flag with FORCE |
boards/<board>/components/bsp/board_config.h | Pin / bus / parameter #defines |
boards/<board>/components/bsp/<hw>_def.h | Static config struct initialised from the above defines |
boards/<board>/components/bsp/board_init.c | Calls <hw>_configure(&cfg) guarded by #if ESP3D_<HW>_FEATURE |
boards/<board>/components/bsp/CMakeLists.txt | Adds the driver component to REQUIRES when the flag is set |
cmake/features.cmake converts the CMake flag to a compile-time ESP3D_<HW>_FEATURE=1 define.
Hardware catalog
Section titled “Hardware catalog”The table below lists every hardware capability currently supported by the firmware.
| Peripheral | CMake flag | Driver path | _def.h required | Notes |
|---|---|---|---|---|
| TFT display | TFT_UI_SERVICE | BSP display driver | — | Always paired with touch |
| Touch controller | TFT_TOUCH_SERVICE | BSP touch driver | — | Shares I2C bus |
| Backlight PWM | TFT_BRIGHTNESS_CONTROL | BSP backlight driver | — | Set inside TFT_UI_SERVICE block |
| SD card | SD_CARD_SERVICE | BSP SD driver | — | SPI or SDIO (SD_INTERFACE_TYPE) |
| Buzzer | BUZZER_SERVICE | hardware/common/drivers/buzzer/ | buzzer_def.h | PWM via LEDC |
| Push buttons | HARDWARE_BUTTONS | BSP buttons driver | — | Debounce in board_config.h |
| Rotary encoder | HARDWARE_ENCODER | BSP encoder driver | — | PCNT peripheral |
| Position switch | HARDWARE_SWITCH | BSP switch driver | — | Multi-position GPIO |
| Potentiometer | HARDWARE_POTENTIOMETER | BSP analog driver | — | ADC oneshot |
| DHT11 / DHT22 | SENSOR_SERVICE + SENSOR_DHT | hardware/common/drivers/sensor_dht/ | sensor_dht_def.h | 1-wire bit-bang; needs external 4.7 kΩ pull-up |
| BMP280 / BME280 | SENSOR_SERVICE + SENSOR_BMP280 | hardware/common/drivers/sensor_bmp280/ | sensor_bmp280_def.h | I2C; can share touch bus |
| Analog sensor | SENSOR_SERVICE + SENSOR_ANALOG | hardware/common/drivers/sensor_analog/ | sensor_analog_def.h | ADC oneshot; converter fn in customizations/sensor_analog/ |
| Camera | (not yet ported) | ESP-IDF camera driver | — | Requires PSRAM; not on PiBot V1 |
Only one sensor type can be active per build (cmake/sanity_check.cmake enforces this).
Adding a sensor to a new board
Section titled “Adding a sensor to a new board”The three sensor drivers (sensor_dht, sensor_bmp280, sensor_analog) live in
hardware/common/drivers/ and are board-agnostic. To wire one to a new board:
Step 1 — Declare the capability in board_config.cmake
Section titled “Step 1 — Declare the capability in board_config.cmake”# Exactly one of the three lines below:set(SENSOR_SERVICE "ON" CACHE BOOL "Sensor available on this board" FORCE)set(SENSOR_DHT "ON" CACHE BOOL "DHT11/DHT22 sensor" FORCE)# set(SENSOR_BMP280 "ON" ...)# set(SENSOR_ANALOG "ON" ...)Step 2 — Add pin/bus defines in board_config.h
Section titled “Step 2 — Add pin/bus defines in board_config.h”DHT11 / DHT22
#define SENSOR_DHT_DATA_PIN GPIO_NUM_4 // any free GPIO; needs 4.7 kΩ pull-up to 3.3 V#define SENSOR_DHT_TYPE SENSOR_DHT22 // SENSOR_DHT11 or SENSOR_DHT22BMP280 / BME280
#define SENSOR_BMP280_I2C_PORT I2C_NUM_1 // can share touch bus if speed matches#define SENSOR_BMP280_I2C_ADDR 0x76 // 0x76 (SDO=GND) or 0x77 (SDO=VCC)Analog sensor
#define SENSOR_ANALOG_ADC_UNIT ADC_UNIT_1#define SENSOR_ANALOG_ADC_CHANNEL ADC_CHANNEL_6 // GPIO34#define SENSOR_ANALOG_ADC_ATTEN ADC_ATTEN_DB_12 // 0–3.9 V#define SENSOR_ANALOG_ADC_BITWIDTH ADC_BITWIDTH_12Step 3 — Create <sensor>_def.h in the BSP
Section titled “Step 3 — Create <sensor>_def.h in the BSP”sensor_dht_def.h
#pragma once#include "board_config.h"#include "sensor_dht_config.h"
sensor_dht_config_t sensor_dht_cfg = { .type = SENSOR_DHT_TYPE, .data_pin = SENSOR_DHT_DATA_PIN,};sensor_bmp280_def.h
#pragma once#include "board_config.h"#include "sensor_bmp280_config.h"
sensor_bmp280_config_t sensor_bmp280_cfg = { .i2c_port = SENSOR_BMP280_I2C_PORT, .i2c_addr = SENSOR_BMP280_I2C_ADDR, .os_temperature = SENSOR_BMP280_OS_1X, .os_pressure = SENSOR_BMP280_OS_1X, .os_humidity = SENSOR_BMP280_OS_1X, .mode = SENSOR_BMP280_MODE_FORCED, .standby = SENSOR_BMP280_STANDBY_1000MS, .filter = SENSOR_BMP280_FILTER_OFF,};sensor_analog_def.h
#pragma once#include "board_config.h"#include "sensor_analog_config.h"#include "sensor_analog/customizations.h" // provides SENSOR_ANALOG_CONVERTER_FN and SENSOR_ANALOG_UNIT_STR
sensor_analog_config_t sensor_analog_cfg = { .adc_unit = SENSOR_ANALOG_ADC_UNIT, .adc_channel = SENSOR_ANALOG_ADC_CHANNEL, .adc_atten = SENSOR_ANALOG_ADC_ATTEN, .adc_bitwidth = SENSOR_ANALOG_ADC_BITWIDTH, .converter = SENSOR_ANALOG_CONVERTER_FN, .unit = SENSOR_ANALOG_UNIT_STR,};For the analog sensor, also create customizations/sensor_analog/customizations.h with the
unit string and converter function for your specific probe:
#pragma once#define SENSOR_ANALOG_UNIT_STR "V"
// converter must be a real function (not a macro) — it is assigned to a function pointerstatic inline float sensor_analog_converter(int raw_value) { return (float)raw_value * 3.9f / 4095.0f;}#define SENSOR_ANALOG_CONVERTER_FN sensor_analog_converterStep 4 — Wire in board_init.c
Section titled “Step 4 — Wire in board_init.c”#if defined(ESP3D_SENSOR_DHT_FEATURE) && ESP3D_SENSOR_DHT_FEATURE == 1#include "sensor_dht_def.h"#endif#if defined(ESP3D_SENSOR_BMP280_FEATURE) && ESP3D_SENSOR_BMP280_FEATURE == 1#include "sensor_bmp280_def.h"#endif#if defined(ESP3D_SENSOR_ANALOG_FEATURE) && ESP3D_SENSOR_ANALOG_FEATURE == 1#include "sensor_analog_def.h"#endif
// inside bsp_init():#if defined(ESP3D_SENSOR_DHT_FEATURE) && ESP3D_SENSOR_DHT_FEATURE == 1 sensor_dht_configure(&sensor_dht_cfg);#endif#if defined(ESP3D_SENSOR_BMP280_FEATURE) && ESP3D_SENSOR_BMP280_FEATURE == 1 sensor_bmp280_configure(&sensor_bmp280_cfg);#endif#if defined(ESP3D_SENSOR_ANALOG_FEATURE) && ESP3D_SENSOR_ANALOG_FEATURE == 1 sensor_analog_configure(&sensor_analog_cfg);#endifStep 5 — Add driver to CMakeLists.txt REQUIRES
Section titled “Step 5 — Add driver to CMakeLists.txt REQUIRES”set(REQUIRES_LIST ...) # existing list
if(SENSOR_SERVICE) if(SENSOR_DHT) list(APPEND REQUIRES_LIST sensor_dht) elseif(SENSOR_BMP280) list(APPEND REQUIRES_LIST sensor_bmp280) elseif(SENSOR_ANALOG) list(APPEND REQUIRES_LIST sensor_analog) endif()endif()
idf_component_register(... REQUIRES ${REQUIRES_LIST})Runtime notes
Section titled “Runtime notes”- Polling —
esp3d_sensor.handle()is called from the network task (Core 0, every ~10 ms).
The actual sensor read fires only when the configured interval elapses (NVS keysens_interval, default 30 000 ms). - DHT timing — the DHT driver holds a FreeRTOS critical section for ~4–5 ms during the read.
Running on Core 0 keeps this away from the LVGL task on Core 1. - Interval — configurable at runtime via
[ESP210] interval=<ms>or[ESP401] P=<idx> T=I V=<ms>. - Exclusivity — only one sensor type per build;
cmake/sanity_check.cmakeraises a fatal error if more than one ofSENSOR_DHT,SENSOR_BMP280,SENSOR_ANALOGis set.