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

nRF52840 SDK16 - High data rate Tx

Hi everyone,

We design an application using the nRF52840 and SDK16. This application is intended to be used for gait analysis and one of the requirements is a real-time data process. We are using nRF52832 as a central device to receive raw data and then UART protocol to communicate nRF52832 with the main MCU. The gait analysis requires a high sampling rate (from 100 to 300Hz). What is the best approach to limit data loss and maintain power consumption as low as possible?

I use notifications (the API sd_ble_gatts_hvx) to send data from the peripheral to the central. I know also that notifications can be transmitted within a connection event of 7.5 ms (this is a frequency of around 133Hz). What if I want to use a sampling rate of 200Hz (32Bytes x 8bits x 200Hz = 12.8Kbps)? What procedure should I follow?

Also when I try to transmit with 100Hz the sd_ble_gatts_hvx function returns too often NRF_ERROR_RESOURCES and I have to wait for BLE_GATTS_EVT_HVN_TX_COMPLETE event. This creates a lug between the real-time readings and the data received by the central (remember that we need real-time data processing)

Any advice?

Nick

Parents
  • Hi Antoniou,

    nRF52 is a single core chip. And you can have only only one strictly real time thread in it. When using BLE stack, which needs to follow very hard time constrained events, it takes up the highest interrupt priority reserved for it. When there is any BLE activity, there is both pre and post processing of that BLE data transmitted and or received. The throughput greatly depends on

    1. Connection interval
    2. Connect event length
    3. Data size in the packet (using DLE and bigger packet size needs bigger event length)
    4. PHY used
    5. data to be sent out already being queued as fast as they can
    6. minimal application post processing of data transmitted and or recieved to give maximum CPU time for application to queue data.

    The suggestions for event length for a connection can be found here.

    You need to understand how much time your application is spending on pre and post processing of data on notifications.
    You also need to understand how many packets you are able to squeeze into one connection interval (how many connection events within a connection interval) based on the data size within a packet. In our thoughput examples we demonstrated that you can achieve very close to theoretical maximum throughput (1.4Mbps) with some room for application pre and post processing. But there is always a balance to make that an Architect of your application will be able to calculate/design and answer. 

    In your case, if you are getting NRF_ERROR_RESOURCES with that low throughput, means that you application has not calibrated the connection correctly OR spending a lot of time in pre or post precessing of data.

Reply
  • Hi Antoniou,

    nRF52 is a single core chip. And you can have only only one strictly real time thread in it. When using BLE stack, which needs to follow very hard time constrained events, it takes up the highest interrupt priority reserved for it. When there is any BLE activity, there is both pre and post processing of that BLE data transmitted and or received. The throughput greatly depends on

    1. Connection interval
    2. Connect event length
    3. Data size in the packet (using DLE and bigger packet size needs bigger event length)
    4. PHY used
    5. data to be sent out already being queued as fast as they can
    6. minimal application post processing of data transmitted and or recieved to give maximum CPU time for application to queue data.

    The suggestions for event length for a connection can be found here.

    You need to understand how much time your application is spending on pre and post processing of data on notifications.
    You also need to understand how many packets you are able to squeeze into one connection interval (how many connection events within a connection interval) based on the data size within a packet. In our thoughput examples we demonstrated that you can achieve very close to theoretical maximum throughput (1.4Mbps) with some room for application pre and post processing. But there is always a balance to make that an Architect of your application will be able to calculate/design and answer. 

    In your case, if you are getting NRF_ERROR_RESOURCES with that low throughput, means that you application has not calibrated the connection correctly OR spending a lot of time in pre or post precessing of data.

