This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Extended advertising and central role compatibility

Hi all,

We are developing an application using nRF52840 device and the idea is to enable the device with peripheral and central roles.

We would like to set two advertising sets, first one for legacy advertising on 1M PHY and a second one, extended, on Coded PHY.

I managed to make both advertising sets work if CONFIG_BT_CENTRAL role is not enabled. However, if CONFIG_BT_CENTRAL is enabled, I have got BT_HCI_ERR_CONN_LIMIT_EXCEEDED error when executing bt_le_ext_adv_start() function.

I am working with nRF Connect SDK v1.8.0 and SoftDevice HCI Controller.

Please find attached debug output screenshot, ble.c application code and prj.conf file.

Any suggestion would be much appreciated.

Kindest regards,

 7573.prj.conf

/*
*	\file 			ble.c
*
*	\author 		Aritz Martikorena
*
*	\date 			2 Nov 2021
*
*	\brief 			BLE application functions
*
*	\par
*
*	COPYRIGHT NOTICE: (c) 2021, Reactec Ltd.
*	All rights reserved.
*/

#include <stdio.h>
#include <logging/log.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/addr.h>
#include <bluetooth/gatt.h>
#include <bluetooth/conn.h>
#include <bluetooth/scan.h>
#include <bluetooth/services/bas.h>
#include <al/data/rap.h>
#include "ble.h"

LOG_MODULE_REGISTER(BLE, LOG_LEVEL_INF);

/////////////////////////////////////////////////////////////////////////////
// Defines

// Device name length
#define DEV_NAME_LEN        15
// Cohort ID size
#define OP_COHORT_SIZE      3
// TX power (dBm)
#if IS_ENABLED(CONFIG_BT_CTLR_TX_PWR_PLUS_8)
#define TX_POWER_LEVEL_DBM 8
#elif IS_ENABLED(CONFIG_BT_CTLR_TX_PWR_PLUS_7)
#define TX_POWER_LEVEL_DBM 7
#elif IS_ENABLED(CONFIG_BT_CTLR_TX_PWR_PLUS_6)
#define TX_POWER_LEVEL_DBM 6
#elif IS_ENABLED(CONFIG_BT_CTLR_TX_PWR_PLUS_5)
#define TX_POWER_LEVEL_DBM 5
#elif IS_ENABLED(CONFIG_BT_CTLR_TX_PWR_PLUS_4)
#define TX_POWER_LEVEL_DBM 4
#elif IS_ENABLED(CONFIG_BT_CTLR_TX_PWR_PLUS_3)
#define TX_POWER_LEVEL_DBM 3
#elif IS_ENABLED(CONFIG_BT_CTLR_TX_PWR_PLUS_2)
#define TX_POWER_LEVEL_DBM 2
#elif IS_ENABLED(CONFIG_BT_CTLR_TX_PWR_0)
#define TX_POWER_LEVEL_DBM 1
#endif

// BLE device queue to process scan response
#define DEV_QUEUE_SIZE      16

/////////////////////////////////////////////////////////////////////////////
// Types

#pragma pack(push, 1)

typedef struct
{
    const uint16_t reactec_id;
    const uint8_t dev_type;
    uint32_t cust_id;
    uint8_t op_guid[OP_GUID_SIZE];
    uint8_t cohort_id[OP_COHORT_SIZE];
}
adv_data_t;

typedef struct
{
    const uint16_t reactec_id;
    const uint8_t dev_type;
    uint32_t cust_id;
    uint8_t op_guid[OP_GUID_SIZE];
    uint32_t cohort_id;
    uint16_t uwb_addr;
}
ext_adv_data_t;

#pragma pack(pop)

typedef struct {
    bt_addr_le_t ble_addr;
    ble_dev_type_t dev_type;
    int8_t rssi;
    uint32_t cust_id;
    uint8_t op_guid[OP_GUID_SIZE];
    bool guid_avail;
    uint32_t cohort_id;
    uint16_t uwb_addr;
}
ble_dev_t;

/////////////////////////////////////////////////////////////////////////////
// Static Data

static char dev_name[DEV_NAME_LEN + 1];
static struct bt_le_ext_adv *std_adv_ctx = NULL;
static struct bt_le_ext_adv *ext_adv_ctx = NULL;

static struct bt_le_adv_param std_adv_param = {
    .id = BT_ID_DEFAULT,
    .sid = 0,
    .secondary_max_skip = 0,
    .options = BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_USE_IDENTITY | BT_LE_ADV_OPT_SCANNABLE,   
    .interval_min = 160,    // 100 ms (160 * 0.625)
    .interval_max = 240,    // 150 ms (240 * 0.625)
    .peer = NULL
};

