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

Sending data to iOS crashes midway

Using NRF51822 + SoftDevice 130, version 2.0.0, I have written a program which reads data from on-chip flash memory and transfers it to smartphone once connection is established. In order to determine if connection is established I wait for event "BLE_GAP_EVT_CONNECTED" in "on_ble_evt" handler. After that data is transferred using "ble_nus_string_send". 

When I connect to the device using NRF Toolbox app (in UART mode) on Android - All data is transferred correctly and can be seen in the application's Log.
When I connect to the device using the same app but this time on iOS - Data strarts to transfer correctly but hangs before all data is received by iPhone.

Parents
  • Hello,

    Any particular reason for why you use such an old SDK? I suggest, if you are not far in your development to move to SDK12.3.0, which is the latest SDK for nRF51 - S130.

    What do you mean by "hangs before all data is received by iPhone"?

    I suspect that you are caught in an APP_ERROR_CHECK(). Probably in the part:

    err_code = ble_nus_string_send(...)
    if (err_code != NRF_ERROR_INVALID_STATE)
    {
        APP_ERROR_CHECK(err_code);
    }

    Maybe you get err_code == NRF_ERROR_RESOURCES (= 19 or 0x13)?

    This means that the softdevice queue is full, and you need to wait for a BLE_EVT_TX_COMPLETE event before you can send more data. The reason this happens on the iPhone and not the Android may be because they are giving you different connection parameters, and that the connection parameters on the Android is allowing a higher throughput.

    You can add the BLE_EVT_TX_COMPLETE event in the on_ble_evt() function in main.c, and use this event to set a flag indicating that you can send more data when this event occurs.

    Best regards,

    Edvin

  • I've actually already implemented waiting for BLE_EVT_TX_COMPLETE event to be generated every 1 word (4 bytes I send). As for the connections parameters, aren't they the same for both Android and iOS as my device is acting as Central and the phone is Peripheral. 

  • Okay, indeed I am using ble_nus_string_send() NOT ble_nus_c_string_send(). So my device is peripheral after all and phone (Android/iOS) is central. The parameters I am setting on device's side are:

    /* Definitions */
    #define IS_SRVC_CHANGED_CHARACT_PRESENT 0                                           		/**< Include the service_changed characteristic. If not enabled, the server's database cannot be changed for the lifetime of the device. */
    
    #define CENTRAL_LINK_COUNT              0                                           		/**< Number of central links used by the application. When changing this number remember to adjust the RAM settings*/
    #define PERIPHERAL_LINK_COUNT           1                                          			/**< Number of peripheral links used by the application. When changing this number remember to adjust the RAM settings*/
    
    #define DEVICE_NAME                     "Nordic_TEST"                               		/**< Name of device. Will be included in the advertising data. */
    #define NUS_SERVICE_UUID_TYPE           BLE_UUID_TYPE_VENDOR_BEGIN                  		/**< UUID type for the Nordic UART Service (vendor specific). */
    
    // Read this: https://www.argenox.com/library/bluetooth-low-energy/ble-advertising-primer/
    #define APP_ADV_INTERVAL                64// 560                                  			/**< The advertising interval (in units of 0.625 ms. This value corresponds to 40 ms). */
    #define APP_ADV_TIMEOUT_IN_SECONDS      120                                         		/**< The advertising timeout (in units of seconds). */
    
    #define APP_TIMER_PRESCALER             0                                           		/**< Value of the RTC1 PRESCALER register. */
    #define APP_TIMER_OP_QUEUE_SIZE         4                                           		/**< Size of timer operation queues. */
    
    #define MIN_CONN_INTERVAL               MSEC_TO_UNITS(20, UNIT_1_25_MS)             		/**< Minimum acceptable connection interval (20 ms), Connection interval uses 1.25 ms units. */
    #define MAX_CONN_INTERVAL               MSEC_TO_UNITS(40, UNIT_1_25_MS)              		/**< Maximum acceptable connection interval (75 ms), Connection interval uses 1.25 ms units. */
    #define SLAVE_LATENCY                   0                                           		/**< Slave latency. */
    #define CONN_SUP_TIMEOUT                MSEC_TO_UNITS(6000, UNIT_10_MS)             		/**< Connection supervisory timeout (4 seconds), Supervision Timeout uses 10 ms units. */
    #define FIRST_CONN_PARAMS_UPDATE_DELAY  APP_TIMER_TICKS(5000, APP_TIMER_PRESCALER)  		/**< Time from initiating event (connect or start of notification) to first time sd_ble_gap_conn_param_update is called (5 seconds). */
    #define NEXT_CONN_PARAMS_UPDATE_DELAY   APP_TIMER_TICKS(30000, APP_TIMER_PRESCALER) 		/**< Time between each call to sd_ble_gap_conn_param_update after the first call (30 seconds). */
    #define MAX_CONN_PARAMS_UPDATE_COUNT    3                                           		/**< Number of attempts before giving up the connection parameter negotiation. */
    

    The normal operation cycle of firmware is as follows:
    1. Gather data from sensors for X time and store in FLASH.
    2. At some point start advertising and wait for connection from smartphone.
    3. Upon connection established event, dump all the data to the smartphone.
    3. Disconnect from smartphone and stop advertising.
    4. Resume cycle with point 1.

    Meanwhile, the default behavior of APP_ERROR_CHECK(err_code); is to call NVIC_SystemReset(); when err_code != NRF_SUCCESS. Because of that I have placed breakpoint in the beginning of my main() in case the controller is reset. After flashing firmware and initial passing over that said breakpoint the program never again reaches it, which means the controller is never reset, which means it passes APP_ERROR_CHECKs successfully. 

    Moreover, when my device finishes dumping the data it just continues its operational cycle as if nothing happened. I've ran a test without point 3. where I disconnect and stop advertising. The connection just stays open and device continues with gathering data. So now it seems like smartphone app just doesn't display all the data in the Log...I'm really confused by now.

  • Ok. So we agree that some time between you gathering the data, and the data being displayed on the phone, some records are lost. 

    Can you please show me the function/place that you use ble_nus_string_send() ?

    Are you sure that you store the data in flash? If so, do you use the FDS module, or just fstorage? Or do you actually just store it in ram, in an array in your application?

    If you are using flash, maybe you fail to load all the flash records fast enough to keep up with the BLE communication?

    So:

    1: Show me how/where you use ble_nus_string_send();

    2: How do you store your data, and how do you fetch it before sending it with ble_nus_string_send() ?

    Best regards,

    Edvin

  • /* Variables */
    extern volatile memCellData_t currentMemCell;
    extern ble_nus_t m_nus;
    uint8_t stringData[4];
    extern volatile bool bleTxComplete;
    
    /* Functions */
    void dataTransfer (void)
    {
    	uint32_t word;
    	uint32_t readAddress = (uint32_t)FIRST_PAGE_START_ADDRESS;
    	uint32_t err_code;
    
    	// While there are words to read from FLASH
    	while(readAddress < currentMemCell.address)
    	{
    		// Read one word. Data LSB is in FLASH lower addresses.
    		word = FLASH_ReadWord((uint32_t *)readAddress);
    
    		// Check if this WORD is TIMESTAMP_MARKER
    		if(word == TIMESTAMP_MARKER)
    		{
    			// This WORD is TIMESTAMP_MARKER. This means next WORD is timeStamp data.
    
    			// Send TIMESTAMP marker to smartphone.
    			// Thus it knows that following WORD is timeStamp data.
    			err_code = ble_nus_string_send(&m_nus, (uint8_t *)integer2String(TIMESTAMP_MARKER), sizeof(stringData));
    			while(!bleTxComplete){};
    			APP_ERROR_CHECK(err_code);
    
    			// Skip to the first byte of next WORD (which is timeStamp data);
    			readAddress += WORD_SIZE;
    
    			// Read timeStamp WORD from FLASH.
    			word = FLASH_ReadWord((uint32_t *)readAddress);
    
    			// Send the timeStamp WORD to smartphone device via BLE.
    			err_code = ble_nus_string_send(&m_nus, (uint8_t *)integer2String(word), sizeof(stringData));
    		    while(!bleTxComplete){};
    			APP_ERROR_CHECK(err_code);
    
    			// Increment address for next read operation
    			readAddress += WORD_SIZE;
    		}
    		else
    		{
    			// Send sensorData WORD via BLE
    			err_code = ble_nus_string_send(&m_nus, (uint8_t *)integer2String(word), sizeof(stringData));
    			while(!bleTxComplete){};
    			APP_ERROR_CHECK(err_code);
    
    			// Increment address for next read operation
    			readAddress += WORD_SIZE;
    		}
    	}
    
    	// All data was extracted from FLASH.
    	// Reset currentMemCell values.
    	currentMemCell.page = (uint8_t)FIRST_PAGE;
    	currentMemCell.pageWasErased = false;
    	currentMemCell.address = (uint32_t)FIRST_PAGE_START_ADDRESS;
    }
    
    static uint8_t * integer2String (uint32_t data)
    {
    	stringData[0] = data >> 24;
    	stringData[1] = data >> 16;
    	stringData[2] = data >> 8;
    	stringData[3] = data;
    
    	return stringData;
    }
    
    uint32_t FLASH_ReadWord (uint32_t * byteAddress)
    {
    	return * byteAddress;
    }
    
    static void FLASH_Save (uint32_t value)
    {
    	// Check if the page, which is about to be written on, was already erased. If it was not - erase it!
    	if(!currentMemCell.pageWasErased)
    	{
    		flashIsBusy = true;
    		sd_flash_page_erase(currentMemCell.page);
    
    		// Wait for erase to complete. This is flag NRF_EVT_FLASH_OPERATION_SUCCESS
    		while(flashIsBusy)
    		{
    			//ToDo: Implement Sleep;
    		}
    		
    		currentMemCell.pageWasErased = true;
    	}
    
    	// After page was erased, write 4 bytes to specified address in the page
    	flashIsBusy = true;
    	sd_flash_write((uint32_t *)currentMemCell.address , &value, (uint32_t)1);
    
    	// Wait for write to complete. This is flag NRF_EVT_FLASH_OPERATION_SUCCESS
    	while(flashIsBusy)
    	{
    		// ToDo: Implement Sleep.
    	}
    
    	// Increment address with 4 bytes (one word) for next write operation
    	currentMemCell.address += 4;
    
    	// Check if updated address is now in the next page. If it is - increment page value, and toggle pageWasErased to false.
    	if((currentMemCell.address - (currentMemCell.page * PAGE_SIZE)) >= PAGE_SIZE)
    	{
    		currentMemCell.page ++;
    		currentMemCell.pageWasErased = false;
    	}
    }

  • I'm never actually resetting bleTxComplete flag. Let's see if that fixes it...

