[nRF52840 USB Dongle] Irregular Notification Intervals from nRF52832 Peripheral (Expected 7.5ms, Observed 0–40ms)

I'm currently developing a USB dongle using the nRF52840, and I'm facing an issue with inconsistent data reception intervals. I’d appreciate your insights on this problem.

The peripheral device I'm connecting to is an nRF52832, which transmits data periodically with a minimum interval of 7.5ms.

When I use tools like nRF Toolbox or nRF Connect for Desktop, I see stable and consistent intervals of around 7–8ms, as expected.

However, when using my own custom firmware/code, the notification intervals become highly irregular, ranging anywhere between 0ms and 40ms. I was expecting consistent intervals like 7.5ms or possibly multiples like 15ms, but that’s not the case.

[Device ] Interval CRC: 0 ms
[Device ] Interval CRC: 29 ms
[Device ] Interval CRC: 3 ms
[Device ] Interval CRC: 11 ms
[Device ] Interval CRC: 4 ms
[Device ] Interval CRC: 11 ms
[Device ] Interval CRC: 14 ms
[Device ] Interval CRC: 4 ms
[Device ] Interval CRC: 0 ms
[Device ] Interval CRC: 25 ms
[Device ] Interval CRC: 3 ms
[Device ] Interval CRC: 11 ms
[Device ] Interval CRC: 14 ms
[Device ] Interval CRC: 14 ms
[Device ] Interval CRC: 3 ms
[Device ] Interval CRC: 11 ms
[Device ] Interval CRC: 0 ms
[Device ] Interval CRC: 14 ms
[Device ] Interval CRC: 14 ms
[Device ] Interval CRC: 3 ms
[Device ] Interval CRC: 11 ms
[Device ] Interval CRC: 14 ms
[Device ] Interval CRC: 3 ms
[Device ] Interval CRC: 11 ms
[Device ] Interval CRC: 0 ms
[Device ] Interval CRC: 49 ms
[Device ] Interval CRC: 3 ms
[Device ] Interval CRC: 5 ms
[Device ] Interval CRC: 3 ms
[Device ] Interval CRC: 3 ms
[Device ] Interval CRC: 23 ms
[Device ] Interval CRC: 4 ms
[Device ] Interval CRC: 11 ms
[Device ] Interval CRC: 14 ms
[Device ] Interval CRC: 3 ms
[Device ] Interval CRC: 11 ms
[Device ] Interval CRC: 2 ms
[Device ] Interval CRC: 12 ms
[Device ] Interval CRC: 14 ms
[Device ] Interval CRC: 3 ms
[Device ] Interval CRC: 11 ms
[Device ] Interval CRC: 14 ms
[Device ] Interval CRC: 14 ms
[Device ] Interval CRC: 11 ms
[Device ] Interval CRC: 0 ms

Is there something I might be overlooking when programming the Bluetooth receiver side on the nRF52840?

Any suggestions or pointers would be greatly appreciated.

/* main.c - Multi-device BLE to USB CDC forwarder */

#include <zephyr/kernel.h>
#include <zephyr/usb/usb_device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/usb/class/usb_cdc.h>
#include <string.h>

LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);

#define MAX_BLE_DEVICES 2
#define BLE_BUF_SIZE    512

static const char *target_addr[MAX_BLE_DEVICES] = {
    "F2:6A:3B:13:6B:9B (random)",
    "C2:F2:D1:AE:EE:9D (random)",
};

static const struct device *cdc_dev;
static struct k_mutex cdc_mutex;

/* NUS UUIDs */
static struct bt_uuid_128 nus_service_uuid = BT_UUID_INIT_128(0x9E, 0xCA, 0xDC, 0x24,
                                                              0x0E, 0xE5, 0xA9, 0xE0,
                                                              0x93, 0xF3, 0xA3, 0xB5,
                                                              0x01, 0x00, 0x40, 0x6E);
static struct bt_uuid_128 nus_tx_uuid = BT_UUID_INIT_128(0x9E, 0xCA, 0xDC, 0x24,
                                                         0x0E, 0xE5, 0xA9, 0xE0,
                                                         0x93, 0xF3, 0xA3, 0xB5,
                                                         0x03, 0x00, 0x40, 0x6E);

struct ble_dev_ctx {
    struct bt_conn *conn;
    struct bt_gatt_discover_params discover_params;
    struct bt_gatt_subscribe_params subscribe_params;
    uint8_t buffer[BLE_BUF_SIZE];
    size_t len;
    bool active;
    uint8_t id; // 1-based device ID
};

static struct ble_dev_ctx devs[MAX_BLE_DEVICES];

/* USB CDC helpers */
static void send_cdc_data(const uint8_t *data, size_t len) {
    if (!cdc_dev || !data || !len) return;
    k_mutex_lock(&cdc_mutex, K_FOREVER);
    size_t sent = 0;
    while (sent < len) {
        int written = uart_fifo_fill(cdc_dev, &data[sent], len - sent);
        if (written <= 0) break;
        sent += written;
    }
    k_mutex_unlock(&cdc_mutex);
}

static void send_cdc_string(const char *str) {
    send_cdc_data((const uint8_t *)str, strlen(str));
}

