Efficient Handling of High-Rate Sensor Data via BLE Notifications

Hi all,

I'm facing a challenge with efficiently transmitting high-frequency sensor data over BLE.

I’m sampling sensor data via SPI from a sensor capable of outputting data at up to 1 kHz. My goal is to transmit this sampled data with low latency and as close to real-time as possible. However, I’m aware of the BLE limitations, particularly the 7.5 ms minimum connection interval, which, in practice, often turns out to be 11.25 ms on Android and 15 ms on iOS.

Given these constraints, it's clear that I can't send individual sensor values in real-time. Therefore, my current approach is to package multiple values into each notification. This introduces a slight delay, but it’s an acceptable trade-off.

To achieve stable notifications, I plan to use the BLE radio callback function, allowing me to queue one packet per connection interval as the callback is called right before the conn event. This setup creates a classic producer-consumer problem:

  • Producer: The sensor, sampling data at 1 kHz.
  • Consumer: The BLE radio callback, transmitting data at the connection interval.

Assuming a constant 15 ms connection interval (to accommodate iOS), I need to send 15 sensor values per notification. However, I’m struggling with how best to handle the mismatch between the producer and consumer rates. Specifically:

  1. Buffer Overflow: If the producer outpaces the consumer, the buffer fills up, increasing latency.
  2. Buffer Underflow: If the consumer outpaces the producer, some connection events will have no data to send, disrupting the desired transmission pattern.

Key Question:

How can I better synchronize the producer and consumer? What would be the best data structure and data-handling strategy to achieve stable, consistent data transfer in this scenario?

In theory i dont care if gathered sensor data gets lost if i just can always send out the latest 15 values. Could this maybe be handled by some sort of LIFO Ring buffer? But this would create the issue of not mixing stale data with new data. 

Any insights or suggestions about to how best go about this issue would be greatly appreciated.

Maybe I'm also going completely wrong about the solution

Here is some of the relevant code:

Producer:

static void amr_work_handler(struct k_work *work) {

    // Some spi stuff to read out 4 sensors simulationsly
    amr_xfer_all(xfer_desc_all_bandwidth);
    amr_xfer_all(xfers_all);
    amr_xfer_all(init_m);
    amr_xfer_all(read_status_xfer);
    if (((r_status1[1] & 0x01) & (r_status2[1] & 0x01) &
          (r_status3[1] & 0x01) & (r_status4[1] & 0x01))) {
      set_spi1_error(true);
      set_spi2_error(true);
    }
    k_work_reschedule(&amr_work, K_MSEC(1000/current_MODR));
}


Consumer:

static void radio_notification_conn_prepare_cb(struct bt_conn *conn)
{
    // ble callback
    // send the gathered sensor data out here by calling bt_gatt_notify(data)
}


Im on ncs 2.9.0


Thanks in advance!

  • Hello,

    (N=15)

    If you are only interested in the last N samples at any time, I suggest that you stack up the first N samples after the previous have been sent,  and when you get the next radio callback, you can queue these messages to the softdevice controller (basically send it, but in reality, you queue it up with the softdevice controller, which is the BLE stack).

    Now one of two things may happen. 1: The packet is transmitted, received or acknowledged. All good. 2: The packet is lost somewhere on the way to the receiver. This means that the packet needs to be retransmitted (it is not possible to avoid this. It is part of the BLE specification). It will be handled by the softdevice controller, so it will be retransmitted on the next connection event (after one connection interval). But what about your new samples? That depends. So far, your application is unaware that the packet has not yet been acked. So you went on with collecting new samples, and storing N of them. The question now is, is it possible to transmit 2 of the packets in the same connection event? Probably, yes, but you may need to increase the MTU (packet size). That should be fine, as most phones/BLE devices support this these days.

    But you should test this. That the connection is able to catch up with packet loss. It should be able to handle at least two packets per connection interval. If not, you will start lagging behind, and you will never be able to catch up and send the very latest sensor data. But it depends on the data size of a sample. If it is 2 bytes, and 15ms, it means 30 bytes, which should be fine. You can even send 60 bytes every connection interval with the connection parameters. But if one sample is 100 bytes, you will start struggling. You can play around a bit in the online power profiler, to see how long the device will use to send one packet, depending on the payload. You can also experiment by trying to send the amount of data that your application will send, and use a sniffer to capture the connection packets. There you should be able to see the timing of the packets, and retransmissions. You can use the nRF Sniffer for Bluetooth LE for this. 

    Finally, you mentioned that some connections will give 15ms, while some will give less, others will give more. It is possible to track this in your application. There is a callback that you can enable to see what kind of connection interval you have. Use this information to decide what you want to set your default message size to during runtime. This way, setting N=15, N=8 (7.5), or whatever your connection interval is. To see how to monitor this callback, in addition to how you can adjust the MTU and enable data length extension, please check out the Bluetooth Fundamentals course on DevAcademy. Lesson 3 exercise 2 is particularly relevant for these topics.

    Best regards,

    Edvin

  • Hi Edvin,

    Thanks for the extensive answer!
    With your suggestions I now have built a stable system. 

    Thank you

  • hey so I kinda have the same problem. Is there any chance that you can provide me you're code so that I can have a look?  

Related