Issue facing in connection between central device (1st "xiao nrf52840 sense") to peripheral device (2nd "xiao nrf52840 sense")

Hi everyone,

I’m a complete beginner and need some help. I’m working with two XIAO nRF52840 Sense boards: one as a central and the other as a peripheral. My goal is to send a notification from the peripheral to the central every time an external touch button connected to the peripheral is pressed. The central should then display the button state in its serial monitor.

The issue I’m facing is that the central does not successfully subscribe to notifications from the peripheral. In the central’s log messages, I only see a “discover complete” message, but nothing about “subscription complete” or “subscribed.” As a result, the button state changes are not reflected on the central’s serial monitor.

Interestingly, when I use the nRF Connect Android app, I’m able to connect to the peripheral, enable notifications, and see the button state changes in the app.

I’ve attached the codes I’m using for both the central and peripheral, along with the central’s serial monitor log. Any guidance or suggestions on what might be causing the issue would be greatly appreciated.

I am using SDK and toolchain both 2.7.0

//Central code
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/byteorder.h>

static struct bt_conn *default_conn;
static struct bt_gatt_discover_params discover_params;
static struct bt_gatt_subscribe_params subscribe_params;

static struct bt_uuid_16 discover_uuid = BT_UUID_INIT_16(0);
#define BT_UUID_CUSTOM_SERVICE_VAL BT_UUID_128_ENCODE(0x00001523, 0x1212, 0xefde, 0x1523, 0x785feabcd123)
static struct bt_uuid_128 custom_service_uuid = BT_UUID_INIT_128(BT_UUID_CUSTOM_SERVICE_VAL);

#define BT_UUID_custom_service_VAL BT_UUID_128_ENCODE(0x00001523, 0x1212, 0xefde, 0x1523, 0x785feabcd123)
#define BT_UUID_custom_char_VAL                                                                     \
BT_UUID_128_ENCODE(0x00001524, 0x1212, 0xefde, 0x1523, 0x785feabcd123)

#define BT_UUID_custom_service BT_UUID_DECLARE_128(BT_UUID_custom_service_VAL)
#define BT_UUID_custom_char BT_UUID_DECLARE_128(BT_UUID_custom_char_VAL)

static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, struct net_buf_simple *ad);

static uint8_t notify_func(struct bt_conn *conn,
			   struct bt_gatt_subscribe_params *params,
			   const void *data, uint16_t length)
{
	if (!data) {
		printk("[UNSUBSCRIBED]\n");
		params->value_handle = 0U;
		return BT_GATT_ITER_STOP;
	}

	printk("[NOTIFICATION] data %p length %u\n", data, length);

	
	return BT_GATT_ITER_CONTINUE;
}

static uint8_t discover_func(struct bt_conn *conn,
			     const struct bt_gatt_attr *attr,
			     struct bt_gatt_discover_params *params)
{
	int err;

	if (!attr) {
		printk("Discover complete\n");
		(void)memset(params, 0, sizeof(*params));
		return BT_GATT_ITER_STOP;
	}

	printk("[ATTRIBUTE] handle %u\n", attr->handle);

	if (!bt_uuid_cmp(discover_params.uuid, BT_UUID_custom_service)) {
		memcpy(&discover_uuid, BT_UUID_custom_char, sizeof(discover_uuid));
		discover_params.uuid = &discover_uuid.uuid;
		discover_params.start_handle = attr->handle + 1;
		discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;

		err = bt_gatt_discover(conn, &discover_params);
		if (err) {
			printk("Discover failed (err %d)\n", err);
		}
	} else if (!bt_uuid_cmp(discover_params.uuid,
				BT_UUID_custom_char)) {
		memcpy(&discover_uuid, BT_UUID_GATT_CCC, sizeof(discover_uuid));
		discover_params.uuid = &discover_uuid.uuid;
		discover_params.start_handle = attr->handle + 2;
		discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR;
		subscribe_params.value_handle = bt_gatt_attr_value_handle(attr);

		err = bt_gatt_discover(conn, &discover_params);
		if (err) {
			printk("Discover failed (err %d)\n", err);
		}
	} else {
		subscribe_params.notify = notify_func;
		subscribe_params.value = BT_GATT_CCC_NOTIFY;
		subscribe_params.ccc_handle = attr->handle;

		err = bt_gatt_subscribe(conn, &subscribe_params);
		if (err && err != -EALREADY) {
			printk("Subscribe failed (err %d)\n", err);
		} else {
			printk("[SUBSCRIBED]\n");
		}

		return BT_GATT_ITER_STOP;
	}

