nRF54L15 sysbuild: merged.hex omits the application, and flashing both images separately gives a silent boar

nRF54L15 custom board: sysbuild merged.hex omits the application, and flashing both images separately gives a silent board

Environment

  • nRF Connect SDK v2.9.2 (Zephyr 3.7.99)
  • Board target: nrf54l15dk/nrf54l15/cpuapp
  • Custom PCB using nRF54L15, powered by an nPM1300 PMIC
  • Toolchain b620d30767, Windows, west command line
  • Debug output over RTT (UART pins are in use as GPIO on this board)

What works

The application without MCUboot runs correctly. BLE advertises, I2C peripherals respond, RTT output is reliable. RTT has been verified working with several other programs on this board.

MCUboot flashed on its own also runs correctly. This is the key data point — the bootloader is not dead on this hardware:

west flash --erase --hex-file build/mcuboot/zephyr/zephyr.hex
*** Booting MCUboot v2.1.0-dev-12e5ee106034 ***
*** Using nRF Connect SDK v2.9.2-4ab7b98fc76f ***
I: Starting bootloader
I: Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Secondary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Boot source: none
W: Failed reading image headers; Image=0
E: Unable to find bootable image

Which is correct with no application present.

Problem 1: merged.hex contains only MCUboot

After a successful west build with SB_CONFIG_BOOTLOADER_MCUBOOT=y, the generated build/merged.hex contains only the bootloader. It ends at address 0x2B64 with no extended-address record and no data anywhere near 0x16000, where the application slot begins.

Last lines of merged.hex:

:102B5000892DE9FDA245E15B15D9D30C5C9B1B5483
:042B6000281B0D051C
:00000001FF

Flashing this and booting gives the same Unable to find bootable image, which is consistent: nothing was written to the primary slot.

The application's own signed hex is generated and looks correct. build/<app>/zephyr/zephyr.signed.hex begins:

:020000040001F9
:106000003DB8F396000000000008000094C20200B2

Base 0x10000 plus offset 0x6000 = 0x16000, and the first word is the MCUboot image magic 0x96F3B83D. So the image is built, signed and linked for the right address — it is simply absent from merged.hex.

Problem 2: flashing both images separately produces a silent board

west flash --erase --hex-file build/mcuboot/zephyr/zephyr.hex
west flash --hex-file build/<app>/zephyr/zephyr.signed.hex

Both report Verifying image and success. After this:

  • No BLE advertising
  • No RTT output from either image — including MCUboot, which logged correctly moments earlier when it was alone on the chip

To rule out RTT picking the wrong control block, the application's RTT was disabled (CONFIG_USE_SEGGER_RTT=n, CONFIG_RTT_CONSOLE=n, CONFIG_CONSOLE=n) so only MCUboot's block exists. Still silent. Tried auto-detect, a fixed address of 0x20000000, and search ranges covering the whole RAM region.

Problem 3: the unmodified smp_svr sample behaves the same

west build -b nrf54l15dk/nrf54l15/cpuapp \
  zephyr/samples/subsys/mgmt/mcumgr/smp_svr -d build_smp --sysbuild
west flash -d build_smp --erase

Builds cleanly with defaults — RSA-2048, MCUboot at 72.69% of 48 KB, no pm_static.yml. After flashing: no BLE, no RTT, nothing.

Partition layout

build/partitions.yml matches the pm_static.yml in the project root:

mcuboot:             0x0      - 0x16000   (0x16000)
mcuboot_pad:         0x16000  - 0x16800   (0x800)
app:                 0x16800  - 0xbd000   (0xa6800)
mcuboot_primary:     0x16000  - 0xbd000   (0xa7000)
mcuboot_secondary:   0xbd000  - 0x164000  (0xa7000)
EMPTY_0:             0x164000 - 0x165000  (0x1000)

MCUboot builds at 53.99% of its 88 KB partition.

