From 6c76b70af62aaaebd465a31142bfe73c6788f85e Mon Sep 17 00:00:00 2001 From: Piotr Pryga Date: Mon, 26 Apr 2021 13:47:46 +0200 Subject: [PATCH] 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 --- .../CMakeLists.txt | 14 + .../README.rst | 78 ++++ .../boards/nrf52833dk_nrf52833.conf | 6 + .../boards/nrf52833dk_nrf52833.overlay | 26 ++ .../overlay-aod.conf | 2 + .../prj.conf | 12 + .../sample.yaml | 10 + .../src/main.c | 371 ++++++++++++++++++ .../CMakeLists.txt | 2 +- 9 files changed, 520 insertions(+), 1 deletion(-) create mode 100644 samples/bluetooth/direction_finding_connectionless_rx/CMakeLists.txt create mode 100644 samples/bluetooth/direction_finding_connectionless_rx/README.rst create mode 100644 samples/bluetooth/direction_finding_connectionless_rx/boards/nrf52833dk_nrf52833.conf create mode 100644 samples/bluetooth/direction_finding_connectionless_rx/boards/nrf52833dk_nrf52833.overlay create mode 100644 samples/bluetooth/direction_finding_connectionless_rx/overlay-aod.conf create mode 100644 samples/bluetooth/direction_finding_connectionless_rx/prj.conf create mode 100644 samples/bluetooth/direction_finding_connectionless_rx/sample.yaml create mode 100644 samples/bluetooth/direction_finding_connectionless_rx/src/main.c diff --git a/samples/bluetooth/direction_finding_connectionless_rx/CMakeLists.txt b/samples/bluetooth/direction_finding_connectionless_rx/CMakeLists.txt new file mode 100644 index 00000000000..47ce641c147 --- /dev/null +++ b/samples/bluetooth/direction_finding_connectionless_rx/CMakeLists.txt @@ -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 +) diff --git a/samples/bluetooth/direction_finding_connectionless_rx/README.rst b/samples/bluetooth/direction_finding_connectionless_rx/README.rst new file mode 100644 index 00000000000..c1dd1448dc4 --- /dev/null +++ b/samples/bluetooth/direction_finding_connectionless_rx/README.rst @@ -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 ` 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. diff --git a/samples/bluetooth/direction_finding_connectionless_rx/boards/nrf52833dk_nrf52833.conf b/samples/bluetooth/direction_finding_connectionless_rx/boards/nrf52833dk_nrf52833.conf new file mode 100644 index 00000000000..857c5ffe2a6 --- /dev/null +++ b/samples/bluetooth/direction_finding_connectionless_rx/boards/nrf52833dk_nrf52833.conf @@ -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 diff --git a/samples/bluetooth/direction_finding_connectionless_rx/boards/nrf52833dk_nrf52833.overlay b/samples/bluetooth/direction_finding_connectionless_rx/boards/nrf52833dk_nrf52833.overlay new file mode 100644 index 00000000000..68f89379d99 --- /dev/null +++ b/samples/bluetooth/direction_finding_connectionless_rx/boards/nrf52833dk_nrf52833.overlay @@ -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>; +}; diff --git a/samples/bluetooth/direction_finding_connectionless_rx/overlay-aod.conf b/samples/bluetooth/direction_finding_connectionless_rx/overlay-aod.conf new file mode 100644 index 00000000000..7fd7cf303ba --- /dev/null +++ b/samples/bluetooth/direction_finding_connectionless_rx/overlay-aod.conf @@ -0,0 +1,2 @@ +# Disable AoD Feature (antenna switching) in Tx mode +CONFIG_BT_CTLR_DF_ANT_SWITCH_RX=n diff --git a/samples/bluetooth/direction_finding_connectionless_rx/prj.conf b/samples/bluetooth/direction_finding_connectionless_rx/prj.conf new file mode 100644 index 00000000000..6de448fa5a4 --- /dev/null +++ b/samples/bluetooth/direction_finding_connectionless_rx/prj.conf @@ -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 diff --git a/samples/bluetooth/direction_finding_connectionless_rx/sample.yaml b/samples/bluetooth/direction_finding_connectionless_rx/sample.yaml new file mode 100644 index 00000000000..3fdd333aad3 --- /dev/null +++ b/samples/bluetooth/direction_finding_connectionless_rx/sample.yaml @@ -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 diff --git a/samples/bluetooth/direction_finding_connectionless_rx/src/main.c b/samples/bluetooth/direction_finding_connectionless_rx/src/main.c new file mode 100644 index 00000000000..1b1d1c4c000 --- /dev/null +++ b/samples/bluetooth/direction_finding_connectionless_rx/src/main.c @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#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(¶m, 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); +} diff --git a/samples/bluetooth/direction_finding_connectionless_tx/CMakeLists.txt b/samples/bluetooth/direction_finding_connectionless_tx/CMakeLists.txt index 7d5da92284f..78c24fb3fd2 100644 --- a/samples/bluetooth/direction_finding_connectionless_tx/CMakeLists.txt +++ b/samples/bluetooth/direction_finding_connectionless_tx/CMakeLists.txt @@ -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)