	return BT_GATT_ITER_STOP;
}

static void connected(struct bt_conn *conn, uint8_t conn_err) {
    
    if (conn_err) {
        printk("Failed to connect (err %u)\n", conn_err);
        return;
    }
    default_conn = bt_conn_ref(conn);
    printk("Connected\n");

    // Initiate GATT discovery
    memcpy(&discover_uuid, BT_UUID_custom_service, sizeof(discover_uuid));
    discover_params.uuid = &discover_uuid.uuid;
    discover_params.func = discover_func; // Register your discover function
    discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
    discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
    discover_params.type = BT_GATT_DISCOVER_PRIMARY;

    int err = bt_gatt_discover(default_conn, &discover_params);
    if (err) {
        printk("Discover failed (err %d)\n", err);
    }

}

static void disconnected(struct bt_conn *conn, uint8_t reason) {
    printk("Disconnected (reason %u)\n", reason);
    if (default_conn) {
        bt_conn_unref(default_conn);
        default_conn = NULL;
    }
    bt_le_scan_start(BT_LE_SCAN_ACTIVE, device_found);
}

BT_CONN_CB_DEFINE(conn_callbacks) = {
    .connected = connected,
    .disconnected = disconnected,
};

static bool eir_found(struct bt_data *data, void *user_data) {
    // Custom service UUID filtering
    if (data->type == BT_DATA_UUID128_ALL || data->type == BT_DATA_UUID128_SOME) {
        struct bt_uuid_128 uuid;
        bt_uuid_create(&uuid.uuid, data->data, data->data_len);
        if (bt_uuid_cmp(&uuid.uuid, &custom_service_uuid.uuid) == 0) {
            printk("Custom UUID matched\n");
            return true;
        }
    }
    return false;
}

// Remove the hardcoded address to avoid matching issues
//static const bt_addr_le_t target_addr = {.type = BT_ADDR_LE_RANDOM, .a = { .val = {0xF5, 0x1F, 0x53, 0xB7, 0xAA, 0xEF} }};

static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, struct net_buf_simple *ad) {
    char addr_str[BT_ADDR_LE_STR_LEN];
    bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
    printk("Device found: %s (RSSI %d)\n", addr_str, rssi);

    // Check for matching UUID rather than just address
    bt_data_parse(ad, eir_found, NULL);

    printk("Attempting to connect...\n");

    struct bt_le_conn_param *param = BT_LE_CONN_PARAM_DEFAULT;
    int err = bt_le_scan_stop();
    if (err) {
        printk("Stop scan failed (err %d)\n", err);
        return;
    }

    err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, param, &default_conn);
    if (err) {
        printk("Create connection failed (err %d)\n", err);
        bt_le_scan_start(BT_LE_SCAN_ACTIVE, device_found);
    } else {
        printk("Connecting to %s\n", addr_str);
    }
}

int main(void) {
    int err;

    printk("Starting Bluetooth Central example\n");

    err = bt_enable(NULL);
    if (err) {
        printk("Bluetooth init failed (err %d)\n", err);
        return err;
    }

    printk("Bluetooth initialized\n");

    err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, device_found);
    if (err) {
        printk("Scanning failed to start (err %d)\n", err);
        return err;
    }

    printk("Scanning started\n");
    return 0;
}

//peripheral code
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gap.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/devicetree.h>

