Connection Parameter Negotiation Using Tolerance Settings - Best Practices

Hello Nordic team,

I'm developing a BLE peripheral using nRF52833 and would like to validate that my approach for handling connection parameters aligns with Nordic's best practices.

We were noticing some intermittent disconnects between our peripheral and iPhone centrals. Based on Apple and Nordic's recommendation we increased the Supervision Timeout (initially set by Apple central to 720 ms) and this fixed the issue. 

I'm using the ble_conn_params module with decreased tolerance values to force negotiation when parameters fall outside acceptable ranges:

c
// Preferred connection parameters
#define MIN_CONN_INTERVAL                               MSEC_TO_UNITS(30, UNIT_1_25_MS)   // 30ms
#define MAX_CONN_INTERVAL                               MSEC_TO_UNITS(100, UNIT_1_25_MS)  // 100ms
#define SLAVE_LATENCY                                   4
#define CONN_SUP_TIMEOUT                                MSEC_TO_UNITS(4000, UNIT_10_MS)   // 4000ms

// Tolerance settings to force negotiation
#define NRF_BLE_CONN_PARAMS_MAX_SLAVE_LATENCY_DEVIATION        0    // Exact match required
#define NRF_BLE_CONN_PARAMS_MAX_SUPERVISION_TIMEOUT_DEVIATION  100  // ±1000ms (in 10ms units)

// Standard conn_params module initialization
#define FIRST_CONN_PARAMS_UPDATE_DELAY  APP_TIMER_TICKS(5000)   // 5 seconds
#define NEXT_CONN_PARAMS_UPDATE_DELAY   APP_TIMER_TICKS(30000)  // 30 seconds  
#define MAX_CONN_PARAMS_UPDATE_COUNT    3                        // 3 attempts

