nRF54L15 - Reflector Crash (BUS FAULT) in Channel Sounding Duration Test (SDK 3.0.2)

We are running a duration test with 10 reflectors and 10 initiators to evaluate the reliability of our system using the nRF54L15 and a project based on the Channel Sounding sample. The original sample project for the reflector part provided by Nordic is configured to reboot after a disconnect event, however, for our application, we require the device not to reboot after disconnect, as this increases power consumption unnecessarily. Instead, we want the reflector to return to a clean slate and be ready for the next connection without a full system reset.

Environment

  • nRF Connect SDK v3.0.2. We are aware there are newer versions available, unfortunately we do not want to upgrade for now due to prior qualification on other parts of the software.
  • Based on sample channel_sounding_ras_reflector with reboot removed

Scenario

Each “gate” (initiator) and each “person” (reflector) perform channel sounding, with reflectors advertising frequently for presence detection. In our duration testing with 10 initiators and 10 reflectors, all in range of each other, we observe that eventually a reflector will crash and reboot. Uptime to failure is between 1 hour and 3 days.

Log Snippet

[Mon Jan 12 09:44:49.398 2026] [00:27:35.711,901] <inf> App_Reflector: CS config creation complete. ID: 0
[Mon Jan 12 09:44:49.398 2026] [00:27:35.911,878] <inf> App_Reflector: CS security enabled.
[Mon Jan 12 09:44:49.398 2026] [00:27:36.161,967] <inf> App_Reflector: CS procedures enabled.
[Mon Jan 12 09:44:55.006 2026] [00:27:41.162,431] <wrn> bt_conn: conn 0x20003f30: not connected
[Mon Jan 12 09:44:55.006 2026] [00:27:41.162,668] <wrn> ras_rrsp: Peer is not subscribed to RAS-CP.
[Mon Jan 12 09:44:55.006 2026] [00:27:41.162,675] <wrn> ras_rrsp: Failed to send indication: -22
[Mon Jan 12 09:44:55.006 2026] [00:27:41.162,715] <inf> App_Reflector: Disconnected (reason 0x13)
                               [00:27:44.222,546] <inf> App_Reflector: Connected to F4:6C:46:3F:D2:03 (random) (err 0x00)
[Mon Jan 12 09:44:57.233 2026] [00:27:44.224,900] <err> os: ***** BUS FAULT *****
[Mon Jan 12 09:44:57.233 2026] [00:27:44.224,908] <err> os:   Precise data bus error
[Mon Jan 12 09:44:57.233 2026] [00:27:44.224,914] <err> os:   BFAR Address: 0x4
[Mon Jan 12 09:44:57.233 2026] [00:27:44.224,926] <err> os: r0/a1:  0x20003ba8  r1/a2:  0x00000000  r2/a3:  0x200011dc
[Mon Jan 12 09:44:57.233 2026] [00:27:44.224,933] <err> os: r3/a4:  0x00000000 r12/ip:  0xb3199073 r14/lr:  0x00048b83
[Mon Jan 12 09:44:57.233 2026] [00:27:44.224,939] <err> os:  xpsr:  0x810002f4
[Mon Jan 12 09:44:57.233 2026] [00:27:44.224,944] <err> os: Faulting instruction address (r15/pc): 0x000488dc
[Mon Jan 12 09:44:57.233 2026] [00:27:44.224,965] <err> os: >>> ZEPHYR FATAL ERROR 25: Unknown error on CPU 0
[Mon Jan 12 09:44:57.233 2026] [00:27:44.224,970] <err> os: Fault during interrupt handling
[Mon Jan 12 09:44:57.233 2026] 
[Mon Jan 12 09:44:57.233 2026] ASSERTION FAIL @ WEST_TOPDIR/zephyr/include/zephyr/spinlock.h:136
[Mon Jan 12 09:44:57.233 2026] [00:27:44.224,995] <err> os: ***** HARD FAULT *****
[Mon Jan 12 09:44:57.233 2026] [00:27:44.225,001] <err> os:   Fault escalation (see below)
[Mon Jan 12 09:44:57.233 2026] [00:27:44.225,007] <err> os: ARCH_EXCEPT with reason 4
[Mon Jan 12 09:44:57.251 2026] 
[Mon Jan 12 09:44:57.251 2026] [00:27:44.225,015] <err> os: r0/a1:  0x00000004  r1/a2:  0x00000088  r2/a3:  0x0000000b
[Mon Jan 12 09:44:57.251 2026] [00:27:44.225,022] <err> os: r3/a4:  0x00000004 r12/ip:  0x00000036 r14/lr:  0x00048a6d
[Mon Jan 12 09:44:57.251 2026] [00:27:44.225,028] <err> os:  xpsr:  0x21000005
[Mon Jan 12 09:44:57.251 2026] [00:27:44.225,033] <err> os: Faulting instruction address (r15/pc): 0x0004df00
[Mon Jan 12 09:44:57.251 2026] [00:27:44.225,052] <err> os: >>> ZEPHYR FATAL ERROR 4: Kernel panic on CPU 0
[Mon Jan 12 09:44:57.251 2026] [00:27:44.225,057] <err> os: Fault during interrupt handling
[Mon Jan 12 09:44:57.251 2026] 
[Mon Jan 12 09:44:57.251 2026] [00:27:44.225,073] <err> os: Current thread: 0x20006018 (unknown)
[Mon Jan 12 09:44:57.251 2026] [00:27:44.371,403] <err> fatal_error: Resetting systemø*** Booting MCUboot v2.1.0-dev-ae1ee57f3906 ***
[Mon Jan 12 09:44:57.251 2026] *** Using nRF Connect SDK v3.0.2-89ba1294ac9b ***
[Mon Jan 12 09:44:57.251 2026] *** Using Zephyr OS v4.0.99-f791c49f492c ***
[Mon Jan 12 09:44:57.251 2026] I: Starting bootloader