Already tried, no change

  • ED25519 signing (SB_CONFIG_BOOT_SIGNATURE_TYPE_ED25519=y) as well as the RSA-2048 default
  • MCUboot partition at the default 0xC000 (99.12% full) and at 0x16000 (54%)
  • With and without pm_static.yml
  • CONFIG_PICOLIBC=y in the MCUboot image
  • LF clock variations in sysbuild/mcuboot.conf:
    • CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC=y + XTAL=n
    • the above plus RC_CALIBRATION=n
    • RC=y + 500PPM=y + RC_CALIBRATION=y + CALIBRATION_LF_ALWAYS_ON=y
  • nrfutil device recover before flashing
  • MCUboot RTT logging enabled (CONFIG_LOG=y, CONFIG_MCUBOOT_LOG_LEVEL_INF=y)

Board details that may matter

  • 32.768 kHz crystal fitted at AX1, 32 MHz at AX2
  • Powered by an nPM1300 PMIC over I2C at 0x6B
  • app.overlay contains &uicr { nfct-pins-as-gpios; } because two GPIOs used as buttons are on the NFC pins. MCUboot's build has no such overlay.

Questions

  1. Why does merged.hex contain only MCUboot and not the application, when the signed application hex is generated correctly?
  2. Why does MCUboot stop producing RTT output as soon as an application is present in the primary slot, having logged correctly when alone?
  3. Could the &uicr node in the application's overlay affect MCUboot, given that UICR is device-wide and MCUboot boots first?

Attached

  • partitions.yml
  • pm_static.yml
  • sysbuild.conf
  • sysbuild/mcuboot.conf
  • prj.conf
  • app.overlay
app_overlay_for_devzone.overlay4213.mcuboot.confprj_for_devzone.conf3247.sysbuild.conf
# Static partition layout, derived from this project's own generated layout
# with MCUboot enlarged from 0xc000 (48 KB) to 0x16000 (88 KB). At 48 KB
# MCUboot reached 99% full.
#
# Once a board ships with this layout it is fixed: an update image must match
# the partition map already on the device.

mcuboot:
  address: 0x0
  end_address: 0x16000
  size: 0x16000
  region: flash_primary

mcuboot_pad:
  address: 0x16000
  end_address: 0x16800
  size: 0x800
  region: flash_primary

app:
  address: 0x16800
  end_address: 0xbd000
  size: 0xa6800
  region: flash_primary

mcuboot_primary:
  address: 0x16000
  end_address: 0xbd000
  size: 0xa7000
  region: flash_primary
  span: [mcuboot_pad, app]

mcuboot_primary_app:
  address: 0x16800
  end_address: 0xbd000
  size: 0xa6800
  region: flash_primary
  span: [app]

mcuboot_secondary:
  address: 0xbd000
  end_address: 0x164000
  size: 0xa7000
  region: flash_primary

EMPTY_0:
  address: 0x164000
  end_address: 0x165000
  size: 0x1000
  region: flash_primary
EMPTY_0:
  address: 0x164000
  end_address: 0x165000
  region: flash_primary
  size: 0x1000
app:
  address: 0x16800
  end_address: 0xbd000
  region: flash_primary
  size: 0xa6800
mcuboot:
  address: 0x0
  end_address: 0x16000
  region: flash_primary
  size: 0x16000
mcuboot_pad:
  address: 0x16000
  end_address: 0x16800
  region: flash_primary
  size: 0x800
mcuboot_primary:
  address: 0x16000
  end_address: 0xbd000
  region: flash_primary
  size: 0xa7000
  span:
  - mcuboot_pad
  - app
mcuboot_primary_app:
  address: 0x16800
  end_address: 0xbd000
  region: flash_primary
  size: 0xa6800
  span:
  - app
mcuboot_secondary:
  address: 0xbd000
  end_address: 0x164000
  region: flash_primary
  size: 0xa7000
otp:
  address: 0xffd500
  end_address: 0xffd9fc
  region: otp
  size: 0x4fc
sram_primary:
  address: 0x20000000
  end_address: 0x2002f000
  region: sram_primary
  size: 0x2f000
