File transfer/large data transfer example NRF Connect sdk

Hi,

I am trying to transfer a dataset of over 100k, via a custom service from a peripheral device.

There are many posts in this forum that describe ways to do this but none seem to apply to the newer nrf Connect SDK. I am using nrf connect 1.7.1 with the VSCode extension.

The peripheral_uart example is often quoted as being a good starting point, but i can't seem to find where i would start some sort of Send data->wait for data sent event-> send next part, kind of cycle. The throughput example appears to transfer a large data set, but i am struggling to interpret that service as it is quite large. 

Is the advised way of doing this still calling notify on a characteristic? I have attempted to call Notify inside an on_sent callback but the code doesn't appear to call callbacks when already executing inside of one.

I would be very grateful if anybody could point me in the right direction towards initiating a large data transfer from a peripheral device.

Parents
  • Thank you for the reply Simon. 

    I see that this is a good solution for handling ISR generated callbacks.

    The questions that i still need answering are as follows:

    1. what is a standard way to implement large data transfer from a peripheral to a central device using nrf connect sdk? Should i initiate a read and then have the peripheral notify again and again? how will i know that i am not starting another notify before the previous has been fully sent? Is notify even the right thing to do? could i not just continue to send data chunks from a single data read request? Does this require a characteristic in the central device for the peripheral to write to in a loop?

    2. Using the suggested work queue worked fine, but waiting for the on_sent notification before sending another notify was very slow. It took a minute to transfer the word "hello", a 500 times... a payload throughput of 48 Bps. I understand that the data length and mtu can be increased, but even if the payload size was 251, 8 transactions per second is a tiny number. 

    3. I see that there is also something referred to as a 'long read' in the peripheral example. What is this and how does it work? should i be using this instead of notify?

    I am using the nrf52832 on the nRF52 dk.

    Cheers

Reply
  • Thank you for the reply Simon. 

    I see that this is a good solution for handling ISR generated callbacks.

    The questions that i still need answering are as follows:

    1. what is a standard way to implement large data transfer from a peripheral to a central device using nrf connect sdk? Should i initiate a read and then have the peripheral notify again and again? how will i know that i am not starting another notify before the previous has been fully sent? Is notify even the right thing to do? could i not just continue to send data chunks from a single data read request? Does this require a characteristic in the central device for the peripheral to write to in a loop?

    2. Using the suggested work queue worked fine, but waiting for the on_sent notification before sending another notify was very slow. It took a minute to transfer the word "hello", a 500 times... a payload throughput of 48 Bps. I understand that the data length and mtu can be increased, but even if the payload size was 251, 8 transactions per second is a tiny number. 

    3. I see that there is also something referred to as a 'long read' in the peripheral example. What is this and how does it work? should i be using this instead of notify?

    I am using the nrf52832 on the nRF52 dk.

    Cheers

