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

Sending large amounts of data from phone to nrf52840

I realise this question must have been asked a thousand times, but the more answers I read, the more confused I am!

I am looking to transfer 7kb of data occasionally (maybe once every hour) from an Android phone to an NRF52840. From my research it seems the logical way of doing this is to opt for the maximum MTU value the NRF52 supports (512 bytes) and transfer the 14 packets before reassembling them into the single large buffer on the NRF52.

I have tried increasing the MTU size up to 512, but this resulted in some of the notifications being sent to the Android phone (these notifications are completely unrelated to the 7kb transfer) being dropped - purely as a result of increasing the MTU size on the NRF52 and the phone asking for the higher MTU size (I didn't even transfer any data over 20 bytes during this test).

I don't mind the 7kb transfer taking a few seconds, so if the data is transferred in 20 byte chunks that is fine.

It seems there are 2/3 options, but all the research of existing questions is confusing me.

Long write - I have read that the max for a long write is ~512bytes?

Reliable write - I have read that there is also a 512 byte limit and there is a lot of overhead in this operation, so not the best way of doing this.

Queued write - I have seen the queued write module in the SDK, is this for Long/Reliable writes, or something completely different? Are there any byte transfer limits for this?

NRF52840, SDK 15.2

  • Hi Chris, 

    Yes, increasing the MTU size will give you a higher throughput compared to using the default ATT MTU size of 23, which gives you 20 byte payload. Although, the maximum size of a GATT attribute is 512 byte, the largest allowed MTU size in SDK v15.2.0 is 247 bytes. 

    Long write is simply multiple Write Requests to the same characteristic with offsets and this is the method that I would choose. 

    Reliable and Queued Write are essentially the same thing, but reliable writes have a lot of overhead as the value is sent in both directions before the actual write is being performed. You can write up-to 512 bytes using this method as its limited to the maximum attribute size. 

    If you want to set the ATT MTU on the nRF side to 247, then you need to use the GATT library( nrf_ble_gatt.c ) which will handle the GATTS ATT_MTU Exchange for you. You can use the ble_app_uart example as a reference. 

    /**@brief Function for initializing the GATT library. */
    void gatt_init(void)
    {
        ret_code_t err_code;
    
        err_code = nrf_ble_gatt_init(&m_gatt, gatt_evt_handler);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_ble_gatt_att_mtu_periph_set(&m_gatt, NRF_SDH_BLE_GATT_MAX_MTU_SIZE);
        APP_ERROR_CHECK(err_code);
    }
    
    // where NRF_SDH_BLE_GATT_MAX_MTU_SIZE == 247.

  • Thank you Bjorn.

    I've set up my long write support as follows:

    void ble_peripheral_service_on_ble_evt(ble_evt_t const *p_ble_evt, void *p_context) {
      ble_peripheral_service_t *p_peripheral_service = (ble_peripheral_service_t *)p_context;
    
      if (p_peripheral_service == NULL || p_ble_evt == NULL) {
        return;
      }
    
    //  SEGGER_RTT_printf(0, "Peripheral on BLE EVT: %d\n", p_ble_evt->header.evt_id);
    
      switch (p_ble_evt->header.evt_id) {
      case BLE_GAP_EVT_CONNECTED:
        SEGGER_RTT_printf(0, "Connected as Peripheral\n");
        on_connect(p_peripheral_service, p_ble_evt);
        break;
      case BLE_GAP_EVT_DISCONNECTED:
        SEGGER_RTT_printf(0, "Disconnected as Peripheral\n");
        on_disconnect(p_peripheral_service, p_ble_evt);
        break;
      case BLE_GATTS_EVT_WRITE:
        SEGGER_RTT_printf(0, "Write as Peripheral\n");
        on_write(p_peripheral_service, p_ble_evt);
        break;
      case BLE_EVT_USER_MEM_REQUEST:
        mem_block.len = 7000;
        mem_block.p_mem = &queued_write_buffer[0];
        ret_code_t err_code = sd_ble_user_mem_reply(p_peripheral_service->conn_handle, &mem_block);
        if (err_code != NRF_SUCCESS) {
          SEGGER_RTT_printf(0,"ERROR sd_ble_user_mem_reply: %u\r\n", (unsigned int)err_code);
        } else {
          SEGGER_RTT_printf(0,"USER_MEM_REQUEST OK\r\n");
        }
        break;
      case BLE_EVT_USER_MEM_RELEASE:
        SEGGER_RTT_printf(0, "MEM RELEASE\n");
        if ((p_ble_evt->evt.common_evt.params.user_mem_release.mem_block.p_mem == mem_block.p_mem) && (p_ble_evt->evt.common_evt.params.user_mem_release.mem_block.len == mem_block.len)) {
          SEGGER_RTT_printf(0, "MEM RELEASED!\n");
        }
        break;
      default:
        // No implementation needed.
        break;
      }
    }

    Characteristic initilization:

    static uint32_t assist_gps_char_add(ble_peripheral_service_t *p_peripheral_service) {
      // OUR_JOB: Step 2.A, Add a custom characteristic UUID
      uint32_t err_code;
      ble_gatts_char_md_t char_md;
      ble_gatts_attr_md_t cccd_md;
      ble_gatts_attr_t attr_char_value;
      ble_gatts_attr_md_t attr_md;
    
      uint8_t encoded_gps_meas[ASSIST_GPS_MEAS_LEN];
    
      ble_uuid_t char_uuid;
      ble_uuid128_t base_uuid = BLE_UUID_PERIPHERAL_BASE_UUID;
      char_uuid.uuid = BLE_UUID_GPS_ASSIST_CHARACTERISTIC_UUID;
    
      err_code = sd_ble_uuid_vs_add(&base_uuid, &char_uuid.type);
      APP_ERROR_CHECK(err_code);
    
      memset(&cccd_md, 0, sizeof(cccd_md));
      BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
      BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);
      cccd_md.vloc = BLE_GATTS_VLOC_STACK;
    
      memset(&char_md, 0, sizeof(char_md));
      char_md.char_props.read = 1;
      char_md.char_props.write = 1;
      char_md.char_props.notify = 0;
      char_md.p_char_user_desc = NULL;
      char_md.p_char_pf = NULL;
      char_md.p_user_desc_md = NULL;
      char_md.p_cccd_md = &cccd_md;
      char_md.p_sccd_md = NULL;  
    
      BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm); //was open
      BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm);
      attr_md.vloc = BLE_GATTS_VLOC_STACK;
      attr_md.rd_auth = 0;
      attr_md.wr_auth = 0;
      attr_md.vlen = 1;
    
      memset(&attr_char_value, 0, sizeof(attr_char_value));
    
      memset(&encoded_gps_meas, 0, ASSIST_GPS_MEAS_LEN);
    
      attr_char_value.p_uuid = &char_uuid;
      attr_char_value.p_attr_md = &attr_md;
      attr_char_value.init_len = ASSIST_GPS_MEAS_LEN;
      attr_char_value.init_offs = 0;
      attr_char_value.max_len = ASSIST_GPS_MEAS_LEN;
      attr_char_value.p_value = encoded_gps_meas;
    
      return sd_ble_gatts_characteristic_add(p_peripheral_service->service_handle,
          &char_md,
          &attr_char_value,
          &p_peripheral_service->gps_assist_char_handle);
    }

    I'm thinking this part of the characterisitc initalization may need tweaking? or the long write behaviour completely independant to the characteristic initilization, and will work on any writable characteristic?

    attr_char_value.init_len = ASSIST_GPS_MEAS_LEN;
    attr_char_value.init_offs = 0;
    attr_char_value.max_len = ASSIST_GPS_MEAS_LEN;

  • Hi Bjorn,

    I've fixed some fairly obvious bugs in the above code (namely, removing the CCCD seeing as its not a notifiable characteristic), and have tried testing the long write by sending a ~8600byte transfer to this characteristic (I'm using Nordic Android BLE Libary to issue this transfer from my Pixel 2 phone).

    BLE_EVT_USER_MEM_REQUEST is successfully called in my peripheral, but when I go to "sd_ble_user_mem_reply", I get a return value of 8, which is NRF_ERROR_INVALID_STATE.

    I can see this is either "Invalid Connection state or no user memory request pending.". Obviously the call to BLE_EVT_USER_MEM_REQUEST should mean the softdevice is expecting a memory block to be passed to it, so that leaves Invalid Connection State - but I am definitely connected at this stage, as the device continues to issue notifications (other characteristics) to the phone both before and directly after this event, with no disconnects inbetween. Any idea what could be wrong here?

    My code now is as follows:

    #define LONG_WRITE_LENGTH 9000
    
    static uint8_t queued_write_buffer[LONG_WRITE_LENGTH];
    static ble_user_mem_block_t mem_block;

    static uint32_t assist_gps_char_add(ble_peripheral_service_t *p_peripheral_service) {
      // OUR_JOB: Step 2.A, Add a custom characteristic UUID
      uint32_t err_code;
      ble_gatts_char_md_t char_md;
    //  ble_gatts_attr_md_t cccd_md;
      ble_gatts_attr_t attr_char_value;
      ble_gatts_attr_md_t attr_md;
    
      uint8_t encoded_gps_meas[ASSIST_GPS_MEAS_LEN];
    
      ble_uuid_t char_uuid;
      ble_uuid128_t base_uuid = BLE_UUID_PERIPHERAL_BASE_UUID;
      char_uuid.uuid = BLE_UUID_GPS_ASSIST_CHARACTERISTIC_UUID;
    
      err_code = sd_ble_uuid_vs_add(&base_uuid, &char_uuid.type);
      APP_ERROR_CHECK(err_code);
    
    //  memset(&cccd_md, 0, sizeof(cccd_md));
    //  BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
    //  BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);
    //  cccd_md.vloc = BLE_GATTS_VLOC_STACK;
    
      memset(&char_md, 0, sizeof(char_md));
      char_md.char_props.read = 1;
      char_md.char_props.write = 1;
      char_md.char_props.notify = 0;
      char_md.p_char_user_desc = NULL;
      char_md.p_char_pf = NULL;
      char_md.p_user_desc_md = NULL;
      char_md.p_cccd_md = NULL;
      char_md.p_sccd_md = NULL;  
    
      memset(&attr_md, 0, sizeof(attr_md));
      BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm); 
      BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm);
      attr_md.vloc = BLE_GATTS_VLOC_STACK;
      attr_md.rd_auth = 0;
      attr_md.wr_auth = 0;
      attr_md.vlen = 1;
    
      memset(&attr_char_value, 0, sizeof(attr_char_value));
    
      memset(&encoded_gps_meas, 0, ASSIST_GPS_MEAS_LEN);
    
      attr_char_value.p_uuid = &char_uuid;
      attr_char_value.p_attr_md = &attr_md;
      attr_char_value.init_len = ASSIST_GPS_MEAS_LEN;
      attr_char_value.init_offs = 0;
      attr_char_value.max_len = LONG_WRITE_LENGTH;
      attr_char_value.p_value = encoded_gps_meas;
    
      return sd_ble_gatts_characteristic_add(p_peripheral_service->service_handle,
          &char_md,
          &attr_char_value,
          &p_peripheral_service->gps_assist_char_handle);
    }
    

    case BLE_EVT_USER_MEM_REQUEST: {
      ret_code_t err_code;
        memset(&mem_block, 0, sizeof(mem_block));
        memset(&queued_write_buffer[0],0,LONG_WRITE_LENGTH);
        mem_block.len = LONG_WRITE_LENGTH;
        mem_block.p_mem = &queued_write_buffer[0];
         if (p_peripheral_service->conn_handle != BLE_CONN_HANDLE_INVALID) {
        err_code = sd_ble_user_mem_reply(p_peripheral_service->conn_handle, &mem_block);
        if (err_code != NRF_SUCCESS) {
          SEGGER_RTT_printf(0,"ERROR sd_ble_user_mem_reply: %u\r\n", (unsigned int)err_code);
        } else {
          SEGGER_RTT_printf(0,"USER_MEM_REQUEST OK\r\n");
        }
        }
        }
        break;
      case BLE_EVT_USER_MEM_RELEASE:
        SEGGER_RTT_printf(0, "MEM RELEASE\n");
        if ((p_ble_evt->evt.common_evt.params.user_mem_release.mem_block.p_mem == mem_block.p_mem) && (p_ble_evt->evt.common_evt.params.user_mem_release.mem_block.len == mem_block.len)) {
          SEGGER_RTT_printf(0, "MEM RELEASED!\n");
        }
        break;

  • Hi Chris, 

    I agree, the fact that you're getting the BLE_EVT_USER_MEM_REQUEST event should  rule out that there is no user memory request pending.  

    However, are you initializing the Queued Write module in services_init(), i.e. calling nrf_ble_qwr_init()?  If so then it could be that the event has already been handled by nrf_ble_qwr_on_ble_evt(). 

    You could try to use the Queued Writes module instead of handling the BLE_EVT_USER_MEM_REQUEST yourself?

  • Ahhh, that would make sense - the Queued Write module is initialized, as I have converted my application from the ble_cscs example.

    I think the thing that is confusing me is that Queued/Reliable Writes are different to Long Writes - so wasn't aware you could implement both different methods using the Queued Write module.

    With the queued write example, am I correct in thinking it shows the Queued/Reliable write implementation? If so, what would I need to modify to make it a Long Write?

    The nrf_ble_qwr_evet_type_t values of EXECUTE_WRITE and AUTH_REQUEST sound very specific to Queued/Reliable writes, and not something useful in a Long Write?

Related