What new features were released in ncs1.7.0 about advertising?

Recently, Nordic released ncs1.7.0, I wonder if it supports the following features:

1. What is the maximum length of broadcast data for extended advertisement? It cannot exceed 251 bytes in ncs1.6.0.

2. Periodic advertising can't set advertising data via calling bt_le_per_adv_set_data() in ncs1.6.0, is it worked now?

3. Can BLE AoA be used for product?

  • Add the question:

    4. Why is CONFIG_BT_PER_ADV_SYNC_MAX maximum is 64? What does limit it? Core spec, timing sequence, memory or others?

    5. For format of IQ sampling, I is 16-bits unsigned and Q is 12-bits signed in nRF52833 production specification, but both I and Q are 8-bit signed in ncs. Why are they different?

  • Hi

    1. The same goes for nRFConnect SDK (NCS) v1.7.0 as well. One advertisement can be up t 251 bytes. You can also used chained advertising to reach up to 255 bytes of advertising. We will support larger chained advertising in the future.

    2. The bt_le_per_adv_set_data seems to be part of the Zephyr stack, so it should be usable if you're using the Zephyr controller.

    3. Yes, the Direction Finding sample projects in NCS are BLE compatible and can be used in products, but you'll likely want to make some changes to the sample projects to fit your product as good as possible.

    4. There does not seem to be a limit in the Bluetooth specification to the max number of simultaneous periodic advertising syncs. From what I can tell the restriction may be due to the use of a uint8_t for indices and memory buffer counts. If more than 64 instances are buildable and functional, you are free to increase this to what you'd like.

    5. From what I can see both I and Q samples are 12 bits signed. Where do you see that the I samples are 16-bits unsigned in the documentation and 8-bits in NCS?

    Best regards,

    Simon

  • Hi Simonr,

    Question-2: I run nrf/samples/bluetooth/sample direction_finding_connectionless_tx, and it use zephyr controller, but it can't set advertising data by calling bt_le_per_adv_set_data. And it's same to run sample zephyr/samples/bluetooth/periodic_adv. Can you help me to confirm the api wether it worked?

    Question-5: I'm sorry that I misunderstood the documentation, But it's 8-bits signed in file "zephyr/include/bluetooth/hci.h", 

  • Hi

    2. The advertising sets in the direction finding example uses the following struct to set up the advertising sets. What kind of issues do you have trying to set up the bt_le_per_adv_set_data?

    static struct bt_le_ext_adv *adv_set;
    
    const static struct bt_le_adv_param param =
    		BT_LE_ADV_PARAM_INIT(BT_LE_ADV_OPT_EXT_ADV |
    				     BT_LE_ADV_OPT_USE_NAME,
    				     BT_GAP_ADV_FAST_INT_MIN_2,
    				     BT_GAP_ADV_FAST_INT_MAX_2,
    				     NULL);
    
    static struct bt_le_ext_adv_start_param ext_adv_start_param = {
    	.timeout = 0,
    	.num_events = 0,
    };
    
    const static struct bt_le_per_adv_param per_adv_param = {
    	.interval_min = BT_GAP_ADV_SLOW_INT_MIN,
    	.interval_max = BT_GAP_ADV_SLOW_INT_MAX,
    	.options = BT_LE_ADV_OPT_USE_TX_POWER,
    };

    5. The Bluetooth SIG specification only supports up to 8 bit resolution of the I and Q samples. which is why these are set to 8 bit in our example project. The nRF52833 supports a resolution of up to 12 bits, but we've not added support for a proprietary extension yet.

    Best regards,

    Simon

  • Hi Simonr,

    Thank you so much.

    I understood it. The reason is my smart phone can't scan periodic advertising data, it led me to think bt_le_per_adv_set_data can't work. So, I run direction_finding_connectionless_rx sample it can scan periodic advertising data.

    But it only create synchronization with a slave even if setting CONFIG_BT_PER_ADV_SYNC_MAX=3 in the direction_finding_connectionless_rx sample. So I did some changes in the sample, here is the source code,

    /*
     * Copyright (c) 2021 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    
    #include <stddef.h>
    #include <errno.h>
    #include <zephyr.h>
    
    #include <bluetooth/bluetooth.h>
    #include <bluetooth/direction.h>
    
    #define DEVICE_NAME CONFIG_BT_DEVICE_NAME
    #define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
    #define PEER_NAME_LEN_MAX 30
    #define TIMEOUT_SYNC_CREATE K_SECONDS(10)
    
    typedef struct {
    	uint8_t flag;
    	uint8_t mac[6];
    } sync_info_t;
    
    static sync_info_t m_sync_info[CONFIG_BT_PER_ADV_SYNC_MAX];
    
    static struct bt_le_per_adv_sync *m_sync;
    static bt_addr_le_t per_addr;
    static bool scan_enabled;
    static uint8_t per_sid;
    
    #if defined(CONFIG_BT_CTLR_DF_ANT_SWITCH_RX)
    /* Example sequence of antenna switch patterns for antenna matrix designed by
     * Nordic. For more information about antenna switch patterns see README.rst.
     */
    static const uint8_t ant_patterns[] = { 0x2, 0x0, 0x5, 0x6, 0x1, 0x4, 0xC, 0x9, 0xE, 0xD, 0x8, 0xA };
    #endif /* CONFIG_BT_CTLR_DF_ANT_SWITCH_RX */
    
    static void rx_per_adv_handler(struct k_work *wk);
    K_WORK_DEFINE(m_sync_create, rx_per_adv_handler);
    
    static void sync_del_handler(struct k_work *wk);
    K_WORK_DEFINE(m_sync_del, sync_del_handler);
    
    static void timeout_handler(struct k_timer *);
    K_TIMER_DEFINE(m_timer, timeout_handler, NULL);
    
    K_SEM_DEFINE(m_sem, 1, 1);
    
    static uint64_t m_start;
    
    static inline uint32_t adv_interval_to_ms(uint16_t interval)
    {
    	return interval * 5 / 4;
    }
    
    static const char *phy2str(uint8_t phy)
    {
    	switch (phy) {
    	case 0:
    		return "No packets";
    	case BT_GAP_LE_PHY_1M:
    		return "LE 1M";
    	case BT_GAP_LE_PHY_2M:
    		return "LE 2M";
    	case BT_GAP_LE_PHY_CODED:
    		return "LE Coded";
    	default:
    		return "Unknown";
    	}
    }
    
    static const char *cte_type2str(uint8_t type)
    {
    	switch (type) {
    	case BT_DF_CTE_TYPE_AOA:
    		return "AOA";
    	case BT_DF_CTE_TYPE_AOD_1US:
    		return "AOD 1 [us]";
    	case BT_DF_CTE_TYPE_AOD_2US:
    		return "AOD 2 [us]";
    	case BT_DF_CTE_TYPE_NONE:
    		return "";
    	default:
    		return "Unknown";
    	}
    }
    
    static const char *packet_status2str(uint8_t status)
    {
    	switch (status) {
    	case BT_DF_CTE_CRC_OK:
    		return "CRC OK";
    	case BT_DF_CTE_CRC_ERR_CTE_BASED_TIME:
    		return "CRC not OK, CTE Info OK";
    	case BT_DF_CTE_CRC_ERR_CTE_BASED_OTHER:
    		return "CRC not OK, Sampled other way";
    	case BT_DF_CTE_INSUFFICIENT_RESOURCES:
    		return "No resources";
    	default:
    		return "Unknown";
    	}
    }
    
    // static bool data_cb(struct bt_data *data, void *user_data)
    // {
    // 	char *name = user_data;
    // 	uint8_t len;
    
    // 	switch (data->type) {
    // 	case BT_DATA_NAME_SHORTENED:
    // 	case BT_DATA_NAME_COMPLETE:
    // 		len = MIN(data->data_len, PEER_NAME_LEN_MAX - 1);
    // 		memcpy(name, data->data, len);
    // 		name[len] = '\0';
    // 		return false;
    // 	default:
    // 		return true;
    // 	}
    // }
    
    static void sync_del_handler(struct k_work *wk)
    {
    	printk("Create sync timeout, and deleting Periodic Advertising Sync...");
    	int err = bt_le_per_adv_sync_delete(m_sync);
    	if (err) {
    		printk("failed (err %d)\n", err);
    		return;
    	}
    	printk("success\n");
    }
    
    static void timeout_handler(struct k_timer *t)
    {
    	k_sem_give(&m_sem);
    
    	k_work_submit(&m_sync_del);
    }
    
    static void enable_cte_rx(void)
    {
    	int err;
    
    	const struct bt_df_per_adv_sync_cte_rx_param cte_rx_params = {
    		.max_cte_count = 5,
    #if defined(CONFIG_BT_CTLR_DF_ANT_SWITCH_RX)
    		.cte_type = BT_DF_CTE_TYPE_ALL,
    		.slot_durations = 0x2,
    		.num_ant_ids = ARRAY_SIZE(ant_patterns),
    		.ant_ids = ant_patterns,
    #else
    		.cte_type = BT_DF_CTE_TYPE_AOD_1US | BT_DF_CTE_TYPE_AOD_2US,
    #endif /* CONFIG_BT_CTLR_DF_ANT_SWITCH_RX */
    	};
    
    	printk("Enable receiving of CTE...\n");
    	err = bt_df_per_adv_sync_cte_rx_enable(m_sync, &cte_rx_params);
    	if (err) {
    		printk("failed (err %d)\n", err);
    		return;
    	}
    	printk("success. CTE receive enabled.\n");
    }
    
    static void sync_cb(struct bt_le_per_adv_sync *sync, struct bt_le_per_adv_sync_synced_info *info)
    {
    	char le_addr[BT_ADDR_LE_STR_LEN];
    
    	k_timer_stop(&m_timer);
    
    	bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
    
    	uint64_t delta = k_uptime_delta(&m_start);
    
    	printk("sync success...[%u]: [DEVICE]: %s, "
    	       "Interval 0x%04x (%u ms), PHY %s -- take %lldms.\n",
    	       bt_le_per_adv_sync_get_index(sync), le_addr, info->interval,
    	       adv_interval_to_ms(info->interval), phy2str(info->phy), delta);
    
    	for (uint8_t i = 0; i < CONFIG_BT_PER_ADV_SYNC_MAX; ++i) {
    		if (m_sync_info[i].flag == 0) {
    			memcpy(m_sync_info[i].mac, info->addr->a.val, 6);
    			m_sync_info[i].flag = 1;
    			break;
    		}
    	}
    
    	k_sem_give(&m_sem);
    
    	enable_cte_rx();
    }
    
    static void term_cb(struct bt_le_per_adv_sync *sync,
    		    const struct bt_le_per_adv_sync_term_info *info)
    {
    	char le_addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
    
    	printk("PER_ADV_SYNC[%u]: [DEVICE]: %s sync terminated\n",
    	       bt_le_per_adv_sync_get_index(sync), le_addr);
    	
    	for (uint8_t i = 0; i < CONFIG_BT_PER_ADV_SYNC_MAX; ++i) {
    		if(memcmp(m_sync_info[i].mac, info->addr->a.val, 6) == 0) {
    			memset(m_sync_info[i].mac, 0, 6);
    			m_sync_info[i].flag = 0;
    		}
    	}
    }
    
    static void recv_cb(struct bt_le_per_adv_sync *sync,
    		    const struct bt_le_per_adv_sync_recv_info *info, struct net_buf_simple *buf)
    {
    	char le_addr[BT_ADDR_LE_STR_LEN];
    	char data_str[BT_GAP_ADV_MAX_EXT_ADV_DATA_LEN];
    
    	bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
    	bin2hex(buf->data, buf->len, data_str, sizeof(data_str));
    
    	printk("PER_ADV_SYNC[%u]: [DEVICE]: %s, tx_power %i, "
    	       "RSSI %i, CTE %s, data length %u, data: %s\n",
    	       bt_le_per_adv_sync_get_index(sync), le_addr, info->tx_power,
    	       info->rssi, cte_type2str(info->cte_type), buf->len, data_str);
    }
    
    static void cte_recv_cb(struct bt_le_per_adv_sync *sync,
    			struct bt_df_per_adv_sync_iq_samples_report const *report)
    {
    	printk("CTE[%u]: samples count %d, cte type %s, slot durations: %u [us], "
    	       "packet status %s, RSSI %i\n",
    	       bt_le_per_adv_sync_get_index(sync), report->sample_count,
    	       cte_type2str(report->cte_type), report->slot_durations,
    	       packet_status2str(report->packet_status), report->rssi);
    }
    
    static struct bt_le_per_adv_sync_cb sync_callbacks = {
    	.synced = sync_cb,
    	.term = term_cb,
    	.recv = recv_cb,
    	.cte_report_cb = cte_recv_cb,
    };
    
    static void scan_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *buf)
    {
    	char le_addr[BT_ADDR_LE_STR_LEN];
    	// char name[PEER_NAME_LEN_MAX];
    
    	// (void)memset(name, 0, sizeof(name));
    
    	// bt_data_parse(buf, data_cb, name);
    
    	// printk("[DEVICE]: %s, AD evt type %u, Tx Pwr: %i, RSSI %i %s C:%u S:%u "
    	//        "D:%u SR:%u E:%u Prim: %s, Secn: %s, Interval: 0x%04x (%u ms), "
    	//        "SID: %u\n",
    	//        le_addr, info->adv_type, info->tx_power, info->rssi, name,
    	//        (info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) != 0,
    	//        (info->adv_props & BT_GAP_ADV_PROP_SCANNABLE) != 0,
    	//        (info->adv_props & BT_GAP_ADV_PROP_DIRECTED) != 0,
    	//        (info->adv_props & BT_GAP_ADV_PROP_SCAN_RESPONSE) != 0,
    	//        (info->adv_props & BT_GAP_ADV_PROP_EXT_ADV) != 0, phy2str(info->primary_phy),
    	//        phy2str(info->secondary_phy), info->interval, adv_interval_to_ms(info->interval),
    	//        info->sid);
    
    	if (info->interval != 0) {
    		for (uint8_t i = 0; i < CONFIG_BT_PER_ADV_SYNC_MAX; ++i) {
    			if ((m_sync_info[i].flag != 0) &&
    			    (memcmp(info->addr->a.val, m_sync_info[i].mac, 6) == 0)) {
    				return;
    			}
    		}
    
    		if (k_sem_take(&m_sem, K_NO_WAIT) == 0) {
    			bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
    			printk("data_len: %d, Mac: %s\n", buf->len, le_addr);
    			per_sid = info->sid;
    			bt_addr_le_copy(&per_addr, info->addr);
    			k_work_submit(&m_sync_create);
    		} else {
    			printk("sync create BUSY...\n");
    		}
    	}
    }
    
    static struct bt_le_scan_cb scan_callbacks = {
    	.recv = scan_recv,
    };
    
    static void create_sync(void)
    {
    	struct bt_le_per_adv_sync_param sync_create_param;
    	int err;
    
    	printk("Creating Periodic Advertising Sync...");
    	bt_addr_le_copy(&sync_create_param.addr, &per_addr);
    	sync_create_param.options = 0;
    	sync_create_param.sid = per_sid;
    	sync_create_param.skip = 0;
    	sync_create_param.timeout = 0xa;
    	err = bt_le_per_adv_sync_create(&sync_create_param, &m_sync);
    	if (err) {
    		printk("failed (err %d)\n", err);
    		return;
    	}
    	printk("success.\n");
    
    	m_start = k_uptime_get();
    
    	printk("Waiting for periodic sync...\n");
    }
    
    static void rx_per_adv_handler(struct k_work *wk)
    {
    	printk("success. Found periodic advertising.\n");
    	
    	create_sync();
    
    	k_timer_start(&m_timer, K_MSEC(1000), K_NO_WAIT);
    }
    
    static int scan_init(void)
    {
    	printk("Scan callbacks register...");
    	bt_le_scan_cb_register(&scan_callbacks);
    	printk("success.\n");
    
    	printk("Periodic Advertising callbacks register...");
    	bt_le_per_adv_sync_cb_register(&sync_callbacks);
    	printk("success.\n");
    
    	return 0;
    }
    
    static int scan_enable(void)
    {
    	struct bt_le_scan_param param = {
    		.type = BT_LE_SCAN_TYPE_ACTIVE,
    		.options = BT_LE_SCAN_OPT_FILTER_DUPLICATE,
    		.interval = BT_GAP_SCAN_FAST_INTERVAL,
    		.window = BT_GAP_SCAN_FAST_WINDOW,
    		.timeout = 0U,
    	};
    
    	int err;
    
    	printk("Start scanning...");
    	err = bt_le_scan_start(&param, NULL);
    	if (err) {
    		printk("failed (err %d)\n", err);
    		return err;
    	}
    	printk("success\n");
    
    	return 0;
    }
    
    // static void scan_disable(void)
    // {
    // 	int err;
    
    // 	printk("Scan disable...");
    // 	err = bt_le_scan_stop();
    // 	if (err) {
    // 		printk("failed (err %d)\n", err);
    // 		return;
    // 	}
    // 	printk("Success.\n");
    
    // 	scan_enabled = false;
    // }
    
    void main(void)
    {
    	int err;
    
    	printk("Starting Connectionless Locator Demo\n");
    
    	printk("Bluetooth initialization...");
    	err = bt_enable(NULL);
    	if (err) {
    		printk("failed (err %d)\n", err);
    	}
    	printk("success\n");
    
    	scan_init();
    
    	scan_enabled = false;
    	k_sleep(K_SECONDS(5));
    	memset(m_sync_info, 0, sizeof(m_sync_info));
    
    	scan_enable();
    
    	while (true) {
    		k_sleep(K_SECONDS(1));
    	}
    }
    

    It can establish synchronization with 3 slaves at the same time, but can only receive IQ data from 1 tag, and sometimes the IQ-data was wrong, the following is running log

Related