With supervision timeout of 4000ms ± 1000ms:

  • Minimum accepted: 3000ms (meets Apple's 2000ms minimum)
  • Maximum accepted: 5000ms (within Apple's 6000ms maximum)
  • iOS's initial 720ms: Rejected by is_conn_params_ok(), triggering negotiation

This approach leverages the Nordic module's existing validation logic without modifying ble_conn_params.c.

Questions:

  1. Is using tolerance values to force negotiation a recommended practice? By setting NRF_BLE_CONN_PARAMS_MAX_SUPERVISION_TIMEOUT_DEVIATION to reject iOS's initial 720ms timeout, we ensure negotiation always occurs. Is this approach robust across different central implementations?
  2. Central rejection handling: If a central device completely rejects our parameter update request (not just negotiates different values, but refuses the L2CAP request entirely):
    • Does the ble_conn_params module automatically continue with the central's original parameters?
    • Could this cause connection failure, or does the module handle this gracefully since we set disconnect_on_fail = false?
  3. Retry behavior: With MAX_CONN_PARAMS_UPDATE_COUNT = 3, if all attempts fail:
    • Will our peripheral be unable to connect at all? It seems like it would be better if we just accepted whatever the central offered if they rejected our 3 previous update requests, but I don't see an obvious way to accomplish this using ble_conn_params.

My Core Concern: While this tolerance-based approach elegantly forces negotiation for iOS devices, I want to ensure it won't cause connection failures with central devices that have unusual connection parameter policies. Does disconnect_on_fail = false setting protect against this? Ours is set to false, I'd like confirmation that the module handles all edge cases gracefully.

Is this tolerance-based approach a valid use of the ble_conn_params module, or would Nordic recommend a different strategy for ensuring Apple-compliant connection parameters?

Thank you for your guidance!

  • Just after posting I noticed something that may be why I'm seeing a disconnect on fail even though disconnect_on_fail is set to false.

    Do I need to modify this function in ble_handler.c to prevent the negotiation failures from causing a disconnect?

    /**@brief Function for handling an event from the Connection Parameters Module.
    *
    * @details This function will be called for all events in the Connection Parameters Module
    * which are passed to the application.
    *
    * @note All this function does is to disconnect. This could have been done by simply setting
    * the disconnect_on_fail config parameter, but instead we use the event handler
    * mechanism to demonstrate its use.
    *
    * @param[in] p_evt Event received from the Connection Parameters Module.
    */
    static void on_conn_params_evt(ble_conn_params_evt_t * p_evt)
    {
    uint32_t err_code;

    if (p_evt->evt_type == BLE_CONN_PARAMS_EVT_FAILED)
    {
    err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_CONN_INTERVAL_UNACCEPTABLE);
    if (err_code != NRF_ERROR_INVALID_STATE)
    {
    MM_VERIFY(err_code == NRF_SUCCESS, ERR_BLE_FAILED);
    }
    }
    }
  • Hello,

    You can use your preferred connection parameters as guiding lines for your central. If you want your peripheral to have a certain set of parameters, then you should set a range which you accept. If a central enters connection with any of the parameters outside this range, it will try to negotiate the parameters MAX_CONN_PARAMS_UPDATE_COUNT (=3 by default) times. First one after 5 seconds, and the other two after 30 seconds each, I believe. These are also defined by some #defines in the application.

    If you, however, want your device to stay connected regardless of connection parameters, you should indeed set disconnect_on_fail = false. This should just ignore the 3rd fail, and remain connected using whatever connection parameters it currently has. 

    If you are saying that the device disconnects even if disconnect_on_fail is set to false, then that sounds like a bug. What SDK version are you using?

    At least in SDK 17.1.0, in ble_conn_params.c, line 207 it says:

    static void update_timeout_handler(void * p_context)
    {
        uint32_t                     conn_handle = (uint32_t)p_context;
        ble_conn_params_instance_t * p_instance  = instance_get(conn_handle);
    
        if (p_instance != NULL)
        {
            // Check if we have reached the maximum number of attempts
            if (p_instance->update_count < m_conn_params_config.max_conn_params_update_count)
            {
                bool update_sent = send_update_request(conn_handle, &p_instance->preferred_conn_params);
                if (update_sent)
                {
                    p_instance->update_count++;
                }
            }
            else
            {
                p_instance->update_count = 0;
    
                // Negotiation failed, disconnect automatically if this has been configured
                if (m_conn_params_config.disconnect_on_fail)
                {
                    ret_code_t err_code;
    
                    err_code = sd_ble_gap_disconnect(conn_handle, BLE_HCI_CONN_INTERVAL_UNACCEPTABLE);
                    if ((err_code != NRF_SUCCESS) && (err_code != NRF_ERROR_INVALID_STATE)) // NRF_ERROR_INVALID_STATE means disconnect is already in progress.
                    {
                        send_error_evt(err_code);
                    }
                }
    
                // Notify the application that the procedure has failed
                if (m_conn_params_config.evt_handler != NULL)
                {
                    ble_conn_params_evt_t evt;
    
                    evt.evt_type = BLE_CONN_PARAMS_EVT_FAILED;
                    evt.conn_handle = conn_handle;
                    m_conn_params_config.evt_handler(&evt);
                }
            }
        }
    }

    Robert Edmonston said:
    static void on_conn_params_evt(ble_conn_params_evt_t * p_evt)
    {
    uint32_t err_code;

    if (p_evt->evt_type == BLE_CONN_PARAMS_EVT_FAILED)
    {
    err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_CONN_INTERVAL_UNACCEPTABLE);

    I do see this in some of the samples. This one is probably in main.c, as it is in the ble_app_uart example. But as you can see from it's description:

     * @note All this function does is to disconnect. This could have been done by simply setting
     *       the disconnect_on_fail config parameter, but instead we use the event handler
     *       mechanism to demonstrate its use.

    So just remove this disconnect call, and it should work as you'd expect.

    Best regards,

    Edvin

  • Thanks Edvin, this is helpful. I removed the on_conn_params_evt handler and now the peripheral stays connected even if the conn parameter update negotiations fail. 

  • Happy to help!

    Let me know if you run into any other, related issues.

    Best regards,

    Edvin

Related