From fe9db75c27a663bbbee5202381386ae81395a36b Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Fri, 17 Oct 2025 15:02:37 +0200 Subject: [PATCH] [nrf52] add xiao_ble board (#10698) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/debug/debug_zephyr.cpp | 52 ++++++++++++++++++- esphome/components/nrf52/__init__.py | 13 +++++ esphome/components/nrf52/boards.py | 10 +++- esphome/components/zephyr/__init__.py | 27 ++++++---- .../components/nrf52/test.nrf52-xiao-ble.yaml | 7 +++ .../build_components_base.nrf52-xiao-ble.yaml | 15 ++++++ 6 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 tests/components/nrf52/test.nrf52-xiao-ble.yaml create mode 100644 tests/test_build_components/build_components_base.nrf52-xiao-ble.yaml diff --git a/esphome/components/debug/debug_zephyr.cpp b/esphome/components/debug/debug_zephyr.cpp index 9a361b158f..231b39a711 100644 --- a/esphome/components/debug/debug_zephyr.cpp +++ b/esphome/components/debug/debug_zephyr.cpp @@ -25,10 +25,37 @@ static void show_reset_reason(std::string &reset_reason, bool set, const char *r reset_reason += reason; } -inline uint32_t read_mem_u32(uintptr_t addr) { +static inline uint32_t read_mem_u32(uintptr_t addr) { return *reinterpret_cast(addr); // NOLINT(performance-no-int-to-ptr) } +static inline uint8_t read_mem_u8(uintptr_t addr) { + return *reinterpret_cast(addr); // NOLINT(performance-no-int-to-ptr) +} + +// defines from https://github.com/adafruit/Adafruit_nRF52_Bootloader which prints those information +constexpr uint32_t SD_MAGIC_NUMBER = 0x51B1E5DB; +constexpr uintptr_t MBR_SIZE = 0x1000; +constexpr uintptr_t SOFTDEVICE_INFO_STRUCT_OFFSET = 0x2000; +constexpr uintptr_t SD_ID_OFFSET = SOFTDEVICE_INFO_STRUCT_OFFSET + 0x10; +constexpr uintptr_t SD_VERSION_OFFSET = SOFTDEVICE_INFO_STRUCT_OFFSET + 0x14; + +static inline bool is_sd_present() { + return read_mem_u32(SOFTDEVICE_INFO_STRUCT_OFFSET + MBR_SIZE + 4) == SD_MAGIC_NUMBER; +} +static inline uint32_t sd_id_get() { + if (read_mem_u8(MBR_SIZE + SOFTDEVICE_INFO_STRUCT_OFFSET) > (SD_ID_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) { + return read_mem_u32(MBR_SIZE + SD_ID_OFFSET); + } + return 0; +} +static inline uint32_t sd_version_get() { + if (read_mem_u8(MBR_SIZE + SOFTDEVICE_INFO_STRUCT_OFFSET) > (SD_VERSION_OFFSET - SOFTDEVICE_INFO_STRUCT_OFFSET)) { + return read_mem_u32(MBR_SIZE + SD_VERSION_OFFSET); + } + return 0; +} + std::string DebugComponent::get_reset_reason_() { uint32_t cause; auto ret = hwinfo_get_reset_cause(&cause); @@ -271,6 +298,29 @@ void DebugComponent::get_device_info_(std::string &device_info) { NRF_UICR->NRFFW[0]); ESP_LOGD(TAG, "MBR param page addr 0x%08x, UICR param page addr 0x%08x", read_mem_u32(MBR_PARAM_PAGE_ADDR), NRF_UICR->NRFFW[1]); + if (is_sd_present()) { + uint32_t const sd_id = sd_id_get(); + uint32_t const sd_version = sd_version_get(); + + uint32_t ver[3]; + ver[0] = sd_version / 1000000; + ver[1] = (sd_version - ver[0] * 1000000) / 1000; + ver[2] = (sd_version - ver[0] * 1000000 - ver[1] * 1000); + + ESP_LOGD(TAG, "SoftDevice: S%u %u.%u.%u", sd_id, ver[0], ver[1], ver[2]); +#ifdef USE_SOFTDEVICE_ID +#ifdef USE_SOFTDEVICE_VERSION + if (USE_SOFTDEVICE_ID != sd_id || USE_SOFTDEVICE_VERSION != ver[0]) { + ESP_LOGE(TAG, "Built for SoftDevice S%u %u.x.y. It may crash due to mismatch of bootloader version.", + USE_SOFTDEVICE_ID, USE_SOFTDEVICE_VERSION); + } +#else + if (USE_SOFTDEVICE_ID != sd_id) { + ESP_LOGE(TAG, "Built for SoftDevice S%u. It may crash due to mismatch of bootloader version.", USE_SOFTDEVICE_ID); + } +#endif +#endif + } #endif } diff --git a/esphome/components/nrf52/__init__.py b/esphome/components/nrf52/__init__.py index 84e505a90a..727607933d 100644 --- a/esphome/components/nrf52/__init__.py +++ b/esphome/components/nrf52/__init__.py @@ -1,5 +1,6 @@ from __future__ import annotations +import logging from pathlib import Path from esphome import pins @@ -48,6 +49,7 @@ from .gpio import nrf52_pin_to_code # noqa CODEOWNERS = ["@tomaszduda23"] AUTO_LOAD = ["zephyr"] IS_TARGET_PLATFORM = True +_LOGGER = logging.getLogger(__name__) def set_core_data(config: ConfigType) -> ConfigType: @@ -127,6 +129,10 @@ def _validate_mcumgr(config): def _final_validate(config): if CONF_DFU in config: _validate_mcumgr(config) + if config[KEY_BOOTLOADER] == BOOTLOADER_ADAFRUIT: + _LOGGER.warning( + "Selected generic Adafruit bootloader. The board might crash. Consider settings `bootloader:`" + ) FINAL_VALIDATE_SCHEMA = _final_validate @@ -157,6 +163,13 @@ async def to_code(config: ConfigType) -> None: if config[KEY_BOOTLOADER] == BOOTLOADER_MCUBOOT: cg.add_define("USE_BOOTLOADER_MCUBOOT") else: + if "_sd" in config[KEY_BOOTLOADER]: + bootloader = config[KEY_BOOTLOADER].split("_") + sd_id = bootloader[2][2:] + cg.add_define("USE_SOFTDEVICE_ID", int(sd_id)) + if (len(bootloader)) > 3: + sd_version = bootloader[3][1:] + cg.add_define("USE_SOFTDEVICE_VERSION", int(sd_version)) # make sure that firmware.zip is created # for Adafruit_nRF52_Bootloader cg.add_platformio_option("board_upload.protocol", "nrfutil") diff --git a/esphome/components/nrf52/boards.py b/esphome/components/nrf52/boards.py index 8e5fb2a23d..6064fe844a 100644 --- a/esphome/components/nrf52/boards.py +++ b/esphome/components/nrf52/boards.py @@ -11,10 +11,18 @@ from .const import ( BOARDS_ZEPHYR = { "adafruit_itsybitsy_nrf52840": { KEY_BOOTLOADER: [ + BOOTLOADER_ADAFRUIT_NRF52_SD140_V6, + BOOTLOADER_ADAFRUIT, + BOOTLOADER_ADAFRUIT_NRF52_SD132, + BOOTLOADER_ADAFRUIT_NRF52_SD140_V7, + ] + }, + "xiao_ble": { + KEY_BOOTLOADER: [ + BOOTLOADER_ADAFRUIT_NRF52_SD140_V7, BOOTLOADER_ADAFRUIT, BOOTLOADER_ADAFRUIT_NRF52_SD132, BOOTLOADER_ADAFRUIT_NRF52_SD140_V6, - BOOTLOADER_ADAFRUIT_NRF52_SD140_V7, ] }, } diff --git a/esphome/components/zephyr/__init__.py b/esphome/components/zephyr/__init__.py index ff4644163e..634c99876b 100644 --- a/esphome/components/zephyr/__init__.py +++ b/esphome/components/zephyr/__init__.py @@ -222,18 +222,25 @@ def copy_files(): ] in ["xiao_ble"]: fake_board_manifest = """ { -"frameworks": [ - "zephyr" -], -"name": "esphome nrf52", -"upload": { - "maximum_ram_size": 248832, - "maximum_size": 815104 -}, -"url": "https://esphome.io/", -"vendor": "esphome" + "frameworks": [ + "zephyr" + ], + "name": "esphome nrf52", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200 + }, + "url": "https://esphome.io/", + "vendor": "esphome", + "build": { + "softdevice": { + "sd_fwid": "0x00B6" + } + } } """ + write_file_if_changed( CORE.relative_build_path(f"boards/{zephyr_data()[KEY_BOARD]}.json"), fake_board_manifest, diff --git a/tests/components/nrf52/test.nrf52-xiao-ble.yaml b/tests/components/nrf52/test.nrf52-xiao-ble.yaml new file mode 100644 index 0000000000..3fe80209b6 --- /dev/null +++ b/tests/components/nrf52/test.nrf52-xiao-ble.yaml @@ -0,0 +1,7 @@ +nrf52: + dfu: + reset_pin: + number: 14 + inverted: true + mode: + output: true diff --git a/tests/test_build_components/build_components_base.nrf52-xiao-ble.yaml b/tests/test_build_components/build_components_base.nrf52-xiao-ble.yaml new file mode 100644 index 0000000000..2f3f91d957 --- /dev/null +++ b/tests/test_build_components/build_components_base.nrf52-xiao-ble.yaml @@ -0,0 +1,15 @@ +esphome: + name: componenttestnrf52 + friendly_name: $component_name + +nrf52: + board: xiao_ble + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_test_file: $component_test_file