Children
  • Thank you for your prompt responce

    Could you elaborate more on 6. "minimal application post processing of data transmitted and or recieved"?

    What do you mean by pre and post processing and how this affect the throughput? Could you provide an example?

  • Every notification data packet your application queues, needs some pre processing (and most likely some post processing after sending) apart from other things your application does. The CPU time used up by the softdevice is mostly fixed per BLE procedure and data transmit/receive. So you need to benchmark how much time your application is using the CPU on preparing each data packet and other logics that needs to maintain the housekeeping of your application. 

    In short, 6) points to the need to benchmark your application CPU time doing different things, you can use GPIOs to toggle at different logics to get an idea on application CPU usage. It seems that the application is sometimes queueing the notifications too fast (and hence get NRF_ERROR_RESOURCES  in the HVX operation) and sometimes have idle periods where it is not utilizing the connection events to transmit data (as the application might be busy doing other stuff).

  • Every notification data packet your application queues, needs some pre processing (and most likely some post processing after sending) apart from other things your application does

    The preprocessing is done by the SD when I call the sd_ble_gatts_hvx?

    It seems that the application is sometimes queueing the notifications too fast

    The queueing time is basically the time intervals between the sd_ble_gatts_hvx calls?

  • Nikosant03 said:
    The preprocessing is done by the SD when I call the sd_ble_gatts_hvx?

     Yes, but the application probably is also preparing the packet to be sent? it would become a bit easier if you can share some code snippets of how you are getting/preparing your data to be transmitted. Also try to change the PHY to 2M to have higher throughput.

  • Hi Susheel,

    Let me give a brief description of my application: The application reads 8 x analog pressure sensors by multiplexing them and it also acquires the position information from an IMU. I use two RTC timers to accomplish that.

    RTC timer 1

    I use this timer to read the pressure sensors. It expires every 800us and every time it expires I trigger sampling, convert the analog input and buffer it (meaning that I need approximately 800us x 8 sensors = 6.4ms to read all the sensors).

    RTC timer 2

    I use this timer to get the IMU data, buffer them, prepare the payload and finally notify the attribute value. This timer expires every 10ms (100Hz) 

    Yes, but the application probably is also preparing the packet to be sent? it would become a bit easier if you can share some code snippets of how you are getting/preparing your data to be transmitted

    Yes, here some code snippets:

    I use 2 x timers for sampling the sensors, get the IMU data and notify the attribute value:

    APP_TIMER_DEF(m_SAADC_timer_id);        // na - app_timer for SAADC captures
    APP_TIMER_DEF(m_notification_timer_id); // na - app_timer for IMU capturs and BLE notifications
    
    #define SAADC_INTERVAL APP_TIMER_TICKS_US(800)           /**< interval to read flexi force in us */
    #define NOTIFICATION_INTERVAL APP_TIMER_TICKS(10)        /**< notification interval in ms */

    When the m_SAADC_timer_id expires (every 800us):

    1. I trigger the sampling

    2. Convert the analog input

    3. Multiplexing the pressure sensors

    // Trigger sampling
    void saadc_sample() {
      ret_code_t err_code;
      err_code = nrf_drv_saadc_sample();
      APP_ERROR_CHECK(err_code);
    }
    
    // Convert samples from analog to digital and buffer the digital values
    void saadc_callback(nrf_drv_saadc_evt_t const *p_event) {
    
      if (p_event->type == NRF_DRV_SAADC_EVT_DONE) { // na - when the buffer is filled then the NRF_DRV_SAADC_EVT_DONE is generated
        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);
    
        ADC_RawData[callback_counter] = p_event->data.done.p_buffer[0]; // na - Buffer the ADC values from flexi force sensors (8 x sensors)
    
        if (callback_counter == NO_SENSORS - 1) {
        
          memset(ADC_RawData, 0, sizeof(ADC_RawData)); // na - Initialize the ADC_RawData buffer to 0
          callback_counter = 0;
          
        }
    
        else {
          callback_counter++;
        }
        selectMuxPin(callback_counter); // na - change MUX channel to read the next sensor
      }
    }
    
    // Multiplexing the pressure sensors
    void selectMuxPin(int channel) {
      for (int i = 0; i < MUX_CTRL_CHA; i++) {
    
        if (channel & (1 << i)) {
          nrf_gpio_pin_set(ctrlPins[i]);
        } else {
          nrf_gpio_pin_clear(ctrlPins[i]);
        }
      }
    }

    When the m_notification_timer_id expires (every 10ms):

    1. I get the IMU data

    2. Prepare the payload

    3. Send notification

    static void notification_timeout_handler(void *p_context) {
    
      UNUSED_PARAMETER(p_context);
      ret_code_t err_code;
    
      m_xfer_done = false;
    
      bmi160_get_sensor_data((BMI160_ACCEL_SEL | BMI160_GYRO_SEL | BMI160_TIME_SEL), &s_accel, &s_gyro, &sensor);
    
      packet_counter++;
    
      int16_t msb_packet_counter = packet_counter >> 16;
      int16_t lsb_packet_counter = packet_counter;
    
      // Passing an array of data through notification to the central
      int16_t m_array[] = {s_accel.x, s_accel.y, s_accel.z, s_gyro.x, s_gyro.y, s_gyro.z, //bmm150.data.x, bmm150.data.y, bmm150.data.z
          ADC_RawData[0], ADC_RawData[1], ADC_RawData[2], ADC_RawData[3],
          ADC_RawData[4], ADC_RawData[5], ADC_RawData[6], ADC_RawData[7], msb_packet_counter, lsb_packet_counter}; // you can set either decimal or hex values
    
      err_code = ble_cus_custom_value_update(&m_cus, m_array);
      // Filter out some errors
      if (err_code != NRF_ERROR_INVALID_STATE && err_code != BLE_ERROR_GATTS_SYS_ATTR_MISSING && err_code != NRF_ERROR_RESOURCES  && err_code != NRF_ERROR_BUSY) { //
        APP_ERROR_CHECK(err_code);
      }
    
      err_code = app_timer_start(m_SAADC_timer_id, SAADC_INTERVAL, NULL); // na - Restart the SAADC sampling
      APP_ERROR_CHECK(err_code);
    }

    and just for reference this if where I implement the notification

    //This is where we implement the notification
    uint32_t ble_cus_custom_value_update(ble_cus_t *p_cus, int16_t custom_value[]) {
    
      if (p_cus == NULL) {
        return NRF_ERROR_NULL;
      }
    
      uint32_t err_code = NRF_SUCCESS;
    
      /** Send value if connected and notifying using sd_ble_gatts_hvx
       *  Verify that we have a valid connection handle, i.e. that we're actually connected to a peer, 
       *  if not we should return an error indicating that we're in an invalid state
       */
      if ((p_cus->conn_handle != BLE_CONN_HANDLE_INVALID)) // Here we check whether or not we are in a valid connection. If you try to send out notifications
                                                           // when not in a connection the SoftDevice will get grumpy and give you errors
      {
        ble_gatts_hvx_params_t hvx_params; // We declare a variable, hvx_params, of type ble_gatts_hvx_params_t
                                           // This will hold the necessary parameters to do a notification and provide them to the sd_ble_gatts_hvx()
                                           // hvx stands for Handle Value X where X symbolize either notification of indication
    
        memset(&hvx_params, 0, sizeof(hvx_params));
    
        uint16_t len = 32; // Set the length of bytes to be written. Remember that you must set the same len size for the characteristic attribute
    
        hvx_params.handle = p_cus->custom_value_handles.value_handle; // Provide the handle of our characteristic
        hvx_params.type = BLE_GATT_HVX_NOTIFICATION;                  // What "hvx type" we want to do, a notification or indication?
        hvx_params.offset = 0;                                        // Your characteristic value might be a sequence of many bytes. If you want to transmit only a couple
                                                                      // of these bytes and the  bytes are located in the middle of the sequence you can use the offset to extract them.
                                                                      // Since we want to update all of our four bytes we will set the offset to zero.
        hvx_params.p_len = &len;                                      // The current value is 2 bytes (sizeof(uint8_t))
        hvx_params.p_data = custom_value;                             // Here we add a pointer to the actual data
    
        /*
            After setting the hvx_params, we notify the peer by calling sd_ble_gatts_hvx()
            Parameters
            [in]         conn_handle	Connection handle.
            [in,out]     p_hvx_params	Pointer to an HVx parameters structure. If ble_gatts_hvx_params_t::p_data contains a non-NULL pointer 
                                            the attribute value will be updated with the contents pointed by it before sending the notification or indication. 
                                            If the attribute value is updated, ble_gatts_hvx_params_t::p_len is updated by the SoftDevice to contain the number 
                                            of actual bytes written, else it will be set to 0.
    
             sd_ble_gatts_hvx() not-only can change the internal value of the characteristic, but also sends it out as a notification/indication 
             to the other side which is subscribed to the characteristic
    
        */
    
        err_code = sd_ble_gatts_hvx(p_cus->conn_handle, &hvx_params); // Notify the peer by passing the connection handle as well as the hvx_params structure
      } else {
        err_code = NRF_ERROR_INVALID_STATE;
        //NRF_LOG_INFO("sd_ble_gatts_hvx result: NRF_ERROR_INVALID_STATE. \r\n");
      }
    
      return err_code;
    }

    Also try to change the PHY to 2M to have higher throughput

    I will try it, at the moment I use the BLE_GAP_PHY_AUTO

    I hope this helps Slight smile

Related