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,
/*
* \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";
}
}