static struct bt_le_adv_param ext_adv_param = {
    .id = BT_ID_DEFAULT,
    .sid = 1,
    .secondary_max_skip = 0,
    .options = BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_EXT_ADV | BT_LE_ADV_OPT_CODED |
               BT_LE_ADV_OPT_USE_IDENTITY | BT_LE_ADV_OPT_USE_NAME,
    .interval_min = 160,    // 100 ms (160 * 0.625)
    .interval_max = 240,    // 150 ms (240 * 0.625)
    .peer = NULL
};

// BLE advertising data
static adv_data_t std_adv_data =
    {.reactec_id = CONFIG_BT_COMPANY_ID, .dev_type = BLE_DEV_STD_WEARABLE};
static struct bt_data std_ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    BT_DATA(BT_DATA_MANUFACTURER_DATA, &std_adv_data, sizeof(std_adv_data)),
};

// Extended advertising data
static ext_adv_data_t ext_adv_data =
    {.reactec_id = CONFIG_BT_COMPANY_ID, .dev_type = BLE_DEV_EXT_WEARABLE};
static struct bt_data ext_ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    BT_DATA(BT_DATA_MANUFACTURER_DATA, &ext_adv_data, sizeof(ext_adv_data)),
    BT_DATA_BYTES(BT_DATA_TX_POWER, TX_POWER_LEVEL_DBM),
};

// Scan response data
static struct bt_data sd[] = {
    BT_DATA(BT_DATA_NAME_COMPLETE, dev_name, DEV_NAME_LEN),
    BT_DATA_BYTES(BT_DATA_TX_POWER, TX_POWER_LEVEL_DBM),
};

// Scan parameters
static struct bt_le_scan_param scan_param = {
    .type               = BT_LE_SCAN_TYPE_PASSIVE,
    .options            = BT_LE_SCAN_OPT_CODED,
    .interval           = CONFIG_BT_BACKGROUND_SCAN_INTERVAL,
    .window             = CONFIG_BT_BACKGROUND_SCAN_WINDOW,
};
static struct bt_scan_init_param init_scan_param = {
    .scan_param         = &scan_param,
    .conn_param         = BT_LE_CONN_PARAM_DEFAULT,
    .connect_if_match   = false,
};

// Flag indicating where scanning is running
static bool ble_scan_started;
// Flag indicating where advertising is running
static bool ble_adv_started;

// Current connection count
static uint8_t ble_conn_count;

static ble_dev_t dev_queue[DEV_QUEUE_SIZE];

/////////////////////////////////////////////////////////////////////////////
// Private Function Prototypes

static void scan_filter_match(struct bt_scan_device_info *device_info,
                              struct bt_scan_filter_match *filter_match,
			                  bool connectable);
static void scan_connecting_error(struct bt_scan_device_info *device_info);
static void scan_connecting(struct bt_scan_device_info *device_info,
			                struct bt_conn *conn);
static void connected_cb(struct bt_conn *conn, uint8_t err);
static void disconnected_cb(struct bt_conn *conn, uint8_t reason);
static bool le_param_req_cb(struct bt_conn *conn, struct bt_le_conn_param *param);
static void le_param_updated_cb(struct bt_conn *conn, uint16_t interval,
				                uint16_t latency, uint16_t timeout);
static void ext_adv_sent(struct bt_le_ext_adv *adv,
                         struct bt_le_ext_adv_sent_info *info);
static void ext_adv_connected(struct bt_le_ext_adv *adv,
                         struct bt_le_ext_adv_connected_info *info);
static void ext_adv_scanned(struct bt_le_ext_adv *adv,
                         struct bt_le_ext_adv_scanned_info *info);
static const char* phy_id_to_str(const uint8_t id);

// Connection callback structure
static const struct bt_conn_cb conn_callbacks = {
    .connected = connected_cb,
    .disconnected = disconnected_cb,
    .le_param_req = le_param_req_cb,
    .le_param_updated = le_param_updated_cb,
};

// Advertising callback structure
static const struct bt_le_ext_adv_cb adv_callbacks = {
    .sent = ext_adv_sent,
    .connected = ext_adv_connected,
    .scanned = ext_adv_scanned,
};

// Scanning definition
BT_SCAN_CB_INIT(scan_callbacks, scan_filter_match, NULL,
		scan_connecting_error, scan_connecting);

/////////////////////////////////////////////////////////////////////////////
// Public Functions

