samples: Bluetooth: Add Direction Finding connectionless Rx sample

Add an application that uses Direction Finding API for reception
and sampling of CTE in connectionless mode (periodic adverising PDUs).

Signed-off-by: Piotr Pryga <piotr.pryga@nordicsemi.no>
This commit is contained in:
Piotr Pryga 2021-04-26 13:47:46 +02:00 committed by Carles Cufí
commit 6c76b70af6
9 changed files with 520 additions and 1 deletions

View file

@ -0,0 +1,14 @@
#
# Copyright (c) 2021 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(direction_finding_connectionless_rx)
target_sources(app PRIVATE
src/main.c
)

View file

@ -0,0 +1,78 @@
.. bluetooth_direction_finding_connectionless_rx:
Bluetooth: Direction Finding Periodic Advertising Locator
#########################################################
Overview
********
A simple application demonstrating the BLE Direction Finding CTE Locator
functionality by receiving and sampling sending Constant Tone Extension with
periodic advertising PDUs.
Requirements
************
* nRF52833DK board with nRF52833 SOC
* antenna matrix for AoA (optional)
Building and Running
********************
This sample can be found under :zephyr_file:`samples/bluetooth/direction_finding_connectionless_rx`
in the Zephyr tree.
By default the application supports Angle of Arrival and Angle of Departure mode.
To use Angle of Departure mode only, build this application as follows:
.. zephyr-app-commands::
:zephyr-app: samples/bluetooth/direction_finding_connectionless_rx
:host-os: unix
:board: nrf52833dk_nrf52833
:gen-args: -DOVERLAY_CONFIG=overlay-aod.conf
:goals: build flash
:compact:
See :ref:`bluetooth samples section <bluetooth-samples>` for common information
about bluetooth samples.
Antenna matrix configuration
****************************
To use this sample when Angle of Arrival mode is enabled, additional GPIOS configuration
is required to control the antenna array. Example of such configuration
is provided in devicetree overlay
:zephyr_file:`samples/bluetooth/direction_finding_connectionless_rx/boards/nrf52833dk_nrf52833.overlay`.
The overlay file provides the information about which GPIOs should be used by the Radio peripheral
to switch between antenna patches during the CTE transmission in the AoD mode. At least two GPIOs
must be provided to enable antenna switching.
The GPIOs are used by the Radio peripheral in order given by the :code:`dfegpio#-gpios` properties.
The order is important because it affects mapping of the antenna switching patterns to GPIOs
(see `Antenna patterns`_).
To successfully use the Direction Finding locator when the AoA mode is enabled, provide the
following data related to antenna matrix design:
* Provide the GPIO pins to :code:`dfegpio#-gpios` properties in
:zephyr_file:`samples/bluetooth/direction_finding_connectionless_rx/boards/nrf52833dk_nrf52833.overlay`
file
* Provide the default antenna that will be used to transmit PDU :code:`dfe-pdu-antenna` property in
:zephyr_file:`samples/bluetooth/direction_finding_connectionless_rx/boards/nrf52833dk_nrf52833.overlay`
file
* Update the antenna switching patterns in :cpp:var:`ant_patterns` array in
:zephyr_file:`samples/bluetooth/direction_finding_connectionless_tx/src/main.c`..
Antenna patterns
****************
The antenna switching pattern is a binary number where each bit is applied to a particular antenna
GPIO pin. For example, the pattern 0x3 means that antenna GPIOs at index 0,1 will be set, while
the following are left unset.
This also means that, for example, when using four GPIOs, the pattern count cannot be greater
than 16 and maximum allowed value is 15.
If the number of switch-sample periods is greater than the number of stored switching patterns,
then the radio loops back to the first pattern.

View file

@ -0,0 +1,6 @@
CONFIG_BT_CTLR_ADV_EXT=y
CONFIG_BT_CTLR_SYNC_PERIODIC=y
# Enable Direction Finding Feature including AoA and AoD
CONFIG_BT_CTLR_DF=y
CONFIG_BT_CTLR_DF_ANT_SWITCH_TX=n

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
&radio {
status = "okay";
/* This is an example number of antennas that may be available
* on antenna matrix board.
*/
dfe-antenna-num = < 10 >;
/* This is an example switch pattern that will be used to set an
* antenna for Tx PDU (period before start of Tx CTE).
*/
dfe-pdu-antenna = <0x1>;
/* These are example GPIO pin numbers that are provided to
* Radio peripheral. The pins will be acquired by Radio to
* drive antenna switching when AoD is enabled.
*/
dfegpio0-gpios = <&gpio0 1 0>;
dfegpio1-gpios = <&gpio0 2 0>;
dfegpio2-gpios = <&gpio0 3 0>;
dfegpio3-gpios = <&gpio0 4 0>;
};

View file

@ -0,0 +1,2 @@
# Disable AoD Feature (antenna switching) in Tx mode
CONFIG_BT_CTLR_DF_ANT_SWITCH_RX=n

View file

@ -0,0 +1,12 @@
CONFIG_BT=y
CONFIG_BT_CTLR=y
CONFIG_BT_LL_SW_SPLIT=y
CONFIG_BT_DEVICE_NAME="DF Connectionless Locator App"
CONFIG_BT_EXT_ADV=y
CONFIG_BT_PER_ADV_SYNC=y
CONFIG_BT_OBSERVER=y
# Enable Direction Finding Feature including AoA and AoD
CONFIG_BT_DF=y
CONFIG_BT_DF_CONNECTIONLESS_CTE_RX=y

View file

