How to safely access BLEcharacteristic.OnDataReceived() data from loop()?

Hi,
How can one safely access data received from a BLE peer?
Can we use an atomic variable to raise a flag and access the data from loop()?

Example code:

// global space
std::atomic<uint32_t> atomicFlag;
uint32_t globalFlag = 0;
char receivedData[500];

// BLE handler for received data
void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context) 
{
    memset(receivedData, 0, 500);
    memcpy(receivedData, data, len);
    atomicFlag.store(1, std::memory_order_relaxed);
}

void loop()
{
    globalFlag = atomicFlag.fetch_and(0, std::memory_order_relaxed);
    if (globaFlag == 1)
    {
        Log.info("Received data: %s", receivedData);  <-- can I do this safely?
    }
}

I believe this code can get me in trouble in a multi-core CPU, but can this work fine on a Gen3 Particle device?
Thank you!

That will work.

Setting a single flag without using the atomic calls generally works because store a value in memory should be atomic. However, where you get into a problem is test and set, test and clear, increment, etc. where there could be a thread swap or ISR in between the two operations.

I believe that BLE callbacks are from a thread, not an ISR. While technically you should use the same care for both, the problem is more noticeable with ISRs. The reason is that threads will only be interrupted if they exceed their time slice (1 millisecond). If they yield before that, they won’t be interrupted, so if the code is always yielding, you tend not to notice atomic operations issues. However, it’s always better to code defensively.

2 Likes

Awesome, thank you.

@rickkas7 : follow up, or double verification on this one.
My main interest was to find out if I could use the receivedData contents safely in loop(), after having copied the data into receivedData with memcpy(receivedData, data, len); (and flagging reception with that atomic flag).

I re-read your answer and I'm not sure if it was clear to you that the focus was on receivedData and data, and not on the atomicFlag.

Would you mind confirming this receivedData processing on loop() is acceptable this way, when you have a chance?

Thanks a ton!
Gustavo.

I didn’t notice that. It’s probably not safe to access receivedData from loop. It will work most of the time, but it can’t be guaranteed. There’s a possibility you could get a half-written buffer. Also you could miss a buffer.

What I would use instead is the os_queue functions. The main downside of this is that you’ll have to do a memory allocation, but on the plus side if multiple blocks of data are received, you’ll get all of them. Fortunately the onDataReceived handler is in a thread, not an ISR, so it should work fine.

The nice thing about the queue functions is that os_queue_put and os_queue_take are multi-thread safe so they’re ideal for use from an onDataReceived handler.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.