[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));
}

Parents
  • Try to measure time on the receiving device and not on the PC. Windows timing can be interesting. See also timeBeginPeriod().

    I also recommend putting an USB 2.0 (or higher) hub between the PC host and the NRF device.

  • [00:00:05.121,337] <inf> main: Notify: interval 5 ms
    [00:00:05.127,502] <inf> main: Notify: interval 6 ms
    [00:00:05.132,385] <inf> main: Notify: interval 5 ms
    [00:00:05.137,268] <inf> main: Notify: interval 5 ms
    [00:00:05.143,524] <inf> main: Notify: interval 6 ms
    [00:00:05.161,956] <inf> main: Notify: interval 18 ms
    [00:00:05.166,931] <inf> main: Notify: interval 5 ms
    [00:00:05.191,467] <inf> main: Notify: interval 25 ms
    [00:00:05.196,350] <inf> main: Notify: interval 5 ms
    [00:00:05.202,484] <inf> main: Notify: interval 6 ms
    [00:00:05.207,397] <inf> main: Notify: interval 5 ms
    [00:00:05.263,977] <inf> main: Notify: interval 56 ms
    [00:00:05.268,890] <inf> main: Notify: interval 5 ms
    [00:00:05.307,952] <inf> main: Notify: interval 39 ms
    [00:00:05.323,974] <inf> main: Notify: interval 16 ms
    [00:00:05.328,887] <inf> main: Notify: interval 5 ms
    [00:00:05.341,949] <inf> main: Notify: interval 13 ms
    [00:00:05.346,832] <inf> main: Notify: interval 5 ms
    [00:00:05.352,996] <inf> main: Notify: interval 6 ms
    [00:00:05.357,910] <inf> main: Notify: interval 5 ms
    [00:00:05.372,436] <inf> main: Notify: interval 15 ms
    [00:00:05.377,349] <inf> main: Notify: interval 5 ms
    [00:00:05.386,993] <inf> main: Notify: interval 9 ms
    [00:00:05.401,947] <inf> main: Notify: interval 15 ms
    [00:00:05.406,860] <inf> main: Notify: interval 5 ms
    [00:00:05.416,992] <inf> main: Notify: interval 10 ms
    [00:00:05.446,472] <inf> main: Notify: interval 30 ms
    [00:00:05.459,472] <inf> main: Notify: interval 13 ms
    [00:00:05.464,355] <inf> main: Notify: interval 5 ms
    [00:00:05.469,207] <inf> main: Notify: interval 5 ms
    [00:00:05.489,471] <inf> main: Notify: interval 20 ms
    [00:00:05.494,354] <inf> main: Notify: interval 5 ms
    [00:00:05.499,237] <inf> main: Notify: interval 5 ms
    [00:00:05.505,493] <inf> main: Notify: interval 6 ms
    [00:00:05.521,942] <inf> main: Notify: interval 16 ms
    [00:00:05.527,252] <inf> main: Notify: interval 6 ms
    [00:00:05.536,987] <inf> main: Notify: interval 9 ms
    [00:00:05.551,940] <inf> main: Notify: interval 15 ms
    [00:00:05.556,854] <inf> main: Notify: interval 5 ms
    [00:00:05.566,986] <inf> main: Notify: interval 10 ms
    [00:00:05.611,968] <inf> main: Notify: interval 45 ms
    [00:00:05.616,882] <inf> main: Notify: interval 5 ms
    [00:00:05.622,985] <inf> main: Notify: interval 6 ms
    [00:00:05.627,899] <inf> main: Notify: interval 5 ms
    [00:00:05.641,937] <inf> main: Notify: interval 14 ms
    [00:00:05.646,881] <inf> main: Notify: interval 5 ms
    [00:00:05.656,982] <inf> main: Notify: interval 10 ms
    [00:00:05.671,966] <inf> main: Notify: interval 15 ms
    [00:00:05.676,879] <inf> main: Notify: interval 5 ms
    [00:00:05.687,011] <inf> main: Notify: interval 10 ms
    [00:00:05.701,965] <inf> main: Notify: interval 15 ms
    [00:00:05.706,878] <inf> main: Notify: interval 5 ms
    [00:00:05.716,979] <inf> main: Notify: interval 10 ms
    [00:00:05.731,964] <inf> main: Notify: interval 15 ms
    [00:00:05.736,877] <inf> main: Notify: interval 5 ms
    [00:00:05.747,009] <inf> main: Notify: interval 10 ms
    [00:00:05.761,962] <inf> main: Notify: interval 15 ms
    [00:00:05.767,242] <inf> main: Notify: interval 6 ms
    [00:00:05.777,008] <inf> main: Notify: interval 9 ms
    [00:00:05.791,961] <inf> main: Notify: interval 15 ms
    [00:00:05.796,875] <inf> main: Notify: interval 5 ms
    [00:00:05.806,976] <inf> main: Notify: interval 10 ms
    [00:00:05.821,960] <inf> main: Notify: interval 15 ms
    [00:00:05.833,068] <inf> main: Notify: interval 12 ms
    [00:00:05.837,982] <inf> main: Notify: interval 4 ms
    [00:00:05.851,959] <inf> main: Notify: interval 14 ms
    [00:00:05.856,872] <inf> main: Notify: interval 5 ms
    [00:00:05.867,004] <inf> main: Notify: interval 10 ms
    [00:00:05.881,958] <inf> main: Notify: interval 15 ms
    [00:00:05.886,871] <inf> main: Notify: interval 5 ms
    [00:00:05.897,003] <inf> main: Notify: interval 10 ms
    [00:00:05.911,956] <inf> main: Notify: interval 15 ms
    [00:00:05.916,870] <inf> main: Notify: interval 5 ms
    [00:00:05.927,001] <inf> main: Notify: interval 10 ms
    [00:00:05.971,954] <inf> main: Notify: interval 45 ms
    [00:00:05.976,837] <inf> main: Notify: interval 5 ms
    [00:00:05.982,971] <inf> main: Notify: interval 6 ms
    [00:00:05.987,884] <inf> main: Notify: interval 5 ms
    [00:00:06.001,953] <inf> main: Notify: interval 14 ms
    [00:00:06.007,263] <inf> main: Notify: interval 6 ms
    [00:00:06.016,998] <inf> main: Notify: interval 9 ms
    [00:00:06.046,936] <inf> main: Notify: interval 30 ms
    [00:00:06.059,997] <inf> main: Notify: interval 13 ms
    [00:00:06.064,880] <inf> main: Notify: interval 5 ms
    [00:00:06.069,763] <inf> main: Notify: interval 5 ms
    [00:00:06.075,958] <inf> main: Notify: interval 6 ms
    [00:00:06.080,841] <inf> main: Notify: interval 5 ms
    [00:00:06.089,965] <inf> main: Notify: interval 9 ms
    [00:00:06.094,909] <inf> main: Notify: interval 5 ms
    [00:00:06.106,994] <inf> main: Notify: interval 12 ms
    [00:00:06.136,993] <inf> main: Notify: interval 30 ms
    [00:00:06.151,947] <inf> main: Notify: interval 15 ms
    [00:00:06.156,860] <inf> main: Notify: interval 5 ms
    [00:00:06.166,992] <inf> main: Notify: interval 10 ms
    [00:00:06.181,945] <inf> main: Notify: interval 15 ms
    [00:00:06.186,859] <inf> main: Notify: interval 5 ms
    [00:00:06.196,990] <inf> main: Notify: interval 10 ms
    [00:00:06.225,463] <inf> main: Notify: interval 29 ms
    [00:00:06.238,494] <inf> main: Notify: interval 13 ms
    [00:00:06.243,377] <inf> main: Notify: interval 5 ms
    [00:00:06.254,974] <inf> main: Notify: interval 11 ms
    [00:00:06.260,223] <inf> main: Notify: interval 6 ms
    [00:00:06.265,106] <inf> main: Notify: interval 5 ms
    [00:00:06.270,965] <inf> main: Notify: interval 5 ms
    [00:00:06.282,531] <inf> main: Notify: interval 12 ms
    [00:00:06.287,445] <inf> main: Notify: interval 5 ms
    [00:00:06.331,939] <inf> main: Notify: interval 44 ms
    [00:00:06.336,822] <inf> main: Notify: interval 5 ms
    [00:00:06.342,987] <inf> main: Notify: interval 6 ms
    [00:00:06.347,900] <inf> main: Notify: interval 5 ms
    [00:00:06.361,999] <inf> main: Notify: interval 14 ms
    [00:00:06.376,983] <inf> main: Notify: interval 15 ms
    [00:00:06.391,937] <inf> main: Notify: interval 15 ms
    [00:00:06.396,881] <inf> main: Notify: interval 5 ms
    [00:00:06.406,982] <inf> main: Notify: interval 10 ms
    [00:00:06.421,966] <inf> main: Notify: interval 15 ms
    [00:00:06.426,879] <inf> main: Notify: interval 5 ms
    [00:00:06.437,011] <inf> main: Notify: interval 10 ms
    [00:00:06.451,965] <inf> main: Notify: interval 15 ms
    [00:00:06.456,878] <inf> main: Notify: interval 5 ms
    [00:00:06.466,979] <inf> main: Notify: interval 10 ms
    [00:00:06.481,964] <inf> main: Notify: interval 15 ms
    [00:00:06.486,877] <inf> main: Notify: interval 5 ms
    [00:00:06.497,375] <inf> main: Notify: interval 11 ms
    [00:00:06.511,962] <inf> main: Notify: interval 14 ms
    [00:00:06.516,876] <inf> main: Notify: interval 5 ms
    [00:00:06.526,977] <inf> main: Notify: interval 10 ms
    [00:00:06.541,961] <inf> main: Notify: interval 15 ms
    [00:00:06.546,875] <inf> main: Notify: interval 5 ms



    I inserted the following code into the notify_cb function to log the receive interval via the debugger:

    uint64_t now = k_uptime_get();
    LOG_INF("Notify: interval %llu ms", now - last_ts);
    last_ts = now;
    

    The logged intervals match what I observed earlier, so it seems the callback function is actually being invoked at those intervals.

  • Perhaps you can try to capture a sniffer trace of the connection. For this, you can use the nRF Sniffer for Bluetooth LE.

    Perhaps there are some retransmissions happening. 

    How long is typically the packets that are received?

    Best regards,

    Edvin

Reply Children
No Data
Related