/*!
*   \brief      BLE initialization
*   \param      None
*   \return     bool, true if successful, false in case of error
*/
bool ble_init(void)
{
    int err;
    bt_addr_le_t bt_addr;
    const struct bt_scan_manufacturer_data scan_manu_data =
        {.data = &ext_adv_data.reactec_id, .data_len = sizeof(ext_adv_data.reactec_id)};

    // Initialize the Bluetooth stack
    err = bt_enable(NULL);
    if (err) {
        LOG_ERR("Bluetooth init failed (err %d)", err);
        return false;
    }

    LOG_INF("Bluetooth stack init success");

    // Get bt address
    bt_id_get(&bt_addr, CONFIG_BT_ID_MAX);
    LOG_INF("addr %02X:%02X:%02X:%02X:%02X:%02X",
            bt_addr.a.val[5], bt_addr.a.val[4], bt_addr.a.val[3],
            bt_addr.a.val[2], bt_addr.a.val[1], bt_addr.a.val[0]);

    // Register for connection callbacks
    bt_conn_cb_register(&conn_callbacks);

    // Set the device name
    snprintf((char *)dev_name, sizeof(dev_name), "Wearable-%02x%02x%02x",
        bt_addr.a.val[2], bt_addr.a.val[1], bt_addr.a.val[0]);
    bt_set_name(dev_name);
    LOG_INF("name %s", bt_get_name());
#if 0
    // Init Scanning
    bt_scan_init(&init_scan_param);
    bt_scan_cb_register(&scan_callbacks);
    err = bt_scan_filter_add(BT_SCAN_FILTER_TYPE_MANUFACTURER_DATA, &scan_manu_data);
    if (err) {
        LOG_ERR("scanning filters cannot be set (err %d)", err);
        return false;
    }
    err = bt_scan_filter_enable(BT_SCAN_MANUFACTURER_DATA_FILTER, false);
    if (err) {
        LOG_ERR("scanning filters cannot be turned on (err %d)", err);
        return false;
    }
#endif
    // TODO: update DIS service characteristics
    // Serial Number, Firmware, Hardware and Software revision

    return true;
}

/*!
*   \brief      Starts BLE scanning
*   \param      void
*   \return     bool, true if successful, false otherwise
*/
bool ble_start_scanning(void)
{
    int err;

    if (!ble_scan_started) {
        err = bt_scan_start(BT_SCAN_TYPE_SCAN_PASSIVE);
        if (err) {
            LOG_ERR("scanning failed to start (err %d)", err);
            return false;
        }
        ble_scan_started = true;
        LOG_INF("scanning started");
    }
    else {
        LOG_INF("scanner already started");
    }

    return true;
}

/*!
*   \brief      Stops BLE scanning
*   \param      void
*   \return     bool, true if successful, false otherwise
*/
bool ble_stop_scanning(void)
{
    int err;

    if (ble_scan_started) {
        err = bt_scan_stop();
        if (err) {
		    LOG_ERR("scanning failed to stop (err %d)", err);
            return false;
	    }
        ble_scan_started = false;
        LOG_INF("scanning stopped");
    }
    else {
        LOG_INF("scanner already stopped");
    }

    return true;
}

/*!
*   \brief      Inits BLE extended advertising
*   \param      void
*   \return     bool, true if successful, false otherwise
*/
bool ble_ext_init_advertising(void)
{
    int err;
    const uint32_t op_cohort_id = rap_get_op_cohort_id();

    // Set extended advertising data
    ext_adv_data.cust_id = rap_get_op_cust_id();
    memcpy(ext_adv_data.op_guid, rap_get_op_guid(), OP_GUID_SIZE);
    ext_adv_data.cohort_id = op_cohort_id;
    // TODO: get UWB address
    ext_adv_data.uwb_addr = 0xBEEF;
    // Set standard advertising data
    std_adv_data.cust_id = rap_get_op_cust_id();
    memcpy(std_adv_data.op_guid, rap_get_op_guid(), OP_GUID_SIZE);
    memcpy(std_adv_data.cohort_id, &op_cohort_id, OP_COHORT_SIZE);

    // Create extended advertising set
    err = bt_le_ext_adv_create(&ext_adv_param, &adv_callbacks, &ext_adv_ctx);
    if (err) {
        LOG_ERR("failed to create extended advertising set (err %d)", err);
        return false;
    }
    // Create standard advertising set
    err = bt_le_ext_adv_create(&std_adv_param, &adv_callbacks, &std_adv_ctx);
    if (err) {
        LOG_ERR("failed to create standard advertising set (err %d)", err);
        return false;
    }

    // Set extended advertising data
    err = bt_le_ext_adv_set_data(ext_adv_ctx, ext_ad, ARRAY_SIZE(ext_ad), NULL, 0);
    if (err) {
        LOG_ERR("failed to set extended advertising data (err %d)", err);
        return false;
    }
    // Set BLE advertising and scan response data
    err = bt_le_ext_adv_set_data(std_adv_ctx, std_ad, ARRAY_SIZE(std_ad), NULL, 0);
    if (err) {
        LOG_ERR("failed to set standard advertising data (err %d)", err);
        return false;
    }

    LOG_INF("extended advertising initialized");
    return true;
}


