Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs
This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

sending ADC sampled audio data over BLE

Hi everybody

I'm working on an application where I want to sample microphone data with the adc (NRF52 DK board, nRF52832) and send it over BLE to a central device. I've already read quite a lot about creating your own BLE service and characteristics but I'm wondering if there is a good starting example or already existing code for my application.

Here are some application requirements:

around 100kb/s data stream

data arrays, probably as large as possible (max. MTU size)

sampled with 4kHz/8kHz/16kHz, 10 bit ADC

Low power application

example commands from central node to peripheral node: start streaming, stop streaming, etc.

The only real example I found so far is BLE NUS service from the ble_app_uart example (using ble_nus_string_send). Is this the way to go for transmitting large amounts of data? (also regarding power consumption)

Best and thanks for your time

Nick

  • Since there is no reply and I got a bit further,  here is an update on my progress:

    Starting out from the ble_app_uart and ble_app_uart_c examples and including the ssadc example (SDK 15.0), I'm now able to sample and send data with the ble_nus_data_send function, between to NRF52 DK boards.

    The way I went about this was to simply add the ssadc functions to the ble_app_uart example and include the necessary drivers. I use the ble_nus_data_send function upon and NRF_DRV_SAADC_DONE event to send data to the ble_app_uart_c via NOTIFY property.

    I'm currently sampling 8-bit adc data so the conversion from p_buffer to transfer buffer is slightly simplified, but it should also work in the 10bit+ case. SAMPLES_IN_BUFFER denotes the adc sampling buffer which is 200 in this case.

    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;
            err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAMPLES_IN_BUFFER);
            APP_ERROR_CHECK(err_code);
            uint16_t length = SAMPLES_IN_BUFFER;
            uint8_t transfer_buffer[SAMPLES_IN_BUFFER];
                   
                for (uint8_t i = 0; i < p_event->data.done.size; i++)
                {
                    transfer_buffer[i] = p_event->data.done.p_buffer[i];
    //                transfer_buffer[(i*2)] = p_event->data.done.p_buffer[i] >> 8;               // in 10bit to 16bit case
    //                transfer_buffer[(i*2)+1] = p_event->data.done.p_buffer[i];                  // in 10bit to 16bit case
                }
                  
                    do
                    {
                        err_code = ble_nus_data_send(&m_nus, transfer_buffer, &length, m_conn_handle);
    
                        if(err_code == NRF_SUCCESS)
                        {
                            queue_count--;
                            NRF_LOG_INFO("queue count: %d", queue_count);
                        }
    
                        else
                        {
                            if( (err_code != NRF_ERROR_INVALID_STATE) && (err_code != NRF_ERROR_BUSY) &&
                                   (err_code != NRF_ERROR_NOT_FOUND) )
                            {
                                APP_ERROR_CHECK(err_code);
                            }
                        }
                               
                    } while (err_code == NRF_ERROR_BUSY);
    
            NRF_LOG_INFO("ADC event number: %d", (int)m_adc_evt_counter);
            m_adc_evt_counter++;           
    
        }
    }
    

    Now to my problem:

    This method seems to work for adc sampling rates up to 1kHz. However, if I go higher than that, I sooner or later get the ERROR 19 [NRF_ERROR_RESOURCES] and the connection stops. From reading up on this issue it most likely happens due to the fact that the TX buffers are filled faster than they can be sent via BLE. From what i can tell there are 2 different approaches to this problem.

    1. Approach:

    Changing the BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT to a large value and introduce a queue counter to keep track of how many TX buffers are left. In case the counter reaches 0, the sampling should be stopped and all the buffers should be sent.

    As can be seen from my code above, I added a queue counter as described in the BLE GATT API (sd_ble_gatts_hvx) decrementing on an NRF_SUCCESS and incrementing on a BLE_GATTS_EVT_HVN_TX_COMPLETE event. I added the event to main.c in ble_evt_handler:

    case BLE_GATTS_EVT_HVN_TX_COMPLETE:
                 queue_count++;
                 break;

    When chosing a BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT size of 3, I can see the counter slowly moving towards 0 after a connection is established. After reaching 0, the connection between central and peripheral breaks off.

    However, the strange part is that even if I increase the queue of TX buffers from the default 3 to e.g 7, the counter does not go down to 0 but stops at 4 and the connection breaks off (ERROR 19). It seems as though the queue of TX buffers is not actually set with BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT. However, I cannot, for the love of god, figure out the reason for this.

    The next, step with this approach would be to somehow stop the sampling process and send all the buffers to the central device.

    2. Approach:

    If possible, I do not want to pause adc data acquisition to send all buffers at once in case they are full, but rather make sure to send data continuously. So I basically want for the Softdevice to keep up with the adc data acquisition at 4kHz - 8kHz (32kbit/s - 64kbit/s). At this point I'm also a bit confused, since I thought the softdevice has the highest priority. Is such a data stream even possible with the ble_app_uart example? If so, what are suitable BLE parameters for such an application?

    Other suggestions or remarks (e.g. regarding power consumption or handling of buffers) are very welcome as well.

    Best,

    Nick

  • Update 2:

    I got my code working with approach number 2:

    I'm now able to sample at 4kHz/8kHz adc sampling frequency by reducing the min. connection interval to 7.5ms and the max. connection interval 15ms (on both central and peripheral), as well as increasing the UART TX buffer size of the central to 1024 (previously set to 256).

    Changing the connection interval makes sense for me, but changing the UART TX buffer not so much.

    Why does it give me an ERROR 19 [NRF_ERROR_RESOURCES] on the peripheral if the TX buffer of the central is not big enough?

    Best,
    Nick

  • Hi Nick, 

    I'd look into setting up ADC to run on PPI. This reduces the CPU overhead of running the ADC.  Allows ADC and BLE radio to run better without stepping on each others toes.  

    Also you are likely hitting the data rate limit for BLE 4.2

  • Hi Dave

    Thanks for your reply.

    I think PPI is already implemented in Nordic's saadc example. I have two adc buffers implemented, each 200 samples in size.

    I don't think the data rate limit is below 100kbit/s, or am I wrong here? But, I am also in the process of implementing an audio codec to reduce the amount of data that has to be sent.

    Much more important for my case is power consumption. The next step will probably be to replace the HFCLK timer in the adc example with an RTC LFCLK source as well as implementing the internal DC/DC converter. I will also take a closer look at this post here: https://devzone.nordicsemi.com/tutorials/b/hardware-and-layout/posts/nrf51-current-consumption-guide.

    Unfortunately, I could not find a recent power guide for the NRF52 series. Do you maybe have any additional tipps for power consumption in my case?

  • Hi,

    Sorry about the late response.

    I suggest you to have a look at our nRF Thingy:52, I think it is what you are looking for. It has microphone streaming from the Thingy(peripheral) to a phone(Central). You can read more about it here.

Related