#define STACKSIZE 1024
#define PRIORITY 7

LOG_MODULE_REGISTER(Touch_Sensor_BLE, LOG_LEVEL_INF);

#define TOUCH_SENSOR_PIN 2
static const struct device *gpio_dev;

// Advertising parameters setup with a connectable, identity-based advertisement
static struct bt_le_adv_param *adv_param = BT_LE_ADV_PARAM(
    (BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_USE_IDENTITY),
    BT_GAP_ADV_FAST_INT_MIN_2, BT_GAP_ADV_FAST_INT_MAX_2, NULL);

#define DEVICE_NAME CONFIG_BT_DEVICE_NAME
#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
#define NOTIFY_INTERVAL 500

#define BT_UUID_custom_service_VAL BT_UUID_128_ENCODE(0x00001523, 0x1212, 0xefde, 0x1523, 0x785feabcd123)
#define BT_UUID_custom_char_VAL                                                                     \
BT_UUID_128_ENCODE(0x00001524, 0x1212, 0xefde, 0x1523, 0x785feabcd123)

#define BT_UUID_custom_service BT_UUID_DECLARE_128(BT_UUID_custom_service_VAL)
#define BT_UUID_custom_char BT_UUID_DECLARE_128(BT_UUID_custom_char_VAL)


static struct bt_data ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
   // BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
    /BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_custom_service_VAL),
};

//static struct bt_data sd[] = {
//    BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_custom_service_VAL),
//};

static uint8_t touch_state = 0;
static bool notify_enabled = false;

static void touch_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
    notify_enabled = (value == BT_GATT_CCC_NOTIFY);
    LOG_INF("Notifications %s", notify_enabled ? "enabled" : "disabled");
}

BT_GATT_SERVICE_DEFINE(touch_svc,
    BT_GATT_PRIMARY_SERVICE(BT_UUID_custom_service),
    BT_GATT_CHARACTERISTIC(BT_UUID_custom_char,
                           BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
                           BT_GATT_PERM_READ,
                           NULL, NULL, &touch_state),
    BT_GATT_CCC(touch_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
);

uint32_t read_touch_sensor_state(void);

static void configure_touch_sensor(void)
{
    gpio_dev = DEVICE_DT_GET(DT_NODELABEL(gpio0));
    if (!device_is_ready(gpio_dev)) {
        LOG_ERR("Failed to get GPIO device");
        return;
    }

    int ret = gpio_pin_configure(gpio_dev, TOUCH_SENSOR_PIN, GPIO_INPUT | GPIO_PULL_UP);
    if (ret < 0) {
        LOG_ERR("Failed to configure touch sensor pin, error: %d", ret);
    } else {
        LOG_INF("Touch sensor configured successfully on pin %d", TOUCH_SENSOR_PIN);
    }
}

uint32_t read_touch_sensor_state(void)
{
    int val = gpio_pin_get(gpio_dev, TOUCH_SENSOR_PIN);
    if (val < 0) {
        LOG_ERR("Failed to read touch sensor state");
    } else {
        LOG_DBG("Touch sensor state read: %d", val);
    }
    return (val == 1) ? 1 : 0;
}

// Declare the connection callback structure
static struct bt_conn_cb conn_callbacks;

// Callback function for when a connection is established
static void connected(struct bt_conn *conn, uint8_t err)
{
    if (err) {
        LOG_ERR("Connection failed (err %u)", err);
    } else {
        LOG_INF("Connected");
    }
}

// Callback function for when a connection is disconnected
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
    LOG_INF("Disconnected (reason %u)", reason);
}