/*!
*   \brief      Starts BLE extended advertising
*   \param      void
*   \return     bool, true if successful, false otherwise
*/
bool ble_ext_start_advertising(void)
{
    int err;

    if (!ble_adv_started) {
#if 1
        err = bt_le_ext_adv_start(ext_adv_ctx, NULL);
        if (err) {
            LOG_ERR("failed to start extended advertising (err %d)", err);
            return false;
        }
#endif
#if 1
        err = bt_le_ext_adv_start(std_adv_ctx, NULL);
        if (err) {
            LOG_ERR("failed to start BLE advertising (err %d)", err);
            bt_le_ext_adv_stop(ext_adv_ctx);
            return false;
        }
#endif
        ble_adv_started = true;
        LOG_INF("advertising started");
    }
    else {
        LOG_INF("advertising already started");
    }

    return true;
}

/*!
*   \brief      Stops BLE extended advertising
*   \param      void
*   \return     bool, true if successful, false otherwise
*/
bool ble_ext_stop_advertising(void)
{
    int err;

    if (ble_adv_started) {
        err = bt_le_ext_adv_stop(ext_adv_ctx);
        if (err) {
            LOG_ERR("extended advertising stop error %d", err);
            return false;
        }
        err = bt_le_ext_adv_stop(std_adv_ctx);
        if (err) {
            LOG_ERR("BLE advertising stop error %d", err);
            return false;
        }
        ble_adv_started = false;
        LOG_INF("advertising stopped");
    }
    else {
        LOG_INF("advertising already stopped");
    }

    return true;
}

/////////////////////////////////////////////////////////////////////////////
// Private Functions

/*!
*   \brief      Scan filter matched callback function
*   \param      device_info, connection and advertising information
*   \param      filter_match, filter match status
*   \param      filter_match, inform that device is connectable
*   \return     None
*/
static void scan_filter_match(struct bt_scan_device_info *device_info,
                              struct bt_scan_filter_match *filter_match,
			                  bool connectable)
{
	char addr[BT_ADDR_LE_STR_LEN];
    ble_dev_t dev;

    const struct bt_le_scan_recv_info *recv_info = device_info->recv_info;

	bt_addr_le_to_str(recv_info->addr, addr, sizeof(addr));
	LOG_INF("device found %s %sconnectable", addr, connectable ? "" : "not ");
	LOG_INF("RSSI %d dBm [prim %s sec %s]", recv_info->rssi,
            phy_id_to_str(recv_info->primary_phy), phy_id_to_str(recv_info->secondary_phy));

    if (parse_scan_rsp(device_info, &dev) == true){
        // TODO: queue device
        LOG_INF("queue device");
    }
}

/*!
*   \brief      Connecting error callback function
*   \param      device_info, connection and advertising information
*   \return     None
*/
static void scan_connecting_error(struct bt_scan_device_info *device_info)
{
	LOG_INF("scan connecting failed");
}

/*!
*   \brief      Connecting callback function
*   \param      device_info, connection and advertising information
*   \return     None
*/
static void scan_connecting(struct bt_scan_device_info *device_info,
			                struct bt_conn *conn)
{
    LOG_INF("scan connecting success");
}

/*!
*   \brief      New connection has been stablished callback function
*   \param      conn, new connection object
*   \param      err, HCI error. Zero for success, non-zero otherwise
*   \return     None
*/
static void connected_cb(struct bt_conn *conn, uint8_t err)
{
    struct bt_conn_info conn_info;
    bt_addr_le_t *addr;
	char addr_str[BT_ADDR_LE_STR_LEN];

    if (err) {
        LOG_ERR("Connection failed with err %d", err);
    }
    else {
        ble_conn_count++;

        bt_conn_get_info(conn, &conn_info);
        addr = bt_conn_get_dst(conn);
        bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));

        LOG_INF("connection opened (%d, %s)",
            ble_conn_count, conn_info.role ==  BT_CONN_ROLE_CENTRAL ? "master" : "slave");
        LOG_INF("%s", addr_str);
    }
}