Reply Children
  • I assume that bleTxComplete is set to true in your TX_COMPLETE event?

    Are you sure you have set the breakpoint in your main() is in a place that is hit? Or that you are still debugging if the device resets?

    Can you please try to define DEBUG in your preprocessor defines and monitor your RTT log? Please let me know if you need help with doing this.

     

    When I connect to the device using the same app but this time on iOS - Data strarts to transfer correctly but hangs before all data is received by iPhone.

     This sounds awfully like an APP_ERROR_CHECK(err_code) when err_code != 0.

  • Yes, bleTxComplete is set true in "on_ble_evt()" handler. I am sure that the breakpoint in "main()" is going to be hit in case of reset, because I have used it a couple of times to detect such a behavior. 

    Now I'm going to place a breakpoint earlier - in the "APP_ERROR_CHECK()" and see what will happen. I know how to set DEBUG in preprocessor, but what do you mean by RTT log, is that the Debugger Log.

  • Sorry, I forgot that you used SDK v 11. No the error will not be printed in the log. My bad.

    However, if you turn off optimization, and define DEBUG in the preprocessor defines, you will see that APP_ERROR_CHECK() will follow this call:

    APP_ERROR_CHECK() -> APP_ERROR_HANDLER() -> app_error_handler(), which contains the error_code, line number and a pointer to the file name that the error occured.

    If this is not hit, then something else is going on, but I don't know exactly what. If it "stops"/"hangs", maybe

    while(!bleTxComplete){};

    never passes, possibly because the message is never sent, because the ble_nus_string_send() returned BLE_ERROR_NO_TX_PACKETS. That may very well be.

    You see, ble_nus_string_send(), which calls the softdevice call, sd_ble_gatts_hvx() only queues the packet if it returns NRF_SUCCESS.

    In the case where some packet has not yet been delivered, and you fill up the queue, sd_ble_gatts_hvx() will not return NRF_SUCCESS because there isn't room in the softdevice queue for this new packet, or some other reason. The return value should give you some hints. 

    The issue with the implementation is that if this doesn't return NRF_SUCCESS, then you will not get the TX_COMPLETE event, because the packet was never queued. In this case, you are waiting for the bleTxComplete to be true, which it never will, and you are stuck.

    Try to call APP_ERROR_CHECK(err_code) before waiting for bleTxComplete. Then you will know if a packet isn't queued. 

  • I have been tweaking the code and I may have found a solution. Sending data like the way shown below seems to work for both Android and iOS. 

    do
    {
    	err_code = ble_nus_string_send(&m_nus, (uint8_t *)integer2String(TIMESTAMP_MARKER), sizeof(stringData));
    }
    while(err_code != NRF_SUCCESS);
    while(!bleTxComplete);
    bleTxComplete = false;

  • Yes. That confirms my suspicion. You probably got the BLE_ERROR_NO_TX_PACKETS.

    The implementation you have above will work just fine, but it is denying the nRF to go to sleep in between the packets. Now it has to wait until it is able to successfully queue the packet, and that it has been ACKed in the TX_COMPLETE event. If current consumption is a concern for your product, I suggest you use flags and counters instead of waiting functions (while() ), so that you can return to the main() context in between the packets. You can use the TX_COMPLETE event to resume the queuing of packets.

    Best regards,

    Edvin

Related