int main(void)
{
    int err;

    // Initialize connection callbacks
    conn_callbacks.connected = connected;
    conn_callbacks.disconnected = disconnected;
    bt_conn_cb_register(&conn_callbacks);

    LOG_INF("Configuring touch sensor...");
    configure_touch_sensor();
    if (!gpio_dev) {
        LOG_ERR("Exiting due to GPIO device error");
        return 0;
    }

    LOG_INF("Initializing Bluetooth...");
    err = bt_enable(NULL);
    if (err) {
        LOG_ERR("Bluetooth init failed (err %d)", err);
        return 0;
    }
    LOG_INF("Bluetooth initialized");

    // Retrieve and print the built-in MAC address
    bt_addr_le_t addrs[1];
    size_t count = 1;

    bt_id_get(addrs, &count);
    if (count > 0) {
        char addr_str[BT_ADDR_LE_STR_LEN];
        bt_addr_le_to_str(&addrs[0], addr_str, sizeof(addr_str));
        LOG_INF("Device MAC Address: %s", addr_str);
    } else {
        LOG_ERR("Failed to retrieve MAC Address");
        return 0;
    }


    LOG_INF("Starting advertising...");

    err = bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad),NULL,0);
    if (err) {
        LOG_ERR("Advertising failed to start (err %d)", err);
        return 0;
    }
    LOG_INF("Advertising started");

    uint8_t last_touch_state = 0;

    while (1) {
        int val = gpio_pin_get(gpio_dev, TOUCH_SENSOR_PIN);
        if (val < 0) {
            LOG_ERR("Failed to read touch sensor state");
        } else {
            uint8_t new_state = (val == 1) ? 1 : 0;
            if (new_state != last_touch_state) {
                last_touch_state = new_state;
                touch_state = new_state;
                LOG_INF("Touch state changed: %s", touch_state ? "ON" : "OFF");

                // Notify connected device of the state change
                if (notify_enabled) {
                    int notify_err = bt_gatt_notify(NULL, &touch_svc.attrs[2], &touch_state, sizeof(touch_state));
                    if (notify_err) {
                        LOG_ERR("Failed to send notification (err %d)", notify_err);
                    } else {
                        LOG_INF("Notification sent: %s", touch_state ? "ON" : "OFF");
                    }
                } else {
                    LOG_DBG("Notification skipped, not enabled");
                }
            }
        }
        k_sleep(K_MSEC(100));
    }
}

//Peripheral serial monitor log
*** Booting nRF Connect SDK v2.7.0-5cb85570ca43 ***
*** Using Zephyr OS v3.6.99-100befc70c74 ***
[00:00:00.003,784] <inf> Touch_Sensor_BLE: Configuring touch sensor...
[00:00:00.003,784] <inf> Touch_Sensor_BLE: Touch sensor configured successfully on pin 2
[00:00:00.003,814] <inf> Touch_Sensor_BLE: Initializing Bluetooth...
[00:00:00.003,906] <inf> bt_sdc_hci_driver: SoftDevice Controller build revision: 
                                            d6 da c7 ae 08 db 72 6f  2a a3 26 49 2a 4d a8 b3 |......ro *.&I*M..
                                            98 0e 07 7f                                      |....             
[00:00:00.006,256] <inf> bt_hci_core: HW Platform: Nordic Semiconductor (0x0002)
[00:00:00.006,286] <inf> bt_hci_core: HW Variant: nRF52x (0x0002)
[00:00:00.006,317] <inf> bt_hci_core: Firmware: Standard Bluetooth controller (0x00) Version 214.51162 Build 1926957230
[00:00:00.007,171] <inf> bt_hci_core: Identity: F5:1F[00:00:00.227,935] <inf> Touch_Sensor_BLE: Connected

//Central serial monitor log

*** Booting nRF Connect SDK v2.7.0-5cb85570ca43 ***
*** Using Zephyr OS v3.6.99-100befc70c74 ***
Starting Bluetooth Central example
[00:00:00.003,814] <inf> bt_sdc_hci_driver: hci_driver_open: SoftDevice Controller build revision: 
                                            d6 da c7 ae 08 db 72 6f  2a a3 26 49 2a 4d a8 b3 |......ro *.&I*M..
                                            98 0e 07 7f                                      |....             