/*!
*   \brief      Connection has been disconnected callback function
*   \param      conn, new connection object
*   \param      reason, HCI reason for the disconnection
*   \return     None
*/
static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
{
    bt_addr_le_t *addr;
	char addr_str[BT_ADDR_LE_STR_LEN];

    addr = bt_conn_get_dst(conn);
    bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));

    if (ble_conn_count > 0) {
        ble_conn_count--;
    }

    LOG_INF("connection closed (0x%x, %d)", reason, ble_conn_count);
    LOG_INF("%s", addr_str);
}

/*!
*   \brief      Request LE connection paramaters callback function
*   \param      conn, connection object
*   \param      param, proposed connection paramaters
*   \return     None
*/
static bool le_param_req_cb(struct bt_conn *conn, struct bt_le_conn_param *param)
{
    bt_addr_le_t *addr;
	char addr_str[BT_ADDR_LE_STR_LEN];

    addr = bt_conn_get_dst(conn);
    bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));

    LOG_INF("connection requested params: interval max=%.2f, min=%.2f, latency=%d, timeout=%d",
                    (float)param->interval_max * 1.25, (float)param->interval_min * 1.25,
                    param->latency, param->timeout * 10);
    LOG_INF("%s", addr_str);

    return true;
}

/*!
*   \brief      LE connection paramaters have been updated callback function
*   \param      conn, connection object
*   \param      interval, connection interval
*   \param      latency, connection latency
*   \param      timeout, connection timeout
*   \return     None
*/
static void le_param_updated_cb(struct bt_conn *conn, uint16_t interval,
				                uint16_t latency, uint16_t timeout)
{
    bt_addr_le_t *addr;
	char addr_str[BT_ADDR_LE_STR_LEN];

    addr = bt_conn_get_dst(conn);
    bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));

    LOG_INF("connection params updated: interval=%.2f, latency=%d, timeout=%d",
                    (float)interval * 1.25, latency, timeout * 10);
    LOG_INF("%s", addr_str);
}

/*!
*   \brief      Advertising set has finished sending adv data callback function
*   \param      adv, advertising set object
*   \param      info, sent event information
*   \return     None
*/
static void ext_adv_sent(struct bt_le_ext_adv *adv,
                         struct bt_le_ext_adv_sent_info *info)
{
	LOG_INF("advertising data sent");
}

/*!
*   \brief      Advertising set has accepted a new connection callback function
*   \param      adv, advertising set object
*   \param      info, connected event information
*   \return     None
*/
static void ext_adv_connected(struct bt_le_ext_adv *adv,
                         struct bt_le_ext_adv_connected_info *info)
{
	LOG_INF("advertising connected");
}

/*!
*   \brief      Advertising set has sent scan response callback function
*   \param      adv, advertising set object
*   \param      info, scanned event information
*   \return     None
*/
static void ext_adv_scanned(struct bt_le_ext_adv *adv,
                         struct bt_le_ext_adv_scanned_info *info)
{
	LOG_INF("advertising connected");
}

/*!
*   \brief      Convert BLE PHY ID to string
*   \param      id, PHY ID
*   \return     const char*, PHY name string
*/
static const char* phy_id_to_str(const uint8_t id)
{
    switch (id)
    {
    case 0:     return "null";
    case 1:     return "1M PHY";
    case 2:     return "2M PHY";
    case 4:     return "Coded PHY";
    default:    return "unknown PHY";
    }
}

  • Hi Artiz, 
    Thanks for the code. I have reproduced the issue here. I have sent the question to our Bluetooth team and will keep you updated if I have an answer from them. 
    Happy New Year ! 

    Hung

  • Morning Hung,

    Do  you have any updates on this ticket please?

    Thanks,

  • Hi Aritz, 

    I have received a tip from the R&D team. You should be able to fix the issue by configure: 
    CONFIG_SDC_SLAVE_COUNT=2 or more. 


    This by default is 1 and it only allows one slave connection. (I assume this is ignored when CONFIG_BT_CENTRAL =n)

  • Hi Hung, 

    Thank you very much for your reply.

    I have done a couple of tests and that fixes the first error I had. I can enable now both advertising sets in 1M and coded PHY at the same time.

    However, unfortunately I am still seeing the second error: device A scanning does not detect Coded PHY advertising packets from device B if advertising is enabled on device A.

    Could you please double check that error with the R&D team?

    Regards,

  • Hi Artisz, 

    Just to clarify, issue with scanning, do you have the issue if you don't do advertising on device A and only do scanning ? Have you tried to reduce the advertising interval on device A ? 


    I assume you have verified that device B was actually doing some advertising (using a phone for example). 

Related