[nrf52] Add the Makerdiary nRF52840 MDK USB Dongle

This commit is contained in:
J. Nick Koston
2026-10-15 09:12:08 -10:00
parent 45ea5f4220
commit e9a5f4be67
7 changed files with 88 additions and 4 deletions
+4
View File
@@ -71,6 +71,7 @@ from esphome.types import ConfigType
from .boards import BOARDS_ZEPHYR, BOOTLOADER_CONFIG, SELF_RESET_PINS
from .const import (
BOOTLOADER_ADAFRUIT,
BOOTLOADER_ADAFRUIT_NRF52_NOSD,
BOOTLOADER_ADAFRUIT_NRF52_SD132,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7,
@@ -200,6 +201,7 @@ def set_framework(config: ConfigType) -> ConfigType:
# DFU bootloader with Nordic's nrfutil; mcuboot uses mcumgr instead
SERIAL_DFU_BOOTLOADERS = (
BOOTLOADER_ADAFRUIT,
BOOTLOADER_ADAFRUIT_NRF52_NOSD,
BOOTLOADER_ADAFRUIT_NRF52_SD132,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7,
@@ -1149,12 +1151,14 @@ def run_compile(args, config: ConfigType) -> bool:
BOOTLOADER_ADAFRUIT_NRF52_SD132: ("0x0051", "0x009D"),
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6: ("0x0052", "0x00B6"),
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7: ("0x0052", "0x00CA,0x0100,0x0123"),
BOOTLOADER_ADAFRUIT_NRF52_NOSD: ("0x0052", "0x00"), # no SoftDevice to require
}
# UF2 family IDs — nRF52832 vs nRF52840 per SoftDevice variant
_UF2_FAMILY_IDS = {
BOOTLOADER_ADAFRUIT_NRF52_SD132: "0x7EAED30A",
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6: "0xADA52840",
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7: "0xADA52840",
BOOTLOADER_ADAFRUIT_NRF52_NOSD: "0xADA52840",
}
# SDK < 2.9.2 places artifacts directly in build_dir/zephyr/.
+16
View File
@@ -3,6 +3,7 @@ from esphome.components.zephyr.const import KEY_BOOTLOADER
from .const import (
BOOTLOADER_ADAFRUIT,
BOOTLOADER_ADAFRUIT_NRF52_NOSD,
BOOTLOADER_ADAFRUIT_NRF52_SD132,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7,
@@ -36,6 +37,16 @@ BOARDS_ZEPHYR = {
},
# Nordic nRF52840 Dongle (PCA10059): factory Open DFU bootloader at 0xE0000, kept in place
"nrf52840dongle": {KEY_BOOTLOADER: [BOOTLOADER_NRF]},
# Makerdiary nRF52840 MDK USB Dongle: its UF2 bootloader is an Adafruit fork without a
# SoftDevice; Adafruit's own builds for the board carry one
"nrf52840_mdk_usb_dongle": {
KEY_BOOTLOADER: [
BOOTLOADER_ADAFRUIT_NRF52_NOSD,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6,
BOOTLOADER_ADAFRUIT_NRF52_SD132,
]
},
}
# Boards with a GPIO wired to their RESET pin: driving it low is a pin reset, which the factory
@@ -72,4 +83,9 @@ BOOTLOADER_CONFIG = {
BOOTLOADER_NRF: [
Section("open_bootloader", 0xE0000, 0x20000, "flash_primary"),
],
# Same MBR page and application start as above; the bootloader, its settings and the MBR
# parameters fill the last 48 KB
BOOTLOADER_ADAFRUIT_NRF52_NOSD: [
Section("Adafruit_nRF52_Bootloader", 0xF4000, 0xC000, "flash_primary"),
],
}
+1
View File
@@ -2,6 +2,7 @@ BOOTLOADER_ADAFRUIT = "adafruit"
BOOTLOADER_ADAFRUIT_NRF52_SD132 = "adafruit_nrf52_sd132"
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6 = "adafruit_nrf52_sd140_v6"
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7 = "adafruit_nrf52_sd140_v7"
BOOTLOADER_ADAFRUIT_NRF52_NOSD = "adafruit_nrf52_nosd"
BOOTLOADER_NRF = "nrf"
EXTRA_ADC = [
@@ -0,0 +1,3 @@
nrf52:
dcdc: False
dfu: true # the 1200 bps touch reboots into the UF2 bootloader
@@ -0,0 +1,15 @@
esphome:
name: componenttestnrf52
friendly_name: $component_name
nrf52:
board: nrf52840_mdk_usb_dongle
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_test_file: $component_test_file
@@ -7,7 +7,10 @@ from unittest.mock import Mock, patch
import pytest
from esphome.components import nrf52
from esphome.components.nrf52.const import BOOTLOADER_ADAFRUIT_NRF52_SD140_V7
from esphome.components.nrf52.const import (
BOOTLOADER_ADAFRUIT_NRF52_NOSD,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7,
)
from esphome.components.zephyr.const import KEY_BOARD, KEY_BOOTLOADER
import esphome.config_validation as cv
from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION, Toolchain
@@ -138,3 +141,24 @@ def test_sd140_v7_dfu_accepts_all_s140_7_softdevices(
cmd = next(c.args[0] for c in run_cmd.call_args_list if "genpkg" in c.args[0])
sd_req = cmd[cmd.index("--sd-req") + 1].split(",")
assert sd_req == ["0x00CA", "0x0100", "0x0123"]
def test_softdevice_free_adafruit_layout_requires_no_softdevice(
build_dir: Path, run_cmd: Mock
) -> None:
"""The Makerdiary MDK USB Dongle's bootloader takes an nRF52840 UF2 and a package that
does not ask for a SoftDevice."""
with patch.object(
nrf52,
"zephyr_data",
return_value={
KEY_BOARD: "board",
KEY_BOOTLOADER: BOOTLOADER_ADAFRUIT_NRF52_NOSD,
},
):
_build(build_dir, cv.Version(3, 4, 0), ["zephyr/zephyr/zephyr.hex"], run_cmd)
uf2 = next(c.args[0] for c in run_cmd.call_args_list if "-f" in c.args[0])
assert uf2[uf2.index("-f") + 1] == "0xADA52840"
genpkg = next(c.args[0] for c in run_cmd.call_args_list if "genpkg" in c.args[0])
assert genpkg[genpkg.index("--dev-type") + 1] == "0x0052"
assert genpkg[genpkg.index("--sd-req") + 1] == "0x00"
+24 -3
View File
@@ -11,7 +11,10 @@ from esphome.components.nrf52 import (
set_core_data,
)
from esphome.components.nrf52.boards import BOOTLOADER_CONFIG
from esphome.components.nrf52.const import BOOTLOADER_NRF
from esphome.components.nrf52.const import (
BOOTLOADER_ADAFRUIT_NRF52_NOSD,
BOOTLOADER_NRF,
)
from esphome.components.zephyr.const import KEY_PM_STATIC, KEY_ZEPHYR
from esphome.components.zephyr_mcumgr.ota import _validate_bootloader
from esphome.config import path_context
@@ -37,12 +40,20 @@ def test_dongle_rejects_foreign_bootloader() -> None:
_detect_bootloader({"board": "nrf52840dongle", "bootloader": "mcuboot"})
def _dongle_core_data() -> None:
def test_mdk_usb_dongle_defaults_to_the_softdevice_free_adafruit_layout() -> None:
"""Makerdiary's UF2 bootloader is an Adafruit fork that ships without a SoftDevice."""
config = _detect_bootloader({"board": "nrf52840_mdk_usb_dongle"})
assert config["bootloader"] == BOOTLOADER_ADAFRUIT_NRF52_NOSD
def _dongle_core_data(
board: str = "nrf52840dongle", bootloader: str = BOOTLOADER_NRF
) -> None:
CORE.data[KEY_CORE] = {}
CORE.testing_mode = (
False # the real flash, not the larger one faked for grouped tests
)
set_core_data({"board": "nrf52840dongle", "bootloader": BOOTLOADER_NRF})
set_core_data({"board": board, "bootloader": bootloader})
@pytest.fixture
@@ -64,6 +75,16 @@ def test_nrf_bootloader_reserves_factory_region() -> None:
assert registered["open_bootloader"].end_address == 0x100000
def test_adafruit_nosd_reserves_only_the_bootloader() -> None:
"""Without a SoftDevice the app follows the MBR page at 0x1000, as with the Open DFU
bootloader; the Adafruit bootloader keeps its usual place at the top of the flash."""
_dongle_core_data("nrf52840_mdk_usb_dongle", BOOTLOADER_ADAFRUIT_NRF52_NOSD)
registered = {s.name: s for s in CORE.data[KEY_ZEPHYR][KEY_PM_STATIC]}
assert set(registered) == {"Adafruit_nRF52_Bootloader"}
assert registered["Adafruit_nRF52_Bootloader"].address == 0xF4000
assert registered["Adafruit_nRF52_Bootloader"].end_address == 0x100000
def test_nrf_bootloader_rejects_mcumgr_ota() -> None:
"""OTA with mcumgr places its slot against a SoftDevice, which this layout has none of."""
_dongle_core_data()