Assert on bt_sdc_hci_drive after updating Tx_power after conneting with bluetooth.

In our application build with SDK 2.7.0 on a NRF52840 chip, we get an assert if we update the TX_Power for the connections. 

If the bluetooth has made connection, we update the TX power.


}

static void connected(struct bt_conn *conn, uint8_t err)
{
	char addr[BT_ADDR_LE_STR_LEN];
	int ret;

	if (err) {
		LOG_ERR("Connection failed (err %u)", err);
		return;
	}

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
	LOG_INF("Connected %s", addr);


	current_conn = bt_conn_ref(conn);

    LOG_INF("current_conn %d", current_conn);

	ret = bt_hci_get_conn_handle(current_conn,&conn_handle);
    LOG_INF("conn_handle %d", conn_handle);
	if (ret) 
	{
		LOG_ERR("No connection handle (err %d)\n", ret);
	} 
	else 
	{        
		set_tx_power(BT_HCI_VS_LL_HANDLE_TYPE_CONN, conn_handle, TX_POWER);
	}
}

static void set_tx_power(uint8_t handle_type, uint16_t handle, int8_t tx_pwr_lvl)
{
	struct bt_hci_cp_vs_write_tx_power_level *cp;
	struct bt_hci_rp_vs_write_tx_power_level *rp;
	struct net_buf *buf, *rsp = NULL;
	int err;

	buf = bt_hci_cmd_create(BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL,
				sizeof(*cp));
	if (!buf) {
		LOG_ERR("Unable to allocate command buffer\n");
		return;
	}

	cp = net_buf_add(buf, sizeof(*cp));
	cp->handle = sys_cpu_to_le16(handle);
	cp->handle_type = handle_type;
	cp->tx_power_level = tx_pwr_lvl;

	err = bt_hci_cmd_send_sync(BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL,
				   buf, &rsp);
	if (err) {
		uint8_t reason = rsp ?
			((struct bt_hci_rp_vs_write_tx_power_level *)
			  rsp->data)->status : 0;
		LOG_INF("Set Tx power err: %d reason 0x%02x\n", err, reason);
		return;
	}

	rp = (void *)rsp->data;
	LOG_INF("Actual Tx Power: %d\n", rp->selected_tx_power);

	net_buf_unref(rsp);
}

code works fine if we disable the row: set_tx_power(BT_HCI_VS_LL_HANDLE_TYPE_CONN, conn_handle, TX_POWER);

if the row is enabled we get an assert on the bt_sdc_hci_driver
We recently update our code from SDK 2.6.0 to 2.7.0 and enabled sysbluild. 
In SDk 2.6.0 we used the same code, and this has not triggert any failure.  
what can be the cause of this fault?
  • In SDK 2.7.0, the assert issue with bt_sdc_hci_drive when updating TX power seems tied to changes in the SoftDevice Controller’s handling of HCI commands. This version introduces stricter error checks, which can cause problems if HCI commands are not handled precisely. To work around this, make sure TX power is set only after the connection is fully stable. Setting it immediately on the connection event might trigger errors, so a small delay helps.

    Sysbuild also needs specific configurations when working with the SoftDevice Controller. In prj.conf, include these lines to make sure TX power control is enabled properly:

    CONFIG_BT_LL_SOFTDEVICE=y
    CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y
    CONFIG_BT_HCI_VS_BUILD_INFO=y
    
    These configurations ensure that all necessary dependencies are in place when sysbuild compiles. Another thing to watch for is timing. SDK 2.7.0 has more rigorous checks, and sending HCI commands close to the connection event can lead to conflicts. Delaying the set_tx_power call by 20–30 milliseconds after the connection is established should help avoid these timing issues. This approach should solve the assertion error without disabling sysbuild.
  • Hello Susheel,

    the next configuration was already set: CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y

    If I add the next configuration, it will result in an build failure: CONFIG_BT_LL_SOFTDEVICE=y 

    I now have delayed the set_tx_power call by 500ms, then it works. if I go to 250ms it still will crash. 

    (on 50ms first connection goes wel, but disconnect and reconnect fails. on 500ms reconnect als works)

    regards,

    Hermen

  • I am sorry, actually those two configs will conflict each other. SoftDevice Controller (SDC) does not fully support vendor-specific HCI commands (like BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL) used in the Zephyr-based controller. You cannot have dynamic power control with SDC. 

    In your solution, Instead of adding a delay of 500ms, I would add a work queue instead to be more cleaner.

    The template code is something like below

    static struct k_work_delayable tx_power_update_work;
    
    // Work handler for setting TX power
    static void tx_power_update_handler(struct k_work *work)
    {
        apply_tx_power(BT_HCI_VS_LL_HANDLE_TYPE_CONN, conn_handle, TX_POWER);
    }
    
    static void connected(struct bt_conn *conn, uint8_t err)
    {
        char addr_str[BT_ADDR_LE_STR_LEN];
        int ret;
    
        if (err) {
            LOG_ERR("Connection failed (err %u)", err);
            return;
        }
    
        bt_addr_le_to_str(bt_conn_get_dst(conn), addr_str, sizeof(addr_str));
        LOG_INF("Connected to %s", addr_str);
    
        current_conn = bt_conn_ref(conn);
        LOG_INF("current_conn ref: %d", current_conn);
    
        ret = bt_hci_get_conn_handle(current_conn, &conn_handle);
        LOG_INF("Connection handle: %d", conn_handle);
    
        if (ret) {
            LOG_ERR("Failed to get connection handle (err %d)", ret);
        } else {
            // Schedule TX power update immediately after connection
            k_work_schedule(&tx_power_update_work, K_MSEC(1));
        }
    }
    
    // Initialize the delayed work for TX power adjustment
    void init_tx_power_update(void)
    {
        k_work_init_delayable(&tx_power_update_work, tx_power_update_handler);
    }
    
    // Call this init function once at startup
    init_tx_power_update();

    You can then see that you  most likely do not have to finetune the delay here.

  • for the delay we used a threat and K_event, but this solution with K_work is a cleaner solution. 

    the delay time is the same, 500ms. 

    thanks for the support.  

  • OK, so it looks like it takes about 500ms for the controller to think that the connection is stable and established. Seems like a long time, but it is what it is.

Related