This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Streaming SAADC data (16kHz sampling rate) over BLE

Hi there,

I'm currently working on getting a streaming application running which is supposed to sample some audio at 16kHz and send it (uncompressed) to a central device via BLE.

Current Status, what works:
The SAADC is configured with a double buffer, each containing 122 samples of uint16 samples. The samples are stored intermediately in an Atomic FIFO until they are then broken up into two uint8 values and sent using the function sd_ble_gatts_hvx(). The transmission works with data-length extension and connection-length extension enabled. The code as of now, works at lower sampling rates of about 1.5kHz, however going beyond this sampling-rate makes the BLE connection fail. Consequently the peripheral device looses the connection to the central device and needs to reconnect. 
Connecting the peripheral with the nRF Connect App on an Android phone, the central (App) side reports: Disconnected Error 0x8 [= GATT CONN TIMEOUT].

What doesn't work:
At this point I am confident to say, it is most likely a timing constraint which is violated, since the BLE connection breaks earlier the higher the sampling-rate. Likewise increasing the distance, i.e. latency of the BLE connection leads to a earlier loss of connection. The connection breaks with the BLE_GAP_EVT_DISCONNECTED case. The code never reconnects after this event and just remains / "freezes" in this state. No error is reported, the program keeps running.

The problem:

  • I have isolated the SAADC part of my code printing the values via UART to my terminal instead of transmitting via BLE. This works just fine, even at sampling-rates as high as 32kHz the code never freezes and the Atomic FIFO never fills up.
  • I have isolated the BLE transmission part, sending simply dummy data in intervals triggered by a timer. This too works at frequencies of up to 20kHz with out loosing connection or "freezing up".

The set-up I use is:
2x NRF52 DevKit (nrf52832) with S132
nrf SDK 15.3.0
Segger Embedded Studio 4.16

The code I am using is based on the ble_app_uart example from the SDK.

Question:

  1. Am I correct in my calculations/ assumptions that this amount of data should be transmittable via BLE?
  2. How can I increase the throughput of my code, such that the data stream may run continuously?
  3. Is there any working example around of streaming large quantities of data while using the SAADC?
  4. How can I resolve the loosing of connection on my device and how to fix the freezing up/ not reconnecting of the devices?

Thanks a lot for your time and help

