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

How to increase the sampling rate of the ble_app_uart__saadc_timer_driven__scan_mode example?

Hi,

in my project I have a custom board with a nRF52832 chip that reads SAADC data and transmits this data (peripheral role). This data is received by a nRF52840-DK board (central role) and written into a COM-Port of a pc. I flashed the "ble_app_uart_c" example into the nRF52840-DK and the "ble_app_uart__saadc_timer_driven__scan_mode" example into the custom board with the nRF52832 chip (nRF5_SDK_17.0.0_9d13099). This configuration works fine. The SAADC data gets transmitted by the nRF52832 custom board, received by the nRF52840-DK and written into the COM-Port of my PC.

My problem is the SAADC sampling rate (SAADC_SAMPLE_RATE) of the "ble_app_uart__saadc_timer_driven__scan_mode" example. By default the SAADC sampling rate is 250ms (4Hz). I need to increase this sampling rate to 1ms (1000Hz). But if I choose the SAADC_SAMPLE_RATE < 10ms (>100Hz) an error occured and the chip resets and tries to reconnect. I receive the following error code if I debug the ble_app_uart__saadc_timer_driven__scan_mode example:

which is produced by the ble_nus_data_send(...) function in main.c:

Unfortunaly, I am not able to find a error description for this error code.

Can you please tell me, how to increase the sampling rate of the "ble_app_uart__saadc_timer_driven__scan_mode" example to 1ms (=1000Hz)?

  • Hi,

    thank you for your response.

    I made your recommended changes and it works.

    Unfortunaly, averaging the SAADC values is no option for my project. For my project all four channels must be sampled with 1ms (1000Hz) and transmitted without averaging of the measurements.

    It looks like that the maximum string length of the ble_nus_data_send() function is 20bytes. This data rate is to low for my project.

    1. Is there a way to increase the maximum string length of the ble_nus_data_send() function to increase the data rate for sending data over BLE via NUS service?

    2. Do you have any suggestions how to solve this issue?

    Thank you so much in advance.

  • Hello,

    Michael01101 said:
    I made your recommended changes and it works.

    No problem at all, I am happy to help!

    Michael01101 said:
    I made your recommended changes and it works.

    I am happy to hear that it works, now that we have sorted out the issue we can look into how to increase the throughput.

    Michael01101 said:

    It looks like that the maximum string length of the ble_nus_data_send() function is 20bytes. This data rate is to low for my project.

    1. Is there a way to increase the maximum string length of the ble_nus_data_send() function to increase the data rate for sending data over BLE via NUS service?



    Yes, there is a way to do this. You will have to include an MTU exchange procedure in your project, to increase the MTU Size.
    Please take a look at the answer my colleague Petter made in this ticket regarding how you may implement this. The answer is a bit old, but the procedure is still the same - the central will need to request the peripheral to increase the MTU size ( which by default is 23 bytes per connection event ), and the peripheral have to accept this with an MTU exchange reply. As Petter notes in the comment, he increased it to 155 bytes per connection event in the project he included. You can read more about the MTU exchange request here. Seeing this sequence chart might also be helpful.

    Michael01101 said:
    2. Do you have any suggestions how to solve this issue?

    Take a look at the modification Petter made in the project he included in his reply, and try to add this MTU exchange procedure to your own project.

    Please do not hesitate to let me know if you should encounter any issues or questions while implementing this.

    Best regards,
    Karl

  • Hello,

    thank you for your response.

    Fortunately, it looks like the MTU exchange is already implemented in the "ble_app_uart_c" and the "ble_app_uart__saadc_timer_driven__scan_mode" example. After I run the program on nRF52832 custom board (peripheral), I immediately receive the following in the debug terminal:

    <info> app: ATT MTU exchange completed. central 0xF7 peripheral 0xF7
    <info> app: Data len is set to 0xF4(244)

    At the moment the following changes are applied to the "ble_app_uart__saadc_timer_driven__scan_mode" example on the peripheral side in main.c:

    #define MIN_CONN_INTERVAL               MSEC_TO_UNITS(7.5, UNIT_1_25_MS)
    #define MAX_CONN_INTERVAL               MSEC_TO_UNITS(10 , UNIT_1_25_MS)
    
    #define SAADC_SAMPLE_RATE               1
    
    static int16_t counter = 1;
    
    static int16_t ch1_values[8];
    static int16_t ch2_values[8];
    static int16_t ch3_values[8];
    static int16_t ch4_values[8];
    
    void saadc_callback(nrf_drv_saadc_evt_t const * p_event)
    {
        if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
        {
            ...
            if (counter <= 8){
                ch1_values[counter-1] = p_event->data.done.p_buffer[0];
                ch2_values[counter-1] = p_event->data.done.p_buffer[1];
                ch3_values[counter-1] = p_event->data.done.p_buffer[2];
                ch4_values[counter-1] = p_event->data.done.p_buffer[3];
            }
            
            if (counter == 8){     
                            
                counter = 0;
                
                uint8_t nus_string[200];                                                                                
                bytes_to_send = sprintf(nus_string,                                                               
                                      "%d%d%d%d_%d%d%d%d_%d%d%d%d_%d%d%d%d_%d%d%d%d_%d%d%d%d_%d%d%d%d_%d%d%d%d\r\n",
                                      ch1_values[0],
                                      ch2_values[0],
                                      ch3_values[0],
                                      ch4_values[0],
                                      ch1_values[1],
                                      ch2_values[1],
                                      ch3_values[1],
                                      ch4_values[1],
                                      ch1_values[2],
                                      ch2_values[2],
                                      ch3_values[2],
                                      ch4_values[2],
                                      ch1_values[3],
                                      ch2_values[3],
                                      ch3_values[3],
                                      ch4_values[3],
                                      ch1_values[4],
                                      ch2_values[4],
                                      ch3_values[4],
                                      ch4_values[4],
                                      ch1_values[5],
                                      ch2_values[5],
                                      ch3_values[5],
                                      ch4_values[5],
                                      ch1_values[6],
                                      ch2_values[6],
                                      ch3_values[6],
                                      ch4_values[6],
                                      ch1_values[7],
                                      ch2_values[7],
                                      ch3_values[7],
                                      ch4_values[7]);
            
                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); 
                }
            }
    	counter++;
    	}
    }

    Basically, I save the SAADC readings for 7ms. In the next millisecond I transmit the actual SAADC readings and the saved SAADC readings of the last 7ms. On the central side I receive every 8ms 8 samples of each channel. This works for quite a few seconds or sometimes for one or two minutes.

    If it stopps after a few seconds I get the following error:

    <error> app: ERROR 4 [NRF_ERROR_NO_MEM] at C:\SDK\SDKv17\nRF5_SDK_17.0.0_9d13099\examples\ble_peripheral\ble_app_uart__saadc_timer_driven__scan_mode_optimized_third\main.c:237
    PC at: 0x0002A58D

    which is caused by the following function:

    static void nus_data_handler(ble_nus_evt_t * p_evt)
    {
    ...
        err_code = app_uart_put(p_evt->params.rx_data.p_data[i])
    }    

    And sometimes I receive the following error:

    <error> app: ERROR 19 [NRF_ERROR_RESOURCES] at C:\SDK\SDKv17\nRF5_SDK_17.0.0_9d13099\examples\ble_peripheral\ble_app_uart__saadc_timer_driven__scan_mode_optimized_third\main.c:833
    PC at: 0x0002A4F1

    which is caused by the ble_nus_data_send(...) function.

    I don´t think, that this is the ideal solution for transmitting my SAADC readings of 4 channels with a sample rate of 1ms (1000Hz). So here are my questions:

    1. Why do I get sometimes a NRF_ERROR_RESOURCES error and sometimes a NRF_ERR_NO_MEM error?

    2. What is the reason for a NRF_ERR_NO_MEM error?

    3. Should I call the ble_nus_data_send(...) function more often than in a 8ms interval (Because my minimal connection interval is 7.5ms)?

    4. Is there a better way to transmit the SAADC readings, when using a sample rate of 1ms(1000Hz) and four channels? Do you have any suggestions?

    Thank you very much in advance.

  • Hello,

    Michael01101 said:
    Basically, I save the SAADC readings for 7ms. In the next millisecond I transmit the actual SAADC readings and the saved SAADC readings of the last 7ms. On the central side I receive every 8ms 8 samples of each channel. This works for quite a few seconds or sometimes for one or two minutes.

    Please remember that the shortest valid connection interval is in fact 7.5 ms

    Michael01101 said:
    1. Why do I get sometimes a NRF_ERROR_RESOURCES error and sometimes a NRF_ERR_NO_MEM error?

    When troubleshooting why a function returned a certain error, it is all written in the specific function documentation. From your error log it seems this is line 237 of main.c
    If this is app_uart_put, returning NRF_ERROR_NO_MEM we take a look at the functions documentation and see that this error is returned when:

    If no more space is available in the TX buffer. NRF_ERROR_NO_MEM may occur if flow control is enabled and CTS signal is high for a long period and the buffer fills up.

    This indicates that messages are being placed in the TX buffer faster than they are being sent over UART. To resolve this, you will either have to reduce the size or frequency of your transfers, or increase the UART transmission frequency.

    Doing the same for ble_nus_data_send we see that it returns the error code NRF_ERROR_RESOURCES returned by sd_ble_gatts_hvx, which happens when:

    Too many notifications queued. Wait for a BLE_GATTS_EVT_HVN_TX_COMPLETE event and retry.

    So, it seems you are still queuing the notifications faster than they are being sent. This makes sense if you are in fact queuing a notification every 7 ms, since that will cause a 0.5 ms shift every 7.5 ms. Eventually, you will run out of memory, resulting in the error.

    Michael01101 said:
    4. Is there a better way to transmit the SAADC readings, when using a sample rate of 1ms(1000Hz) and four channels? Do you have any suggestions?

    I think this should be fine, but you will need to make sure that there at least is a match or even some leeway for every transfer.

    To visualize and confirm the current issue, you can write out a messages to the log with the contents of your buffers when the program is running, and how often you are calling ble_nus_data_send vs. how often you are receiving the BLE_GATTS_EVT_HVN_TX_COMPLETE event. I think you will then see that you are generating and queuing data faster than you are processing it. You should see that the buffers / mismatch keeps growing until an error is eventually generated.

    Best regards,
    Karl

  • Hello,

    sorry for my late response.

    I implemented a counter (counterTX), which counts how often a BLE_GATTS_EVT_HVN_TX_COMPLETE event happend.

    I did this in ble_nus.c:

    void ble_nus_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
    {
        if ((p_context == NULL) || (p_ble_evt == NULL))
        {
            return;
        }
    
        ble_nus_t * p_nus = (ble_nus_t *)p_context;
    
        switch (p_ble_evt->header.evt_id)
        {
            case BLE_GAP_EVT_CONNECTED:
                on_connect(p_nus, p_ble_evt);
                break;
    
            case BLE_GATTS_EVT_WRITE:
                on_write(p_nus, p_ble_evt);
                break;
    
            case BLE_GATTS_EVT_HVN_TX_COMPLETE:
    
                counterTX++;
                NRF_LOG_INFO("TX_COMPLETE: %d", counterTX);
    
                on_hvx_tx_complete(p_nus, p_ble_evt);
    
                break;
    
            default:
                // No implementation needed.
                break;
        }
    }

    But even in the unmodified "ble_app_uart__saadc_timer_driven__scan_mode" example on the peripheral side, there is never a BLE_GATTS_EVT_HVN_TX_COMPLETE event happening. The counterTX never increases.

    1. Is this the right place for counting the BLE_GATTS_EVT_HVN_TX_COMPLETE events?

    2. Why are no BLE_GATTS_EVT_HVN_TX_COMPLETE events happening in this example?

    Thank you very much in advance.

Related