@ -0,0 +1,10 @@
sample:
name: Direction Finding Connectionless Locator
tests:
sample.bluetooth.direction_finding_connectionless_rx:
harness: bluetooth
platform_allow: nrf52833dk_nrf52833
tags: bluetooth
sample.bluetooth.direction_finding_connectionless_rx.aod:
extra_args: OVERLAY_CONFIG="overlay-aod.conf"
platform_allow: nrf52833dk_nrf52833

View file

@ -0,0 +1,371 @@
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include <errno.h>
#include <zephyr.h>
#include <sys/printk.h>
#include <sys/byteorder.h>
#include <sys/util.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/direction.h>
#define DEVICE_NAME CONFIG_BT_DEVICE_NAME
#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
#define NAME_LEN 30
#define TIMEOUT_SYNC_CREATE K_SECONDS(10)
static struct bt_le_per_adv_sync_param sync_create_param;
static struct bt_le_per_adv_sync *sync;
static bt_addr_le_t per_addr;
static bool per_adv_found;
static bool scan_enabled;
static uint8_t per_sid;
static K_SEM_DEFINE(sem_per_adv, 0, 1);
static K_SEM_DEFINE(sem_per_sync, 0, 1);
static K_SEM_DEFINE(sem_per_sync_lost, 0, 1);
#if defined(CONFIG_BT_CTLR_DF_ANT_SWITCH_RX)
const static uint8_t ant_patterns[] = { 0x1, 0x2, 0x3, 0x4, 0x5,
0x6, 0x7, 0x8, 0x9, 0xA };
#endif /* CONFIG_BT_CTLR_DF_ANT_SWITCH_RX */
static bool data_cb(struct bt_data *data, void *user_data);
static void create_sync(void);
static void scan_recv(const struct bt_le_scan_recv_info *info,
struct net_buf_simple *buf);
static void sync_cb(struct bt_le_per_adv_sync *sync,
struct bt_le_per_adv_sync_synced_info *info);
static void term_cb(struct bt_le_per_adv_sync *sync,
const struct bt_le_per_adv_sync_term_info *info);
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);
static void scan_recv(const struct bt_le_scan_recv_info *info,
struct net_buf_simple *buf);
static void scan_disable(void);
static void cte_recv_cb(struct bt_le_per_adv_sync *sync,
struct bt_df_per_adv_sync_iq_samples_report const *report);
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 struct bt_le_scan_cb scan_callbacks = {
.recv = scan_recv,
};
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 *pocket_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, NAME_LEN - 1);
memcpy(name, data->data, len);
name[len] = '\0';
return false;
default:
return true;
}
}
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];
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
printk("PER_ADV_SYNC[%u]: [DEVICE]: %s synced, "
"Interval 0x%04x (%u ms), PHY %s\n",
bt_le_per_adv_sync_get_index(sync), le_addr,
info->interval, info->interval * 5 / 4, phy2str(info->phy));
k_sem_give(&sem_per_sync);
}
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);
k_sem_give(&sem_per_sync_lost);
}
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[129];
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,
pocket_status2str(report->packet_status), report->rssi);
}
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[NAME_LEN];
(void)memset(name, 0, sizeof(name));
bt_data_parse(buf, data_cb, name);
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
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, info->interval * 5 / 4, info->sid);
if (!per_adv_found && info->interval != 0) {
per_adv_found = true;
per_sid = info->sid;
bt_addr_le_copy(&per_addr, info->addr);
k_sem_give(&sem_per_adv);
}
}
static void create_sync(void)
{
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, &sync);
if (err != 0) {
printk("failed (err %d)\n", err);
return;
}
printk("success.\n");
}
static int delete_sync(void)
{
int err;
printk("Deleting Periodic Advertising Sync...");
err = bt_le_per_adv_sync_delete(sync);
if (err != 0) {
printk("failed (err %d)\n", err);
return err;
}
printk("success\n");
return 0;
}
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(sync, &cte_rx_params);
if (err != 0) {
printk("failed (err %d)\n", err);
return;
}
printk("success. CTE receive enabled.\n");
}
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;
if (!scan_enabled) {
printk("Start scanning...");
err = bt_le_scan_start(&param, NULL);
if (err != 0) {
printk("failed (err %d)\n", err);
return err;
}
printk("success\n");
scan_enabled = true;
}
return 0;
}
static void scan_disable(void)
{
int err;
printk("Scan disable...");
err = bt_le_scan_stop();
if (err != 0) {
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 != 0) {
printk("failed (err %d)\n", err);
}
printk("success\n");
scan_init();
scan_enabled = false;
do {
scan_enable();
printk("Waiting for periodic advertising...");
per_adv_found = false;
err = k_sem_take(&sem_per_adv, K_FOREVER);
if (err != 0) {
printk("failed (err %d)\n", err);
return;
}
printk("success. Found periodic advertising.\n");
create_sync();
printk("Waiting for periodic sync...\n");
err = k_sem_take(&sem_per_sync, TIMEOUT_SYNC_CREATE);
if (err != 0) {
printk("failed (err %d)\n", err);
err = delete_sync();
if (err != 0) {
return;
}
continue;
}
printk("success. Periodic sync established.\n");
enable_cte_rx();
/* Disable scan to cleanup output */
scan_disable();
printk("Waiting for periodic sync lost...\n");
err = k_sem_take(&sem_per_sync_lost, K_FOREVER);
if (err != 0) {
printk("failed (err %d)\n", err);
return;
}
printk("Periodic sync lost.\n");
} while (true);
}

View file

@ -1,7 +1,7 @@
#
# Copyright (c) 2021 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.13.1)