Oliver

  • Hi Oliver

    First of all, thanks for a detailed question. We appreciate that you take the time to describe your setup and what you are doing!

    1. You could try implementing the NRF_DRV_SAADC_EVT_LIMIT to see if the converted sample on a given channel is exceeding a limit. I suspect this is the reason you are experiencing disconnects. Have you been able to see an error code when the BLE_GAP_EVT_DISCONNECTED occurs? Might give us some pointers to what exactly is causing the disconnection.

    2. What are your connection parameters (This might be the reason you aren't able to connect to the Android device) etc.? Also check that your BLE_GAP_PHY_2MBPS is set for the highest possible throughput.

    3. As far as I know, we don't have any "official" examples of this, but someone might have done it earlier either here on DevZone, or on GitHub.

    4. This can be a variety of reasons. First of all, what pins do you use as UART pins? The ones described in the PS as close to the radio might cause interference if active simultaneously as the radio is. Another reason might be that your slave latency is set to 0. This is the number of failed connection events before the link is broken. By increasing this number the link will tolerate that number of missing connection events before the connection is broken. I suggest setting this to 3 or 4 and tweak it if necessary.

    Best regards,

    Simon 

  • Hi Oliver. 

    The code as of now, works at lower sampling rates of about 1.5kHz

    I have made my code on top of the same example, but I cannot go beyond 420Hz - 430Hz without disconnecting. Can you kindly share your code with me so that I can get an idea of what to do? My application requires at least 1k which I am desperately trying to achieve but have not been able to do so yet.

    Any help is greatly appreciated

    Best regards,

    Usman

  • How are you transferring the data from the ADC buffer to the BLE handler? For example, the Nordic uart service example is slow and clumsy treating incoming uart strings as individual bytes instead of packets of data. This is a sample of a packet transfer:

    uint32_t StoredPacketTransmit(BlePacketFormat_t *pPacketToTransmit, uint16_t PacketLength)
    {
        uint32_t err_code = SOMETHING_BAD;
        // Define the structure to identify the Nordic UART Service
        ble_nus_t * p_m_nus = get_mNUS();
        if (pPacketToTransmit && PacketLength && p_m_nus)
        {
            err_code = ble_nus_data_send(p_m_nus, pPacketToTransmit, &PacketLength);
            if (err_code == NRF_SUCCESS)
            {
                // Packet sent, allow next packet to be built when sufficent SAADC samples available
            }
            else
            {
                // Packet not sent, next packet build is disallowed until current packet is sent
                mDelayedPacketCount++;
            }
        }
        // Allow caller to see if packet actually went out
        return err_code;
    }

    ble_nus_data_send() used to be called ble_nus_string_send() depending on which SDK you are using.

    If not already doing so also increase the PHY to 2MHz instead of the default 1MHz

  • Hi Hmolesworth

    Thank you for the information. 

    My SAADC callback function looks like this:

    void saadc_callback(nrf_drv_saadc_evt_t const * p_event)
    {
        if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
        {
            ret_code_t err_code;
            uint16_t value[SAADC_SAMPLES_IN_BUFFER];
            uint16_t bytes_to_send;
            
            err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAADC_SAMPLES_IN_BUFFER);
            APP_ERROR_CHECK(err_code);
    
            uint8_t nus_string[50];
    
            bytes_to_send = sprintf(nus_string, "%d %d", data.done.p_buffer[0], data.done.p_buffer[1]);
            err_code = ble_nus_data_send(&m_nus, nus_string, &bytes_to_send, m_conn_handle);
    
            if ((err_code != NRF_ERROR_INVALID_STATE) && (err_code != NRF_ERROR_NOT_FOUND))
            {
                APP_ERROR_CHECK(err_code);
            }
    	
            m_adc_evt_counter++;
        }
    }

    It's very similar to your pseudocode. But it fails only after a few seconds. I reached a max of 420Hz by increasing the samples in buffer to 24 (instead of 2), sending the 10 samples in one string, and then calling ble_nus_data_send again in the very next line and sending 10 more samples in a string. 

    So my code became something like this

    void saadc_callback(nrf_drv_saadc_evt_t const *p_event)
    {
        if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
        {
            ret_code_t err_code;
            uint16_t value[SAADC_SAMPLES_IN_BUFFER];
            uint16_t bytes_to_send;
    
            err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAADC_SAMPLES_IN_BUFFER);
            APP_ERROR_CHECK(err_code);
    
            for (int i = 0; i < SAADC_SAMPLES_IN_BUFFER; i++)
            {
                if (p_event->data.done.p_buffer[i] <= 0)
                {
                    value[i] = 0;
                }
                else
                {
                    value[i] = p_event->data.done.p_buffer[i];
                }
            }
    
            uint8_t nus_string[50];
    
            bytes_to_send = sprintf(nus_string, "%d %d %d %d %d %d %d %d %d %d", value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9]);
            err_code = ble_nus_data_send(&m_nus, nus_string, &bytes_to_send, m_conn_handle);
    
            bytes_to_send = sprintf(nus_string, "%d %d %d %d %d %d %d %d %d %d", value[10], value[11], value[12], value[13], value[14], value[15], value[16], value[17], value[18], value[19]);
            err_code = ble_nus_data_send(&m_nus, nus_string, &bytes_to_send, m_conn_handle);
    
            if ((err_code != NRF_ERROR_INVALID_STATE) && (err_code != NRF_ERROR_NOT_FOUND))
            {
                if (data_sent_flag == 0)
                {
                    data_sent_count = m_adc_evt_counter;
                }
                data_sent_flag = 1;
                if (err_code == NRF_SUCCESS)
                {
                    // NRF_LOG_INFO("Packet %d sent since %d.\n", m_adc_evt_counter - data_sent_count, data_sent_count); //---------------------------UART
                }
                APP_ERROR_CHECK(err_code);
            }
            m_adc_evt_counter++;
        }
    }

    What am I missing here? 

  • I looked at my older 14.2 code and use a slightly different test:

        do
        {
            uint16_t length = (uint16_t)index;
            err_code = ble_nus_string_send(&m_nus, data_array, &length);
            if ( (err_code != NRF_ERROR_INVALID_STATE) && (err_code != NRF_ERROR_BUSY) )
            {
                APP_ERROR_CHECK(err_code);
            }
        } while (err_code == NRF_ERROR_BUSY);

    What are the BLE settings and SoftDevice version?

Related