This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

RTOS/CC2640R2F: Connection timeout after connection parameter update

Part Number: CC2640R2F
Other Parts Discussed in Thread: CC2541, CC2650

Tool/software: TI-RTOS

I have a custom BLE central device running on a CC2640R2F and a custom peripheral on a CC2640R2F. These devices are designed to be permanently bonded and connected with each other. I am able to discover, establish connection, bond, encrypt link, and read/write to characteristics. In the majority of the time the devices should be connected at the longest connection interval and only decrease during certain data transfer periods. What I'm finding is after issuing a connection parameter update, the connection would drop due to 'LL_STATUS_ERROR_CONNECTION_TIMEOUT' (gapCentralRoleEvent_t.linkTerminate.reason == 0x08).  I see a stable connection at connection intervals below 200ms, but above that such as 4 seconds does not work.

The parameters I need are:

Connection Interval: 4 seconds

Slave Latency: 0

Timeout: 12 seconds

  • I want to add that we are using SDK 1.40.00.45 and disabled data packet length extension on both central and peripheral.

    Can you point me in the direction where I can find the cause of the problem?

    Thanks.
  • Hi Michael,

    Which device is issuing the Connection Parameter Update Request? What is the peer response to the parameters? Do you have a sniffer capture of this behavior?
  • The central device is issuing the connection parameter update request. Here are images of the breakpoint during reception of GAP_LINK_PARAM_UPDATE_EVENT message from the stack.

    Fast Interval gapCentralRoleEvent_t

    Slow Interval

    Sniffer log of 1 second connection interval is attached also.

    Thanks.Slow conn interval sniffer.psd

  • Hi Michael,

    The sniffer capture you sent is encrypted. Please disable security and take the capture again.

    A couple things to look at in the meantime:

    1. On the peripheral side: what is DEFAULT_ENABLE_UPDATE_REQUEST set to?
    2. Are you receiving a GAP_LINK_PARAM_UPDATE_EVENT on the peripheral?
  • Here is the sniffer capture when encryption is disabled.

    On the Peripheral side:

    GAPRole_SetParameter( GAPROLE_PARAM_UPDATE_ENABLE,
                           sizeof( uint8 ),
                           &enable_update_request );

    uint8 enable_update_request  = DEF_ENABLE_UPDATE_REQUEST;

    #define DEF_ENABLE_UPDATE_REQUEST                (FALSE)

    I've noticed this is named differently than the example simple_peripheral code. But FALSE = 0 = GAPROLE_LINK_PARAM_UPDATE_WAIT_REMOTE_PARAMS. The peripheral accepts the connection parameters and calls the application callback function when GAP_LINK_PARAM_UPDATE_EVENT is received. So GAP_LINK_PARAM_UPDATE_EVENT was indeed received.

    Thanks.ConnParamUpdateFail1000ms.psd

  • Hi Michael,

    It looks like you missed the connection establishment with your sniffer. Please make sure your sniffer capture contains an ADV_CONNREQ PDU.

    Here is the SmartRF Packet Sniffer Guide for more help: http://processors.wiki.ti.com/index.php/BLE_sniffer_guide#Connection_establishment

  • I see it in in Pkt num 3015.

  • Hi Michael,

    Thanks for pointing that out and I can see that too now. However, I only see a Connection Update Request from the master to the slave. I don't see the slave's response.

    Are you receiving a GAP_UPDATE_LINK_PARAM_REQ_EVENT? Even if the parameters were not accepted for some reason, the reply should have been sent out.
  • Hi Rachel,

    Yes, we are receiving the GAP_UPDATE_LINK_PARAM_REQ_EVENT in peripheral.c: gapRole_processGAPMsg. It does

    // Send application's requested parameters back.
    VOID GAP_UpdateLinkParamReqReply(&rsp);

    but I don't see the Link reply in the sniffer. I noticed the direction column in the sniffer capture is '?' after the connection param update request. What could that mean?
  • Hi Michael,

    Ok, in the GAP_UPDATE_LINK_PARAM_REQ_EVENT code you posted above can you modify it to check for the return value like what follows:

            bStatus_t status = 0;
            // Send application's requested parameters back.
            status = GAP_UpdateLinkParamReqReply(&rsp);
            if (status != SUCCESS)
            {
              gapRole_state = GAPROLE_ERROR;
            }

    The default code doesn't check to make sure the message was received properly by the controller. Since it's not going out over the air, this is probably a good place to check. What is the value of status?

  • Hi Rachel,

    status is SUCCESS. On our older product that runs on CC2541, I also do not see a connection param update reply packet over the sniffer. But that product works great on 4second connection interval.

  • Hi Rachel,

    I dug a little deeper into where the communication drops when using slower connection intervals. The problem seems to be when the central makes a call to GATT_ReadCharValue after the connection update. With the 62ms connection interval, the central is able to use GATT_ReadCharValue and return SUCCESS several times. But using 1000ms connection interval, GATT_ReadCharValue returns SUCCESS on the first call, but returns blePending on the second call. From the sniffer it is clear that the call never actually resulted in a request being sent to the peripheral. Any idea what could be going wrong?

  • Hi Michael,

    I am attempting to reproduce your connection parameter update issue with Simple Central and Simple Peripheral. You mentioned you disabled Data Length Extension. Can you give me details of how you did that?

    What other modifications did you make to the default projects?

  • Hi Rachel,

    We disabled data length extension suggested by JXS in another post because of incompatibality with our older CC2541 peripherals.

    Here is the Init code that disables data length extension. We also disabled some BLE 4.2 features in setting up the bond manager.

    static void Central_Init(void)
    {
        uint8_t featSet[8] = {0};
    
        // ******************************************************************
        // N0 STACK API CALLS CAN OCCUR BEFORE THIS CALL TO ICall_registerApp
        // ******************************************************************
        // Register the current thread as an ICall dispatcher application
        // so that the application can send and receive messages.
        ICall_registerApp(&mSelfEntity, &mSyncEvent);
    
    #if defined( USE_FPGA )
        // configure RF Core SMI Data Link
        IOCPortConfigureSet(IOID_12, IOC_PORT_RFC_GPO0, IOC_STD_OUTPUT);
        IOCPortConfigureSet(IOID_11, IOC_PORT_RFC_GPI0, IOC_STD_INPUT);
    
        // configure RF Core SMI Command Link
        IOCPortConfigureSet(IOID_10, IOC_IOCFG0_PORT_ID_RFC_SMI_CL_OUT, IOC_STD_OUTPUT);
        IOCPortConfigureSet(IOID_9, IOC_IOCFG0_PORT_ID_RFC_SMI_CL_IN, IOC_STD_INPUT);
    
        // configure RF Core tracer IO
        IOCPortConfigureSet(IOID_8, IOC_PORT_RFC_TRC, IOC_STD_OUTPUT);
    #else // !USE_FPGA
    #if defined( DEBUG_SW_TRACE )
        // configure RF Core tracer IO
        IOCPortConfigureSet(IOID_8, IOC_PORT_RFC_TRC, IOC_STD_OUTPUT | IOC_CURRENT_4MA | IOC_SLEW_ENABLE);
    #endif // DEBUG_SW_TRACE
    #endif // USE_FPGA
    
        // Configuration Initialization
        DeviceConfigInit();
    
        // Create an RTOS queue for message from profile to be sent to app.
        mAppMsgQueue = Util_constructQueue(&mAppMsg);
    
        // Setup discovery delay as a one-shot timer
        Util_constructClock(&mStartDiscClock, SimpleBLECentral_startDiscHandler,
                            DEFAULT_SVC_DISCOVERY_DELAY, 0, false, 0);
    
    
        // Setup the Central GAPRole Profile. For more information see the GAP section
        // in the User's Guide:
        // software-dl.ti.com/.../
        {
            uint8_t scanRes = DEFAULT_MAX_SCAN_RES;
    
            GAPCentralRole_SetParameter(GAPCENTRALROLE_MAX_SCAN_RES, sizeof(uint8_t),
                                        &scanRes);
        }
    
        // Setup Scanning Parameters
        SetupScanning( DEFAULT_SCAN_WINDOW, DEFAULT_SCAN_INTERVAL, false );
    
        GGS_SetParameter(GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN,
                         (void *)mAttDeviceName);
    
        // Setup the GAP Bond Manager. For more information see the GAP Bond Manager
        // section in the User's Guide:
        // software-dl.ti.com/.../
        SetupBondMgr();
    
        // Initialize GATT Client
        VOID GATT_InitClient();
    
        // Initialize GATT attributes
        GGS_AddService(GATT_ALL_SERVICES);         // GAP
        GATTServApp_AddService(GATT_ALL_SERVICES); // GATT attributes
    
        // Start the Device
        VOID GAPCentralRole_StartDevice(&SimpleBLECentral_roleCB);
    
        // Register with bond manager after starting device
        GAPBondMgr_Register(&BleCentral_bondCB);
    
        // Register with the Op-Mode FSM Task
        VOID OpModeTask_Register(&BleCentral_TaskEnqueue);
    
        // Register with the Service Client Task
        VOID ServiceTask_RegisterCBs( &ServiceCBs );
    
        // Register with GAP for HCI/Host messages (for RSSI)
        GAP_RegisterForMsgs(mSelfEntity);
    
        //Set default values for Data Length Extension
        {
            //Set initial values to maximum, RX is set to max. by default(251 octets, 2120us)
    #define APP_SUGGESTED_PDU_SIZE 251 //default is 27 octets(TX)
    #define APP_SUGGESTED_TX_TIME 2120 //default is 328us(TX)
    
            //This API is documented in hci.h
            //See the LE Data Length Extension section in the BLE-Stack User's Guide for information on using this command:
            //software-dl.ti.com/.../index.html
            //HCI_LE_WriteSuggestedDefaultDataLenCmd(APP_SUGGESTED_PDU_SIZE, APP_SUGGESTED_TX_TIME);
            // Disalble Data length extension feature of v4.2
    #define APP_TX_PDU_SIZE 27
    #define APP_RX_PDU_SIZE 27
    #define APP_TX_TIME 328
    #define APP_RX_TIME 328
            HCI_EXT_SetMaxDataLenCmd(APP_TX_PDU_SIZE ,  APP_TX_TIME,
                                     APP_RX_PDU_SIZE, APP_RX_TIME);
            CLR_FEATURE_FLAG( featSet[0], LL_FEATURE_DATA_PACKET_LENGTH_EXTENSION );
            SET_FEATURE_FLAG( featSet[0], LL_FEATURE_ENCRYPTION);
            SET_FEATURE_FLAG( featSet[0], LL_FEATURE_CONN_PARAMS_REQ);
            HCI_EXT_SetLocalSupportedFeaturesCmd( featSet );
        }
    
    }
    
    static void SetupBondMgr( void )
    {
        // Gaurd condition prevents connections until passcode is not INVALID_PASSCODE
        uint32 passkey   = INVALID_PASSCODE;
        // Wait for Pairing Request
        uint8 pairMode   = GAPBOND_PAIRING_MODE_INITIATE;
        // Require a Passcode
        uint8 mitm       = TRUE;
        // This will use Default Passcode
        uint8 ioCap      = GAPBOND_IO_CAP_KEYBOARD_ONLY;
        // Store Pairing Information
        uint8 bonding    = TRUE;
        // White List Synchronized with Bonds
        uint8 autoSync   = TRUE;
        // Will manage internal to this task
        uint8 failAction = GAPBOND_FAIL_NO_ACTION;
        // Disable secure connection (a BLE 4.2 feature)
        uint8_t gapbondSecure = GAPBOND_SECURE_CONNECTION_NONE;
    
        GAPBondMgr_SetParameter( GAPBOND_DEFAULT_PASSCODE,
                                 sizeof( uint32 ),
                                 &passkey );
    
        GAPBondMgr_SetParameter( GAPBOND_PAIRING_MODE,
                                 sizeof( uint8 ),
                                 &pairMode );
    
        GAPBondMgr_SetParameter( GAPBOND_MITM_PROTECTION,
                                 sizeof( uint8 ),
                                 &mitm );
    
        GAPBondMgr_SetParameter( GAPBOND_IO_CAPABILITIES,
                                 sizeof( uint8 ),
                                 &ioCap );
    
        GAPBondMgr_SetParameter( GAPBOND_BONDING_ENABLED,
                                 sizeof( uint8 ),
                                 &bonding );
    
        GAPBondMgr_SetParameter( GAPBOND_AUTO_SYNC_WL,
                                 sizeof( uint8 ),
                                 &autoSync );
    
        GAPBondMgr_SetParameter( GAPBOND_BOND_FAIL_ACTION,
                                 sizeof( uint8 ),
                                 &failAction );
    
        GAPBondMgr_SetParameter( GAPBOND_SECURE_CONNECTION,
                                 sizeof( uint8 ),
                                 &gapbondSecure );
    }

    We started with the blestack\simple_central example project using SDK 1.40 and added 3 additional tasks.
    We are using IAR 8.11.2.
    Linker config: CC2650=2 FLASH_ROM_BUILD=2
    Preprocessor defines(app):
    BOARD_DISPLAY_USE_LCD=0
    BOARD_DISPLAY_USE_UART=0
    BOARD_DISPLAY_USE_UART_ANSI=0
    CC2640R2DK_4XS
    CC26XX
    CC26XX_R2
    DeviceFamily_CC26X0R2
    Display_DISABLE_ALL
    ICALL_EVENTS
    ICALL_JT
    ICALL_LITE
    ICALL_MAX_NUM_ENTITIES=10
    ICALL_MAX_NUM_TASKS=7
    ICALL_STACK0_ADDR
    POWER_SAVING
    RF_SINGLEMODE
    STACK_LIBRARY
    USE_ICALL
    xdc_runtime_Assert_DISABLE_ALL
    xdc_runtime_Log_DISABLE_ALL
    CENTRAL_ROLE
    HEAPMGR_METRICS

    Preprocessor defines(stack):
    CC26XX
    CC26XX_R2
    DATA=
    DeviceFamily_CC26X0R2
    EXT_HAL_ASSERT
    FLASH_ROM_BUILD
    ICALL_EVENTS
    ICALL_JT
    ICALL_LITE
    NEAR_FUNC=
    OSAL_CBTIMER_NUM_TASKS=1
    OSAL_SNV=1
    POWER_SAVING
    RF_SINGLEMODE
    STACK_LIBRARY
    USE_ICALL
    OSAL_MAX_NUM_PROXY_TASKS=6

    HostInterface Task: Priority 1, Stack size 768
    OpMode Task: Priority 3, Stack size 644
    Service task: Priority 2, Stack size 768
    GAPCentralRole task is priority 4, stack size is 440
    Simple_Central Task is priority 2, stack size is 640

    static void HostInterface_taskFxn(UArg a0, UArg a1)
    {
        uint32_t events;
    
        ICall_registerApp(&HostInterfaceTaskID, &HostInterfaceSyncEvent);
    
        // Create an RTOS queue for message from profile to be sent to here.
        hostInterfaceMsgQueue = Util_constructQueue(&hostInterfaceMsg);
    
        for (;;)
        {
            events = Event_pend(HostInterfaceSyncEvent, Event_Id_NONE, SBP_ALL_EVENTS,
                                ICALL_TIMEOUT_FOREVER); // Add
    
            if (events)
            {
                ICall_EntityID dest;
                ICall_ServiceEnum src;
                ICall_HciExtEvt *pMsg = NULL;
    
                if (ICall_fetchServiceMsg(&src, &dest, (void **)&pMsg) == ICALL_ERRNO_SUCCESS)
                {
                    if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == HostInterfaceTaskID))
                    {
                        // Process inter-task message
                        HostInterfaceTask_ProcessMsg((ICall_Hdr *)pMsg);
                    }
                    if (pMsg)
                    {
                        ICall_freeMsg(pMsg);
                    }
                }
    
                // If RTOS queue is not empty, process app message.
                if (!Queue_empty(hostInterfaceMsgQueue))
                {
                    ICall_Hdr *pMsg = (ICall_Hdr *)Util_dequeueMsg(hostInterfaceMsgQueue);
                    if (pMsg)
                    {
                        // Process message.
                        HostInterfaceTask_ProcessMsg((ICall_Hdr *)pMsg);
    
                        // Do NOT free the space from the internal message, it is static
                        // and there are only two: msgMemoryRxFSMData and msgMemoryAckRsp
                    }
                }
            }
            // there are no other events to handle
        }
    }
    
    static void OpMode_taskFxn(UArg a0, UArg a1)
    {
      uint32_t opModeEvents;
      
      // Initialize application
      OpMode_init();
    
      for (;;)
      {
        opModeEvents = Event_pend(OpModeSyncEvent, Event_Id_NONE, SBP_ALL_EVENTS,
                                  ICALL_TIMEOUT_FOREVER);
    
        if (opModeEvents)
        {
          ICall_EntityID dest;
          ICall_ServiceEnum src;
          ICall_HciExtEvt *pMsg = NULL;
    
          if (ICall_fetchServiceMsg(&src, &dest, (void **)&pMsg) == ICALL_ERRNO_SUCCESS)
          {
            if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == OpModeTaskID))
            {
              // Process inter-task message
              OpModeTask_ProcessMsg((ICall_Hdr *)pMsg);
            }
    
            if (pMsg)
            {
              ICall_freeMsg(pMsg);
            }
          }
    
          // If RTOS queue is not empty, process app message.
          if (!Queue_empty(opModeMsgQueue))
          {
            ICall_Hdr *pMsg = (ICall_Hdr *)Util_dequeueMsg(opModeMsgQueue);
            if (pMsg)
            {
              // Process message.
              OpModeTask_ProcessMsg((ICall_Hdr *)pMsg);
    
              // Free the space from the message.
              ICall_freeMsg(pMsg);
            }
          }
        }
    
        OpModeTask_ProcessEvent(opModeEvents);
      }
    }
    
    static void Service_TaskFxn(UArg a0, UArg a1)
    {
        uint32_t events;
    
        // Initialize application
        ServiceTask_Init();
    
        // Application main loop
        for (;;)
        {
            events = Event_pend(mSyncEvent, Event_Id_NONE, SRC_ALL_EVENTS, ICALL_TIMEOUT_FOREVER);
    
            if (events)
            {
                ICall_EntityID dest;
                ICall_ServiceEnum src;
                ICall_HciExtEvt *pMsg = NULL;
    
                if (ICall_fetchServiceMsg(&src, &dest, (void **)&pMsg) == ICALL_ERRNO_SUCCESS)
                {
                    if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == mSelfEntity))
                    {
                        // Process inter-task message
                        ServiceTask_ProcessStackMsg((ICall_Hdr *) pMsg);
                    }
                    if (pMsg)
                    {
                        ICall_freeMsg(pMsg);
                    }
                }
    
                // If RTOS queue is not empty, process app message
                if (events & SRC_QUEUE_EVT)
                {
                    while (!Queue_empty(mAppMsgQueue))
                    {
                        evt_t* pMsg = (evt_t*)Util_dequeueMsg(mAppMsgQueue);
                        if (pMsg)
                        {
                            // Process message
                            ServiceTask_ProcessAppMsg(pMsg);
                        }
                    }
                }
    
                // Link State Change Event
                if ( events & SRC_STATE_CHANGE_EVT )
                {
                    StateEventHandler();
                }
    
                // BLE Procedure Timeout Event
                if ( events & SRC_BLE_PROC_TIMEOUT_EVT )
                {
                    if ( ( curLinkState > NOT_READY )
                         && ( curLinkState < READY )
                         && ( intProcCmd.procCmd != ProcCmd_None ) )
                    {
                        VOID ProcReqHandler( &intProcCmd, true );
                    }
                    else if ( ( curLinkState == READY )
                              && ( intProcCmd.procCmd == ProcCmd_ReadServiceStatus )
                              && ( intProcCmd.procStatus != ProcStatus_Complete ) )
                    {
                        VOID ProcReqHandler( &intProcCmd, true );
                    }
                    else
                    {
                        ClearInternalProc( __LINE__ );
                    }
                }
    
                // Start Link Event
                if ( events & SRC_START_LINK_EVT )
                {
                    if ( curLinkState == NOT_READY )
                    {
                        SetState( SYNC_STATUS, true );
                    }
                }
            }
        }
    }