[00:00:00.006,378] <inf> bt_hci_core: hci_vs_init: HW Platform: Nordic Semiconductor (0x0002)
[00:00:00.006,408] <inf> bt_hci_core: hci_vs_init: HW Variant: nRF52x (0x0002)
[00:00:00.006,439] <inf> bt_hci_core: hci_vs_init: Firmware: Standard Bluetooth controller (0x00) Version 214.51162 Build 1926957230
[00:00:00.007,446] <inf> bt_hci_core: bt_dev_show_info: Identity: CD:23:2A:CD:4E:A2 (random)
[00:00:00.007,476] <inf> bt_hci_core: bt_dev_show_info: HCI: version 5.4 (0x0d) revision 0x11fb, manufacturer 0x005Device found: F5:1F:53:B7:AA:EF (random) (RSSI -65)
Attempting to connect...
Connecting to F5:1F:53:B7:AA:EF (random)
Connected
Discover complete

Parents
  • Hello,

    Sorry for the late reply.

    What is happening here is that the discovery callback is triggered, but with an UUID that is not the one you are looking for. When you then reutn BT_GATT_ITER_STOP, it stops the discovery process to continue searching for more services. The service that it typically will find first is the Generic Access Service, which just holds the name and appearance (icon ID) of the device. 

    I suggest that you look into the NCS\nrf\samples\bluetooth\central_uart and how it does it's discovery process. You can notice that it uses bt_gatt_dm_start() (dm = discovery manager), to handle the discovery callbacks, and then let it handle when to proceed and when to stop. Then, in the callbacks, you can print the UUIDs of the discovered service. You will probably see the discovery of some services that you are not interested in, but eventually, you will see the one you are looking for.

    Alternatively, you can look at the discovery flow in the discovery module (ncs\nrf\subsys\bluetooth\gatt_dm.c), and how it handles it's callbacks, when it returns BT_GATT_ITER_STOP, and when it returns BT_GATT_ITER_CONTINUE.

    Best regards,

    Edvin.

  • Hi Edvin,

    Thank you so much! It worked perfectly. The central device is now successfully connected and subscribed to notifications from the peripheral. I implemented the discovery mechanism from NCS\nrf\samples\bluetooth\central_uart.

    Since I am entirely new to this and coding in general, I would like to understand why the previous code didn't work. Could you explain the possible mistakes I made in my earlier code?

     

  • That is great to hear! Even watching the sample, it is not trivial to get this working, so well done!

    If you compare your old implementation with the current one (the one from the central_uart sample), you can see that the one in the central_uart sample uses the API that you used to use a few steps into the implementation. The difference is that when it receives the equivalent of your old discovery_func callback, it does some checks, and returns BT_GATT_ITER_CONTINUE in some cases. Actually, in most cases, unless it is the very last characteristic in the very last service. 

    Your implementation, however, returned BT_GATT_ITER_STOP if the first UUID didn't match the one you were looking for. Since the first UUID belongs to some general service that most devices has (containing name and type and so on), you told it to stop the discovery process, instead of continuing to the next UUID in the peripheral. Hence, you never found the service/characteristic that you were looking for.

    Best regards,

    Edvin

Reply
  • That is great to hear! Even watching the sample, it is not trivial to get this working, so well done!

    If you compare your old implementation with the current one (the one from the central_uart sample), you can see that the one in the central_uart sample uses the API that you used to use a few steps into the implementation. The difference is that when it receives the equivalent of your old discovery_func callback, it does some checks, and returns BT_GATT_ITER_CONTINUE in some cases. Actually, in most cases, unless it is the very last characteristic in the very last service. 

    Your implementation, however, returned BT_GATT_ITER_STOP if the first UUID didn't match the one you were looking for. Since the first UUID belongs to some general service that most devices has (containing name and type and so on), you told it to stop the discovery process, instead of continuing to the next UUID in the peripheral. Hence, you never found the service/characteristic that you were looking for.

    Best regards,

    Edvin

Children
No Data
Related