System hangs when using dual UARTs (UART20 & UART30) on nRF54L15

Hello, 

I am developing a project on the nRF54L15 and encountered a system hang when using two UART instances simultaneously: UART20 (for logging and printk) and UART30 (for external device communication).

Symptoms:

  • When compiled with Default settings, the system hangs as soon as there is concurrent output from both the logs and UART30.

  • When compiled with the -g (Debug) flag, the program runs normally without any issues.

  • If I set CONFIG_LOG=n in the Default build (printk can output), the program also runs normally.

Configuration:

  • UART30 is configured with CONFIG_UART_INTERRUPT_DRIVEN=y and CONFIG_UART_30_INTERRUPT_DRIVEN=y.

I am unsure why the logging system is causing the crash in the release/default build while the debug build works fine. Are there known conflicts between the logging backend and interrupt-driven UART on this series? How can I resolve this?

  • Hi,

    On the nRF54L15 this behavior is expected if TF-M is enabled and the same UART instance is used by both the secure and non-secure domains. As the system is split into a secure image (TF-M) and a non-secure application. The application you build and run is the non-secure one, while TF-M runs separately in the secure world and is built automatically when enabled. By default on nRF54L, the non-secure application typically uses UART20, while TF-M uses UART30 for logging.

    If the non-secure application also configures UART30 (for example, for external device communication with CONFIG_UART_INTERRUPT_DRIVEN), this can lead to undefined behavior or hangs when logging is active, as both domains end up interacting with the same UART. This also explains why the issue disappears when logging is disabled, and why it may not reproduce in a debug (-g) build due to timing and layout differences.

    To resolve this, we recommend one of the following:

    1. Disable TF-M logging if it is not needed which will free up UART30 by adding this to your prj.conf:
      CONFIG_TFM_LOG_LEVEL_SILENCE=y
    2. Avoid using UART30 in the non-secure application and move the external device to another UART instance (e.g. UART21 or another available UART)
    3. Switch application logging to a non-UART backend, such as RTT, to avoid UART resource conflicts during development.

    After applying one of the above, using multiple UARTs concurrently in the non-secure application should work as expected. Best Regards,

    Syed Maysum

  • Hi Syed,

    I previously tested my project using the cpuapp/ns (Non-Secure) target and confirmed that setting CONFIG_TFM_LOG_LEVEL_SILENCE=y allowed both the application logs and UART_CONSOLE to function correctly.

    Currently, I am building for the cpuapp target. I have the following technical questions regarding the behavior I've observed:

    TF-M Activation in cpuapp Builds: When building for the cpuapp target, is TF-M still enabled by default? Specifically, if MCUBoot is included in the project, does it automatically trigger the activation of TF-M (Trusted Firmware-M)?

    Current Workaround: I have confirmed that adding CONFIG_TFM_LOG_LEVEL_SILENCE=y to my current cpuapp build resolves the system hang. Without this setting, the program freezes as soon as UART30 begins transmission.

    Resource Contention: Why does silencing TF-M logs prevent the application from hanging? It appears that the Secure Domain's logging mechanism conflicts with the application's interrupt-driven UART (UART30), even when the application is not explicitly built as "Non-Secure."

    Can the UART output instance of TF-M be switched?

  • Hi Syed,

    Today, after adding the configuration, I realized that I had forgotten to change the -g option in the build settings back to Use Default.
    After switching it to Use Default and flashing it to the target board, I found that it still did not work. Even after adding CONFIG_TFM_LOG_LEVEL_SILENCE=y, the problem persisted.
    It seems the issue is still present. I tried running the same code on the nRF52840 DK and found that it works correctly when compiled with -g, but fails when using -Os or -O2.


    The compilation conditions are the same for the same program.

    ncs 3.2.0 nrf52840DK for -g pass
    ncs 3.2.0 nrf52840DK for -Os fail
    ncs 3.2.0 nrf52840DK for -O2 fail

    ncs 3.0.2 nrf52840DK for -g pass
    ncs 3.0.2 nrf52840DK for -Os fail
    ncs 3.0.2 nrf52840DK for -O2 fail

    ncs 2.9.1 nrf52840DK for -g pass
    ncs 2.9.1 nrf52840DK for -Os fail
    ncs 2.9.1 nrf52840DK for -O2 fail

  • Hi,

    Thanks for re-testing, It looks since the same behavior also occurs on the nRF52840 DK (which does not use TF-M) and only depends on the optimization level (-g works, -Os/-O2 hangs), this points to an application-level, optimization-sensitive issue, not a TF-M or UART ownership problem. This type of issue is usually caused by things like:

    • doing too much work or logging in UART ISRs/callbacks,
    • missing synchronization between ISR and thread context,
    • or UART buffer/stack usage problems that only show up in optimized builds.

    The reason -g appears to “fix” it is that debug builds change timing, code layout, and stack usage, which can hide race conditions, starvation, or marginal stack problems that become visible with -Os/-O2

    As a next step, please check that no LOG_* or printk calls are made from UART callbacks/ISRs, move any heavier processing to a thread or work queue, and make sure UART buffers remain valid until transfers complete. Also double-check stack sizes for threads handling UART and logging. If you can share the UART callback code and relevant config, we can help pinpoint it further.

    Best Regards,
    Syed Maysum

Related