Children
  • I have not worked much with Bluetooth that much lately, and need to do some more research to fully answer all your question. But I will try to answer your questions the best I can now.

    cosmotic_instant said:
    1. what is a standard way to implement large data transfer from a peripheral to a central device using nrf connect sdk? Should i initiate a read and then have the peripheral notify again and again? how will i know that i am not starting another notify before the previous has been fully sent? Is notify even the right thing to do? could i not just continue to send data chunks from a single data read request? Does this require a characteristic in the central device for the peripheral to write to in a loop?

    I think notifications would be the most efficient way, see this answer https://devzone.nordicsemi.com/f/nordic-q-a/7052/what-is-the-difference-between-a-ble-central-reading-from-a-peripheral-versus-a-peripheral-notifying-the-central-in-term-of-resources-drained (it's for the nRF5 SDK, but should be relevant for NCS as well)

    cosmotic_instant said:
    2. Using the suggested work queue worked fine, but waiting for the on_sent notification before sending another notify was very slow. It took a minute to transfer the word "hello", a 500 times... a payload throughput of 48 Bps. I understand that the data length and mtu can be increased, but even if the payload size was 251, 8 transactions per second is a tiny number. 

    According to this answer (nRF5 SDK), it should be possible to get a transfer speed of 1400 kbps with the nRF52832, to achieve this you need to set the connection interval to 400ms, the MTU size to 247 bytes and enable DLE. Check out Bluetooth 5 speed maximum throughput.

    I will try to do some tests with the peripheral_uart sample (NCS) this week and see if I'm able to reach this speed.

    cosmotic_instant said:
    3. I see that there is also something referred to as a 'long read' in the peripheral example. What is this and how does it work? should i be using this instead of notify?

    I will get back to you on this.

    Best regards,

    Simon

  • I did some quick measurements using the peripheral_uart and the central_uart and I measured a speed of 4603bytes/second (36.8kbps), which is certainly better than 48bps.

    I sent the data accordingly from the peripheral_uart sample:

    #define MTU_SIZE 20
    #define TEST_BUF_SIZE 2000
    
    uint8_t test_buf[TEST_BUF_SIZE];
    ..
    
    
    for(int y =0; y< TEST_BUF_SIZE; y+=MTU_SIZE){
    	if (bt_nus_send(NULL, internal_data, MTU_SIZE)) {
    		LOG_WRN("."); //failed
    	}else{
    		LOG_INF("-"); //success
    	}
    }

    I don't think you need to wait for on_sent, It seems like the Zephyr Bluetooth host will queue the data.

    By using the parameters mentions in this answer you can increase the speed even more.

  • Hi Simon,

    Were the measurements that you have taken done with nRF connect SDK? I know that there is plenty of support on this forum for Nrf5 SDK but i am struggling to get examples for the connect SDK, using Zephyr's Bluetooth implementation. As that is now the recommended way of developing moving forward for Nordic, I started developing using the NCS. I know the BLE theory is the same, but how you setup characteristics etc is not. 

    The answer you linked to This answer, appears to reference a softdevice. The Zephyr implementation doesn't use a soft device correct? it's an open source implementation software implementation that is built along with the zephyr OS? 

    How can i find the size of the TX buffers? I assume these can be altered?

    Your test only sends 2000 bytes (or 200 packets @ 20 mtu size. What you have demonstrated is 36kbps but on a data set that is smaller than the tx buffer size. If it were larger, you probably could not add in a for loop as you are showing, without the function failing when it fills. I will try this example until it fails and continue to add the next packet until it is successful. This way, the throughput should be the same. Though, it does seem a shame to not a more event driven method of doing this.

    I guess, if i know the TX buffer size, i also know based on the already measured throughput, how long it will take to empty the buffer. So once i receive a fail, i can sleep until i expect it to be say half empty, before attempting to add the next packets. This will give a yield point instead of being stuck in an endless loop of attempting to add to the transmit queue.

    Any news on the Long read?

  • cosmotic_instant said:
    Were the measurements that you have taken done with nRF connect SDK? I know that there is plenty of support on this forum for Nrf5 SDK but i am struggling to get examples for the connect SDK, using Zephyr's Bluetooth implementation. As that is now the recommended way of developing moving forward for Nordic, I started developing using the NCS. I know the BLE theory is the same, but how you setup characteristics etc is not. 

    I used the nRF Connect SDK, see https://github.com/nrfconnect/sdk-nrf/tree/main/samples/bluetooth.

    cosmotic_instant said:
    The answer you linked to This answer, appears to reference a softdevice. The Zephyr implementation doesn't use a soft device correct? it's an open source implementation software implementation that is built along with the zephyr OS? 

    In the reply I linked to, the nRF5 SDK was used. It was not meant as a guide exactly how to improve the speed in Zephyr/NCS, but rather what parameters you should use. In NCS BLE samples, we use the SoftDevice Controller (similar to the SoftDevice). You can also use the Zephyr BLE controller.

    cosmotic_instant said:

    How can i find the size of the TX buffers? I assume these can be altered?

    Your test only sends 2000 bytes (or 200 packets @ 20 mtu size. What you have demonstrated is 36kbps but on a data set that is smaller than the tx buffer size. If it were larger, you probably could not add in a for loop as you are showing, without the function failing when it fills. I will try this example until it fails and continue to add the next packet until it is successful. This way, the throughput should be the same. Though, it does seem a shame to not a more event driven method of doing this.

    I guess, if i know the TX buffer size, i also know based on the already measured throughput, how long it will take to empty the buffer. So once i receive a fail, i can sleep until i expect it to be say half empty, before attempting to add the next packets. This will give a yield point instead of being stuck in an endless loop of attempting to add to the transmit queue.

    Any news on the Long read?

    I will get back to you on these question, and will try to put off some time the next days to try to achive a speed close to 1400 kbps.

  • I got some progress here, I added these to the peripheral_uart sample (NCS v1.8.0):

    CONFIG_BT_BUF_ACL_RX_SIZE=251
    CONFIG_BT_ATT_PREPARE_COUNT=2
    CONFIG_BT_L2CAP_TX_BUF_COUNT=10
    CONFIG_BT_L2CAP_TX_MTU=247
    CONFIG_BT_CTLR_RX_BUFFERS=2
    CONFIG_BT_BUF_ACL_TX_COUNT=10
    CONFIG_BT_BUF_ACL_TX_SIZE=251
    CONFIG_BT_CTLR_DATA_LENGTH_MAX=251

    and changed MTU_SIZE to 244, such that bt_nus_send would send 244 block at the time. Then I achieved a speed of 46511.6 bytes/second=372kbps. I communicated with the nRF Connect Mobile app. From the mobile app you have to enable notification for the TX char and request an MTU of 250 bytes.

    I also increased TEST_BUF_SIZE to 20000, such that a total of 20k bytes would be sent.

    Is this fast enough speed for your purpose? If not try to increase the connection interval to 400ms (tell me if you need help with this).

     Here is the changes I applied to the peripheral_uart sample:

    diff --git a/samples/bluetooth/peripheral_uart/prj.conf b/samples/bluetooth/peripheral_uart/prj.conf
    index e5262de64..d3e77f24e 100644
    --- a/samples/bluetooth/peripheral_uart/prj.conf
    +++ b/samples/bluetooth/peripheral_uart/prj.conf
    @@ -1,50 +1,65 @@
    -#
    -# Copyright (c) 2018 Nordic Semiconductor
    -#
    -# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
    -#
    -
    -# Enable the UART driver
    -CONFIG_UART_ASYNC_API=y
    -CONFIG_NRFX_UARTE0=y
    -CONFIG_SERIAL=y
    -
    -CONFIG_GPIO=y
    -
    -# Make sure printk is printing to the UART console
    -CONFIG_CONSOLE=y
    -CONFIG_UART_CONSOLE=y
    -
    -CONFIG_HEAP_MEM_POOL_SIZE=2048
    -
    -CONFIG_BT=y
    -CONFIG_BT_PERIPHERAL=y
    -CONFIG_BT_DEVICE_NAME="Nordic_UART_Service"
    -CONFIG_BT_DEVICE_APPEARANCE=833
    -CONFIG_BT_MAX_CONN=1
    -CONFIG_BT_MAX_PAIRED=1
    -
    -# Enable the NUS service
    -CONFIG_BT_NUS=y
    -
    -# Enable bonding
    -CONFIG_BT_SETTINGS=y
    -CONFIG_FLASH=y
    -CONFIG_FLASH_PAGE_LAYOUT=y
    -CONFIG_FLASH_MAP=y
    -CONFIG_NVS=y
    -CONFIG_SETTINGS=y
    -
    -# Enable DK LED and Buttons library
    -CONFIG_DK_LIBRARY=y
    -
    -# This example requires more workqueue stack
    -CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
    -
    -# Config logger
    -CONFIG_LOG=y
    -CONFIG_USE_SEGGER_RTT=y
    -CONFIG_LOG_BACKEND_RTT=y
    -CONFIG_LOG_BACKEND_UART=n
    -
    -CONFIG_ASSERT=y
    +#
    +# Copyright (c) 2018 Nordic Semiconductor
    +#
    +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
    +#
    +
    +# Enable the UART driver
    +CONFIG_UART_ASYNC_API=y
    +CONFIG_NRFX_UARTE0=y
    +CONFIG_SERIAL=y
    +
    +CONFIG_GPIO=y
    +
    +# Make sure printk is printing to the UART console
    +CONFIG_CONSOLE=y
    +CONFIG_UART_CONSOLE=y
    +
    +CONFIG_HEAP_MEM_POOL_SIZE=2048
    +
    +CONFIG_BT=y
    +CONFIG_BT_PERIPHERAL=y
    +CONFIG_BT_DEVICE_NAME="Nordic_UART_Service"
    +CONFIG_BT_DEVICE_APPEARANCE=833
    +CONFIG_BT_MAX_CONN=1
    +CONFIG_BT_MAX_PAIRED=1
    +
    +# Enable the NUS service
    +CONFIG_BT_NUS=y
    +
    +# Enable bonding
    +CONFIG_BT_SETTINGS=y
    +CONFIG_FLASH=y
    +CONFIG_FLASH_PAGE_LAYOUT=y
    +CONFIG_FLASH_MAP=y
    +CONFIG_NVS=y
    +CONFIG_SETTINGS=y
    +
    +# Enable DK LED and Buttons library
    +CONFIG_DK_LIBRARY=y
    +
    +# This example requires more workqueue stack
    +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
    +
    +# Config logger
    +CONFIG_LOG=y
    +CONFIG_USE_SEGGER_RTT=y
    +CONFIG_LOG_BACKEND_RTT=y
    +CONFIG_LOG_BACKEND_UART=n
    +
    +CONFIG_ASSERT=y
    +CONFIG_BT_USER_DATA_LEN_UPDATE=y
    +
    +
    +CONFIG_BT_BUF_ACL_RX_SIZE=251
    +#CONFIG_BT_GATT_CLIENT=y
    +CONFIG_BT_ATT_PREPARE_COUNT=2
    +#CONFIG_BT_CONN_TX_MAX=10
    +CONFIG_BT_L2CAP_TX_BUF_COUNT=10
    +CONFIG_BT_L2CAP_TX_MTU=247
    +#CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y
    +#CONFIG_BT_CTLR_PHY_2M=y
    +CONFIG_BT_CTLR_RX_BUFFERS=2
    +CONFIG_BT_BUF_ACL_TX_COUNT=10
    +CONFIG_BT_BUF_ACL_TX_SIZE=251
    +CONFIG_BT_CTLR_DATA_LENGTH_MAX=251
    \ No newline at end of file
    diff --git a/samples/bluetooth/peripheral_uart/src/main.c b/samples/bluetooth/peripheral_uart/src/main.c
    index 2acac0051..37ad3724b 100644
    --- a/samples/bluetooth/peripheral_uart/src/main.c
    +++ b/samples/bluetooth/peripheral_uart/src/main.c
    @@ -240,7 +240,7 @@ static int uart_init(void)
     		return -ENXIO;
     	}
     
    -	if (IS_ENABLED(CONFIG_USB_DEVICE_STACK)) {
    +	if (IS_ENABLED(CONFIG_USB)) {
     		err = usb_enable(NULL);
     		if (err) {
     			LOG_ERR("Failed to enable USB");
    @@ -319,6 +319,30 @@ static int uart_init(void)
     	return uart_rx_enable(uart, rx->data, sizeof(rx->data), 50);
     }
     
    +static void le_param_updated(struct bt_conn *conn, uint16_t interval,
    +			     uint16_t latency, uint16_t timeout)
    +{
    +	printk("Connection parameters updated.\n"
    +	       " interval: %d, latency: %d, timeout: %d\n",
    +	       interval, latency, timeout);
    +
    +}
    +
    +static void le_data_length_updated(struct bt_conn *conn,
    +				   struct bt_conn_le_data_len_info *info)
    +{
    +	/*if (!data_length_req) {
    +		return;
    +	}*/
    +
    +	printk("LE data len updated: TX (len: %d time: %d)"
    +	       " RX (len: %d time: %d)\n", info->tx_max_len,
    +	       info->tx_max_time, info->rx_max_len, info->rx_max_time);
    +
    +	/*data_length_req = false;
    +	k_sem_give(&throughput_sem);*/
    +}
    +
     static void connected(struct bt_conn *conn, uint8_t err)
     {
     	char addr[BT_ADDR_LE_STR_LEN];
    @@ -377,6 +401,8 @@ static void security_changed(struct bt_conn *conn, bt_security_t level,
     static struct bt_conn_cb conn_callbacks = {
     	.connected    = connected,
     	.disconnected = disconnected,
    +	.le_param_updated = le_param_updated,
    +	.le_data_len_updated = le_data_length_updated,
     #ifdef CONFIG_BT_NUS_SECURITY_ENABLED
     	.security_changed = security_changed,
     #endif
    @@ -415,6 +441,18 @@ static void auth_cancel(struct bt_conn *conn)
     }
     
     
    +static void pairing_confirm(struct bt_conn *conn)
    +{
    +	char addr[BT_ADDR_LE_STR_LEN];
    +
    +	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    +
    +	bt_conn_auth_pairing_confirm(conn);
    +
    +	LOG_INF("Pairing confirmed: %s", log_strdup(addr));
    +}
    +
    +
     static void pairing_complete(struct bt_conn *conn, bool bonded)
     {
     	char addr[BT_ADDR_LE_STR_LEN];
    @@ -441,6 +479,7 @@ static struct bt_conn_auth_cb conn_auth_callbacks = {
     	.passkey_display = auth_passkey_display,
     	.passkey_confirm = auth_passkey_confirm,
     	.cancel = auth_cancel,
    +	.pairing_confirm = pairing_confirm,
     	.pairing_complete = pairing_complete,
     	.pairing_failed = pairing_failed
     };
    @@ -555,9 +594,29 @@ static void configure_gpio(void)
     		LOG_ERR("Cannot init LEDs (err: %d)", err);
     	}
     }
    +#define MTU_SIZE 244
    +#define BYTES_TO_SEND 20000
    +
    +uint8_t dummy[MTU_SIZE];
    +uint64_t stamp;
    +int64_t delta;
    +
    +#define PIN_GPIO  (31UL)
     
     void main(void)
     {
    +	for(int x=0; x< MTU_SIZE;x++){
    +		dummy[x] = x;
    +		//printk("dummy[%d] = %d\n", x, dummy[x]);
    +	}
    +
    +	NRF_GPIO->PIN_CNF[PIN_GPIO] = (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) |
    +							(GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) |
    +							(GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
    +							(GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) |
    +							(GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos);
    +
    +	//LOG_INF("LOG_INF, Delta: %lld ms", delta);
     	int blink_status = 0;
     	int err = 0;
     
    @@ -615,12 +674,36 @@ void ble_write_thread(void)
     		/* Wait indefinitely for data to be sent over bluetooth */
     		struct uart_data_t *buf = k_fifo_get(&fifo_uart_rx_data,
     						     K_FOREVER);
    +		LOG_INF("Start transmission of %d bytes", BYTES_TO_SEND);
    +		stamp = k_uptime_get_32();
    +		NRF_GPIO->OUTSET = (1UL << PIN_GPIO);  //turns led (P0.17) off
    +		NRF_GPIO->OUTCLR = (1UL << PIN_GPIO); 
    +
    +		for(int y =0; y< BYTES_TO_SEND; y+=MTU_SIZE){
    +			//LOG_INF("y=%d/%d\n", y, BYTES_TO_SEND);
    +			if (bt_nus_send(NULL, dummy, MTU_SIZE)) {
    +				LOG_WRN("."); //failed
    +			}else{
    +				LOG_INF("-"); //success
    +			}
    +		}
    +		delta = k_uptime_delta(&stamp); //k_uptime_delta(&stamp);
    +		//LOG_INF("Should be 3000 ms| it is %" PRId64"  ms", delta);
    +		printk("Delta: %lld ms\n", delta);
    +		LOG_INF("Transmission ended");
    +		NRF_GPIO->OUTSET = (1UL << PIN_GPIO);  //turns led (P0.17) off
    +		NRF_GPIO->OUTCLR = (1UL << PIN_GPIO); 
    +		printk("Sent %d bytes (%u KB) in %lld ms\n",
    +	       BYTES_TO_SEND, BYTES_TO_SEND / 1024, delta); //, ((uint64_t)data * 8 / delta));
    +		/* Wait indefinitely for data to be sent over bluetooth */
    +		/*struct uart_data_t *buf = k_fifo_get(&fifo_uart_rx_data,
    +						     K_FOREVER);
     
     		if (bt_nus_send(NULL, buf->data, buf->len)) {
     			LOG_WRN("Failed to send data over BLE connection");
     		}
     
    -		k_free(buf);
    +		k_free(buf);*/
     	}
     }
     
    

Related