Parents
  • Hi,

    The "merged.hex" file should contain both the Application and MCUBoot.

    I just tried to program the smp_svr sample with RTT enabled for both MCUBoot and the Application, and it worked just fine:

    00> *** Booting MCUboot v2.1.0-dev-12e5ee106034 ***
    00> *** Using nRF Connect SDK v2.9.2-4ab7b98fc76f ***
    00> *** Using Zephyr OS v3.7.99-aa34a5632971 ***
    00> I: Starting bootloader
    00> I: Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
    00> I: Secondary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
    00> I: Boot source: none
    00> I: Image index: 0, Swap type: none
    00> I: Bootloader chainload address offset: 0xc000
    00> I: Jumping to the first image slot
    00> *** Booting nRF Connect SDK v2.9.2-4ab7b98fc76f ***
    00> *** Using Zephyr OS v3.7.99-aa34a5632971 ***
    00> [00:00:00.534,366] <inf> smp_sample: build time: Sep 30 2026 14:27:12

    This is the 5127.merged.hex that I just built. Can you maybe try to flash it to a nRF54L15 DK and check if it works?

    Best regards,

    Simon

  • Flashed your 7485.merged.hex to my custom nRF54L15 board. It boots and the application runs — smp_sample: build time: Sep 30 2026 14:27:12. So the hardware is fine and the problem is in my build.

    Two differences from your run: MCUboot's own banner does not appear on my RTT, only the application's. And there's a spi_nor error, since my board has no external flash.

    Also: SB_CONFIG_MERGED_HEX_FILES is undefined in NCS 2.9.2 — setting it aborts the build.

    My merged.hex contains only MCUboot, ending at 0x2B64 with nothing near the application slot. What would cause sysbuild to omit the application from the merge?

  • Hi,

    Teja Vardhan said:
    Two differences from your run: MCUboot's own banner does not appear on my RTT, only the application's. And there's a spi_nor error, since my board has no external flash.

    What did you use to connect to RTT, and when did you connect to it?

    Can you please tell me the exact sequence of action you performed when flashing and testing the device?

    Teja Vardhan said:
    Flashed your 7485.merged.hex to my custom nRF54L15 board. It boots and the application runs — smp_sample: build time: Sep 30 2026 14:27:12. So the hardware is fine and the problem is in my build.

    Can you also send me the logs you get in the terminal when you perform a pristine build and the logs from the flash command?

    Teja Vardhan said:
    My merged.hex contains only MCUboot, ending at 0x2B64 with nothing near the application slot. What would cause sysbuild to omit the application from the merge?

    I'm not sure if you can actually check this like that, it seems very strange from my point of view that the merged.hex file contains only one part of the entire firmware stack.

    Teja Vardhan said:
    Also: SB_CONFIG_MERGED_HEX_FILES is undefined in NCS 2.9.2 — setting it aborts the build.

    This is right, but this command is not needed in NCS 2.9.2 as it always create the merged binary.

    Best regards,

    Simon

  • Hi Simon,

    Resolved — it was pm_static.yml. Removing it produced a complete merged.hex and the board boots and advertises normally. Apologies for the delay in reporting back; answers to your questions below, plus what the difference turned out to be.

    What fixed it

    My project root contained a pm_static.yml that enlarged the MCUboot partition from the default 0xC000 (48 KB) to 0x16000 (88 KB), because MCUboot with ED25519 was building at 99.12% of the default.

    With that file present:

    merged.hex ends at 0x279C   (~10 KB, MCUboot only)

    With the file renamed away and everything else identical:

    merged.hex ends at 0x8779C  (~555 KB, both images)

    The generated build/partitions.yml matched pm_static.yml exactly in the failing case - mcuboot 0x0-0x16000, mcuboot_primary 0x16000-0xbd000 - so partition manager appeared to accept the static layout, but the merge step still omitted the application.

    Flashing that complete merged.hex works: the application runs and advertises. OTA over BLE via nRF Connect Device Manager then worked first time - uploaded a signed image, the board swapped it in on reset and came up with the new firmware.

    Your questions

    What did you use for RTT, and when did you connect?

    J-Link RTT Viewer, connecting with auto-detect and attaching before power cycling the board.

    What I eventually realised is that with two images there are two RTT control blocks in RAM, and auto-detect does not reliably pick MCUboot's. That explains the asymmetry I reported: when I flashed MCUboot alone it logged perfectly, and once an application was present I saw only the application's output, or nothing. It was not MCUboot failing to log - it was the viewer finding the wrong block.

    For the same reason, your run showing both banners on a DK and mine showing only one is probably a viewer artefact rather than a real difference.

    Exact sequence when flashing and testing

    rmdir /s /q build
    west build -b nrf54l15dk/nrf54l15/cpuapp .
    west flash --erase

    then open RTT Viewer and power-cycle. --erase because my application sets nfct-pins-as-gpios in UICR.

    How I checked merged.hex

    Intel HEX is a text format, so I opened it in an editor and looked at the address records. The failing file had no extended-address record and no data beyond ~0x2B64, with nothing near 0x16000 where the application slot begins. The application's own zephyr.signed.hex was correct in the same build - starting at 0x16000 with the MCUboot image magic 0x96F3B83D - so the image was built and signed properly and simply was not included in the merge.

    On SB_CONFIG_MERGED_HEX_FILES

    Understood, thanks - that was a suggestion from elsewhere and it is clearly not applicable here.

    The part that may be worth your attention

    A pm_static.yml that partition manager accepts, and that produces a matching partitions.yml, can still result in a merged.hex containing only the bootloader - with no warning or error at any point in the build. The build reports success and the resulting file is silently incomplete.

    If that is unexpected, I am happy to provide the pm_static.yml and the full build log so it can be reproduced.

    Thanks for your help with this.