Observations

  • The crash always follows a connection event when the previous connection cycle ended prematurely.
  • Log shows warnings about connection state and peer not being subscribed to RAS-CP. Likely due to the connection already being disconnected.

Questions

  • Has anyone observed stability issues with the reflector application under high frequency connection/disconnection cycles, specifically with SDK 3.0.2 on nRF54L15
  • What steps are necessary to guarantee the reflector’s state is correctly reset on disconnect, without requiring a full system reboot?

Previous post for extra context: https://devzone.nordicsemi.com/f/nordic-q-a/125500/nrf54l15---perform-channel-sounding-while-reflector-advertises

Kind regards,

Arne

  • Hi Arne

    Thank you for your thorough report here. You say it only happens when a previous connection ended prematurely, does that mean while the channel sounding procedure was ongoing, or due to a connection loss anywhere in the connection "loop"? 

    The first step to avoid the reboot would be to remove the sys_reboot() function in the disconnected_cb(), but I assume you have already done that. It seems like you get a connected callback almost immediately after the disconnection, which is likely why the application crashes, and I think you need to add something to the disconnected callback so that it is ready to advertise and connect again before you go straight back to the connected state. As is now, how does your application handle disconnections? 

    I think you should look at some of the disconnected_cb() functions in the other nRF Connect SDK samples for reference. For example in ...\nrf\tests\bluetooth\iso\modules\peripheral.c or ...\nrf\applications\nrf5340_audio\src\bluetooth\bt_management\bt_mgmt.c

    Best regards,

    Simon

  • Hi Simon,

    I have looked at the peripheral.c and bt_mgmt.c disconnected_cb() functions and it looks like we are doing almost the same thing, including submitting the restarting of advertising to a work queue. The peripheral.c one seems to be the more similar one, except that we are not checking for 

    if (conn != default_conn)

    Our reflector only accepts one connection at a time, so I doubt that this would be the source of our issue.

    Kind regards,

    Arne

  • Hi

    Can you share the disconnected_cb() and main loop so we can see how the disconnection is handled on your end and what the application does directly after a disconnection? It might also be helpful to have a sniffer trace so we can get an idea of what's going on over the air here. Using a dedicated Ellisys sniffer or an nRF5x DK and the nRF Sniffer can be used for this.

    Best regards,

    Simon

  • Hi!

    Main connection loop:

    static void App_Reflector_ConnectionPoll(void)
    {
      while (1)
      {
        // Thread waits until semaphore can be taken...
        k_sem_take(&sem_connected, K_FOREVER);
        
        const struct bt_le_cs_set_default_settings_param default_settings = 
        {
          .enable_initiator_role = false,
          .enable_reflector_role = true,
          .cs_sync_antenna_selection = BT_LE_CS_ANTENNA_SELECTION_OPT_REPETITIVE,
          .max_tx_power = BT_HCI_OP_LE_CS_MAX_MAX_TX_POWER, // absolute maximum, can be reduced to decrease power consumption if possible
        };
    
        int err = bt_le_cs_set_default_settings(s_Connection, &default_settings);
        
        if (err) 
        {
          LOG_ERR("Failed to configure default CS settings (err %d)", err);
        }
      }
    }

    Connected callback:

    static void connected_cb(struct bt_conn *conn, uint8_t err)
    {
      char addr[BT_ADDR_LE_STR_LEN];
    
      (void)bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
      LOG_INF("Connected to %s (err 0x%02X)", addr, err);
    
      if (err) 
      {
        bt_conn_unref(conn);
        s_Connection = NULL;
      }
    
      s_Connection = bt_conn_ref(conn);
    
      k_timer_start(&App_Reflector_ConnectionTimeoutTimer, K_SECONDS(BLE_CONNECTION_TIMEOUT_SEC), K_NO_WAIT); // Single shot timer
    
      k_sem_give(&sem_connected);
    }

    Disconnected callback + relevant called functions:

    static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
    {
      LOG_INF("Disconnected (reason 0x%02X)", reason);
    
      bt_conn_unref(conn);
      s_Connection = NULL;
    
      k_timer_stop(&App_Reflector_ConnectionTimeoutTimer);
       // Start advertising again. Submitting it to a work queue as recommended by the documentation.
      k_work_submit(&s_StartAdvertisingCBWorkQueue);
    }
    
    static void App_Reflector_StartAdvertisingCallbackQueueHandler(struct k_work *inWork)
    {
      App_Reflector_StopAdvertising();
    
      // Only start advertising if we are in the ACTIVE state
      if (AppFSM_GetState() == APP_FSM_ACTIVE)
      {
        App_Reflector_StartAdvertising();
      }
    }
    
    bool App_Reflector_StopAdvertising(void)
    {
      int err = bt_le_ext_adv_stop(s_AdvSet);
    
      if (err)
      {
        LOG_ERR("Bluetooth Advertising Failed to Stop (err %d)", err);
        return false;
      }
    
      s_AdvRunning = false;
    
      return true;
    }
    
    bool App_Reflector_StartAdvertising(void)
    {
      if (s_AdvRunning)
      {
        LOG_DBG("Advertising already started");
        return true;
      }
    
      int err;
      err = bt_le_ext_adv_update_param(s_AdvSet, BT_LE_ADV_CONN_FAST_2);
      if (err)
      {
        LOG_ERR("Advertising failed to update parameters (err %d)", err);
      }
    
      err = bt_le_ext_adv_set_data(s_AdvSet, s_Ranging_Advertisement, ARRAY_SIZE(s_Ranging_Advertisement), NULL, 0);
      if (err)
      {
        LOG_ERR("Advertising failed to set data (err %d)", err);
      }
    
      err = bt_le_ext_adv_start(s_AdvSet, BT_LE_EXT_ADV_START_DEFAULT);
      if (err) 
      {
        LOG_ERR("Advertising failed to start (err %d)", err);
        return false;
      }
    
      s_AdvRunning = true;
    
      return true;
    }

    We already logged and analysed the bluetooth traffic using the nRF sniffer but had no luck in finding anything noteworthy. I will filter the relevant parts and post it here tomorrow.

    Kind regards,

    Arne

  • Hi

    Can't say I se anything obvious from these callbacks either. Let me know when you have the sniffer traces and I'll spend some time on Monday with diving deeper here.

    Best regards,

    Simon

Related