/* BLE → CDC 프레이밍 */
static void flush_ble_buffer(struct ble_dev_ctx *ctx) {
    if (!ctx || ctx->len == 0 || ctx->len > 255) return;

    uint8_t packet[260];
    packet[0] = 0xA5;
    packet[1] = ctx->id;
    packet[2] = (uint8_t)ctx->len;
    memcpy(&packet[3], ctx->buffer, ctx->len);
    uint8_t checksum = 0;
    for (size_t i = 0; i < ctx->len; ++i) checksum += ctx->buffer[i];
    packet[3 + ctx->len] = checksum;

    send_cdc_data(packet, 4 + ctx->len);
    send_cdc_string("\r\n");

    ctx->len = 0;
}

/* GATT Notify */
static uint8_t notify_cb(struct bt_conn *conn,
                         struct bt_gatt_subscribe_params *params,
                         const void *data, uint16_t len) {
    struct ble_dev_ctx *ctx = CONTAINER_OF(params, struct ble_dev_ctx, subscribe_params);
    if (!data) return BT_GATT_ITER_STOP;

    // if ((ctx->len + len) > BLE_BUF_SIZE) flush_ble_buffer(ctx);
    memcpy(&ctx->buffer[ctx->len], data, len);
    ctx->len += len;
    flush_ble_buffer(ctx);

    return BT_GATT_ITER_CONTINUE;
}

/* GATT Discovery */
static uint8_t discover_func(struct bt_conn *conn,
                             const struct bt_gatt_attr *attr,
                             struct bt_gatt_discover_params *params) {
    struct ble_dev_ctx *ctx = CONTAINER_OF(params, struct ble_dev_ctx, discover_params);

    if (!attr) return BT_GATT_ITER_STOP;

    const struct bt_gatt_chrc *chrc = attr->user_data;
    if (!bt_uuid_cmp(chrc->uuid, &nus_tx_uuid.uuid)) {
        ctx->subscribe_params.ccc_handle = chrc->value_handle + 1;
        ctx->subscribe_params.value_handle = chrc->value_handle;
        ctx->subscribe_params.value = BT_GATT_CCC_NOTIFY;
        ctx->subscribe_params.notify = notify_cb;

        int err = bt_gatt_subscribe(conn, &ctx->subscribe_params);
        LOG_INF("Slot %d subscribe: %s", ctx->id, err ? "fail" : "ok");
        return BT_GATT_ITER_STOP;
    }

    return BT_GATT_ITER_CONTINUE;
}

/* Scan */
static void start_scan(void);

static void device_found(const bt_addr_le_t *addr, int8_t rssi,
                         uint8_t type, struct net_buf_simple *ad) {
    if (type != BT_GAP_ADV_TYPE_ADV_IND &&
        type != BT_GAP_ADV_TYPE_ADV_DIRECT_IND) return;

    char addr_str[BT_ADDR_LE_STR_LEN];
    bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));

    /* Check if target */
    int slot = -1;
    for (int i = 0; i < MAX_BLE_DEVICES; ++i)
        if (!strcmp(addr_str, target_addr[i]))
            slot = i;

    if (slot < 0 || devs[slot].active) return;

    bt_le_scan_stop();

    int err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN,
                                BT_LE_CONN_PARAM_DEFAULT, &devs[slot].conn);
    if (err) {
        LOG_WRN("conn err %d", err);
        start_scan();
        return;
    }

    devs[slot].active = true;
    devs[slot].id = slot + 1;
}

static void start_scan(void) {
    static const struct bt_le_scan_param scan_param = {
        .type = BT_HCI_LE_SCAN_ACTIVE,
        .options = BT_LE_SCAN_OPT_NONE,
        .interval = BT_GAP_SCAN_FAST_INTERVAL,
        .window = BT_GAP_SCAN_FAST_WINDOW,
    };
    bt_le_scan_start(&scan_param, device_found);
}

/* Connection callbacks */
static void connected(struct bt_conn *conn, uint8_t err) {
    if (err) {
        LOG_WRN("BLE connect failed (%d)", err);
        start_scan();
        return;
    }

    for (int i = 0; i < MAX_BLE_DEVICES; ++i) {
        if (devs[i].conn == conn) {
            devs[i].discover_params.uuid = &nus_tx_uuid.uuid;
            devs[i].discover_params.func = discover_func;
            devs[i].discover_params.start_handle = 0x0001;
            devs[i].discover_params.end_handle = 0xFFFF;
            devs[i].discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
            bt_gatt_discover(conn, &devs[i].discover_params);

            LOG_INF("Connected slot %d", devs[i].id);
            break;
        }
    }

    // Keep scanning for remaining devices
    for (int i = 0; i < MAX_BLE_DEVICES; ++i)
        if (!devs[i].active) { start_scan(); break; }
}

static void disconnected(struct bt_conn *conn, uint8_t reason) {
    for (int i = 0; i < MAX_BLE_DEVICES; ++i) {
        if (devs[i].conn == conn) {
            LOG_INF("Disconnected slot %d (0x%02X)", devs[i].id, reason);
            bt_conn_unref(devs[i].conn);
            devs[i].conn = NULL;
            devs[i].active = false;
            devs[i].len = 0;
        }
    }
    start_scan();
}

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

/* Main */
int main(void) {
    k_mutex_init(&cdc_mutex);
    memset(devs, 0, sizeof(devs));

    if (usb_enable(NULL)) {
        LOG_ERR("USB enable failed");
        return 1;
    }

    cdc_dev = device_get_binding("CDC_ACM_0");
    if (!cdc_dev) {
        LOG_ERR("CDC device not found");
            return 1;
    }

    if (bt_enable(NULL)) {
        LOG_ERR("BLE enable failed");
        return 1;
    }

    start_scan();

    while (1) k_sleep(K_MSEC(100));
}

Related