Reply
  • Hi Simon,

    Resolved — it was pm_static.yml. Removing it produced a complete merged.hex and the board boots and advertises normally. Apologies for the delay in reporting back; answers to your questions below, plus what the difference turned out to be.

    What fixed it

    My project root contained a pm_static.yml that enlarged the MCUboot partition from the default 0xC000 (48 KB) to 0x16000 (88 KB), because MCUboot with ED25519 was building at 99.12% of the default.

    With that file present:

    merged.hex ends at 0x279C   (~10 KB, MCUboot only)

    With the file renamed away and everything else identical:

    merged.hex ends at 0x8779C  (~555 KB, both images)

    The generated build/partitions.yml matched pm_static.yml exactly in the failing case - mcuboot 0x0-0x16000, mcuboot_primary 0x16000-0xbd000 - so partition manager appeared to accept the static layout, but the merge step still omitted the application.

    Flashing that complete merged.hex works: the application runs and advertises. OTA over BLE via nRF Connect Device Manager then worked first time - uploaded a signed image, the board swapped it in on reset and came up with the new firmware.

    Your questions

    What did you use for RTT, and when did you connect?

    J-Link RTT Viewer, connecting with auto-detect and attaching before power cycling the board.

    What I eventually realised is that with two images there are two RTT control blocks in RAM, and auto-detect does not reliably pick MCUboot's. That explains the asymmetry I reported: when I flashed MCUboot alone it logged perfectly, and once an application was present I saw only the application's output, or nothing. It was not MCUboot failing to log - it was the viewer finding the wrong block.

    For the same reason, your run showing both banners on a DK and mine showing only one is probably a viewer artefact rather than a real difference.

    Exact sequence when flashing and testing

    rmdir /s /q build
    west build -b nrf54l15dk/nrf54l15/cpuapp .
    west flash --erase

    then open RTT Viewer and power-cycle. --erase because my application sets nfct-pins-as-gpios in UICR.

    How I checked merged.hex

    Intel HEX is a text format, so I opened it in an editor and looked at the address records. The failing file had no extended-address record and no data beyond ~0x2B64, with nothing near 0x16000 where the application slot begins. The application's own zephyr.signed.hex was correct in the same build - starting at 0x16000 with the MCUboot image magic 0x96F3B83D - so the image was built and signed properly and simply was not included in the merge.

    On SB_CONFIG_MERGED_HEX_FILES

    Understood, thanks - that was a suggestion from elsewhere and it is clearly not applicable here.

    The part that may be worth your attention

    A pm_static.yml that partition manager accepts, and that produces a matching partitions.yml, can still result in a merged.hex containing only the bootloader - with no warning or error at any point in the build. The build reports success and the resulting file is silently incomplete.

    If that is unexpected, I am happy to provide the pm_static.yml and the full build log so it can be reproduced.

    Thanks for your help with this.

Children
No Data
Related