Skip to content

Porting

This document explains how to add a new board and how to add build variants for an existing board.

  • tools/build_scripts/build_mgr.py is the central launcher (interactive when run without arguments, see docs/guides/tools.md).
  • It discovers variants by reading VARIANTS and FACTORY_VARIANTS from each board’s build_scripts/variants.py. A board is listed only if its build_scripts/ folder holds both variants.py and build_one.py.
  • Each board provides three fixed scripts: variants.py (variant definitions), common.py (shared helpers), and build_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.py verifies option consistency between CMakeLists.txt and board variants.
  1. 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> to PLATFORMS_LIST (cmake/sanity_check.cmake requires exactly one selected platform from this list).
    • cmake/targets.cmake includes every boards/*/board_config.cmake, so no other registration is needed.
  2. Create boards/<board>/board_config.cmake.

    • Wrap its whole content in if (<BOARD_OPTION>) … endif().
    • Set TFT_TARGET for 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.
  3. Create a board-specific build_scripts/ directory.

    • boards/<board>/build_scripts/
  4. Create a variants.py file for the board.

    • Define ROOT_CMAKE_OPTIONS if this board uses the shared root option table.
    • Use a DEFAULT_OFF_CMAKE_ARGS strategy so each variant only enables the options it needs.
    • Keep variant definitions small and readable.
  5. 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 in VARIANTS and FACTORY_VARIANTS.
    • Copy the pattern from an existing board’s build_one.py.
    • No per-variant script file is needed.
  6. If the board has a factory project, add a Factory/ build config as needed.

  7. Optional — if the board needs its own boot logo, icons, fonts or theme colors, create boards/<board>/resources/ mirroring the layout of resources/. Overrides are automatically scoped to the board’s resolution and to the target firmware. See Board-level overrides in docs/ui_resources/development.md. Skip this step (and do not create an empty folder) if the shared assets are fine.

  8. 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>).
  1. 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 OFF flags in each variant.
  2. If you introduce a new root-level CMake option:

    • Add the option to CMakeLists.txt.
    • Add the option to ROOT_CMAKE_OPTIONS in the board variants.py file.
    • Update README.md if the option should be documented.
  3. Validate the change.

    • python tools/build_scripts/validate_build_scripts.py
    • python tools/build_scripts/build_mgr.py --list
    • python tools/build_scripts/build_mgr.py --build_variant <board>/<new_variant> (the <board>/ prefix is required when the variant name exists on several boards)
  • Board capabilities belong in boards/<board>/board_config.cmake.
  • Feature options belong in CMakeLists.txt and are managed by variant definitions.
  • python tools/build_scripts/validate_build_scripts.py checks both:

    • the ROOT_CMAKE_OPTIONS table matches CMakeLists.txt
    • build scripts only use supported options.
  • python tools/build_scripts/build_mgr.py --list shows all available build targets.


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.

TypeWhere declaredMechanism
Hardware capabilityboards/<board>/board_config.cmakeset(FLAG "ON" CACHE BOOL "..." FORCE) — cannot be overridden by the user
Software feature optionCMakeLists.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).

Every hardware peripheral follows the same five-file contract:

FileRole
boards/<board>/board_config.cmakeDeclares the capability flag with FORCE
boards/<board>/components/bsp/board_config.hPin / bus / parameter #defines
boards/<board>/components/bsp/<hw>_def.hStatic config struct initialised from the above defines
boards/<board>/components/bsp/board_init.cCalls <hw>_configure(&cfg) guarded by #if ESP3D_<HW>_FEATURE
boards/<board>/components/bsp/CMakeLists.txtAdds 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.


The table below lists every hardware capability currently supported by the firmware.

PeripheralCMake flagDriver path_def.h requiredNotes
TFT displayTFT_UI_SERVICEBSP display driver—Always paired with touch
Touch controllerTFT_TOUCH_SERVICEBSP touch driver—Shares I2C bus
Backlight PWMTFT_BRIGHTNESS_CONTROLBSP backlight driver—Set inside TFT_UI_SERVICE block
SD cardSD_CARD_SERVICEBSP SD driver—SPI or SDIO (SD_INTERFACE_TYPE)
BuzzerBUZZER_SERVICEhardware/common/drivers/buzzer/buzzer_def.hPWM via LEDC
Push buttonsHARDWARE_BUTTONSBSP buttons driver—Debounce in board_config.h
Rotary encoderHARDWARE_ENCODERBSP encoder driver—PCNT peripheral
Position switchHARDWARE_SWITCHBSP switch driver—Multi-position GPIO
PotentiometerHARDWARE_POTENTIOMETERBSP analog driver—ADC oneshot
DHT11 / DHT22SENSOR_SERVICE + SENSOR_DHThardware/common/drivers/sensor_dht/sensor_dht_def.h1-wire bit-bang; needs external 4.7 kΩ pull-up
BMP280 / BME280SENSOR_SERVICE + SENSOR_BMP280hardware/common/drivers/sensor_bmp280/sensor_bmp280_def.hI2C; can share touch bus
Analog sensorSENSOR_SERVICE + SENSOR_ANALOGhardware/common/drivers/sensor_analog/sensor_analog_def.hADC 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).


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_DHT22

BMP280 / 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_12

Step 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 pointer
static inline float sensor_analog_converter(int raw_value) {
return (float)raw_value * 3.9f / 4095.0f;
}
#define SENSOR_ANALOG_CONVERTER_FN sensor_analog_converter
#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);
#endif

Step 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})

  • 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 key sens_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.cmake raises a fatal error if more than one of SENSOR_DHT, SENSOR_BMP280, SENSOR_ANALOG is set.